import type { CardAct, CardSection } from "@rakazo/contracts"; import { cardTimerRemaining, formatCardClock } from "@rakazo/core"; import { useKeepAwake } from "expo-keep-awake"; import { useEffect, useState } from "react"; import { Modal, Pressable, SafeAreaView, StyleSheet, Text, View } from "react-native"; import { mobileTokens } from "../lib/appearance"; import { useI18n } from "../lib/i18n"; import { NativeSymbol } from "./native-symbol"; type Steps = Extract; /** The current time, ticking every second while `active`. */ export function useNow(active: boolean): number { const [now, setNow] = useState(() => Date.now()); useEffect(() => { if (!active) return; setNow(Date.now()); const timer = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(timer); }, [active]); return now; } /** * A card's steps one at a time in full screen, large enough to read from the stove, with the * screen kept on. The step and any timer live on the card, so the web and a reload agree. */ export function CookingMode({ title, steps, now, busy, onAct, onClose, }: { title: string; steps: Steps; now: number; busy: boolean; onAct: (act: CardAct) => Promise; onClose: () => void; }) { useKeepAwake(); const tokens = mobileTokens(); const { t } = useI18n(); const index = Math.min(steps.current ?? 0, steps.steps.length - 1); const step = steps.steps[index]; const last = index === steps.steps.length - 1; const remaining = cardTimerRemaining(step?.timerEndsAt, now); const running = remaining !== null && remaining > 0; const go = (next: number) => void onAct({ type: "step", sectionId: steps.id, index: next }); return ( {title} {t("Step {position} of {total}", { position: index + 1, total: steps.steps.length, })} {step?.text} {step?.timerSeconds ? ( void onAct({ type: "timer", sectionId: steps.id, stepId: step.id, running: !running, }) } style={[ styles.timer, { borderColor: running ? tokens.primary : tokens.border, backgroundColor: running ? tokens.primary : "transparent", }, ]} > {formatCardClock(remaining ?? step.timerSeconds)} ) : null} go(index - 1)} style={[styles.nav, { borderColor: tokens.border, opacity: index === 0 ? 0.4 : 1 }]} > {t("Back")} (last ? onClose() : go(index + 1))} style={[styles.nav, { borderColor: tokens.primary, backgroundColor: tokens.primary }]} > {last ? t("Done") : t("Next")} ); } const styles = StyleSheet.create({ screen: { flex: 1, paddingHorizontal: 24 }, top: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: 12, paddingTop: 16, }, title: { flex: 1, fontSize: 15, fontWeight: "600" }, middle: { flex: 1, justifyContent: "center", gap: 28 }, counter: { fontSize: 15 }, step: { fontSize: 30, lineHeight: 40, fontWeight: "600" }, timer: { alignSelf: "flex-start", flexDirection: "row", alignItems: "center", gap: 10, borderWidth: 1, borderRadius: 16, paddingHorizontal: 18, paddingVertical: 12, }, clock: { fontSize: 26, fontWeight: "600", fontVariant: ["tabular-nums"] }, bottom: { flexDirection: "row", justifyContent: "space-between", gap: 12, paddingBottom: 16 }, nav: { flex: 1, alignItems: "center", borderWidth: 1, borderRadius: 14, paddingVertical: 14, }, navText: { fontSize: 16, fontWeight: "600" }, });