@adminide-stack/yantra-mobile 12.0.125-alpha.1 → 12.0.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/lib/features/canvas/AudioEditorExperience.js +207 -109
  2. package/lib/features/canvas/AudioEditorExperience.js.map +1 -1
  3. package/lib/features/canvas/CanvasAgentChatPane.js +87 -72
  4. package/lib/features/canvas/CanvasAgentChatPane.js.map +1 -1
  5. package/lib/features/canvas/CanvasBoardsGrid.js +494 -387
  6. package/lib/features/canvas/CanvasBoardsGrid.js.map +1 -1
  7. package/lib/features/canvas/CanvasChatResultCard.js +78 -59
  8. package/lib/features/canvas/CanvasChatResultCard.js.map +1 -1
  9. package/lib/features/canvas/CanvasEditToolsPanel.js +126 -84
  10. package/lib/features/canvas/CanvasEditToolsPanel.js.map +1 -1
  11. package/lib/features/canvas/CanvasExperience.js +108 -126
  12. package/lib/features/canvas/CanvasExperience.js.map +1 -1
  13. package/lib/features/canvas/CanvasGeneratePanel.js +178 -166
  14. package/lib/features/canvas/CanvasGeneratePanel.js.map +1 -1
  15. package/lib/features/canvas/CanvasLibrarySheet.js +119 -123
  16. package/lib/features/canvas/CanvasLibrarySheet.js.map +1 -1
  17. package/lib/features/canvas/CanvasLooksPanel.js +113 -89
  18. package/lib/features/canvas/CanvasLooksPanel.js.map +1 -1
  19. package/lib/features/canvas/CanvasModelPicker.js +79 -79
  20. package/lib/features/canvas/CanvasModelPicker.js.map +1 -1
  21. package/lib/features/canvas/CanvasTasksPanel.js +193 -128
  22. package/lib/features/canvas/CanvasTasksPanel.js.map +1 -1
  23. package/lib/features/canvas/CanvasTemplatesPanel.js +106 -94
  24. package/lib/features/canvas/CanvasTemplatesPanel.js.map +1 -1
  25. package/lib/features/canvas/CanvasToolDock.js +195 -127
  26. package/lib/features/canvas/CanvasToolDock.js.map +1 -1
  27. package/lib/features/canvas/FrameExtractorExperience.js +163 -100
  28. package/lib/features/canvas/FrameExtractorExperience.js.map +1 -1
  29. package/lib/features/canvas/GeneratorReferenceSlots.js +132 -69
  30. package/lib/features/canvas/GeneratorReferenceSlots.js.map +1 -1
  31. package/lib/features/canvas/ImageEditorExperience.js +278 -209
  32. package/lib/features/canvas/ImageEditorExperience.js.map +1 -1
  33. package/lib/features/canvas/InfluencerStudioExperience.js +167 -125
  34. package/lib/features/canvas/InfluencerStudioExperience.js.map +1 -1
  35. package/lib/features/canvas/MoodboardExperience.js +102 -85
  36. package/lib/features/canvas/MoodboardExperience.js.map +1 -1
  37. package/lib/features/canvas/NativeCanvasViewer.js +203 -192
  38. package/lib/features/canvas/NativeCanvasViewer.js.map +1 -1
  39. package/lib/features/canvas/PromoContextFields.js +125 -92
  40. package/lib/features/canvas/PromoContextFields.js.map +1 -1
  41. package/lib/features/canvas/SetStudioExperience.js +225 -143
  42. package/lib/features/canvas/SetStudioExperience.js.map +1 -1
  43. package/lib/features/canvas/VideoClipsExperience.js +126 -92
  44. package/lib/features/canvas/VideoClipsExperience.js.map +1 -1
  45. package/lib/features/canvas/VideoEditExperience.js +154 -142
  46. package/lib/features/canvas/VideoEditExperience.js.map +1 -1
  47. package/lib/features/canvas/VideoEditorExperience.js +551 -492
  48. package/lib/features/canvas/VideoEditorExperience.js.map +1 -1
  49. package/lib/features/canvas/VideoGeneratorExperience.js +284 -131
  50. package/lib/features/canvas/VideoGeneratorExperience.js.map +1 -1
  51. package/lib/features/canvas/canvasTasks.js +7 -3
  52. package/lib/features/canvas/canvasTasks.js.map +1 -1
  53. package/lib/features/canvas/canvasTheme.js +265 -0
  54. package/lib/features/canvas/canvasTheme.js.map +1 -0
  55. package/lib/features/canvas/canvasUi.js +381 -0
  56. package/lib/features/canvas/canvasUi.js.map +1 -0
  57. package/lib/features/canvas/recorder/CameraRecorderExperience.js +109 -52
  58. package/lib/features/canvas/recorder/CameraRecorderExperience.js.map +1 -1
  59. package/lib/features/canvas/serverTasks.js +1 -1
  60. package/lib/features/canvas/serverTasks.js.map +1 -1
  61. package/lib/features/canvas/set/SetViewportWebView.js +1 -1
  62. package/lib/features/canvas/set/SetViewportWebView.js.map +1 -1
  63. package/lib/features/canvas/videoEditor/BlurBoxOverlay.js +8 -7
  64. package/lib/features/canvas/videoEditor/BlurBoxOverlay.js.map +1 -1
  65. package/lib/features/canvas/videoEditor/ExportTaskCard.js +44 -41
  66. package/lib/features/canvas/videoEditor/ExportTaskCard.js.map +1 -1
  67. package/lib/features/canvas/videoEditor/agent/AgentCutPane.js +50 -45
  68. package/lib/features/canvas/videoEditor/agent/AgentCutPane.js.map +1 -1
  69. package/lib/features/canvas/videoEditor/exportTask.js +3 -2
  70. package/lib/features/canvas/videoEditor/exportTask.js.map +1 -1
  71. package/lib/features/canvas/videoEditor/formWidgets.js +116 -64
  72. package/lib/features/canvas/videoEditor/formWidgets.js.map +1 -1
  73. package/lib/features/canvas/videoEditor/produce/ProduceVideoSheet.js +77 -63
  74. package/lib/features/canvas/videoEditor/produce/ProduceVideoSheet.js.map +1 -1
  75. package/lib/features/canvas/videoEditor/tools/ToolsDrawer.js +95 -79
  76. package/lib/features/canvas/videoEditor/tools/ToolsDrawer.js.map +1 -1
  77. package/lib/features/design/DesignEditorExperience.js +241 -160
  78. package/lib/features/design/DesignEditorExperience.js.map +1 -1
  79. package/lib/features/design/DesignExperience.js +300 -143
  80. package/lib/features/design/DesignExperience.js.map +1 -1
  81. package/lib/features/health/HealthSyncRunner.js +9 -1
  82. package/lib/features/health/HealthSyncRunner.js.map +1 -1
  83. package/lib/features/health/backgroundSync.js +41 -0
  84. package/lib/features/health/backgroundSync.js.map +1 -0
  85. package/lib/features/health/healthConnectBackground.js +32 -0
  86. package/lib/features/health/healthConnectBackground.js.map +1 -0
  87. package/lib/features/health/healthConnectReader.js +16 -2
  88. package/lib/features/health/healthConnectReader.js.map +1 -1
  89. package/lib/features/health/healthSync.js +31 -3
  90. package/lib/features/health/healthSync.js.map +1 -1
  91. package/lib/features/health/healthSyncStore.js +89 -5
  92. package/lib/features/health/healthSyncStore.js.map +1 -1
  93. package/lib/features/health/healthkitBackground.js +17 -0
  94. package/lib/features/health/healthkitBackground.js.map +1 -0
  95. package/lib/features/health/reader.js.map +1 -1
  96. package/lib/features/health/sessionVault.js +38 -0
  97. package/lib/features/health/sessionVault.js.map +1 -0
  98. package/lib/features/health/syncState.js.map +1 -1
  99. package/lib/features/webbuilder/WebBuilderExperience.js +1 -1
  100. package/lib/features/webbuilder/WebBuilderExperience.js.map +1 -1
  101. package/lib/features/webbuilder/buildFragments.js +3 -2
  102. package/lib/features/webbuilder/buildFragments.js.map +1 -1
  103. package/lib/features/webbuilder/components/PublishSheet.js +1 -1
  104. package/lib/features/webbuilder/components/PublishSheet.js.map +1 -1
  105. package/lib/features/webbuilder/webbuilderPrompts.js +2 -5
  106. package/lib/features/webbuilder/webbuilderPrompts.js.map +1 -1
  107. package/lib/index.js +1 -1
  108. package/lib/index.js.map +1 -1
  109. package/lib/screens/Health/index.js +12 -2
  110. package/lib/screens/Health/index.js.map +1 -1
  111. package/lib/screens/WebBuilderNative/sandboxProxyApi.js +120 -0
  112. package/lib/screens/WebBuilderNative/sandboxProxyApi.js.map +1 -0
  113. package/lib/screens/WebBuilderNative/useRestoredSandboxes.js.map +1 -1
  114. package/lib/screens/WebBuilderNative/useWebBuilderHost.js +26 -4
  115. package/lib/screens/WebBuilderNative/useWebBuilderHost.js.map +1 -1
  116. package/lib/screens/WebBuilderNative/webbuilderTransport.js +110 -7
  117. package/lib/screens/WebBuilderNative/webbuilderTransport.js.map +1 -1
  118. package/package.json +6 -4
