Fix Live Activity lock screen rendering and polish multi-account auth.
Flatten widget layouts and use system colors so banner and expanded regions render on vibrant lock screens; migrate auth sessions per account to prevent double sign-in; scope app lock PIN to accounts; default clock description to "Clock In"; add architecture docs and deferred form validation on auth screens. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,105 +1,104 @@
|
||||
import { HStack, Image, Text, VStack } from "@expo/ui/swift-ui";
|
||||
import { HStack, Image, Text } from "@expo/ui/swift-ui";
|
||||
import {
|
||||
font,
|
||||
foregroundStyle,
|
||||
frame,
|
||||
lineLimit,
|
||||
minimumScaleFactor,
|
||||
monospacedDigit,
|
||||
padding,
|
||||
widgetAccentedRenderingMode,
|
||||
} from "@expo/ui/swift-ui/modifiers";
|
||||
import { createLiveActivity, type LiveActivityEnvironment } from "expo-widgets";
|
||||
|
||||
import type { TimeClockActivityProps } from "@/lib/time-clock-live-activity.types";
|
||||
|
||||
const TIMER_GREEN = "#4ADE80";
|
||||
const SUBTLE_TEXT = "#E5E5E5";
|
||||
const MUTED_TEXT = "#D4D4D4";
|
||||
|
||||
function ElapsedText({
|
||||
value,
|
||||
size,
|
||||
weight = "semibold",
|
||||
}: {
|
||||
value: string;
|
||||
size: number;
|
||||
weight?: "regular" | "medium" | "semibold" | "bold";
|
||||
}) {
|
||||
return (
|
||||
<Text
|
||||
modifiers={[
|
||||
font({ design: "monospaced", weight, size }),
|
||||
monospacedDigit(),
|
||||
foregroundStyle(TIMER_GREEN),
|
||||
]}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
function BrandMark({ uri, size }: { uri?: string; size: number }) {
|
||||
if (uri) {
|
||||
return <Image uiImage={uri} modifiers={[frame({ width: size, height: size })]} />;
|
||||
}
|
||||
|
||||
return <Image systemName="dollarsign" color="#FAFAFA" size={size} />;
|
||||
}
|
||||
|
||||
function BrandLogo({ uri, height = 18 }: { uri?: string; height?: number }) {
|
||||
if (uri) {
|
||||
return <Image uiImage={uri} modifiers={[frame({ width: 132, height })]} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text modifiers={[font({ weight: "bold", size: 16 }), foregroundStyle("#FFFFFF")]}>
|
||||
beenvoice
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
function TimeClockActivity(props: TimeClockActivityProps, _environment: LiveActivityEnvironment) {
|
||||
"widget";
|
||||
|
||||
const title = props.description.trim() || "Timer running";
|
||||
const green = "green";
|
||||
const title = props.description.trim() || "Clock In";
|
||||
const subtitle = [props.clientName, props.invoiceLabel].filter(Boolean).join(" · ");
|
||||
const detailLine = subtitle ? `${title}\n${subtitle}` : title;
|
||||
|
||||
const timerMods = [
|
||||
font({ design: "monospaced", weight: "bold", size: 20 }),
|
||||
monospacedDigit(),
|
||||
foregroundStyle(green),
|
||||
lineLimit(1),
|
||||
minimumScaleFactor(0.85),
|
||||
];
|
||||
const compactTimerMods = [
|
||||
font({ design: "monospaced", weight: "semibold", size: 11 }),
|
||||
monospacedDigit(),
|
||||
foregroundStyle(green),
|
||||
lineLimit(1),
|
||||
minimumScaleFactor(0.8),
|
||||
];
|
||||
const brandMods = [
|
||||
font({ weight: "semibold", size: 13 }),
|
||||
foregroundStyle(green),
|
||||
lineLimit(1),
|
||||
minimumScaleFactor(0.85),
|
||||
];
|
||||
const detailMods = [
|
||||
font({ weight: "medium", size: 12 }),
|
||||
foregroundStyle({ type: "hierarchical", style: "secondary" }),
|
||||
lineLimit(2),
|
||||
minimumScaleFactor(0.85),
|
||||
];
|
||||
|
||||
return {
|
||||
banner: (
|
||||
<HStack modifiers={[padding({ all: 14 })]}>
|
||||
<BrandLogo uri={props.logoImageUri} />
|
||||
<ElapsedText value={props.elapsedShort} size={20} weight="bold" />
|
||||
<HStack alignment="center" spacing={10} modifiers={[padding({ all: 12 })]}>
|
||||
<Image
|
||||
systemName="dollarsign.circle.fill"
|
||||
color={green}
|
||||
size={22}
|
||||
modifiers={[widgetAccentedRenderingMode("fullColor")]}
|
||||
/>
|
||||
<Text modifiers={brandMods}>beenvoice</Text>
|
||||
<Text modifiers={timerMods}>{props.elapsedShort}</Text>
|
||||
</HStack>
|
||||
),
|
||||
compactLeading: <BrandMark uri={props.markImageUri} size={18} />,
|
||||
compactTrailing: <ElapsedText value={props.elapsedShort} size={14} />,
|
||||
minimal: <ElapsedText value={props.elapsedShort} size={12} weight="bold" />,
|
||||
bannerSmall: (
|
||||
<HStack alignment="center" spacing={8} modifiers={[padding({ all: 10 })]}>
|
||||
<Image
|
||||
systemName="dollarsign.circle.fill"
|
||||
color={green}
|
||||
size={18}
|
||||
modifiers={[widgetAccentedRenderingMode("fullColor")]}
|
||||
/>
|
||||
<Text modifiers={brandMods}>beenvoice</Text>
|
||||
<Text modifiers={compactTimerMods}>{props.elapsedShort}</Text>
|
||||
</HStack>
|
||||
),
|
||||
compactLeading: (
|
||||
<Image
|
||||
systemName="dollarsign.circle.fill"
|
||||
color={green}
|
||||
size={15}
|
||||
modifiers={[widgetAccentedRenderingMode("fullColor")]}
|
||||
/>
|
||||
),
|
||||
compactTrailing: <Text modifiers={compactTimerMods}>{props.elapsedShort}</Text>,
|
||||
minimal: (
|
||||
<Image
|
||||
systemName="dollarsign.circle.fill"
|
||||
color={green}
|
||||
size={12}
|
||||
modifiers={[widgetAccentedRenderingMode("fullColor")]}
|
||||
/>
|
||||
),
|
||||
expandedLeading: (
|
||||
<VStack modifiers={[padding({ all: 12 })]}>
|
||||
<BrandLogo uri={props.logoImageUri} height={20} />
|
||||
<Text modifiers={[font({ size: 12, design: "monospaced" }), foregroundStyle(SUBTLE_TEXT)]}>
|
||||
{props.clockTime}
|
||||
</Text>
|
||||
</VStack>
|
||||
),
|
||||
expandedTrailing: (
|
||||
<VStack modifiers={[padding({ all: 12 })]}>
|
||||
<ElapsedText value={props.elapsedShort} size={32} weight="bold" />
|
||||
<Text modifiers={[font({ size: 12 }), foregroundStyle(SUBTLE_TEXT)]}>elapsed</Text>
|
||||
</VStack>
|
||||
),
|
||||
expandedBottom: (
|
||||
<VStack modifiers={[padding({ horizontal: 12, bottom: 12 })]}>
|
||||
<Text modifiers={[font({ weight: "semibold", size: 15 }), foregroundStyle("#FFFFFF")]}>
|
||||
{title}
|
||||
</Text>
|
||||
{subtitle ? (
|
||||
<Text modifiers={[font({ size: 13 }), foregroundStyle(SUBTLE_TEXT)]}>{subtitle}</Text>
|
||||
) : null}
|
||||
<HStack>
|
||||
<ElapsedText value={props.elapsed} size={12} weight="regular" />
|
||||
<Text modifiers={[font({ size: 12 }), foregroundStyle(MUTED_TEXT)]}> total</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
<Image
|
||||
systemName="dollarsign.circle.fill"
|
||||
color={green}
|
||||
size={20}
|
||||
modifiers={[widgetAccentedRenderingMode("fullColor")]}
|
||||
/>
|
||||
),
|
||||
expandedTrailing: <Text modifiers={timerMods}>{props.elapsedShort}</Text>,
|
||||
expandedBottom: <Text modifiers={detailMods}>{detailLine}</Text>,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user