@@ -1 +1 @@
1
- {"version":3,"file":"DesignExperience.js","sources":["../../../src/features/design/DesignExperience.tsx"],"sourcesContent":["/**\n * The design studio - templates, saved designs, the editor, and export.\n *\n * Delivery-first architecture, same rules as CanvasExperience: everything\n * here ships OTA inside a federated surface, host seams win when present and\n * the remote builds its own connector/upload seams when they are not\n * (binaries that predate them still get the full studio). Designs persist to\n * the surface index under `surface: design`; export flattens pages through\n * the image connector and can land the results on a canvas board.\n */\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, Linking, Pressable, ScrollView, Share, StyleSheet, Text, View } from 'react-native';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { Image } from 'expo-image';\nimport { friendlyMediaError } from '../canvas/videoEditor/mediaActions';\nimport type { RunMediaAction } from '../canvas/videoEditor/mediaActions';\nimport { createRemoteRunAction, createRemoteUploadMedia } from '../canvas/videoEditor/remoteMediaClient';\nimport { buildBoardState, frameForUpload, newBoardId, type CanvasItem } from '../canvas/canvasCore';\nimport { fetchMyCanvasBoards, newBoardChannelId, pushCanvasBoard } from '../canvas/surfaceIndex';\nimport {\n buildDesignState,\n designSignature,\n newDesignChannelId,\n newDesignDoc,\n parseDesignDoc,\n type DesignKind,\n type PersistedDesignDoc,\n} from './designCore';\nimport { defaultTitle, fetchMyDesigns, pushDesignDoc, type DesignDocSummary } from './designIndex';\nimport { deriveBlankPixelUrl, renderDesignDoc } from './exportDesign';\nimport { DesignEditorExperience } from './DesignEditorExperience';\n\nconst SAVE_DEBOUNCE_MS = 2200;\n\nexport interface DesignExperienceProps {\n token?: string | null;\n indexUrl: string;\n /** Open straight into the editor (from the canvas grid); omit for home. */\n entry?: { kind: DesignKind } | { channelId: string };\n /** Back to whoever embedded the studio (canvas grid, host tab). */\n onClose?: () => void;\n onPickLocalMedia?: () => Promise<CanvasItem[]>;\n uploadMedia?: (localUri: string) => Promise<string>;\n runMediaAction?: RunMediaAction;\n testID?: string;\n}\n\ninterface OpenDoc {\n channelId: string;\n doc: PersistedDesignDoc;\n}\n\nexport function DesignExperience({\n token,\n indexUrl,\n entry,\n onClose,\n onPickLocalMedia,\n uploadMedia,\n runMediaAction,\n testID,\n}: DesignExperienceProps) {\n // Host seams win (they can refresh auth); otherwise the remote builds its\n // own from what it already holds - same fallback rule as the canvas.\n const effectiveRunAction = useMemo<RunMediaAction | undefined>(\n () => runMediaAction ?? (token && indexUrl ? createRemoteRunAction(token, indexUrl) : undefined),\n [runMediaAction, token, indexUrl],\n );\n const effectiveUploadMedia = useMemo(\n () => uploadMedia ?? (token && indexUrl ? createRemoteUploadMedia(token, indexUrl) : undefined),\n [uploadMedia, token, indexUrl],\n );\n\n const [designs, setDesigns] = useState<DesignDocSummary[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [open, setOpen] = useState<OpenDoc | null>(() =>\n entry && 'kind' in entry ? { channelId: newDesignChannelId(), doc: newDesignDoc(entry.kind) } : null,\n );\n const [canvasImages, setCanvasImages] = useState<string[]>([]);\n const [exporting, setExporting] = useState<{ stage: string; error?: string } | null>(null);\n const [results, setResults] = useState<{\n pages: Array<{ name: string; url: string }>;\n savedToCanvas: boolean;\n } | null>(null);\n const lastSavedRef = useRef<Record<string, string>>({});\n\n // Load the caller's designs (home grid; also resolves an entry channelId).\n useEffect(() => {\n if (!token || !indexUrl) {\n setLoading(false);\n return undefined;\n }\n const controller = new AbortController();\n let cancelled = false;\n setLoading(true);\n void fetchMyDesigns({ url: indexUrl, token, signal: controller.signal }).then((result) => {\n if (cancelled) return;\n setDesigns(result.designs);\n setError(result.error);\n setLoading(false);\n if (entry && 'channelId' in entry) {\n const match = result.designs.find((d) => d.channelId === entry.channelId);\n if (match) setOpen({ channelId: match.channelId, doc: match.doc });\n }\n });\n return () => {\n cancelled = true;\n controller.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [token, indexUrl]);\n\n // Image source for the editor's OTA-safe picker: the user's canvas media.\n useEffect(() => {\n if (!token || !indexUrl || !open) return undefined;\n const controller = new AbortController();\n void fetchMyCanvasBoards({ url: indexUrl, token, signal: controller.signal }).then((result) => {\n const urls: string[] = [];\n for (const board of result.boards) {\n for (const item of board.board.items) {\n if (item.type === 'image' && /^https?:/i.test(item.src)) urls.push(item.src);\n }\n }\n setCanvasImages([...new Set(urls)].slice(0, 24));\n });\n return () => controller.abort();\n }, [token, indexUrl, open?.channelId]);\n\n // Debounced persistence - signature-gated, wholesale metadata replace.\n useEffect(() => {\n if (!open || !token || !indexUrl) return undefined;\n const sig = designSignature(open.doc);\n if (lastSavedRef.current[open.channelId] === sig) return undefined;\n const timer = setTimeout(() => {\n const state = buildDesignState(open.doc);\n void pushDesignDoc({ url: indexUrl, token, channelId: open.channelId, doc: state }).then((ok) => {\n if (ok) lastSavedRef.current[open.channelId] = sig;\n });\n }, SAVE_DEBOUNCE_MS);\n return () => clearTimeout(timer);\n }, [open, token, indexUrl]);\n\n const closeEditor = useCallback(() => {\n setOpen(null);\n setResults(null);\n // Straight-to-editor embeds (canvas grid) return to their owner.\n if (entry) onClose?.();\n else if (token && indexUrl) {\n void fetchMyDesigns({ url: indexUrl, token }).then((r) => setDesigns(r.designs));\n }\n }, [entry, onClose, token, indexUrl]);\n\n const exportBusyRef = useRef(false);\n const handleExport = useCallback(\n (doc: PersistedDesignDoc) => {\n const runAction = effectiveRunAction;\n const blankUrl = deriveBlankPixelUrl(indexUrl);\n if (!runAction || !blankUrl) {\n setExporting({ stage: '', error: 'Export needs a signed-in session.' });\n return;\n }\n if (exportBusyRef.current) return;\n exportBusyRef.current = true;\n setExporting({ stage: 'Starting render' });\n void renderDesignDoc(doc.pages, {\n runAction,\n blankUrl,\n uploadMedia: effectiveUploadMedia,\n onProgress: (p) => setExporting(p),\n })\n .then((urls) => {\n setExporting(null);\n setResults({\n pages: urls.map((url, i) => ({ name: doc.pages[i]?.name ?? `Page ${i + 1}`, url })),\n savedToCanvas: false,\n });\n })\n .catch((err: unknown) => {\n setExporting({ stage: '', error: friendlyMediaError(err, 'design export') });\n })\n .finally(() => {\n exportBusyRef.current = false;\n });\n },\n [effectiveRunAction, effectiveUploadMedia, indexUrl],\n );\n\n // Land the rendered pages on a canvas board so they show up beside the\n // user's other media (and on the web canvas).\n const saveResultsToCanvas = useCallback(async () => {\n if (!results || !token || !indexUrl || !open) return;\n const items: CanvasItem[] = results.pages.map((p, i) => ({\n id: newBoardId(),\n type: 'image',\n src: p.url,\n generatedAssetName: `${open.doc.title || defaultTitle(open.doc)} - ${p.name}`,\n zIndex: i + 1,\n ...frameForUpload('image'),\n x: 100 + i * 560,\n }));\n const state = buildBoardState(items, 1, open.doc.title || defaultTitle(open.doc));\n const ok = await pushCanvasBoard({ url: indexUrl, token, channelId: newBoardChannelId(), state });\n setResults((prev) => (prev ? { ...prev, savedToCanvas: ok } : prev));\n }, [results, token, indexUrl, open]);\n\n if (open) {\n return (\n <View style={styles.fill} testID={testID}>\n <DesignEditorExperience\n doc={open.doc}\n onChange={(doc) => setOpen({ channelId: open.channelId, doc })}\n onClose={closeEditor}\n onExport={handleExport}\n onPickLocalMedia={onPickLocalMedia}\n canvasImages={canvasImages}\n />\n {exporting ? (\n <View style={styles.overlay}>\n <View style={styles.overlayCard}>\n {exporting.error ? (\n <>\n <Text style={styles.overlayError}>Export failed</Text>\n <Text style={styles.overlayStage} numberOfLines={4}>\n {exporting.error}\n </Text>\n <Pressable onPress={() => setExporting(null)} style={styles.overlayDismiss}>\n <Text style={styles.overlayDismissLabel}>Dismiss</Text>\n </Pressable>\n </>\n ) : (\n <>\n <ActivityIndicator color=\"#F7F7FA\" />\n <Text style={styles.overlayStage}>{exporting.stage}</Text>\n </>\n )}\n </View>\n </View>\n ) : null}\n {results ? (\n <View style={styles.overlay}>\n <View style={styles.overlayCard}>\n <Text style={styles.overlayTitle}>Design exported</Text>\n {results.pages.map((p) => (\n <View key={p.url} style={styles.resultRow}>\n <Image source={{ uri: p.url }} style={styles.resultThumb} contentFit=\"cover\" />\n <Text style={styles.resultName}>{p.name}</Text>\n <Pressable onPress={() => void Linking.openURL(p.url)} hitSlop={8}>\n <Text style={styles.resultAction}>Open</Text>\n </Pressable>\n <Pressable\n onPress={() => void Share.share({ url: p.url, message: p.url })}\n hitSlop={8}\n >\n <Text style={styles.resultAction}>Share</Text>\n </Pressable>\n </View>\n ))}\n <Pressable\n onPress={() => void saveResultsToCanvas()}\n style={[styles.overlayCta, results.savedToCanvas && styles.overlayCtaDone]}\n disabled={results.savedToCanvas}\n >\n <MaterialCommunityIcons\n name={results.savedToCanvas ? 'check' : 'view-dashboard-outline'}\n size={15}\n color=\"#0f172a\"\n />\n <Text style={styles.overlayCtaLabel}>\n {results.savedToCanvas ? 'Saved to canvas' : 'Save to canvas'}\n </Text>\n </Pressable>\n <Pressable onPress={() => setResults(null)} style={styles.overlayDismiss}>\n <Text style={styles.overlayDismissLabel}>Done</Text>\n </Pressable>\n </View>\n </View>\n ) : null}\n </View>\n );\n }\n\n // Home: template cards + saved designs.\n return (\n <ScrollView style={styles.home} contentContainerStyle={styles.homeContent} testID={testID}>\n <Text style={styles.homeTitle}>Design studio</Text>\n <View style={styles.templateRow}>\n <TemplateCard\n icon=\"shape-square-plus\"\n label=\"Design\"\n hint=\"1080 × 1080\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('design') })}\n />\n <TemplateCard\n icon=\"tshirt-crew-outline\"\n label=\"T-shirt\"\n hint=\"Front & back\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('tshirt') })}\n />\n <TemplateCard\n icon=\"email-outline\"\n label=\"Postcard\"\n hint=\"Front & back\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('postcard') })}\n />\n <TemplateCard\n icon=\"view-carousel-outline\"\n label=\"Carousel\"\n hint=\"3 slides\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('carousel') })}\n />\n </View>\n {loading ? (\n <ActivityIndicator style={styles.homeSpinner} />\n ) : error ? (\n <Text style={styles.homeError}>{error}</Text>\n ) : designs.length ? (\n <>\n <Text style={styles.homeSection}>Your designs</Text>\n {designs.map((d) => (\n <Pressable\n key={d.channelId}\n style={styles.designRow}\n onPress={() => {\n const doc = parseDesignDoc(d.doc) ?? d.doc;\n setOpen({ channelId: d.channelId, doc });\n }}\n >\n <MaterialCommunityIcons\n name={\n d.doc.kind === 'tshirt'\n ? 'tshirt-crew-outline'\n : d.doc.kind === 'postcard'\n ? 'email-outline'\n : d.doc.kind === 'carousel'\n ? 'view-carousel-outline'\n : 'shape-square-plus'\n }\n size={20}\n color=\"#475569\"\n />\n <View style={styles.designMeta}>\n <Text style={styles.designTitle}>{d.title || defaultTitle(d.doc)}</Text>\n <Text style={styles.designSub}>\n {d.doc.pages.length} page{d.doc.pages.length === 1 ? '' : 's'} ·{' '}\n {d.doc.pages.reduce((n, p) => n + p.layers.length, 0)} layers\n </Text>\n </View>\n <MaterialCommunityIcons name=\"chevron-right\" size={20} color=\"#94a3b8\" />\n </Pressable>\n ))}\n </>\n ) : (\n <Text style={styles.homeHint}>Pick a template to start - designs save automatically.</Text>\n )}\n </ScrollView>\n );\n}\n\nfunction TemplateCard({\n icon,\n label,\n hint,\n onPress,\n}: {\n icon: string;\n label: string;\n hint: string;\n onPress: () => void;\n}) {\n return (\n <Pressable onPress={onPress} style={styles.templateCard} accessibilityRole=\"button\">\n <MaterialCommunityIcons name={icon as 'plus'} size={26} color=\"#0f172a\" />\n <Text style={styles.templateLabel}>{label}</Text>\n <Text style={styles.templateHint}>{hint}</Text>\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n overlay: {\n ...StyleSheet.absoluteFillObject,\n backgroundColor: 'rgba(0,0,0,0.65)',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 28,\n },\n overlayCard: {\n width: '100%',\n maxWidth: 360,\n alignItems: 'center',\n gap: 10,\n backgroundColor: '#17171C',\n borderRadius: 16,\n paddingVertical: 20,\n paddingHorizontal: 20,\n },\n overlayTitle: { fontSize: 16, fontWeight: '600', color: '#F7F7FA' },\n overlayStage: { fontSize: 13, color: '#CBD5E1', textAlign: 'center' },\n overlayError: { fontSize: 15, fontWeight: '600', color: '#F87171' },\n overlayDismiss: { marginTop: 4, paddingHorizontal: 14, paddingVertical: 8 },\n overlayDismissLabel: { fontSize: 13, fontWeight: '600', color: '#A5B4FC' },\n overlayCta: {\n marginTop: 6,\n flexDirection: 'row',\n alignItems: 'center',\n gap: 6,\n backgroundColor: '#A5B4FC',\n borderRadius: 16,\n paddingHorizontal: 16,\n paddingVertical: 9,\n },\n overlayCtaDone: { backgroundColor: '#86EFAC' },\n overlayCtaLabel: { fontSize: 13, fontWeight: '600', color: '#0f172a' },\n resultRow: { flexDirection: 'row', alignItems: 'center', gap: 10, alignSelf: 'stretch' },\n resultThumb: { width: 44, height: 44, borderRadius: 8, backgroundColor: '#26262E' },\n resultName: { flex: 1, fontSize: 13, color: '#E2E8F0' },\n resultAction: { fontSize: 13, fontWeight: '600', color: '#A5B4FC', paddingHorizontal: 6 },\n home: { flex: 1, backgroundColor: '#eef1f4' },\n homeContent: { padding: 20, gap: 12 },\n homeTitle: { fontSize: 20, fontWeight: '600', color: '#0f172a' },\n templateRow: { flexDirection: 'row', gap: 12 },\n templateCard: {\n flex: 1,\n alignItems: 'center',\n gap: 4,\n backgroundColor: '#ffffff',\n borderRadius: 14,\n borderWidth: 1,\n borderColor: 'rgba(15,23,42,0.08)',\n paddingVertical: 18,\n },\n templateLabel: { fontSize: 14, fontWeight: '600', color: '#0f172a' },\n templateHint: { fontSize: 11, color: '#94a3b8' },\n homeSection: { marginTop: 8, fontSize: 13, fontWeight: '600', color: '#475569' },\n homeSpinner: { marginTop: 24 },\n homeError: { marginTop: 16, fontSize: 13, color: '#b91c1c' },\n homeHint: { marginTop: 12, fontSize: 13, color: '#64748b' },\n designRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 12,\n backgroundColor: '#ffffff',\n borderRadius: 12,\n borderWidth: 1,\n borderColor: 'rgba(15,23,42,0.08)',\n paddingHorizontal: 14,\n paddingVertical: 12,\n },\n designMeta: { flex: 1 },\n designTitle: { fontSize: 14, fontWeight: '600', color: '#0f172a' },\n designSub: { marginTop: 2, fontSize: 11, color: '#94a3b8' },\n});\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DesignExperience.js","sources":["../../../src/features/design/DesignExperience.tsx"],"sourcesContent":["/**\n * The design studio - templates, saved designs, the editor, and export.\n *\n * Delivery-first architecture, same rules as CanvasExperience: everything\n * here ships OTA inside a federated surface, host seams win when present and\n * the remote builds its own connector/upload seams when they are not\n * (binaries that predate them still get the full studio). Designs persist to\n * the surface index under `surface: design`; export flattens pages through\n * the image connector and can land the results on a canvas board.\n */\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, Linking, ScrollView, Share, StyleSheet, Text, View } from 'react-native';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { Image } from 'expo-image';\nimport { friendlyMediaError } from '../canvas/videoEditor/mediaActions';\nimport type { RunMediaAction } from '../canvas/videoEditor/mediaActions';\nimport { createRemoteRunAction, createRemoteUploadMedia } from '../canvas/videoEditor/remoteMediaClient';\nimport { buildBoardState, frameForUpload, newBoardId, type CanvasItem } from '../canvas/canvasCore';\nimport { fetchMyCanvasBoards, newBoardChannelId, pushCanvasBoard } from '../canvas/surfaceIndex';\nimport { themedStyles, useCanvasTheme, type CanvasPalette } from '../canvas/canvasTheme';\nimport { HIT, Pressable, RADIUS, SectionHeader, Skeleton, SPACE, TYPE, pressedStyle } from '../canvas/canvasUi';\nimport {\n buildDesignState,\n designSignature,\n newDesignChannelId,\n newDesignDoc,\n parseDesignDoc,\n type DesignKind,\n type PersistedDesignDoc,\n} from './designCore';\nimport { defaultTitle, fetchMyDesigns, pushDesignDoc, type DesignDocSummary } from './designIndex';\nimport { deriveBlankPixelUrl, renderDesignDoc } from './exportDesign';\nimport { DesignEditorExperience } from './DesignEditorExperience';\n\nconst SAVE_DEBOUNCE_MS = 2200;\n\nexport interface DesignExperienceProps {\n token?: string | null;\n indexUrl: string;\n /** Open straight into the editor (from the canvas grid); omit for home. */\n entry?: { kind: DesignKind } | { channelId: string };\n /** Back to whoever embedded the studio (canvas grid, host tab). */\n onClose?: () => void;\n onPickLocalMedia?: () => Promise<CanvasItem[]>;\n uploadMedia?: (localUri: string) => Promise<string>;\n runMediaAction?: RunMediaAction;\n testID?: string;\n}\n\ninterface OpenDoc {\n channelId: string;\n doc: PersistedDesignDoc;\n}\n\nexport function DesignExperience({\n token,\n indexUrl,\n entry,\n onClose,\n onPickLocalMedia,\n uploadMedia,\n runMediaAction,\n testID,\n}: DesignExperienceProps) {\n const theme = useCanvasTheme();\n const styles = makeStyles(theme);\n // Host seams win (they can refresh auth); otherwise the remote builds its\n // own from what it already holds - same fallback rule as the canvas.\n const effectiveRunAction = useMemo<RunMediaAction | undefined>(\n () => runMediaAction ?? (token && indexUrl ? createRemoteRunAction(token, indexUrl) : undefined),\n [runMediaAction, token, indexUrl],\n );\n const effectiveUploadMedia = useMemo(\n () => uploadMedia ?? (token && indexUrl ? createRemoteUploadMedia(token, indexUrl) : undefined),\n [uploadMedia, token, indexUrl],\n );\n\n const [designs, setDesigns] = useState<DesignDocSummary[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [open, setOpen] = useState<OpenDoc | null>(() =>\n entry && 'kind' in entry ? { channelId: newDesignChannelId(), doc: newDesignDoc(entry.kind) } : null,\n );\n const [canvasImages, setCanvasImages] = useState<string[]>([]);\n const [exporting, setExporting] = useState<{ stage: string; error?: string } | null>(null);\n const [results, setResults] = useState<{\n pages: Array<{ name: string; url: string }>;\n savedToCanvas: boolean;\n } | null>(null);\n const lastSavedRef = useRef<Record<string, string>>({});\n\n // Load the caller's designs (home grid; also resolves an entry channelId).\n useEffect(() => {\n if (!token || !indexUrl) {\n setLoading(false);\n return undefined;\n }\n const controller = new AbortController();\n let cancelled = false;\n setLoading(true);\n void fetchMyDesigns({ url: indexUrl, token, signal: controller.signal }).then((result) => {\n if (cancelled) return;\n setDesigns(result.designs);\n setError(result.error);\n setLoading(false);\n if (entry && 'channelId' in entry) {\n const match = result.designs.find((d) => d.channelId === entry.channelId);\n if (match) setOpen({ channelId: match.channelId, doc: match.doc });\n }\n });\n return () => {\n cancelled = true;\n controller.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [token, indexUrl]);\n\n // Image source for the editor's OTA-safe picker: the user's canvas media.\n useEffect(() => {\n if (!token || !indexUrl || !open) return undefined;\n const controller = new AbortController();\n void fetchMyCanvasBoards({ url: indexUrl, token, signal: controller.signal }).then((result) => {\n const urls: string[] = [];\n for (const board of result.boards) {\n for (const item of board.board.items) {\n if (item.type === 'image' && /^https?:/i.test(item.src)) urls.push(item.src);\n }\n }\n setCanvasImages([...new Set(urls)].slice(0, 24));\n });\n return () => controller.abort();\n }, [token, indexUrl, open?.channelId]);\n\n // Debounced persistence - signature-gated, wholesale metadata replace.\n useEffect(() => {\n if (!open || !token || !indexUrl) return undefined;\n const sig = designSignature(open.doc);\n if (lastSavedRef.current[open.channelId] === sig) return undefined;\n const timer = setTimeout(() => {\n const state = buildDesignState(open.doc);\n void pushDesignDoc({ url: indexUrl, token, channelId: open.channelId, doc: state }).then((ok) => {\n if (ok) lastSavedRef.current[open.channelId] = sig;\n });\n }, SAVE_DEBOUNCE_MS);\n return () => clearTimeout(timer);\n }, [open, token, indexUrl]);\n\n const closeEditor = useCallback(() => {\n setOpen(null);\n setResults(null);\n // Straight-to-editor embeds (canvas grid) return to their owner.\n if (entry) onClose?.();\n else if (token && indexUrl) {\n void fetchMyDesigns({ url: indexUrl, token }).then((r) => setDesigns(r.designs));\n }\n }, [entry, onClose, token, indexUrl]);\n\n const exportBusyRef = useRef(false);\n const handleExport = useCallback(\n (doc: PersistedDesignDoc) => {\n const runAction = effectiveRunAction;\n const blankUrl = deriveBlankPixelUrl(indexUrl);\n if (!runAction || !blankUrl) {\n setExporting({ stage: '', error: 'Export needs a signed-in session.' });\n return;\n }\n if (exportBusyRef.current) return;\n exportBusyRef.current = true;\n setExporting({ stage: 'Starting render' });\n void renderDesignDoc(doc.pages, {\n runAction,\n blankUrl,\n uploadMedia: effectiveUploadMedia,\n onProgress: (p) => setExporting(p),\n })\n .then((urls) => {\n setExporting(null);\n setResults({\n pages: urls.map((url, i) => ({ name: doc.pages[i]?.name ?? `Page ${i + 1}`, url })),\n savedToCanvas: false,\n });\n })\n .catch((err: unknown) => {\n setExporting({ stage: '', error: friendlyMediaError(err, 'design export') });\n })\n .finally(() => {\n exportBusyRef.current = false;\n });\n },\n [effectiveRunAction, effectiveUploadMedia, indexUrl],\n );\n\n // Land the rendered pages on a canvas board so they show up beside the\n // user's other media (and on the web canvas).\n const saveResultsToCanvas = useCallback(async () => {\n if (!results || !token || !indexUrl || !open) return;\n const items: CanvasItem[] = results.pages.map((p, i) => ({\n id: newBoardId(),\n type: 'image',\n src: p.url,\n generatedAssetName: `${open.doc.title || defaultTitle(open.doc)} - ${p.name}`,\n zIndex: i + 1,\n ...frameForUpload('image'),\n x: 100 + i * 560,\n }));\n const state = buildBoardState(items, 1, open.doc.title || defaultTitle(open.doc));\n const ok = await pushCanvasBoard({ url: indexUrl, token, channelId: newBoardChannelId(), state });\n setResults((prev) => (prev ? { ...prev, savedToCanvas: ok } : prev));\n }, [results, token, indexUrl, open]);\n\n if (open) {\n return (\n <View style={styles.fill} testID={testID}>\n <DesignEditorExperience\n doc={open.doc}\n onChange={(doc) => setOpen({ channelId: open.channelId, doc })}\n onClose={closeEditor}\n onExport={handleExport}\n onPickLocalMedia={onPickLocalMedia}\n canvasImages={canvasImages}\n />\n {exporting ? (\n <View style={styles.overlay}>\n <View style={styles.overlayCard}>\n {exporting.error ? (\n <>\n <Text style={styles.overlayError}>Export failed</Text>\n <Text style={styles.overlayStage} numberOfLines={4}>\n {exporting.error}\n </Text>\n <Pressable onPress={() => setExporting(null)} style={styles.overlayDismiss}>\n <Text style={styles.overlayDismissLabel}>Dismiss</Text>\n </Pressable>\n </>\n ) : (\n <>\n <ActivityIndicator color={theme.foreground} />\n <Text style={styles.overlayStage}>{exporting.stage}</Text>\n </>\n )}\n </View>\n </View>\n ) : null}\n {results ? (\n <View style={styles.overlay}>\n <View style={styles.overlayCard}>\n <Text style={styles.overlayTitle}>Design exported</Text>\n {results.pages.map((p) => (\n <View key={p.url} style={styles.resultRow}>\n <Image source={{ uri: p.url }} style={styles.resultThumb} contentFit=\"cover\" />\n <Text style={styles.resultName}>{p.name}</Text>\n <Pressable onPress={() => void Linking.openURL(p.url)} hitSlop={8}>\n <Text style={styles.resultAction}>Open</Text>\n </Pressable>\n <Pressable\n onPress={() => void Share.share({ url: p.url, message: p.url })}\n hitSlop={8}\n >\n <Text style={styles.resultAction}>Share</Text>\n </Pressable>\n </View>\n ))}\n <Pressable\n onPress={() => void saveResultsToCanvas()}\n style={[styles.overlayCta, results.savedToCanvas && styles.overlayCtaDone]}\n disabled={results.savedToCanvas}\n >\n <MaterialCommunityIcons\n name={results.savedToCanvas ? 'check' : 'view-dashboard-outline'}\n size={18}\n color={results.savedToCanvas ? theme.foreground : theme.primaryForeground}\n />\n <Text\n style={[\n styles.overlayCtaLabel,\n results.savedToCanvas && styles.overlayCtaLabelDone,\n ]}\n >\n {results.savedToCanvas ? 'Saved to canvas' : 'Save to canvas'}\n </Text>\n </Pressable>\n <Pressable onPress={() => setResults(null)} style={styles.overlayDismiss}>\n <Text style={styles.overlayDismissLabel}>Done</Text>\n </Pressable>\n </View>\n </View>\n ) : null}\n </View>\n );\n }\n\n // Home: template cards + saved designs.\n return (\n <ScrollView style={styles.home} contentContainerStyle={styles.homeContent} testID={testID}>\n <Text style={styles.homeTitle} accessibilityRole=\"header\">\n Design studio\n </Text>\n <SectionHeader title=\"Start a design\" />\n <View style={styles.templateRow}>\n <TemplateCard\n icon=\"shape-square-plus\"\n label=\"Design\"\n hint=\"1080 × 1080\"\n shape=\"design\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('design') })}\n />\n <TemplateCard\n icon=\"tshirt-crew-outline\"\n label=\"T-shirt\"\n hint=\"Front & back\"\n shape=\"tshirt\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('tshirt') })}\n />\n <TemplateCard\n icon=\"email-outline\"\n label=\"Postcard\"\n hint=\"Front & back\"\n shape=\"postcard\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('postcard') })}\n />\n <TemplateCard\n icon=\"view-carousel-outline\"\n label=\"Carousel\"\n hint=\"3 slides\"\n shape=\"carousel\"\n onPress={() => setOpen({ channelId: newDesignChannelId(), doc: newDesignDoc('carousel') })}\n />\n </View>\n {loading ? (\n <View style={styles.designGrid}>\n {[0, 1].map((i) => (\n <View key={i} style={styles.designTile}>\n <View style={styles.designCover}>\n <Skeleton radius={RADIUS.lg} />\n </View>\n <Skeleton style={styles.skeletonLine} radius={4} />\n </View>\n ))}\n </View>\n ) : error ? (\n <Text style={styles.homeError}>{error}</Text>\n ) : designs.length ? (\n <>\n <SectionHeader title=\"Your designs\" detail={String(designs.length)} style={styles.sectionGap} />\n <View style={styles.designGrid}>\n {designs.map((d) => (\n <Pressable\n key={d.channelId}\n style={({ pressed }) => [styles.designTile, pressedStyle(pressed)]}\n onPress={() => {\n const doc = parseDesignDoc(d.doc) ?? d.doc;\n setOpen({ channelId: d.channelId, doc });\n }}\n >\n <View style={styles.designCover}>\n <DesignPreview doc={d.doc} />\n </View>\n <Text style={styles.designTitle} numberOfLines={1}>\n {d.title || defaultTitle(d.doc)}\n </Text>\n <Text style={styles.designSub} numberOfLines={1}>\n {d.doc.pages.length} page{d.doc.pages.length === 1 ? '' : 's'} ·{' '}\n {d.doc.pages.reduce((n, p) => n + p.layers.length, 0)} layers\n </Text>\n </Pressable>\n ))}\n </View>\n </>\n ) : (\n <Text style={styles.homeHint}>Pick a template to start. Designs save automatically.</Text>\n )}\n </ScrollView>\n );\n}\n\ntype TemplateShape = 'design' | 'tshirt' | 'postcard' | 'carousel';\n\nfunction TemplateCard({\n icon,\n label,\n hint,\n shape,\n onPress,\n}: {\n icon: string;\n label: string;\n hint: string;\n shape: TemplateShape;\n onPress: () => void;\n}) {\n const theme = useCanvasTheme();\n const styles = makeStyles(theme);\n return (\n <Pressable\n onPress={onPress}\n style={({ pressed }) => [styles.templateCard, pressedStyle(pressed)]}\n accessibilityRole=\"button\"\n >\n <View style={styles.templateCover}>\n {shape === 'design' ? (\n // The everyday start: the one lime action on the home.\n <View style={styles.newPlus}>\n <MaterialCommunityIcons name=\"plus\" size={24} color={theme.primaryForeground} />\n </View>\n ) : shape === 'tshirt' ? (\n <MaterialCommunityIcons name={icon as 'plus'} size={56} color={theme.mutedForeground} />\n ) : shape === 'postcard' ? (\n <View style={[styles.sketchPage, { width: '72%', aspectRatio: 1.5 }]}>\n <SketchBars />\n </View>\n ) : (\n <View style={styles.carouselStack}>\n <View style={[styles.sketchPage, styles.carouselBack]} />\n <View style={[styles.sketchPage, styles.carouselFront]}>\n <SketchBars />\n </View>\n </View>\n )}\n </View>\n <Text style={styles.templateLabel}>{label}</Text>\n <Text style={styles.templateHint}>{hint}</Text>\n </Pressable>\n );\n}\n\n/** A faint layout sketch (title, lines, picture) for a page with nothing on it. */\nfunction SketchBars({ label }: { label?: string }) {\n const styles = makeStyles(useCanvasTheme());\n return (\n <View style={styles.sketch}>\n <View style={[styles.sketchBar, { width: '62%', height: '9%' }]} />\n <View style={[styles.sketchBar, { width: '86%', height: '5%' }]} />\n <View style={[styles.sketchBar, { width: '70%', height: '5%' }]} />\n <View style={[styles.sketchBar, styles.sketchBlock]}>\n <MaterialCommunityIcons name=\"image-outline\" size={14} color=\"#b4b4bb\" />\n </View>\n {label ? <Text style={styles.sketchLabel}>{label}</Text> : null}\n </View>\n );\n}\n\nconst PREVIEW_BOX = 120;\n\n/** A saved design's first page, scaled into its tile as laid out. */\nfunction DesignPreview({ doc }: { doc: PersistedDesignDoc }) {\n const theme = useCanvasTheme();\n const styles = makeStyles(theme);\n const page = doc.pages[0];\n if (!page || !(page.width > 0) || !(page.height > 0)) {\n return <MaterialCommunityIcons name=\"shape-outline\" size={28} color={theme.mutedForeground} />;\n }\n const scale = Math.min(PREVIEW_BOX / page.width, PREVIEW_BOX / page.height);\n const layers = [...page.layers].sort((a, b) => a.zIndex - b.zIndex);\n return (\n <View\n pointerEvents=\"none\"\n style={[\n styles.previewPage,\n {\n width: page.width * scale,\n height: page.height * scale,\n backgroundColor: page.background || '#ffffff',\n },\n ]}\n >\n {layers.length === 0 ? (\n <SketchBars label={defaultTitle(doc)} />\n ) : (\n layers.map((layer) => {\n const box = {\n position: 'absolute' as const,\n left: layer.x * scale,\n top: layer.y * scale,\n width: layer.width * scale,\n height: layer.height * scale,\n opacity: layer.opacity,\n };\n if (layer.type === 'image') {\n return <Image key={layer.id} source={{ uri: layer.src }} style={box} contentFit=\"cover\" />;\n }\n if (layer.type === 'shape') {\n return (\n <View\n key={layer.id}\n style={[box, { backgroundColor: layer.fill, borderRadius: layer.cornerRadius * scale }]}\n />\n );\n }\n return (\n <Text\n key={layer.id}\n numberOfLines={3}\n style={[\n box,\n {\n color: layer.color,\n fontSize: Math.max(4, layer.fontSize * scale),\n lineHeight: Math.max(5, layer.fontSize * scale * 1.15),\n fontWeight: layer.bold ? '700' : '400',\n textAlign: layer.align,\n },\n ]}\n >\n {layer.text}\n </Text>\n );\n })\n )}\n </View>\n );\n}\n\nconst GUTTER = SPACE.lg + 4;\n\nconst makeStyles = themedStyles((theme: CanvasPalette) =>\n StyleSheet.create({\n fill: { flex: 1 },\n overlay: {\n ...StyleSheet.absoluteFillObject,\n backgroundColor: 'rgba(0,0,0,0.55)',\n alignItems: 'center',\n justifyContent: 'center',\n padding: SPACE.xl,\n },\n overlayCard: {\n width: '100%',\n maxWidth: 360,\n alignItems: 'center',\n gap: SPACE.md,\n backgroundColor: theme.surface,\n borderRadius: RADIUS.xl,\n paddingVertical: SPACE.xl,\n paddingHorizontal: SPACE.xl - 4,\n },\n overlayTitle: { ...TYPE.title, color: theme.foreground },\n overlayStage: { ...TYPE.label, fontWeight: '500', color: theme.mutedForeground, textAlign: 'center' },\n overlayError: { ...TYPE.title, color: theme.destructiveText },\n overlayDismiss: {\n minHeight: HIT,\n paddingHorizontal: SPACE.lg,\n alignItems: 'center',\n justifyContent: 'center',\n },\n overlayDismissLabel: { ...TYPE.label, fontSize: 15, color: theme.foreground },\n overlayCta: {\n marginTop: SPACE.xs,\n alignSelf: 'stretch',\n minHeight: HIT,\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n gap: SPACE.sm,\n backgroundColor: theme.primary,\n borderRadius: RADIUS.pill,\n paddingHorizontal: SPACE.lg,\n },\n overlayCtaDone: { backgroundColor: theme.card },\n overlayCtaLabel: { ...TYPE.label, fontSize: 15, color: theme.primaryForeground },\n overlayCtaLabelDone: { color: theme.foreground },\n resultRow: { flexDirection: 'row', alignItems: 'center', gap: SPACE.md, alignSelf: 'stretch' },\n resultThumb: { width: 48, height: 48, borderRadius: RADIUS.sm, backgroundColor: theme.card },\n resultName: { ...TYPE.label, flex: 1, color: theme.foreground },\n resultAction: { ...TYPE.label, color: theme.mutedForeground, paddingHorizontal: SPACE.xs + 2 },\n home: { flex: 1, backgroundColor: theme.background },\n homeContent: { paddingHorizontal: GUTTER, paddingTop: SPACE.lg, paddingBottom: SPACE.xxl },\n homeTitle: { ...TYPE.heading, color: theme.foreground, marginBottom: SPACE.xl - 4 },\n sectionGap: { marginTop: SPACE.xl },\n templateRow: { flexDirection: 'row', flexWrap: 'wrap', columnGap: SPACE.md, rowGap: SPACE.lg },\n templateCard: { flexBasis: '46%', flexGrow: 1 },\n templateCover: {\n aspectRatio: 1.25,\n borderRadius: RADIUS.lg,\n overflow: 'hidden',\n backgroundColor: theme.card,\n alignItems: 'center',\n justifyContent: 'center',\n marginBottom: SPACE.sm,\n },\n newPlus: {\n width: 48,\n height: 48,\n borderRadius: 24,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme.primary,\n },\n // Sketch pages are paper, not chrome: fixed white with a soft lift.\n sketchPage: {\n backgroundColor: '#ffffff',\n borderRadius: 3,\n overflow: 'hidden',\n shadowColor: '#000',\n shadowOpacity: theme.isDark ? 0 : 0.1,\n shadowRadius: 6,\n shadowOffset: { width: 0, height: 2 },\n elevation: 2,\n },\n carouselStack: { width: '52%', aspectRatio: 1 },\n carouselBack: {\n position: 'absolute',\n top: -6,\n left: 10,\n right: -10,\n bottom: 6,\n opacity: theme.isDark ? 0.35 : 0.7,\n },\n carouselFront: { ...StyleSheet.absoluteFillObject },\n sketch: { ...StyleSheet.absoluteFillObject, padding: '10%', gap: 5 },\n sketchBar: { borderRadius: 3, backgroundColor: '#ececef' },\n sketchBlock: { flex: 1, width: '100%', marginTop: 3, alignItems: 'center', justifyContent: 'center' },\n sketchLabel: { fontSize: 10, fontWeight: '600', color: '#a1a1aa', textAlign: 'center' },\n templateLabel: { ...TYPE.label, fontSize: 14, color: theme.foreground },\n templateHint: { ...TYPE.caption, marginTop: 1, color: theme.mutedForeground },\n homeError: { ...TYPE.label, fontWeight: '500', marginTop: SPACE.xl, color: theme.destructiveText },\n homeHint: { ...TYPE.label, fontWeight: '500', marginTop: SPACE.xl, color: theme.mutedForeground },\n designGrid: { flexDirection: 'row', flexWrap: 'wrap', columnGap: SPACE.md, rowGap: SPACE.lg },\n designTile: { flexBasis: '46%', flexGrow: 1, maxWidth: '50%' },\n designCover: {\n aspectRatio: 1,\n borderRadius: RADIUS.lg,\n overflow: 'hidden',\n backgroundColor: theme.card,\n alignItems: 'center',\n justifyContent: 'center',\n marginBottom: SPACE.sm,\n },\n previewPage: {\n overflow: 'hidden',\n borderRadius: 3,\n shadowColor: '#000',\n shadowOpacity: theme.isDark ? 0 : 0.1,\n shadowRadius: 6,\n shadowOffset: { width: 0, height: 2 },\n elevation: 2,\n },\n skeletonLine: { height: 12, width: '60%' },\n designTitle: { ...TYPE.label, fontSize: 14, color: theme.foreground },\n designSub: { ...TYPE.caption, marginTop: 1, color: theme.mutedForeground },\n }),\n);\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {jsx}from'react/jsx-runtime';import {useEffect}from'react';import {AppState}from'react-native';import {useSelector}from'react-redux';import {isAuthenticatedSelector}from'@adminide-stack/user-auth0-client';import {config}from'../../config/env-config.js';import {useCanvasSession}from'../canvas/useCanvasSession.js';import {sessionIdentityFromToken}from'../canvas/sessionIdentity.js';import {useHealthSync,healthSyncStore}from'./healthSync.js';import {useDevices,deviceStore}from'../devices/devices.js';function ConnectedHealthSync({
1
+ import {jsx}from'react/jsx-runtime';import {useEffect,useRef}from'react';import {AppState}from'react-native';import {useSelector}from'react-redux';import {isAuthenticatedSelector}from'@adminide-stack/user-auth0-client';import {config}from'../../config/env-config.js';import {useCanvasSession}from'../canvas/useCanvasSession.js';import {sessionIdentityFromToken}from'../canvas/sessionIdentity.js';import {useHealthSync,healthSyncStore}from'./healthSync.js';import {useDevices,deviceStore}from'../devices/devices.js';function ConnectedHealthSync({
2
2
  syncOn
3
3
  }) {
4
4
  const {
@@ -43,6 +43,14 @@ function HealthSyncRunner() {
43
43
  useEffect(() => {
44
44
  void healthSyncStore.load();
45
45
  }, []);
46
+ const wasAuthenticated = useRef(false);
47
+ useEffect(() => {
48
+ if (isAuthenticated) wasAuthenticated.current = true;
49
+ else if (wasAuthenticated.current) {
50
+ wasAuthenticated.current = false;
51
+ void healthSyncStore.forgetSavedSession();
52
+ }
53
+ }, [isAuthenticated]);
46
54
  const syncOn = snapshot.loaded && snapshot.sync.connected;
47
55
  const hasDevices = devices.loaded && (devices.devices.length > 0 || devices.pendingDates.length > 0);
48
56
  if (!isAuthenticated || !syncOn && !hasDevices) return null;
@@ -1 +1 @@
1
- {"version":3,"file":"HealthSyncRunner.js","sources":["../../../src/features/health/HealthSyncRunner.tsx"],"sourcesContent":["/**\n * Mounted once at the app root (portable-devices/mobile App.tsx), next to\n * the invite deep-link handlers. When health sync is on, or a Bluetooth device\n * is paired, and someone is signed in, it keeps a platform session for the\n * Fitness backend: health syncs on every return to the foreground (throttled\n * in the store), and device readings waiting to be sent go out. Renders nothing.\n *\n * Signed out, or neither sync nor a device: no session is minted and nothing runs.\n */\nimport { useEffect } from 'react';\nimport { AppState } from 'react-native';\nimport { useSelector } from 'react-redux';\nimport { isAuthenticatedSelector } from '@adminide-stack/user-auth0-client';\nimport { config } from '../../config/env-config';\nimport { useCanvasSession } from '../canvas/useCanvasSession';\nimport { sessionIdentityFromToken } from '../canvas/sessionIdentity';\nimport { healthSyncStore, useHealthSync } from './healthSync';\nimport { deviceStore, useDevices } from '../devices/devices';\n\nfunction ConnectedHealthSync({ syncOn }: { syncOn: boolean }) {\n // The surface session: the same platform system JWT the canvas and the\n // other surface backends accept, renewed before it expires.\n const { token, error } = useCanvasSession();\n const url = config.FITNESS_BACKEND_GRAPHQL_URL;\n\n useEffect(() => {\n if (token && url) {\n const session = { token, url, owner: sessionIdentityFromToken(token).user };\n healthSyncStore.setSession(session);\n deviceStore.setSession(session);\n } else {\n healthSyncStore.setSession(\n null,\n !url ? 'The Fitness service address is not configured in this build.' : error,\n );\n deviceStore.setSession(null);\n }\n }, [token, url, error]);\n\n useEffect(\n () => () => {\n healthSyncStore.setSession(null);\n deviceStore.setSession(null);\n },\n [],\n );\n\n useEffect(() => {\n if (syncOn) void healthSyncStore.syncIfDue();\n void deviceStore.flush();\n const sub = AppState.addEventListener('change', (next) => {\n if (next !== 'active') return;\n if (syncOn) void healthSyncStore.syncIfDue();\n void deviceStore.flush();\n });\n return () => sub.remove();\n }, [syncOn]);\n\n return null;\n}\n\nexport function HealthSyncRunner() {\n const snapshot = useHealthSync();\n const devices = useDevices();\n const isAuthenticated = useSelector(isAuthenticatedSelector);\n\n useEffect(() => {\n void healthSyncStore.load();\n }, []);\n\n const syncOn = snapshot.loaded && snapshot.sync.connected;\n const hasDevices = devices.loaded && (devices.devices.length > 0 || devices.pendingDates.length > 0);\n if (!isAuthenticated || (!syncOn && !hasDevices)) return null;\n return <ConnectedHealthSync syncOn={syncOn} />;\n}\n"],"names":[],"mappings":"4fAkBA,SAAS,mBAAoB,CAAA;AAAA,EAC3B;AACF,CAEG,EAAA;AAGD,EAAM,MAAA;AAAA,IACJ,KAAA;AAAA,IACA;AAAA,MACE,gBAAiB,EAAA;AACrB,EAAA,MAAM,MAAM,MAAO,CAAA,2BAAA;AACnB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAS,GAAK,EAAA;AAChB,MAAA,MAAM,OAAU,GAAA;AAAA,QACd,KAAA;AAAA,QACA,GAAA;AAAA,QACA,KAAA,EAAO,wBAAyB,CAAA,KAAK,CAAE,CAAA;AAAA,OACzC;AACA,MAAA,eAAA,CAAgB,WAAW,OAAO,CAAA;AAClC,MAAA,WAAA,CAAY,WAAW,OAAO,CAAA;AAAA,KACzB,MAAA;AACL,MAAA,eAAA,CAAgB,UAAW,CAAA,IAAA,EAAM,CAAC,GAAA,GAAM,iEAAiE,KAAK,CAAA;AAC9G,MAAA,WAAA,CAAY,WAAW,IAAI,CAAA;AAAA;AAC7B,GACC,EAAA,CAAC,KAAO,EAAA,GAAA,EAAK,KAAK,CAAC,CAAA;AACtB,EAAA,SAAA,CAAU,MAAM,MAAM;AACpB,IAAA,eAAA,CAAgB,WAAW,IAAI,CAAA;AAC/B,IAAA,WAAA,CAAY,WAAW,IAAI,CAAA;AAAA,GAC7B,EAAG,EAAE,CAAA;AACL,EAAA,SAAA,CAAU,MAAM;AACd,IAAI,IAAA,MAAA,EAAa,KAAA,eAAA,CAAgB,SAAU,EAAA;AAC3C,IAAA,KAAK,YAAY,KAAM,EAAA;AACvB,IAAA,MAAM,GAAM,GAAA,QAAA,CAAS,gBAAiB,CAAA,QAAA,EAAU,CAAQ,IAAA,KAAA;AACtD,MAAA,IAAI,SAAS,QAAU,EAAA;AACvB,MAAI,IAAA,MAAA,EAAa,KAAA,eAAA,CAAgB,SAAU,EAAA;AAC3C,MAAA,KAAK,YAAY,KAAM,EAAA;AAAA,KACxB,CAAA;AACD,IAAO,OAAA,MAAM,IAAI,MAAO,EAAA;AAAA,GAC1B,EAAG,CAAC,MAAM,CAAC,CAAA;AACX,EAAO,OAAA,IAAA;AACT;AACO,SAAS,gBAAmB,GAAA;AACjC,EAAA,MAAM,WAAW,aAAc,EAAA;AAC/B,EAAA,MAAM,UAAU,UAAW,EAAA;AAC3B,EAAM,MAAA,eAAA,GAAkB,YAAY,uBAAuB,CAAA;AAC3D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,KAAK,gBAAgB,IAAK,EAAA;AAAA,GAC5B,EAAG,EAAE,CAAA;AACL,EAAA,MAAM,MAAS,GAAA,QAAA,CAAS,MAAU,IAAA,QAAA,CAAS,IAAK,CAAA,SAAA;AAChD,EAAM,MAAA,UAAA,GAAa,QAAQ,MAAW,KAAA,OAAA,CAAQ,QAAQ,MAAS,GAAA,CAAA,IAAK,OAAQ,CAAA,YAAA,CAAa,MAAS,GAAA,CAAA,CAAA;AAClG,EAAA,IAAI,CAAC,eAAmB,IAAA,CAAC,MAAU,IAAA,CAAC,YAAmB,OAAA,IAAA;AACvD,EAAO,uBAAA,GAAA,CAAC,uBAAoB,MAAgB,EAAA,CAAA;AAC9C"}
1
+ {"version":3,"file":"HealthSyncRunner.js","sources":["../../../src/features/health/HealthSyncRunner.tsx"],"sourcesContent":["/**\n * Mounted once at the app root (portable-devices/mobile App.tsx), next to\n * the invite deep-link handlers. When health sync is on, or a Bluetooth device\n * is paired, and someone is signed in, it keeps a platform session for the\n * Fitness backend: health syncs on every return to the foreground (throttled\n * in the store), and device readings waiting to be sent go out. Renders nothing.\n *\n * Signed out, or neither sync nor a device: no session is minted and nothing runs.\n * Signing out also forgets the session kept for background syncs (backgroundSync.ts).\n */\nimport { useEffect, useRef } from 'react';\nimport { AppState } from 'react-native';\nimport { useSelector } from 'react-redux';\nimport { isAuthenticatedSelector } from '@adminide-stack/user-auth0-client';\nimport { config } from '../../config/env-config';\nimport { useCanvasSession } from '../canvas/useCanvasSession';\nimport { sessionIdentityFromToken } from '../canvas/sessionIdentity';\nimport { healthSyncStore, useHealthSync } from './healthSync';\nimport { deviceStore, useDevices } from '../devices/devices';\n\nfunction ConnectedHealthSync({ syncOn }: { syncOn: boolean }) {\n // The surface session: the same platform system JWT the canvas and the\n // other surface backends accept, renewed before it expires.\n const { token, error } = useCanvasSession();\n const url = config.FITNESS_BACKEND_GRAPHQL_URL;\n\n useEffect(() => {\n if (token && url) {\n const session = { token, url, owner: sessionIdentityFromToken(token).user };\n healthSyncStore.setSession(session);\n deviceStore.setSession(session);\n } else {\n healthSyncStore.setSession(\n null,\n !url ? 'The Fitness service address is not configured in this build.' : error,\n );\n deviceStore.setSession(null);\n }\n }, [token, url, error]);\n\n useEffect(\n () => () => {\n healthSyncStore.setSession(null);\n deviceStore.setSession(null);\n },\n [],\n );\n\n useEffect(() => {\n if (syncOn) void healthSyncStore.syncIfDue();\n void deviceStore.flush();\n const sub = AppState.addEventListener('change', (next) => {\n if (next !== 'active') return;\n if (syncOn) void healthSyncStore.syncIfDue();\n void deviceStore.flush();\n });\n return () => sub.remove();\n }, [syncOn]);\n\n return null;\n}\n\nexport function HealthSyncRunner() {\n const snapshot = useHealthSync();\n const devices = useDevices();\n const isAuthenticated = useSelector(isAuthenticatedSelector);\n\n useEffect(() => {\n void healthSyncStore.load();\n }, []);\n\n // Only a real sign-out (signed in, then not): a cold start reads false before auth hydrates.\n const wasAuthenticated = useRef(false);\n useEffect(() => {\n if (isAuthenticated) wasAuthenticated.current = true;\n else if (wasAuthenticated.current) {\n wasAuthenticated.current = false;\n void healthSyncStore.forgetSavedSession();\n }\n }, [isAuthenticated]);\n\n const syncOn = snapshot.loaded && snapshot.sync.connected;\n const hasDevices = devices.loaded && (devices.devices.length > 0 || devices.pendingDates.length > 0);\n if (!isAuthenticated || (!syncOn && !hasDevices)) return null;\n return <ConnectedHealthSync syncOn={syncOn} />;\n}\n"],"names":[],"mappings":"mgBAmBA,SAAS,mBAAoB,CAAA;AAAA,EAC3B;AACF,CAEG,EAAA;AAGD,EAAM,MAAA;AAAA,IACJ,KAAA;AAAA,IACA;AAAA,MACE,gBAAiB,EAAA;AACrB,EAAA,MAAM,MAAM,MAAO,CAAA,2BAAA;AACnB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAS,GAAK,EAAA;AAChB,MAAA,MAAM,OAAU,GAAA;AAAA,QACd,KAAA;AAAA,QACA,GAAA;AAAA,QACA,KAAA,EAAO,wBAAyB,CAAA,KAAK,CAAE,CAAA;AAAA,OACzC;AACA,MAAA,eAAA,CAAgB,WAAW,OAAO,CAAA;AAClC,MAAA,WAAA,CAAY,WAAW,OAAO,CAAA;AAAA,KACzB,MAAA;AACL,MAAA,eAAA,CAAgB,UAAW,CAAA,IAAA,EAAM,CAAC,GAAA,GAAM,iEAAiE,KAAK,CAAA;AAC9G,MAAA,WAAA,CAAY,WAAW,IAAI,CAAA;AAAA;AAC7B,GACC,EAAA,CAAC,KAAO,EAAA,GAAA,EAAK,KAAK,CAAC,CAAA;AACtB,EAAA,SAAA,CAAU,MAAM,MAAM;AACpB,IAAA,eAAA,CAAgB,WAAW,IAAI,CAAA;AAC/B,IAAA,WAAA,CAAY,WAAW,IAAI,CAAA;AAAA,GAC7B,EAAG,EAAE,CAAA;AACL,EAAA,SAAA,CAAU,MAAM;AACd,IAAI,IAAA,MAAA,EAAa,KAAA,eAAA,CAAgB,SAAU,EAAA;AAC3C,IAAA,KAAK,YAAY,KAAM,EAAA;AACvB,IAAA,MAAM,GAAM,GAAA,QAAA,CAAS,gBAAiB,CAAA,QAAA,EAAU,CAAQ,IAAA,KAAA;AACtD,MAAA,IAAI,SAAS,QAAU,EAAA;AACvB,MAAI,IAAA,MAAA,EAAa,KAAA,eAAA,CAAgB,SAAU,EAAA;AAC3C,MAAA,KAAK,YAAY,KAAM,EAAA;AAAA,KACxB,CAAA;AACD,IAAO,OAAA,MAAM,IAAI,MAAO,EAAA;AAAA,GAC1B,EAAG,CAAC,MAAM,CAAC,CAAA;AACX,EAAO,OAAA,IAAA;AACT;AACO,SAAS,gBAAmB,GAAA;AACjC,EAAA,MAAM,WAAW,aAAc,EAAA;AAC/B,EAAA,MAAM,UAAU,UAAW,EAAA;AAC3B,EAAM,MAAA,eAAA,GAAkB,YAAY,uBAAuB,CAAA;AAC3D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,KAAK,gBAAgB,IAAK,EAAA;AAAA,GAC5B,EAAG,EAAE,CAAA;AAGL,EAAM,MAAA,gBAAA,GAAmB,OAAO,KAAK,CAAA;AACrC,EAAA,SAAA,CAAU,MAAM;AACd,IAAI,IAAA,eAAA,mBAAkC,OAAU,GAAA,IAAA;AAAA,SAAA,IAAc,iBAAiB,OAAS,EAAA;AACtF,MAAA,gBAAA,CAAiB,OAAU,GAAA,KAAA;AAC3B,MAAA,KAAK,gBAAgB,kBAAmB,EAAA;AAAA;AAC1C,GACF,EAAG,CAAC,eAAe,CAAC,CAAA;AACpB,EAAA,MAAM,MAAS,GAAA,QAAA,CAAS,MAAU,IAAA,QAAA,CAAS,IAAK,CAAA,SAAA;AAChD,EAAM,MAAA,UAAA,GAAa,QAAQ,MAAW,KAAA,OAAA,CAAQ,QAAQ,MAAS,GAAA,CAAA,IAAK,OAAQ,CAAA,YAAA,CAAa,MAAS,GAAA,CAAA,CAAA;AAClG,EAAA,IAAI,CAAC,eAAmB,IAAA,CAAC,MAAU,IAAA,CAAC,YAAmB,OAAA,IAAA;AACvD,EAAO,uBAAA,GAAA,CAAC,uBAAoB,MAAgB,EAAA,CAAA;AAC9C"}
@@ -0,0 +1,41 @@
1
+ import {AppState}from'react-native';import {loadPlatformBackground,healthSyncStore}from'./healthSync.js';const COALESCE_MS = 3e3;
2
+ function createBackgroundSync(deps) {
3
+ let timer = null;
4
+ let waiting = [];
5
+ let started = null;
6
+ const onChange = () => new Promise((resolve) => {
7
+ waiting.push(resolve);
8
+ if (timer) clearTimeout(timer);
9
+ timer = setTimeout(() => {
10
+ timer = null;
11
+ const done = waiting;
12
+ waiting = [];
13
+ const run = deps.isForeground() ? deps.store.syncIfDue() : deps.store.syncInBackground();
14
+ void run.catch(() => void 0).then(() => done.forEach((d) => d()));
15
+ }, COALESCE_MS);
16
+ });
17
+ return {
18
+ onChange,
19
+ /** Subscribe (iOS delivers anything queued since launch), then make sure delivery is on. */
20
+ start() {
21
+ if (!started) {
22
+ started = (async () => {
23
+ const source = await deps.loadSource();
24
+ if (!source) return;
25
+ source.watch(onChange);
26
+ await deps.store.ensureBackgroundDelivery();
27
+ })().catch(() => void 0);
28
+ }
29
+ return started;
30
+ }
31
+ };
32
+ }
33
+ const healthBackgroundSync = createBackgroundSync({
34
+ store: healthSyncStore,
35
+ loadSource: loadPlatformBackground,
36
+ // A background launch reads 'background' (or 'unknown' this early): both are the background path.
37
+ isForeground: () => AppState.currentState === "active"
38
+ });
39
+ function startHealthBackgroundSync() {
40
+ void healthBackgroundSync.start();
41
+ }export{COALESCE_MS,createBackgroundSync,healthBackgroundSync,startHealthBackgroundSync};//# sourceMappingURL=backgroundSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgroundSync.js","sources":["../../../src/features/health/backgroundSync.ts"],"sourcesContent":["/**\n * Health sync while Yantra is closed, the way Bevel stays current.\n *\n * Started once at every launch (portable-devices/mobile App.tsx), including\n * the launches made in the background: iOS when the watch saves new data\n * (healthkitBackground.ts), Android for its hourly job, since Health Connect\n * cannot wake an app (healthConnectBackground.ts). A burst of changes - sleep,\n * HRV and resting heart rate land together - becomes one sync: in the\n * background through `syncInBackground` (the saved session, never a sign-in),\n * and while the app is open through the foreground's own throttled\n * `syncIfDue`, so an open app does not sync every time the watch saves a step.\n */\nimport { AppState } from 'react-native';\nimport type { BackgroundSource, HealthSyncStore } from './healthSyncStore';\nimport { healthSyncStore, loadPlatformBackground } from './healthSync';\n\n/** Changes this close together are one burst. */\nexport const COALESCE_MS = 3_000;\n\nexport function createBackgroundSync(deps: {\n store: Pick<HealthSyncStore, 'syncInBackground' | 'syncIfDue' | 'ensureBackgroundDelivery'>;\n loadSource: () => Promise<BackgroundSource | null>;\n isForeground: () => boolean;\n}) {\n let timer: ReturnType<typeof setTimeout> | null = null;\n let waiting: Array<() => void> = [];\n let started: Promise<void> | null = null;\n /** Resolves once the sync this change joined has finished (Android's job waits for it). */\n const onChange = (): Promise<void> =>\n new Promise((resolve) => {\n waiting.push(resolve);\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const done = waiting;\n waiting = [];\n const run = deps.isForeground() ? deps.store.syncIfDue() : deps.store.syncInBackground();\n void run.catch(() => undefined).then(() => done.forEach((d) => d()));\n }, COALESCE_MS);\n });\n return {\n onChange,\n /** Subscribe (iOS delivers anything queued since launch), then make sure delivery is on. */\n start(): Promise<void> {\n if (!started) {\n started = (async () => {\n const source = await deps.loadSource();\n if (!source) return;\n source.watch(onChange);\n await deps.store.ensureBackgroundDelivery();\n })().catch(() => undefined);\n }\n return started;\n },\n };\n}\n\nexport const healthBackgroundSync = createBackgroundSync({\n store: healthSyncStore,\n loadSource: loadPlatformBackground,\n // A background launch reads 'background' (or 'unknown' this early): both are the background path.\n isForeground: () => AppState.currentState === 'active',\n});\n\nexport function startHealthBackgroundSync(): void {\n void healthBackgroundSync.start();\n}\n"],"names":[],"mappings":"yGAiBO,MAAM,WAAc,GAAA;AACpB,SAAS,qBAAqB,IAIlC,EAAA;AACD,EAAA,IAAI,KAA8C,GAAA,IAAA;AAClD,EAAA,IAAI,UAA6B,EAAC;AAClC,EAAA,IAAI,OAAgC,GAAA,IAAA;AAEpC,EAAA,MAAM,QAAW,GAAA,MAAqB,IAAI,OAAA,CAAQ,CAAW,OAAA,KAAA;AAC3D,IAAA,OAAA,CAAQ,KAAK,OAAO,CAAA;AACpB,IAAI,IAAA,KAAA,eAAoB,KAAK,CAAA;AAC7B,IAAA,KAAA,GAAQ,WAAW,MAAM;AACvB,MAAQ,KAAA,GAAA,IAAA;AACR,MAAA,MAAM,IAAO,GAAA,OAAA;AACb,MAAA,OAAA,GAAU,EAAC;AACX,MAAM,MAAA,GAAA,GAAM,IAAK,CAAA,YAAA,EAAiB,GAAA,IAAA,CAAK,MAAM,SAAU,EAAA,GAAI,IAAK,CAAA,KAAA,CAAM,gBAAiB,EAAA;AACvF,MAAA,KAAK,GAAI,CAAA,KAAA,CAAM,MAAM,MAAS,CAAE,CAAA,IAAA,CAAK,MAAM,IAAA,CAAK,OAAQ,CAAA,CAAA,CAAA,KAAK,CAAE,EAAC,CAAC,CAAA;AAAA,OAChE,WAAW,CAAA;AAAA,GACf,CAAA;AACD,EAAO,OAAA;AAAA,IACL,QAAA;AAAA;AAAA,IAEA,KAAuB,GAAA;AACrB,MAAA,IAAI,CAAC,OAAS,EAAA;AACZ,QAAA,OAAA,GAAA,CAAW,YAAY;AACrB,UAAM,MAAA,MAAA,GAAS,MAAM,IAAA,CAAK,UAAW,EAAA;AACrC,UAAA,IAAI,CAAC,MAAQ,EAAA;AACb,UAAA,MAAA,CAAO,MAAM,QAAQ,CAAA;AACrB,UAAM,MAAA,IAAA,CAAK,MAAM,wBAAyB,EAAA;AAAA,SACzC,GAAA,CAAE,KAAM,CAAA,MAAM,MAAS,CAAA;AAAA;AAE5B,MAAO,OAAA,OAAA;AAAA;AACT,GACF;AACF;AACO,MAAM,uBAAuB,oBAAqB,CAAA;AAAA,EACvD,KAAO,EAAA,eAAA;AAAA,EACP,UAAY,EAAA,sBAAA;AAAA;AAAA,EAEZ,YAAA,EAAc,MAAM,QAAA,CAAS,YAAiB,KAAA;AAChD,CAAC;AACM,SAAS,yBAAkC,GAAA;AAChD,EAAA,KAAK,qBAAqB,KAAM,EAAA;AAClC"}
@@ -0,0 +1,32 @@
1
+ import*as BackgroundTask from'expo-background-task';import*as TaskManager from'expo-task-manager';import {canReadInBackground}from'./healthConnectReader.js';const HEALTH_CONNECT_TASK = "yantra-health-connect-sync";
2
+ const INTERVAL_MINUTES = 60;
3
+ let onWake = null;
4
+ TaskManager.defineTask(HEALTH_CONNECT_TASK, async () => {
5
+ try {
6
+ if (await canReadInBackground()) await (onWake == null ? void 0 : onWake());
7
+ else await BackgroundTask.unregisterTaskAsync(HEALTH_CONNECT_TASK);
8
+ return BackgroundTask.BackgroundTaskResult.Success;
9
+ } catch (e) {
10
+ return BackgroundTask.BackgroundTaskResult.Failed;
11
+ }
12
+ });
13
+ const healthConnectBackground = {
14
+ async enable() {
15
+ if (await canReadInBackground()) {
16
+ await BackgroundTask.registerTaskAsync(HEALTH_CONNECT_TASK, {
17
+ minimumInterval: INTERVAL_MINUTES
18
+ });
19
+ } else {
20
+ await BackgroundTask.unregisterTaskAsync(HEALTH_CONNECT_TASK);
21
+ }
22
+ },
23
+ async disable() {
24
+ await BackgroundTask.unregisterTaskAsync(HEALTH_CONNECT_TASK);
25
+ },
26
+ watch(onChange) {
27
+ onWake = onChange;
28
+ return () => {
29
+ if (onWake === onChange) onWake = null;
30
+ };
31
+ }
32
+ };export{HEALTH_CONNECT_TASK,INTERVAL_MINUTES,healthConnectBackground};//# sourceMappingURL=healthConnectBackground.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healthConnectBackground.js","sources":["../../../src/features/health/healthConnectBackground.ts"],"sourcesContent":["/**\n * Health Connect while Yantra is closed. Unlike Apple Health it cannot wake\n * an app when new data arrives, so an hourly job (expo-background-task, run by\n * Android's WorkManager when there is a network) syncs instead. Health Connect\n * only lets it read with the READ_HEALTH_DATA_IN_BACKGROUND permission, asked\n * with the data types (healthConnectReader.ts): without it the job is not\n * scheduled and Yantra syncs each time it is opened, as before.\n *\n * WorkManager skips a run while the app is open, and waits for the sync to\n * finish before scheduling the next.\n *\n * Loaded with a dynamic import on Android only (healthSync.ts): an older build\n * has neither native module, and importing either would throw.\n */\nimport * as BackgroundTask from 'expo-background-task';\nimport * as TaskManager from 'expo-task-manager';\nimport { canReadInBackground } from './healthConnectReader';\nimport type { BackgroundSource } from './healthSyncStore';\n\nexport const HEALTH_CONNECT_TASK = 'yantra-health-connect-sync';\n\n/**\n * Minutes between runs. Android's floor is 15 and it stretches the gap when the\n * phone is idle or the battery low. Hourly matches Apple Health, which\n * delivers steps at most hourly.\n */\nexport const INTERVAL_MINUTES = 60;\n\nlet onWake: (() => Promise<void>) | null = null;\n\n// Defined while this module loads, before TaskManager can hand over the run\n// that launched the app: a run it finds no task for is unregistered for good.\nTaskManager.defineTask(HEALTH_CONNECT_TASK, async () => {\n try {\n if (await canReadInBackground()) await onWake?.();\n // Turned off in Health Connect since: stop waking up for nothing.\n else await BackgroundTask.unregisterTaskAsync(HEALTH_CONNECT_TASK);\n return BackgroundTask.BackgroundTaskResult.Success;\n } catch {\n return BackgroundTask.BackgroundTaskResult.Failed;\n }\n});\n\nexport const healthConnectBackground: BackgroundSource = {\n async enable() {\n // Both are no-ops when already in that state.\n if (await canReadInBackground()) {\n await BackgroundTask.registerTaskAsync(HEALTH_CONNECT_TASK, { minimumInterval: INTERVAL_MINUTES });\n } else {\n await BackgroundTask.unregisterTaskAsync(HEALTH_CONNECT_TASK);\n }\n },\n async disable() {\n await BackgroundTask.unregisterTaskAsync(HEALTH_CONNECT_TASK);\n },\n watch(onChange) {\n onWake = onChange;\n return () => {\n if (onWake === onChange) onWake = null;\n };\n },\n};\n"],"names":[],"mappings":"6JAkBO,MAAM,mBAAsB,GAAA;AAO5B,MAAM,gBAAmB,GAAA;AAChC,IAAI,MAAuC,GAAA,IAAA;AAI3C,WAAY,CAAA,UAAA,CAAW,qBAAqB,YAAY;AACtD,EAAI,IAAA;AACF,IAAI,IAAA,MAAM,mBAAoB,EAAA,EAAS,OAAA,MAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,MAAA,EAAA,CAAA;AAAA,SAElC,MAAM,cAAe,CAAA,mBAAA,CAAoB,mBAAmB,CAAA;AACjE,IAAA,OAAO,eAAe,oBAAqB,CAAA,OAAA;AAAA,GACrC,CAAA,OAAA,CAAA,EAAA;AACN,IAAA,OAAO,eAAe,oBAAqB,CAAA,MAAA;AAAA;AAE/C,CAAC,CAAA;AACM,MAAM,uBAA4C,GAAA;AAAA,EACvD,MAAM,MAAS,GAAA;AAEb,IAAI,IAAA,MAAM,qBAAuB,EAAA;AAC/B,MAAM,MAAA,cAAA,CAAe,kBAAkB,mBAAqB,EAAA;AAAA,QAC1D,eAAiB,EAAA;AAAA,OAClB,CAAA;AAAA,KACI,MAAA;AACL,MAAM,MAAA,cAAA,CAAe,oBAAoB,mBAAmB,CAAA;AAAA;AAC9D,GACF;AAAA,EACA,MAAM,OAAU,GAAA;AACd,IAAM,MAAA,cAAA,CAAe,oBAAoB,mBAAmB,CAAA;AAAA,GAC9D;AAAA,EACA,MAAM,QAAU,EAAA;AACd,IAAS,MAAA,GAAA,QAAA;AACT,IAAA,OAAO,MAAM;AACX,MAAI,IAAA,MAAA,KAAW,UAAmB,MAAA,GAAA,IAAA;AAAA,KACpC;AAAA;AAEJ"}
@@ -40,6 +40,10 @@ const PERMISSIONS = [...new Set(Object.values(RECORD_TYPES))].map((recordType) =
40
40
  accessType: "read",
41
41
  recordType
42
42
  }));
43
+ const BACKGROUND_READ = {
44
+ accessType: "read",
45
+ recordType: "BackgroundAccessPermission"
46
+ };
43
47
  const PAGE_SIZE = 5e3;
44
48
  const MAX_PAGES = 200;
45
49
  let initPromise = null;
@@ -56,6 +60,15 @@ function typesFromGranted(granted) {
56
60
  const read = new Set(granted.filter((p) => p.accessType === "read").map((p) => p.recordType));
57
61
  return TYPES.filter((t) => read.has(RECORD_TYPES[t]));
58
62
  }
63
+ async function canReadInBackground() {
64
+ try {
65
+ await ensureInitialized();
66
+ const granted = await getGrantedPermissions();
67
+ return granted.some((p) => p.accessType === "read" && p.recordType === BACKGROUND_READ.recordType);
68
+ } catch (e) {
69
+ return false;
70
+ }
71
+ }
59
72
  async function readAll(recordType, start, end) {
60
73
  const out = [];
61
74
  let pageToken;
@@ -113,9 +126,10 @@ function createHealthConnectReader() {
113
126
  },
114
127
  async requestAccess() {
115
128
  await ensureInitialized();
116
- const granted = await requestPermission(PERMISSIONS);
129
+ const granted = await requestPermission([...PERMISSIONS, BACKGROUND_READ]);
117
130
  return typesFromGranted(granted);
118
131
  },
132
+ canReadInBackground,
119
133
  async grantedTypes() {
120
134
  await ensureInitialized();
121
135
  const granted = await getGrantedPermissions();
@@ -246,4 +260,4 @@ function createHealthConnectReader() {
246
260
  }
247
261
  }
248
262
  };
249
- }export{createHealthConnectReader};//# sourceMappingURL=healthConnectReader.js.map
263
+ }export{canReadInBackground,createHealthConnectReader};//# sourceMappingURL=healthConnectReader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"healthConnectReader.js","sources":["../../../src/features/health/healthConnectReader.ts"],"sourcesContent":["/**\n * Health Connect reader, Android only. Yantra writes only readings from\n * devices paired in the app (healthConnectWriter.ts); those are skipped here,\n * the backend already has them as source `device`.\n *\n * - Daily totals use Health Connect's aggregation per local day\n * (aggregateGroupByPeriod, DAYS), which de-duplicates phone + watch. Steps\n * per quarter-hour, for the 15-minute buckets, are the same aggregation per\n * 15 minutes (aggregateGroupByDuration), so two apps counting the same walk\n * are not added together.\n * - Records are paged (`pageToken`) so a long backfill never truncates.\n * - Types the person did not allow are skipped and reported, not retried\n * into a SecurityException on every sync.\n * - Without the READ_HEALTH_DATA_HISTORY permission Health Connect only\n * returns the 30 days before the first grant, which is exactly the\n * backfill, so the app does not ask for history access.\n */\nimport {\n aggregateGroupByDuration,\n aggregateGroupByPeriod,\n aggregateRecord,\n getGrantedPermissions,\n getSdkStatus,\n initialize,\n openHealthConnectSettings,\n readRecords,\n requestPermission,\n revokeAllPermissions,\n SdkAvailabilityStatus,\n type Permission,\n type ReadRecordsOptions,\n type RecordResult,\n type RecordType,\n} from 'react-native-health-connect';\nimport {\n hcBloodPressure,\n hcDailyTotals,\n hcGlucose,\n hcHeartRate,\n hcNotOwn,\n hcRecorded,\n hcSleepSegments,\n hcStepSpans,\n hcValues,\n hcWorkout,\n} from './healthConnectMapping';\nimport { ownAppId } from './ownSource';\nimport { emptyRawHealthData } from './model';\nimport type { HealthAvailability, HealthDataType, HealthReader, ReadResult } from './reader';\nimport { INTRADAY_BUCKET_MINUTES } from './types';\n\nimport { HEALTH_CONNECT_PACKAGE } from './healthConnectLinks';\n\n/**\n * What is read on Android. Health Connect has no record for BMI, waist, time\n * in daylight or (in react-native-health-connect 4.0.0) mindfulness, so those\n * are never read here. Not read on purpose:\n * - Skin temperature: SkinTemperatureRecord is a series of deltas against an\n * optional baseline, not the absolute overnight temperature the coach\n * compares (Apple Health's sleeping wrist temperature).\n * - Resting energy: Health Connect keeps a basal metabolic RATE (kcal/day,\n * BasalMetabolicRateRecord), not the energy burned at rest that day.\n */\nconst RECORD_TYPES: Partial<Record<HealthDataType, RecordType>> = {\n steps: 'Steps',\n distance: 'Distance',\n activeCalories: 'ActiveCaloriesBurned',\n exercise: 'ExerciseSession',\n workouts: 'ExerciseSession',\n elevation: 'ElevationGained',\n heartRate: 'HeartRate',\n restingHr: 'RestingHeartRate',\n hrv: 'HeartRateVariabilityRmssd',\n sleep: 'SleepSession',\n weight: 'Weight',\n vo2Max: 'Vo2Max',\n spo2: 'OxygenSaturation',\n respiratoryRate: 'RespiratoryRate',\n bodyFat: 'BodyFat',\n leanMass: 'LeanBodyMass',\n bodyTemp: 'BodyTemperature',\n bloodPressure: 'BloodPressure',\n bloodGlucose: 'BloodGlucose',\n};\n\nconst TYPES = Object.keys(RECORD_TYPES) as HealthDataType[];\n\nconst PERMISSIONS: Permission[] = [...new Set(Object.values(RECORD_TYPES) as RecordType[])].map((recordType) => ({\n accessType: 'read',\n recordType,\n}));\n\nconst PAGE_SIZE = 5000;\n/** A runaway pager (provider bug) stops here instead of looping forever. */\nconst MAX_PAGES = 200;\n\nlet initPromise: Promise<boolean> | null = null;\nfunction ensureInitialized(): Promise<boolean> {\n if (!initPromise) {\n initPromise = initialize().catch((err) => {\n initPromise = null;\n throw err;\n });\n }\n return initPromise;\n}\n\nfunction typesFromGranted(granted: ReadonlyArray<{ accessType?: string; recordType?: string }>): HealthDataType[] {\n const read = new Set(granted.filter((p) => p.accessType === 'read').map((p) => p.recordType));\n return TYPES.filter((t) => read.has(RECORD_TYPES[t]));\n}\n\nasync function readAll<T extends RecordType>(recordType: T, start: number, end: number): Promise<RecordResult<T>[]> {\n const out: RecordResult<T>[] = [];\n let pageToken: string | undefined;\n for (let page = 0; page < MAX_PAGES; page++) {\n const options: ReadRecordsOptions = {\n timeRangeFilter: {\n operator: 'between',\n startTime: new Date(start).toISOString(),\n endTime: new Date(end).toISOString(),\n },\n ascendingOrder: true,\n pageSize: PAGE_SIZE,\n ...(pageToken ? { pageToken } : {}),\n };\n const res = await readRecords(recordType, options);\n out.push(...res.records);\n if (!res.pageToken || res.pageToken === pageToken) break;\n pageToken = res.pageToken;\n }\n return out;\n}\n\nexport function createHealthConnectReader(): HealthReader {\n return {\n source: 'health-connect',\n storeName: 'Health Connect',\n types: TYPES,\n\n async availability(): Promise<HealthAvailability> {\n let status: number;\n try {\n status = await getSdkStatus(HEALTH_CONNECT_PACKAGE);\n } catch {\n return { status: 'unsupported', reason: 'Health Connect is not available in this build of the app.' };\n }\n if (status === SdkAvailabilityStatus.SDK_UNAVAILABLE) return { status: 'not-installed' };\n if (status === SdkAvailabilityStatus.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED)\n return { status: 'needs-update' };\n try {\n await ensureInitialized();\n } catch {\n return { status: 'unsupported', reason: 'Health Connect could not be started on this phone.' };\n }\n return { status: 'available' };\n },\n\n async requestAccess() {\n await ensureInitialized();\n const granted = await requestPermission(PERMISSIONS);\n return typesFromGranted(granted as Array<{ accessType?: string; recordType?: string }>);\n },\n\n async grantedTypes() {\n await ensureInitialized();\n const granted = await getGrantedPermissions();\n return typesFromGranted(granted as Array<{ accessType?: string; recordType?: string }>);\n },\n\n async read(range, clock): Promise<ReadResult> {\n await ensureInitialized();\n const granted = new Set(\n typesFromGranted(\n (await getGrantedPermissions()) as Array<{ accessType?: string; recordType?: string }>,\n ),\n );\n const unreadable = new Set<HealthDataType>();\n const raw = emptyRawHealthData('rmssd');\n const own = ownAppId('android');\n const filter = {\n operator: 'between' as const,\n startTime: new Date(range.start).toISOString(),\n endTime: new Date(range.end).toISOString(),\n };\n const slicer = { period: 'DAYS' as const, length: 1 };\n\n async function attempt<T>(type: HealthDataType, run: () => Promise<T>): Promise<T | undefined> {\n if (!granted.has(type)) {\n unreadable.add(type);\n return undefined;\n }\n try {\n return await run();\n } catch {\n unreadable.add(type);\n return undefined;\n }\n }\n\n const [steps, activeCalories, distance, exercise, quarterSteps] = await Promise.all([\n attempt('steps', () =>\n aggregateGroupByPeriod({ recordType: 'Steps', timeRangeFilter: filter, timeRangeSlicer: slicer }),\n ),\n attempt('activeCalories', () =>\n aggregateGroupByPeriod({\n recordType: 'ActiveCaloriesBurned',\n timeRangeFilter: filter,\n timeRangeSlicer: slicer,\n }),\n ),\n attempt('distance', () =>\n aggregateGroupByPeriod({\n recordType: 'Distance',\n timeRangeFilter: filter,\n timeRangeSlicer: slicer,\n }),\n ),\n attempt('exercise', () =>\n aggregateGroupByPeriod({\n recordType: 'ExerciseSession',\n timeRangeFilter: filter,\n timeRangeSlicer: slicer,\n }),\n ),\n attempt('steps', () =>\n aggregateGroupByDuration({\n recordType: 'Steps',\n timeRangeFilter: filter,\n timeRangeSlicer: { duration: 'MINUTES', length: INTRADAY_BUCKET_MINUTES },\n }),\n ),\n ]);\n raw.dailyTotals = hcDailyTotals({ steps, activeCalories, distance, exercise }, clock);\n raw.stepSpans = hcStepSpans(quarterSteps ?? []);\n\n const [fat, lean, temp, pressure, glucose] = await Promise.all([\n attempt('bodyFat', () => readAll('BodyFat', range.start, range.end)),\n attempt('leanMass', () => readAll('LeanBodyMass', range.start, range.end)),\n attempt('bodyTemp', () => readAll('BodyTemperature', range.start, range.end)),\n attempt('bloodPressure', () => readAll('BloodPressure', range.start, range.end)),\n attempt('bloodGlucose', () => readAll('BloodGlucose', range.start, range.end)),\n ]);\n raw.bodyFatPct = hcValues(hcNotOwn(fat ?? [], own), (r) => r.percentage);\n raw.leanMassKg = hcValues(hcNotOwn(lean ?? [], own), (r) => r.mass?.inKilograms);\n raw.bodyTempC = hcValues(hcNotOwn(temp ?? [], own), (r) => r.temperature?.inCelsius);\n raw.bloodPressure = hcBloodPressure(hcNotOwn(pressure ?? [], own));\n raw.glucose = hcGlucose(hcNotOwn(glucose ?? [], own));\n\n const [hr, rhr, hrv, sleep, weight, vo2, spo2, resp, sessions] = await Promise.all([\n attempt('heartRate', () => readAll('HeartRate', range.start, range.end)),\n attempt('restingHr', () => readAll('RestingHeartRate', range.start, range.end)),\n attempt('hrv', () => readAll('HeartRateVariabilityRmssd', range.start, range.end)),\n attempt('sleep', () => readAll('SleepSession', range.start, range.end)),\n attempt('weight', () => readAll('Weight', range.start, range.end)),\n attempt('vo2Max', () => readAll('Vo2Max', range.start, range.end)),\n attempt('spo2', () => readAll('OxygenSaturation', range.start, range.end)),\n attempt('respiratoryRate', () => readAll('RespiratoryRate', range.start, range.end)),\n attempt('workouts', () => readAll('ExerciseSession', range.start, range.end)),\n ]);\n raw.heartRate = hcHeartRate(hcNotOwn(hr ?? [], own));\n raw.restingHr = hcValues(rhr ?? [], (r) => r.beatsPerMinute);\n raw.hrv = hcValues(hrv ?? [], (r) => r.heartRateVariabilityMillis);\n raw.sleep = hcSleepSegments(sleep ?? []);\n raw.weightKg = hcValues(hcNotOwn(weight ?? [], own), (r) => r.weight?.inKilograms);\n raw.vo2Max = hcValues(vo2 ?? [], (r) => r.vo2MillilitersPerMinuteKilogram);\n raw.spo2 = hcValues(hcNotOwn(spo2 ?? [], own), (r) => r.percentage);\n raw.respiratoryRate = hcValues(resp ?? [], (r) => r.rate);\n\n for (const s of sessions ?? []) {\n const window = { operator: 'between' as const, startTime: s.startTime, endTime: s.endTime };\n const [cal, dist, elevation] = await Promise.all([\n granted.has('activeCalories')\n ? aggregateRecord({ recordType: 'ActiveCaloriesBurned', timeRangeFilter: window }).catch(\n () => undefined,\n )\n : undefined,\n granted.has('distance')\n ? aggregateRecord({ recordType: 'Distance', timeRangeFilter: window }).catch(() => undefined)\n : undefined,\n granted.has('elevation')\n ? aggregateRecord({ recordType: 'ElevationGained', timeRangeFilter: window }).catch(\n () => undefined,\n )\n : undefined,\n ]);\n const workout = hcWorkout(s, {\n calories: cal?.ACTIVE_CALORIES_TOTAL?.inKilocalories,\n distanceMeters: dist?.DISTANCE?.inMeters,\n elevationMeters: hcRecorded(elevation, (r) => r.ELEVATION_GAINED_TOTAL?.inMeters),\n });\n if (workout) raw.workouts.push(workout);\n }\n\n return { raw, unreadable: [...unreadable] };\n },\n\n async openSettings() {\n openHealthConnectSettings();\n },\n\n async revoke() {\n try {\n await ensureInitialized();\n await revokeAllPermissions();\n } catch {\n // Stopping sync does not depend on it; the person can still\n // remove access in Health Connect.\n }\n },\n };\n}\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"healthConnectReader.js","sources":["../../../src/features/health/healthConnectReader.ts"],"sourcesContent":["/**\n * Health Connect reader, Android only. Yantra writes only readings from\n * devices paired in the app (healthConnectWriter.ts); those are skipped here,\n * the backend already has them as source `device`.\n *\n * - Daily totals use Health Connect's aggregation per local day\n * (aggregateGroupByPeriod, DAYS), which de-duplicates phone + watch. Steps\n * per quarter-hour, for the 15-minute buckets, are the same aggregation per\n * 15 minutes (aggregateGroupByDuration), so two apps counting the same walk\n * are not added together.\n * - Records are paged (`pageToken`) so a long backfill never truncates.\n * - Types the person did not allow are skipped and reported, not retried\n * into a SecurityException on every sync.\n * - Without the READ_HEALTH_DATA_HISTORY permission Health Connect only\n * returns the 30 days before the first grant, which is exactly the\n * backfill, so the app does not ask for history access.\n * - READ_HEALTH_DATA_IN_BACKGROUND is asked with the data types: without it\n * the hourly job that syncs while Yantra is closed (healthConnectBackground.ts)\n * cannot read.\n */\nimport {\n aggregateGroupByDuration,\n aggregateGroupByPeriod,\n aggregateRecord,\n getGrantedPermissions,\n getSdkStatus,\n initialize,\n openHealthConnectSettings,\n readRecords,\n requestPermission,\n revokeAllPermissions,\n SdkAvailabilityStatus,\n type BackgroundAccessPermission,\n type Permission,\n type ReadRecordsOptions,\n type RecordResult,\n type RecordType,\n} from 'react-native-health-connect';\nimport {\n hcBloodPressure,\n hcDailyTotals,\n hcGlucose,\n hcHeartRate,\n hcNotOwn,\n hcRecorded,\n hcSleepSegments,\n hcStepSpans,\n hcValues,\n hcWorkout,\n} from './healthConnectMapping';\nimport { ownAppId } from './ownSource';\nimport { emptyRawHealthData } from './model';\nimport type { HealthAvailability, HealthDataType, HealthReader, ReadResult } from './reader';\nimport { INTRADAY_BUCKET_MINUTES } from './types';\n\nimport { HEALTH_CONNECT_PACKAGE } from './healthConnectLinks';\n\n/**\n * What is read on Android. Health Connect has no record for BMI, waist, time\n * in daylight or (in react-native-health-connect 4.0.0) mindfulness, so those\n * are never read here. Not read on purpose:\n * - Skin temperature: SkinTemperatureRecord is a series of deltas against an\n * optional baseline, not the absolute overnight temperature the coach\n * compares (Apple Health's sleeping wrist temperature).\n * - Resting energy: Health Connect keeps a basal metabolic RATE (kcal/day,\n * BasalMetabolicRateRecord), not the energy burned at rest that day.\n */\nconst RECORD_TYPES: Partial<Record<HealthDataType, RecordType>> = {\n steps: 'Steps',\n distance: 'Distance',\n activeCalories: 'ActiveCaloriesBurned',\n exercise: 'ExerciseSession',\n workouts: 'ExerciseSession',\n elevation: 'ElevationGained',\n heartRate: 'HeartRate',\n restingHr: 'RestingHeartRate',\n hrv: 'HeartRateVariabilityRmssd',\n sleep: 'SleepSession',\n weight: 'Weight',\n vo2Max: 'Vo2Max',\n spo2: 'OxygenSaturation',\n respiratoryRate: 'RespiratoryRate',\n bodyFat: 'BodyFat',\n leanMass: 'LeanBodyMass',\n bodyTemp: 'BodyTemperature',\n bloodPressure: 'BloodPressure',\n bloodGlucose: 'BloodGlucose',\n};\n\nconst TYPES = Object.keys(RECORD_TYPES) as HealthDataType[];\n\nconst PERMISSIONS: Permission[] = [...new Set(Object.values(RECORD_TYPES) as RecordType[])].map((recordType) => ({\n accessType: 'read',\n recordType,\n}));\n\n/** Health Connect shows it as an extra page after the data types. */\nconst BACKGROUND_READ: BackgroundAccessPermission = { accessType: 'read', recordType: 'BackgroundAccessPermission' };\n\nconst PAGE_SIZE = 5000;\n/** A runaway pager (provider bug) stops here instead of looping forever. */\nconst MAX_PAGES = 200;\n\nlet initPromise: Promise<boolean> | null = null;\nfunction ensureInitialized(): Promise<boolean> {\n if (!initPromise) {\n initPromise = initialize().catch((err) => {\n initPromise = null;\n throw err;\n });\n }\n return initPromise;\n}\n\nfunction typesFromGranted(granted: ReadonlyArray<{ accessType?: string; recordType?: string }>): HealthDataType[] {\n const read = new Set(granted.filter((p) => p.accessType === 'read').map((p) => p.recordType));\n return TYPES.filter((t) => read.has(RECORD_TYPES[t]));\n}\n\n/** Health Connect lets Yantra read while it is closed. False whenever it cannot say. */\nexport async function canReadInBackground(): Promise<boolean> {\n try {\n await ensureInitialized();\n const granted = (await getGrantedPermissions()) as Array<{ accessType?: string; recordType?: string }>;\n return granted.some((p) => p.accessType === 'read' && p.recordType === BACKGROUND_READ.recordType);\n } catch {\n return false;\n }\n}\n\nasync function readAll<T extends RecordType>(recordType: T, start: number, end: number): Promise<RecordResult<T>[]> {\n const out: RecordResult<T>[] = [];\n let pageToken: string | undefined;\n for (let page = 0; page < MAX_PAGES; page++) {\n const options: ReadRecordsOptions = {\n timeRangeFilter: {\n operator: 'between',\n startTime: new Date(start).toISOString(),\n endTime: new Date(end).toISOString(),\n },\n ascendingOrder: true,\n pageSize: PAGE_SIZE,\n ...(pageToken ? { pageToken } : {}),\n };\n const res = await readRecords(recordType, options);\n out.push(...res.records);\n if (!res.pageToken || res.pageToken === pageToken) break;\n pageToken = res.pageToken;\n }\n return out;\n}\n\nexport function createHealthConnectReader(): HealthReader {\n return {\n source: 'health-connect',\n storeName: 'Health Connect',\n types: TYPES,\n\n async availability(): Promise<HealthAvailability> {\n let status: number;\n try {\n status = await getSdkStatus(HEALTH_CONNECT_PACKAGE);\n } catch {\n return { status: 'unsupported', reason: 'Health Connect is not available in this build of the app.' };\n }\n if (status === SdkAvailabilityStatus.SDK_UNAVAILABLE) return { status: 'not-installed' };\n if (status === SdkAvailabilityStatus.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED)\n return { status: 'needs-update' };\n try {\n await ensureInitialized();\n } catch {\n return { status: 'unsupported', reason: 'Health Connect could not be started on this phone.' };\n }\n return { status: 'available' };\n },\n\n async requestAccess() {\n await ensureInitialized();\n const granted = await requestPermission([...PERMISSIONS, BACKGROUND_READ]);\n return typesFromGranted(granted as Array<{ accessType?: string; recordType?: string }>);\n },\n\n canReadInBackground,\n\n async grantedTypes() {\n await ensureInitialized();\n const granted = await getGrantedPermissions();\n return typesFromGranted(granted as Array<{ accessType?: string; recordType?: string }>);\n },\n\n async read(range, clock): Promise<ReadResult> {\n await ensureInitialized();\n const granted = new Set(\n typesFromGranted(\n (await getGrantedPermissions()) as Array<{ accessType?: string; recordType?: string }>,\n ),\n );\n const unreadable = new Set<HealthDataType>();\n const raw = emptyRawHealthData('rmssd');\n const own = ownAppId('android');\n const filter = {\n operator: 'between' as const,\n startTime: new Date(range.start).toISOString(),\n endTime: new Date(range.end).toISOString(),\n };\n const slicer = { period: 'DAYS' as const, length: 1 };\n\n async function attempt<T>(type: HealthDataType, run: () => Promise<T>): Promise<T | undefined> {\n if (!granted.has(type)) {\n unreadable.add(type);\n return undefined;\n }\n try {\n return await run();\n } catch {\n unreadable.add(type);\n return undefined;\n }\n }\n\n const [steps, activeCalories, distance, exercise, quarterSteps] = await Promise.all([\n attempt('steps', () =>\n aggregateGroupByPeriod({ recordType: 'Steps', timeRangeFilter: filter, timeRangeSlicer: slicer }),\n ),\n attempt('activeCalories', () =>\n aggregateGroupByPeriod({\n recordType: 'ActiveCaloriesBurned',\n timeRangeFilter: filter,\n timeRangeSlicer: slicer,\n }),\n ),\n attempt('distance', () =>\n aggregateGroupByPeriod({\n recordType: 'Distance',\n timeRangeFilter: filter,\n timeRangeSlicer: slicer,\n }),\n ),\n attempt('exercise', () =>\n aggregateGroupByPeriod({\n recordType: 'ExerciseSession',\n timeRangeFilter: filter,\n timeRangeSlicer: slicer,\n }),\n ),\n attempt('steps', () =>\n aggregateGroupByDuration({\n recordType: 'Steps',\n timeRangeFilter: filter,\n timeRangeSlicer: { duration: 'MINUTES', length: INTRADAY_BUCKET_MINUTES },\n }),\n ),\n ]);\n raw.dailyTotals = hcDailyTotals({ steps, activeCalories, distance, exercise }, clock);\n raw.stepSpans = hcStepSpans(quarterSteps ?? []);\n\n const [fat, lean, temp, pressure, glucose] = await Promise.all([\n attempt('bodyFat', () => readAll('BodyFat', range.start, range.end)),\n attempt('leanMass', () => readAll('LeanBodyMass', range.start, range.end)),\n attempt('bodyTemp', () => readAll('BodyTemperature', range.start, range.end)),\n attempt('bloodPressure', () => readAll('BloodPressure', range.start, range.end)),\n attempt('bloodGlucose', () => readAll('BloodGlucose', range.start, range.end)),\n ]);\n raw.bodyFatPct = hcValues(hcNotOwn(fat ?? [], own), (r) => r.percentage);\n raw.leanMassKg = hcValues(hcNotOwn(lean ?? [], own), (r) => r.mass?.inKilograms);\n raw.bodyTempC = hcValues(hcNotOwn(temp ?? [], own), (r) => r.temperature?.inCelsius);\n raw.bloodPressure = hcBloodPressure(hcNotOwn(pressure ?? [], own));\n raw.glucose = hcGlucose(hcNotOwn(glucose ?? [], own));\n\n const [hr, rhr, hrv, sleep, weight, vo2, spo2, resp, sessions] = await Promise.all([\n attempt('heartRate', () => readAll('HeartRate', range.start, range.end)),\n attempt('restingHr', () => readAll('RestingHeartRate', range.start, range.end)),\n attempt('hrv', () => readAll('HeartRateVariabilityRmssd', range.start, range.end)),\n attempt('sleep', () => readAll('SleepSession', range.start, range.end)),\n attempt('weight', () => readAll('Weight', range.start, range.end)),\n attempt('vo2Max', () => readAll('Vo2Max', range.start, range.end)),\n attempt('spo2', () => readAll('OxygenSaturation', range.start, range.end)),\n attempt('respiratoryRate', () => readAll('RespiratoryRate', range.start, range.end)),\n attempt('workouts', () => readAll('ExerciseSession', range.start, range.end)),\n ]);\n raw.heartRate = hcHeartRate(hcNotOwn(hr ?? [], own));\n raw.restingHr = hcValues(rhr ?? [], (r) => r.beatsPerMinute);\n raw.hrv = hcValues(hrv ?? [], (r) => r.heartRateVariabilityMillis);\n raw.sleep = hcSleepSegments(sleep ?? []);\n raw.weightKg = hcValues(hcNotOwn(weight ?? [], own), (r) => r.weight?.inKilograms);\n raw.vo2Max = hcValues(vo2 ?? [], (r) => r.vo2MillilitersPerMinuteKilogram);\n raw.spo2 = hcValues(hcNotOwn(spo2 ?? [], own), (r) => r.percentage);\n raw.respiratoryRate = hcValues(resp ?? [], (r) => r.rate);\n\n for (const s of sessions ?? []) {\n const window = { operator: 'between' as const, startTime: s.startTime, endTime: s.endTime };\n const [cal, dist, elevation] = await Promise.all([\n granted.has('activeCalories')\n ? aggregateRecord({ recordType: 'ActiveCaloriesBurned', timeRangeFilter: window }).catch(\n () => undefined,\n )\n : undefined,\n granted.has('distance')\n ? aggregateRecord({ recordType: 'Distance', timeRangeFilter: window }).catch(() => undefined)\n : undefined,\n granted.has('elevation')\n ? aggregateRecord({ recordType: 'ElevationGained', timeRangeFilter: window }).catch(\n () => undefined,\n )\n : undefined,\n ]);\n const workout = hcWorkout(s, {\n calories: cal?.ACTIVE_CALORIES_TOTAL?.inKilocalories,\n distanceMeters: dist?.DISTANCE?.inMeters,\n elevationMeters: hcRecorded(elevation, (r) => r.ELEVATION_GAINED_TOTAL?.inMeters),\n });\n if (workout) raw.workouts.push(workout);\n }\n\n return { raw, unreadable: [...unreadable] };\n },\n\n async openSettings() {\n openHealthConnectSettings();\n },\n\n async revoke() {\n try {\n await ensureInitialized();\n await revokeAllPermissions();\n } catch {\n // Stopping sync does not depend on it; the person can still\n // remove access in Health Connect.\n }\n },\n };\n}\n"],"names":["_a"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import AsyncStorage from'@react-native-async-storage/async-storage';import {Platform}from'react-native';import {useSyncExternalStore}from'react';import {HealthSyncStore}from'./healthSyncStore.js';async function createPlatformReader() {
1
+ import AsyncStorage from'@react-native-async-storage/async-storage';import {Platform}from'react-native';import {useSyncExternalStore}from'react';import {readAuth0IdToken}from'../../bridge/readAuth0Session.js';import {HealthSyncStore}from'./healthSyncStore.js';import {secureSessionVault}from'./sessionVault.js';async function createPlatformReader() {
2
2
  try {
3
3
  if (Platform.OS === "ios") return (await import('./healthkitReader.js')).createHealthKitReader();
4
4
  if (Platform.OS === "android") return (await import('./healthConnectReader.js')).createHealthConnectReader();
@@ -6,10 +6,38 @@ import AsyncStorage from'@react-native-async-storage/async-storage';import {Plat
6
6
  }
7
7
  return null;
8
8
  }
9
+ let background = null;
10
+ function loadPlatformBackground() {
11
+ if (!background) {
12
+ background = (async () => {
13
+ try {
14
+ if (Platform.OS === "ios") return (await import('./healthkitBackground.js')).healthkitBackground;
15
+ if (Platform.OS === "android") {
16
+ return (await import('./healthConnectBackground.js')).healthConnectBackground;
17
+ }
18
+ } catch (e) {
19
+ }
20
+ return null;
21
+ })();
22
+ }
23
+ return background;
24
+ }
9
25
  const healthSyncStore = new HealthSyncStore({
10
26
  storage: AsyncStorage,
11
- createReader: createPlatformReader
27
+ createReader: createPlatformReader,
28
+ vault: secureSessionVault,
29
+ background: {
30
+ enable: async () => {
31
+ var _a;
32
+ await ((_a = await loadPlatformBackground()) == null ? void 0 : _a.enable());
33
+ },
34
+ disable: async () => {
35
+ var _a;
36
+ await ((_a = await loadPlatformBackground()) == null ? void 0 : _a.disable());
37
+ }
38
+ },
39
+ signedIn: async () => Boolean(await readAuth0IdToken())
12
40
  });
13
41
  function useHealthSync() {
14
42
  return useSyncExternalStore(healthSyncStore.subscribe, healthSyncStore.getSnapshot, healthSyncStore.getSnapshot);
15
- }export{healthSyncStore,useHealthSync};//# sourceMappingURL=healthSync.js.map
43
+ }export{healthSyncStore,loadPlatformBackground,useHealthSync};//# sourceMappingURL=healthSync.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"healthSync.js","sources":["../../../src/features/health/healthSync.ts"],"sourcesContent":["/**\n * The app's one HealthSyncStore, wired to AsyncStorage and this platform's\n * reader.\n *\n * The readers load with a dynamic import, inside a try: both native\n * libraries create their native objects when first imported, and an app\n * binary built before they were added (an over-the-air JS update reaching an\n * older build) would crash at startup on a static import. Here it only means\n * \"not available in this build\". Rollup keeps `import()` as its own module.\n */\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { Platform } from 'react-native';\nimport { useSyncExternalStore } from 'react';\nimport type { HealthReader } from './reader';\nimport { HealthSyncStore, type HealthSyncSnapshot } from './healthSyncStore';\n\nasync function createPlatformReader(): Promise<HealthReader | null> {\n try {\n if (Platform.OS === 'ios') return (await import('./healthkitReader')).createHealthKitReader();\n if (Platform.OS === 'android') return (await import('./healthConnectReader')).createHealthConnectReader();\n } catch {\n // This binary does not include the native module.\n }\n return null;\n}\n\nexport const healthSyncStore = new HealthSyncStore({\n storage: AsyncStorage,\n createReader: createPlatformReader,\n});\n\nexport function useHealthSync(): HealthSyncSnapshot {\n return useSyncExternalStore(healthSyncStore.subscribe, healthSyncStore.getSnapshot, healthSyncStore.getSnapshot);\n}\n"],"names":[],"mappings":"oMAeA,eAAe,oBAAqD,GAAA;AAClE,EAAI,IAAA;AACF,IAAI,IAAA,QAAA,CAAS,OAAO,KAAO,EAAA,OAAA,CAAQ,MAAM,OAAO,sBAAmB,GAAG,qBAAsB,EAAA;AAC5F,IAAI,IAAA,QAAA,CAAS,OAAO,SAAW,EAAA,OAAA,CAAQ,MAAM,OAAO,0BAAuB,GAAG,yBAA0B,EAAA;AAAA,GAClG,CAAA,OAAA,CAAA,EAAA;AAAA;AAGR,EAAO,OAAA,IAAA;AACT;AACa,MAAA,eAAA,GAAkB,IAAI,eAAgB,CAAA;AAAA,EACjD,OAAS,EAAA,YAAA;AAAA,EACT,YAAc,EAAA;AAChB,CAAC;AACM,SAAS,aAAoC,GAAA;AAClD,EAAA,OAAO,qBAAqB,eAAgB,CAAA,SAAA,EAAW,eAAgB,CAAA,WAAA,EAAa,gBAAgB,WAAW,CAAA;AACjH"}
1
+ {"version":3,"file":"healthSync.js","sources":["../../../src/features/health/healthSync.ts"],"sourcesContent":["/**\n * The app's one HealthSyncStore, wired to AsyncStorage and this platform's\n * reader.\n *\n * The readers load with a dynamic import, inside a try: both native\n * libraries create their native objects when first imported, and an app\n * binary built before they were added (an over-the-air JS update reaching an\n * older build) would crash at startup on a static import. Here it only means\n * \"not available in this build\". Rollup keeps `import()` as its own module.\n */\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { Platform } from 'react-native';\nimport { useSyncExternalStore } from 'react';\nimport { readAuth0IdToken } from '../../bridge/readAuth0Session';\nimport type { HealthReader } from './reader';\nimport { HealthSyncStore, type BackgroundSource, type HealthSyncSnapshot } from './healthSyncStore';\nimport { secureSessionVault } from './sessionVault';\n\nasync function createPlatformReader(): Promise<HealthReader | null> {\n try {\n if (Platform.OS === 'ios') return (await import('./healthkitReader')).createHealthKitReader();\n if (Platform.OS === 'android') return (await import('./healthConnectReader')).createHealthConnectReader();\n } catch {\n // This binary does not include the native module.\n }\n return null;\n}\n\nlet background: Promise<BackgroundSource | null> | null = null;\n\n/**\n * Syncing while closed: Apple Health background delivery, or Health Connect's\n * hourly job. Null where there is neither (web, a build without the module).\n */\nexport function loadPlatformBackground(): Promise<BackgroundSource | null> {\n if (!background) {\n background = (async () => {\n try {\n if (Platform.OS === 'ios') return (await import('./healthkitBackground')).healthkitBackground;\n if (Platform.OS === 'android') {\n return (await import('./healthConnectBackground')).healthConnectBackground;\n }\n } catch {\n // This build has no such native module.\n }\n return null;\n })();\n }\n return background;\n}\n\nexport const healthSyncStore = new HealthSyncStore({\n storage: AsyncStorage,\n createReader: createPlatformReader,\n vault: secureSessionVault,\n background: {\n enable: async () => {\n await (await loadPlatformBackground())?.enable();\n },\n disable: async () => {\n await (await loadPlatformBackground())?.disable();\n },\n },\n signedIn: async () => Boolean(await readAuth0IdToken()),\n});\n\nexport function useHealthSync(): HealthSyncSnapshot {\n return useSyncExternalStore(healthSyncStore.subscribe, healthSyncStore.getSnapshot, healthSyncStore.getSnapshot);\n}\n"],"names":[],"mappings":"uTAiBA,eAAe,oBAAqD,GAAA;AAClE,EAAI,IAAA;AACF,IAAI,IAAA,QAAA,CAAS,OAAO,KAAO,EAAA,OAAA,CAAQ,MAAM,OAAO,sBAAmB,GAAG,qBAAsB,EAAA;AAC5F,IAAI,IAAA,QAAA,CAAS,OAAO,SAAW,EAAA,OAAA,CAAQ,MAAM,OAAO,0BAAuB,GAAG,yBAA0B,EAAA;AAAA,GAClG,CAAA,OAAA,CAAA,EAAA;AAAA;AAGR,EAAO,OAAA,IAAA;AACT;AACA,IAAI,UAAsD,GAAA,IAAA;AAMnD,SAAS,sBAA2D,GAAA;AACzE,EAAA,IAAI,CAAC,UAAY,EAAA;AACf,IAAA,UAAA,GAAA,CAAc,YAAY;AACxB,MAAI,IAAA;AACF,QAAA,IAAI,SAAS,EAAO,KAAA,KAAA,UAAe,MAAM,OAAO,0BAAuB,CAAG,EAAA,mBAAA;AAC1E,QAAI,IAAA,QAAA,CAAS,OAAO,SAAW,EAAA;AAC7B,UAAQ,OAAA,CAAA,MAAM,OAAO,8BAA2B,CAAG,EAAA,uBAAA;AAAA;AACrD,OACM,CAAA,OAAA,CAAA,EAAA;AAAA;AAGR,MAAO,OAAA,IAAA;AAAA,KACN,GAAA;AAAA;AAEL,EAAO,OAAA,UAAA;AACT;AACa,MAAA,eAAA,GAAkB,IAAI,eAAgB,CAAA;AAAA,EACjD,OAAS,EAAA,YAAA;AAAA,EACT,YAAc,EAAA,oBAAA;AAAA,EACd,KAAO,EAAA,kBAAA;AAAA,EACP,UAAY,EAAA;AAAA,IACV,QAAQ,YAAY;AArDxB,MAAA,IAAA,EAAA;AAsDM,MAAO,OAAA,CAAA,EAAA,GAAA,MAAM,sBAAuB,EAAA,KAA7B,IAAiC,GAAA,MAAA,GAAA,EAAA,CAAA,MAAA,EAAA,CAAA;AAAA,KAC1C;AAAA,IACA,SAAS,YAAY;AAxDzB,MAAA,IAAA,EAAA;AAyDM,MAAO,OAAA,CAAA,EAAA,GAAA,MAAM,sBAAuB,EAAA,KAA7B,IAAiC,GAAA,MAAA,GAAA,EAAA,CAAA,OAAA,EAAA,CAAA;AAAA;AAC1C,GACF;AAAA,EACA,QAAU,EAAA,YAAY,OAAQ,CAAA,MAAM,kBAAkB;AACxD,CAAC;AACM,SAAS,aAAoC,GAAA;AAClD,EAAA,OAAO,qBAAqB,eAAgB,CAAA,SAAA,EAAW,eAAgB,CAAA,WAAA,EAAa,gBAAgB,WAAW,CAAA;AACjH"}