@adminide-stack/yantra-mobile 12.0.70-alpha.4 → 12.0.70

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.
@@ -103,7 +103,7 @@ const baseConfig = cleanEnv(env, {
103
103
  }),
104
104
  /** Yantra Chat registration: offer "Continue with test OTP" (123456, no SMS). Never on App Store builds. */
105
105
  CHAT_ALLOW_TEST_OTP: bool({
106
- default: true
106
+ default: false
107
107
  }),
108
108
  PROXY_STAGING_GATEWAY_URL: str({
109
109
  default: ""
@@ -1 +1 @@
1
- {"version":3,"file":"env-config.js","sources":["../../src/config/env-config.ts"],"sourcesContent":["import { str, cleanEnv, bool, num } from 'envalid';\n\nconst env = (process as { APP_ENV?: NodeJS.ProcessEnv; env?: NodeJS.ProcessEnv }).APP_ENV || process.env;\n\nconst baseConfig = cleanEnv(env, {\n GRAPHQL_URL: str({ default: 'http://localhost:8080/graphql' }),\n CLIENT_URL: str({ default: 'http://localhost:3000' }),\n /** Standalone Web Builder surface embedded in the mobile WebView shell. */\n WEB_BUILDER_URL: str({ default: 'http://localhost:5174/' }),\n /** Standalone Design surface — local vite is 5174/4174 next to canvas 5175. */\n DESIGN_SURFACE_URL: str({ default: 'http://localhost:5174/' }),\n /** Standalone Canvas surface embedded in the same WebView shell. */\n CANVAS_SURFACE_URL: str({ default: 'http://localhost:5175/' }),\n /**\n * GraphQL backend whose JWT keys the canvas index will verify.\n * Must stay in lockstep with canvas-surface `.env.development`\n * (`VITE_BACKEND_GRAPHQL_URL` / `VITE_SURFACE_INDEX_URL`). A locally\n * minted token 401s against `surface-index-backend.yantra-app-v1`.\n */\n CANVAS_BACKEND_GRAPHQL_URL: str({ default: 'https://ideback.yantra-app-v1.cdebase.dev/graphql' }),\n /** Explicit canvas index GraphQL URL. Empty → derived from the canvas auth backend. */\n SURFACE_INDEX_URL: str({ default: '' }),\n /**\n * Origin template for every other deployed surface: `{slug}` is the app's\n * `surfaceSlug`. One var instead of 20+ near-identical URLs, and it keeps the\n * environment (dev cluster / prod) in one place.\n */\n SURFACE_URL_TEMPLATE: str({ default: 'https://{slug}-surface.yantra-app-v1.cdebase.dev/' }),\n /**\n * Characters gallery manifest (`characters.json`). Empty by default →\n * DERIVED from SURFACE_URL_TEMPLATE + the wipe-immune `characters-native`\n * slug (see below), so a prod build reads prod and a dev build reads dev\n * with no per-profile URL to maintain. Set explicitly only to pin a\n * non-standard origin.\n */\n CHARACTERS_NATIVE_MANIFEST_URL: str({ default: '' }),\n /**\n * Character community subgraph GraphQL URL the federated character studio\n * reads (Discover feed, publish/edit). Empty by default → DERIVED from the\n * same backend the surface token is minted against (swap the first DNS label\n * `ideback.x.y` → `character-subgraph.x.y`, the convention the character\n * surfaces use). Set explicitly only to pin a non-standard origin.\n */\n CHARACTERS_SUBGRAPH_URL: str({ default: '' }),\n APP_NAME: str({ default: 'Yantra' }),\n GROQ_API_KEY: str({ default: '' }),\n CDECLI_AGENT_ENDPOINT: str({ default: 'https://cdecli-agent.cdebase.dev' }),\n CDECLI_AGENT_AUTH_TOKEN: str({ default: '' }),\n /** Seconds to wait for CDeCLI stream completion before timing out (then fallback chat API runs). Min 15, max 600. */\n /** Aligned with web `useCdecliChannel` (120s). Mobile hook uses the same constant inline. */\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: num({ default: 120 }),\n BACKEND_URL: str({ default: '' }),\n USE_PROXY_STAGING_GATEWAY: bool({ default: false }),\n /** Yantra Chat registration: offer \"Continue with test OTP\" (123456, no SMS). Never on App Store builds. */\n CHAT_ALLOW_TEST_OTP: bool({ default: true }),\n PROXY_STAGING_GATEWAY_URL: str({ default: '' }),\n});\n\n/**\n * Where the app fetches the characters gallery manifest.\n *\n * Mirrors metro.federation.js `charactersRemoteUrl()`: the manifest lives on\n * the dedicated `characters-native` artifacts slug, NOT `character-surface`.\n * The bulk web-surface republish redeploys character-surface from its pinned\n * web bundle and wipes anything hand-added under /native, so the gallery data\n * (like the federated remote) must ride the wipe-immune slug. The origin\n * DERIVES from SURFACE_URL_TEMPLATE, so prod builds read prod and dev builds\n * read dev automatically. An explicit CHARACTERS_NATIVE_MANIFEST_URL still\n * wins for a one-off pin.\n */\nfunction resolveCharactersManifestUrl(explicit: string, template: string): string {\n if (explicit) return explicit;\n const base = (template || 'https://{slug}-surface.yantra-app-v1.cdebase.dev/')\n .replace('{slug}', 'characters-native')\n .replace(/\\/+$/, '');\n return `${base}/native/characters.json`;\n}\n\n/**\n * Character community subgraph URL. Derives from the SAME backend the surface\n * session token is minted against, so it stays authoritative across dev/prod and\n * matches the token's audience: a locally minted token is scoped to\n * CANVAS_BACKEND_GRAPHQL_URL (the cluster backend), so a local GRAPHQL_URL uses\n * that cluster host too - a subgraph on localhost would 401 the same JWT.\n */\nfunction resolveCharacterSubgraphUrl(explicit: string, graphqlUrl: string, canvasBackend: string): string {\n if (explicit) return explicit;\n const isLocal = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:|\\/|$)/i.test((graphqlUrl || '').trim());\n const backend = isLocal ? canvasBackend : graphqlUrl;\n const match = (backend || '').trim().match(/^(https?:)\\/\\/([^/]+)/i);\n if (!match) return '';\n const labels = match[2].split('.');\n if (labels.length < 3) return '';\n return `${match[1]}//character-subgraph.${labels.slice(1).join('.')}/graphql`;\n}\n\nfunction resolveAgentGatewayGraphqlUrl(): string {\n if (baseConfig.USE_PROXY_STAGING_GATEWAY && baseConfig.PROXY_STAGING_GATEWAY_URL) {\n return baseConfig.PROXY_STAGING_GATEWAY_URL;\n }\n if (baseConfig.GRAPHQL_URL) return baseConfig.GRAPHQL_URL;\n if (baseConfig.BACKEND_URL) return `${baseConfig.BACKEND_URL.replace(/\\/$/, '')}/graphql`;\n return '/graphql';\n}\n\nfunction resolveBaseWsUrl(graphqlUrl: string): string {\n const base = graphqlUrl.replace(/\\/graphql$/, '');\n return base.replace(/^http/, 'ws');\n}\n\nfunction resolveAgentGatewayWsUrl(graphqlUrl: string): string {\n return `${resolveBaseWsUrl(graphqlUrl)}/ws/openclaw-chat`;\n}\n\nconst agentGatewayGraphqlUrl = resolveAgentGatewayGraphqlUrl();\n\nconst clampedCdecliTimeoutSec = Math.min(600, Math.max(15, Math.round(baseConfig.CDECLI_CHAT_RESPONSE_TIMEOUT_SEC)));\n\nexport const config = {\n ...baseConfig,\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: clampedCdecliTimeoutSec,\n CHARACTERS_NATIVE_MANIFEST_URL: resolveCharactersManifestUrl(\n baseConfig.CHARACTERS_NATIVE_MANIFEST_URL,\n baseConfig.SURFACE_URL_TEMPLATE,\n ),\n CHARACTERS_SUBGRAPH_URL: resolveCharacterSubgraphUrl(\n baseConfig.CHARACTERS_SUBGRAPH_URL,\n baseConfig.GRAPHQL_URL,\n baseConfig.CANVAS_BACKEND_GRAPHQL_URL,\n ),\n AGENT_GATEWAY_GRAPHQL_URL: agentGatewayGraphqlUrl,\n AGENT_GATEWAY_WS_URL: resolveAgentGatewayWsUrl(agentGatewayGraphqlUrl),\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"env-config.js","sources":["../../src/config/env-config.ts"],"sourcesContent":["import { str, cleanEnv, bool, num } from 'envalid';\n\nconst env = (process as { APP_ENV?: NodeJS.ProcessEnv; env?: NodeJS.ProcessEnv }).APP_ENV || process.env;\n\nconst baseConfig = cleanEnv(env, {\n GRAPHQL_URL: str({ default: 'http://localhost:8080/graphql' }),\n CLIENT_URL: str({ default: 'http://localhost:3000' }),\n /** Standalone Web Builder surface embedded in the mobile WebView shell. */\n WEB_BUILDER_URL: str({ default: 'http://localhost:5174/' }),\n /** Standalone Design surface — local vite is 5174/4174 next to canvas 5175. */\n DESIGN_SURFACE_URL: str({ default: 'http://localhost:5174/' }),\n /** Standalone Canvas surface embedded in the same WebView shell. */\n CANVAS_SURFACE_URL: str({ default: 'http://localhost:5175/' }),\n /**\n * GraphQL backend whose JWT keys the canvas index will verify.\n * Must stay in lockstep with canvas-surface `.env.development`\n * (`VITE_BACKEND_GRAPHQL_URL` / `VITE_SURFACE_INDEX_URL`). A locally\n * minted token 401s against `surface-index-backend.yantra-app-v1`.\n */\n CANVAS_BACKEND_GRAPHQL_URL: str({ default: 'https://ideback.yantra-app-v1.cdebase.dev/graphql' }),\n /** Explicit canvas index GraphQL URL. Empty → derived from the canvas auth backend. */\n SURFACE_INDEX_URL: str({ default: '' }),\n /**\n * Origin template for every other deployed surface: `{slug}` is the app's\n * `surfaceSlug`. One var instead of 20+ near-identical URLs, and it keeps the\n * environment (dev cluster / prod) in one place.\n */\n SURFACE_URL_TEMPLATE: str({ default: 'https://{slug}-surface.yantra-app-v1.cdebase.dev/' }),\n /**\n * Characters gallery manifest (`characters.json`). Empty by default →\n * DERIVED from SURFACE_URL_TEMPLATE + the wipe-immune `characters-native`\n * slug (see below), so a prod build reads prod and a dev build reads dev\n * with no per-profile URL to maintain. Set explicitly only to pin a\n * non-standard origin.\n */\n CHARACTERS_NATIVE_MANIFEST_URL: str({ default: '' }),\n /**\n * Character community subgraph GraphQL URL the federated character studio\n * reads (Discover feed, publish/edit). Empty by default → DERIVED from the\n * same backend the surface token is minted against (swap the first DNS label\n * `ideback.x.y` → `character-subgraph.x.y`, the convention the character\n * surfaces use). Set explicitly only to pin a non-standard origin.\n */\n CHARACTERS_SUBGRAPH_URL: str({ default: '' }),\n APP_NAME: str({ default: 'Yantra' }),\n GROQ_API_KEY: str({ default: '' }),\n CDECLI_AGENT_ENDPOINT: str({ default: 'https://cdecli-agent.cdebase.dev' }),\n CDECLI_AGENT_AUTH_TOKEN: str({ default: '' }),\n /** Seconds to wait for CDeCLI stream completion before timing out (then fallback chat API runs). Min 15, max 600. */\n /** Aligned with web `useCdecliChannel` (120s). Mobile hook uses the same constant inline. */\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: num({ default: 120 }),\n BACKEND_URL: str({ default: '' }),\n USE_PROXY_STAGING_GATEWAY: bool({ default: false }),\n /** Yantra Chat registration: offer \"Continue with test OTP\" (123456, no SMS). Never on App Store builds. */\n CHAT_ALLOW_TEST_OTP: bool({ default: false }),\n PROXY_STAGING_GATEWAY_URL: str({ default: '' }),\n});\n\n/**\n * Where the app fetches the characters gallery manifest.\n *\n * Mirrors metro.federation.js `charactersRemoteUrl()`: the manifest lives on\n * the dedicated `characters-native` artifacts slug, NOT `character-surface`.\n * The bulk web-surface republish redeploys character-surface from its pinned\n * web bundle and wipes anything hand-added under /native, so the gallery data\n * (like the federated remote) must ride the wipe-immune slug. The origin\n * DERIVES from SURFACE_URL_TEMPLATE, so prod builds read prod and dev builds\n * read dev automatically. An explicit CHARACTERS_NATIVE_MANIFEST_URL still\n * wins for a one-off pin.\n */\nfunction resolveCharactersManifestUrl(explicit: string, template: string): string {\n if (explicit) return explicit;\n const base = (template || 'https://{slug}-surface.yantra-app-v1.cdebase.dev/')\n .replace('{slug}', 'characters-native')\n .replace(/\\/+$/, '');\n return `${base}/native/characters.json`;\n}\n\n/**\n * Character community subgraph URL. Derives from the SAME backend the surface\n * session token is minted against, so it stays authoritative across dev/prod and\n * matches the token's audience: a locally minted token is scoped to\n * CANVAS_BACKEND_GRAPHQL_URL (the cluster backend), so a local GRAPHQL_URL uses\n * that cluster host too - a subgraph on localhost would 401 the same JWT.\n */\nfunction resolveCharacterSubgraphUrl(explicit: string, graphqlUrl: string, canvasBackend: string): string {\n if (explicit) return explicit;\n const isLocal = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:|\\/|$)/i.test((graphqlUrl || '').trim());\n const backend = isLocal ? canvasBackend : graphqlUrl;\n const match = (backend || '').trim().match(/^(https?:)\\/\\/([^/]+)/i);\n if (!match) return '';\n const labels = match[2].split('.');\n if (labels.length < 3) return '';\n return `${match[1]}//character-subgraph.${labels.slice(1).join('.')}/graphql`;\n}\n\nfunction resolveAgentGatewayGraphqlUrl(): string {\n if (baseConfig.USE_PROXY_STAGING_GATEWAY && baseConfig.PROXY_STAGING_GATEWAY_URL) {\n return baseConfig.PROXY_STAGING_GATEWAY_URL;\n }\n if (baseConfig.GRAPHQL_URL) return baseConfig.GRAPHQL_URL;\n if (baseConfig.BACKEND_URL) return `${baseConfig.BACKEND_URL.replace(/\\/$/, '')}/graphql`;\n return '/graphql';\n}\n\nfunction resolveBaseWsUrl(graphqlUrl: string): string {\n const base = graphqlUrl.replace(/\\/graphql$/, '');\n return base.replace(/^http/, 'ws');\n}\n\nfunction resolveAgentGatewayWsUrl(graphqlUrl: string): string {\n return `${resolveBaseWsUrl(graphqlUrl)}/ws/openclaw-chat`;\n}\n\nconst agentGatewayGraphqlUrl = resolveAgentGatewayGraphqlUrl();\n\nconst clampedCdecliTimeoutSec = Math.min(600, Math.max(15, Math.round(baseConfig.CDECLI_CHAT_RESPONSE_TIMEOUT_SEC)));\n\nexport const config = {\n ...baseConfig,\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: clampedCdecliTimeoutSec,\n CHARACTERS_NATIVE_MANIFEST_URL: resolveCharactersManifestUrl(\n baseConfig.CHARACTERS_NATIVE_MANIFEST_URL,\n baseConfig.SURFACE_URL_TEMPLATE,\n ),\n CHARACTERS_SUBGRAPH_URL: resolveCharacterSubgraphUrl(\n baseConfig.CHARACTERS_SUBGRAPH_URL,\n baseConfig.GRAPHQL_URL,\n baseConfig.CANVAS_BACKEND_GRAPHQL_URL,\n ),\n AGENT_GATEWAY_GRAPHQL_URL: agentGatewayGraphqlUrl,\n AGENT_GATEWAY_WS_URL: resolveAgentGatewayWsUrl(agentGatewayGraphqlUrl),\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,383 @@
1
+ import {jsx,jsxs}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,ScrollView,View,Pressable,Text,Modal,ActivityIndicator}from'react-native';import {Image}from'expo-image';import {MaterialCommunityIcons}from'@expo/vector-icons';import {SafeAreaView,SafeAreaProvider}from'react-native-safe-area-context';import {attachmentUrl}from'./chatModel.js';const IMAGE_MAX = 240;
2
+ function durationLabel(ms) {
3
+ const total = Math.max(0, Math.round((ms != null ? ms : 0) / 1e3));
4
+ return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
5
+ }
6
+ function imageSize(a) {
7
+ var _a, _b;
8
+ const w = (_a = a.width) != null ? _a : 0;
9
+ const h = (_b = a.height) != null ? _b : 0;
10
+ if (!w || !h) return {
11
+ width: IMAGE_MAX,
12
+ height: IMAGE_MAX * 0.75
13
+ };
14
+ const scale = Math.min(IMAGE_MAX / w, IMAGE_MAX / h);
15
+ return {
16
+ width: Math.max(120, Math.round(w * scale)),
17
+ height: Math.max(90, Math.round(h * scale))
18
+ };
19
+ }
20
+ function MessageAttachments({
21
+ attachments,
22
+ mine,
23
+ theme,
24
+ fileBaseUrl,
25
+ audio,
26
+ uploading,
27
+ onOpenImage
28
+ }) {
29
+ const images = attachments.filter((a) => a.kind === "image");
30
+ const voices = attachments.filter((a) => a.kind === "audio");
31
+ const others = attachments.filter((a) => a.kind === "file");
32
+ const textColor = mine ? theme.bubbleMineText : theme.bubbleTheirsText;
33
+ return /* @__PURE__ */ jsxs(View, { children: [
34
+ images.length ? /* @__PURE__ */ jsx(View, { style: [styles.imageGrid, {
35
+ flexDirection: "row"
36
+ }], children: images.map((a) => {
37
+ const url = attachmentUrl(a, fileBaseUrl);
38
+ const size = images.length === 1 ? imageSize(a) : {
39
+ width: 116,
40
+ height: 116
41
+ };
42
+ return /* @__PURE__ */ jsxs(Pressable, { onPress: onOpenImage ? () => onOpenImage(url) : void 0, accessibilityRole: "imagebutton", accessibilityLabel: "Photo", children: [
43
+ /* @__PURE__ */ jsx(Image, { source: {
44
+ uri: url
45
+ }, style: [styles.image, size, {
46
+ backgroundColor: theme.surface
47
+ }], contentFit: "cover", transition: 120 }),
48
+ uploading ? /* @__PURE__ */ jsx(View, { style: [StyleSheet.absoluteFill, styles.imageOverlay], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: "#ffffff" }) }) : null
49
+ ] }, a.id);
50
+ }) }) : null,
51
+ voices.map((a) => /* @__PURE__ */ jsx(VoiceNote, { url: attachmentUrl(a, fileBaseUrl), durationMs: a.durationMs, audio, color: textColor, accent: mine ? textColor : theme.accent, disabled: uploading }, a.id)),
52
+ others.map((a) => /* @__PURE__ */ jsxs(View, { style: [styles.fileRow, {
53
+ flexDirection: "row"
54
+ }], children: [
55
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "file-outline", size: 20, color: textColor }),
56
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.fileName, {
57
+ color: textColor
58
+ }], children: a.name })
59
+ ] }, a.id))
60
+ ] });
61
+ }
62
+ function VoiceNote({
63
+ url,
64
+ durationMs,
65
+ audio,
66
+ color,
67
+ accent,
68
+ disabled
69
+ }) {
70
+ const [playing, setPlaying] = React.useState(false);
71
+ const stopRef = React.useRef(null);
72
+ React.useEffect(() => () => {
73
+ var _a;
74
+ return (_a = stopRef.current) == null ? void 0 : _a.call(stopRef);
75
+ }, []);
76
+ const toggle = React.useCallback(async () => {
77
+ var _a;
78
+ if (!audio) return;
79
+ if (playing) {
80
+ (_a = stopRef.current) == null ? void 0 : _a.call(stopRef);
81
+ stopRef.current = null;
82
+ setPlaying(false);
83
+ return;
84
+ }
85
+ setPlaying(true);
86
+ try {
87
+ stopRef.current = await audio.play(url, () => {
88
+ stopRef.current = null;
89
+ setPlaying(false);
90
+ });
91
+ } catch (e) {
92
+ setPlaying(false);
93
+ }
94
+ }, [audio, playing, url]);
95
+ return /* @__PURE__ */ jsxs(View, { style: [styles.voice, {
96
+ flexDirection: "row"
97
+ }], children: [
98
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => void toggle(), disabled: !audio || disabled, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: playing ? "Pause voice message" : "Play voice message", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: playing ? "pause-circle" : "play-circle", size: 34, color: accent }) }),
99
+ /* @__PURE__ */ jsx(View, { style: [styles.voiceWave, {
100
+ flexDirection: "row"
101
+ }], children: WAVE.map((h, i) => /* @__PURE__ */ jsx(View, { style: [styles.voiceBar, {
102
+ height: h,
103
+ backgroundColor: color,
104
+ opacity: 0.55
105
+ }] }, i)) }),
106
+ /* @__PURE__ */ jsx(Text, { style: [styles.voiceTime, {
107
+ color
108
+ }], children: durationLabel(durationMs) })
109
+ ] });
110
+ }
111
+ const WAVE = [6, 12, 18, 10, 22, 14, 8, 16, 24, 12, 6, 14, 20, 10, 16, 8, 12, 18];
112
+ function ImageViewer({
113
+ url,
114
+ onClose
115
+ }) {
116
+ return /* @__PURE__ */ jsx(Modal, { visible: Boolean(url), transparent: true, animationType: "fade", statusBarTranslucent: true, onRequestClose: onClose, children: /* @__PURE__ */ jsx(SafeAreaProvider, { children: /* @__PURE__ */ jsxs(View, { style: styles.viewer, children: [
117
+ url ? /* @__PURE__ */ jsx(Pressable, { style: StyleSheet.absoluteFill, onPress: onClose, accessibilityLabel: "Close photo", children: /* @__PURE__ */ jsx(Image, { source: {
118
+ uri: url
119
+ }, style: StyleSheet.absoluteFill, contentFit: "contain" }) }) : null,
120
+ /* @__PURE__ */ jsx(SafeAreaView, { edges: ["top"], style: styles.viewerTop, pointerEvents: "box-none", children: /* @__PURE__ */ jsx(Pressable, { onPress: onClose, hitSlop: 16, accessibilityRole: "button", accessibilityLabel: "Close", style: styles.viewerClose, testID: "chat-image-viewer-close", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 24, color: "#ffffff" }) }) })
121
+ ] }) }) });
122
+ }
123
+ function AttachSheet({
124
+ visible,
125
+ theme,
126
+ onClose,
127
+ onPhotos,
128
+ onCamera,
129
+ onDismissed
130
+ }) {
131
+ const row = (icon, label, run) => /* @__PURE__ */ jsx(Pressable, { onPress: run, accessibilityRole: "button", accessibilityLabel: label, style: ({
132
+ pressed
133
+ }) => ({
134
+ backgroundColor: pressed ? theme.surface : "transparent"
135
+ }), children: /* @__PURE__ */ jsxs(View, { style: [styles.sheetRow, {
136
+ flexDirection: "row"
137
+ }], children: [
138
+ /* @__PURE__ */ jsx(View, { style: [styles.sheetIcon, {
139
+ backgroundColor: theme.surface
140
+ }], children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: icon, size: 22, color: theme.accent }) }),
141
+ /* @__PURE__ */ jsx(Text, { style: [styles.sheetLabel, {
142
+ color: theme.text
143
+ }], children: label })
144
+ ] }) });
145
+ return /* @__PURE__ */ jsxs(Modal, { visible, transparent: true, animationType: "slide", onRequestClose: onClose, onDismiss: onDismissed, children: [
146
+ /* @__PURE__ */ jsx(Pressable, { style: styles.sheetBackdrop, onPress: onClose, accessibilityLabel: "Close" }),
147
+ /* @__PURE__ */ jsxs(SafeAreaView, { edges: ["bottom"], style: [styles.sheet, {
148
+ backgroundColor: theme.bg
149
+ }], children: [
150
+ /* @__PURE__ */ jsx(View, { style: [styles.sheetHandle, {
151
+ backgroundColor: theme.border
152
+ }] }),
153
+ row("image-multiple-outline", "Photos", onPhotos),
154
+ row("camera-outline", "Camera", onCamera)
155
+ ] })
156
+ ] });
157
+ }
158
+ function StagedAttachments({
159
+ files,
160
+ theme,
161
+ onRemove
162
+ }) {
163
+ if (!files.length) return null;
164
+ return /* @__PURE__ */ jsx(
165
+ ScrollView,
166
+ {
167
+ horizontal: true,
168
+ showsHorizontalScrollIndicator: false,
169
+ style: [styles.stagedBar, {
170
+ backgroundColor: theme.composerBar
171
+ }],
172
+ contentContainerStyle: styles.staged,
173
+ children: files.map((f, i) => /* @__PURE__ */ jsxs(View, { style: styles.stagedItem, children: [
174
+ /* @__PURE__ */ jsx(Image, { source: {
175
+ uri: f.uri
176
+ }, style: styles.stagedImage, contentFit: "cover" }),
177
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => onRemove(i), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Remove photo", style: styles.stagedRemove, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close-circle", size: 20, color: "#ffffff" }) })
178
+ ] }, `${f.uri}-${i}`))
179
+ }
180
+ );
181
+ }
182
+ function RecordingBar({
183
+ theme,
184
+ startedAt,
185
+ onCancel,
186
+ onSend
187
+ }) {
188
+ const [now, setNow] = React.useState(Date.now());
189
+ React.useEffect(() => {
190
+ const t = setInterval(() => setNow(Date.now()), 250);
191
+ return () => clearInterval(t);
192
+ }, []);
193
+ return /* @__PURE__ */ jsxs(View, { style: [styles.recording, {
194
+ flexDirection: "row",
195
+ backgroundColor: theme.composerBar
196
+ }], children: [
197
+ /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Cancel recording", style: styles.recordingButton, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 26, color: theme.textMuted }) }),
198
+ /* @__PURE__ */ jsxs(View, { style: [styles.recordingPill, {
199
+ flexDirection: "row",
200
+ backgroundColor: theme.bg,
201
+ borderColor: theme.border
202
+ }], children: [
203
+ /* @__PURE__ */ jsx(View, { style: [styles.recordingDot, {
204
+ backgroundColor: theme.danger
205
+ }] }),
206
+ /* @__PURE__ */ jsx(Text, { style: [styles.recordingTime, {
207
+ color: theme.text
208
+ }], children: durationLabel(now - startedAt) }),
209
+ /* @__PURE__ */ jsx(Text, { style: [styles.recordingHint, {
210
+ color: theme.textMuted
211
+ }], children: "Recording\u2026" })
212
+ ] }),
213
+ /* @__PURE__ */ jsx(Pressable, { onPress: onSend, accessibilityRole: "button", accessibilityLabel: "Send voice message", style: [styles.recordingSend, {
214
+ backgroundColor: theme.accent
215
+ }], testID: "chat-voice-send", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "arrow-up", size: 22, color: theme.onAccent }) })
216
+ ] });
217
+ }
218
+ const styles = StyleSheet.create({
219
+ imageGrid: {
220
+ flexWrap: "wrap",
221
+ gap: 4,
222
+ marginVertical: 2
223
+ },
224
+ image: {
225
+ borderRadius: 12
226
+ },
227
+ imageOverlay: {
228
+ alignItems: "center",
229
+ justifyContent: "center",
230
+ backgroundColor: "rgba(0,0,0,0.25)",
231
+ borderRadius: 12
232
+ },
233
+ fileRow: {
234
+ alignItems: "center",
235
+ paddingVertical: 4,
236
+ maxWidth: 220
237
+ },
238
+ fileName: {
239
+ marginLeft: 6,
240
+ fontSize: 15,
241
+ flexShrink: 1
242
+ },
243
+ voice: {
244
+ alignItems: "center",
245
+ paddingVertical: 2,
246
+ paddingHorizontal: 6,
247
+ minWidth: 200
248
+ },
249
+ voiceWave: {
250
+ alignItems: "center",
251
+ flex: 1,
252
+ marginHorizontal: 8,
253
+ gap: 2
254
+ },
255
+ voiceBar: {
256
+ width: 3,
257
+ borderRadius: 2
258
+ },
259
+ voiceTime: {
260
+ fontSize: 13,
261
+ fontVariant: ["tabular-nums"]
262
+ },
263
+ viewer: {
264
+ flex: 1,
265
+ backgroundColor: "#000000"
266
+ },
267
+ viewerTop: {
268
+ position: "absolute",
269
+ top: 0,
270
+ left: 0,
271
+ right: 0,
272
+ paddingHorizontal: 16,
273
+ paddingTop: 8,
274
+ zIndex: 2
275
+ },
276
+ viewerClose: {
277
+ width: 36,
278
+ height: 36,
279
+ borderRadius: 18,
280
+ alignItems: "center",
281
+ justifyContent: "center",
282
+ backgroundColor: "rgba(255,255,255,0.18)"
283
+ },
284
+ sheetBackdrop: {
285
+ flex: 1,
286
+ backgroundColor: "rgba(15, 23, 42, 0.35)"
287
+ },
288
+ sheet: {
289
+ borderTopLeftRadius: 16,
290
+ borderTopRightRadius: 16,
291
+ paddingTop: 8,
292
+ paddingBottom: 8
293
+ },
294
+ sheetHandle: {
295
+ alignSelf: "center",
296
+ width: 36,
297
+ height: 5,
298
+ borderRadius: 3,
299
+ marginBottom: 8
300
+ },
301
+ sheetRow: {
302
+ alignItems: "center",
303
+ paddingHorizontal: 20,
304
+ paddingVertical: 12
305
+ },
306
+ sheetIcon: {
307
+ width: 40,
308
+ height: 40,
309
+ borderRadius: 20,
310
+ alignItems: "center",
311
+ justifyContent: "center"
312
+ },
313
+ sheetLabel: {
314
+ fontSize: 17,
315
+ marginLeft: 14
316
+ },
317
+ stagedBar: {
318
+ flexGrow: 0,
319
+ flexShrink: 0
320
+ },
321
+ staged: {
322
+ paddingHorizontal: 12,
323
+ paddingTop: 8,
324
+ gap: 8
325
+ },
326
+ stagedItem: {
327
+ width: 72,
328
+ height: 72
329
+ },
330
+ stagedImage: {
331
+ width: 72,
332
+ height: 72,
333
+ borderRadius: 10
334
+ },
335
+ stagedRemove: {
336
+ position: "absolute",
337
+ top: 2,
338
+ right: 2,
339
+ backgroundColor: "rgba(0,0,0,0.45)",
340
+ borderRadius: 10
341
+ },
342
+ recording: {
343
+ alignItems: "center",
344
+ paddingHorizontal: 8,
345
+ paddingVertical: 8
346
+ },
347
+ recordingButton: {
348
+ width: 36,
349
+ height: 36,
350
+ alignItems: "center",
351
+ justifyContent: "center"
352
+ },
353
+ recordingPill: {
354
+ flex: 1,
355
+ alignItems: "center",
356
+ minHeight: 38,
357
+ borderRadius: 20,
358
+ borderWidth: StyleSheet.hairlineWidth,
359
+ paddingHorizontal: 12,
360
+ marginHorizontal: 6
361
+ },
362
+ recordingDot: {
363
+ width: 10,
364
+ height: 10,
365
+ borderRadius: 5
366
+ },
367
+ recordingTime: {
368
+ fontSize: 16,
369
+ marginLeft: 8,
370
+ fontVariant: ["tabular-nums"]
371
+ },
372
+ recordingHint: {
373
+ fontSize: 15,
374
+ marginLeft: 10
375
+ },
376
+ recordingSend: {
377
+ width: 32,
378
+ height: 32,
379
+ borderRadius: 16,
380
+ alignItems: "center",
381
+ justifyContent: "center"
382
+ }
383
+ });export{AttachSheet,ImageViewer,MessageAttachments,RecordingBar,StagedAttachments,durationLabel};//# sourceMappingURL=ChatAttachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatAttachments.js","sources":["../../../src/features/messaging/ChatAttachments.tsx"],"sourcesContent":["/**\n * Attachments in the chat: photos and voice notes inside bubbles, the full-screen photo viewer,\n * and the composer pieces (the \"+\" menu, picked photos, the recording bar).\n *\n * Plain react-native + expo-image only (host-shared); picking, uploading, recording and playback\n * all go through the host seams (`ChatMediaSeam`, `ChatAudioSeam`).\n */\nimport * as React from 'react';\nimport { ActivityIndicator, Modal, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';\nimport { Image } from 'expo-image';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';\nimport type { ChatTheme } from './ChatComponents';\nimport type { ChatAudioSeam, ChatPickedImage } from './chatHost';\nimport { attachmentUrl, type ChatAttachment } from './chatModel';\n\nconst IMAGE_MAX = 240;\n\nexport function durationLabel(ms: number | null | undefined): string {\n const total = Math.max(0, Math.round((ms ?? 0) / 1000));\n return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, '0')}`;\n}\n\nfunction imageSize(a: ChatAttachment): { width: number; height: number } {\n const w = a.width ?? 0;\n const h = a.height ?? 0;\n if (!w || !h) return { width: IMAGE_MAX, height: IMAGE_MAX * 0.75 };\n const scale = Math.min(IMAGE_MAX / w, IMAGE_MAX / h);\n return { width: Math.max(120, Math.round(w * scale)), height: Math.max(90, Math.round(h * scale)) };\n}\n\n/** Photos and voice notes of one message. */\nexport function MessageAttachments({\n attachments,\n mine,\n theme,\n fileBaseUrl,\n audio,\n uploading,\n onOpenImage,\n}: {\n attachments: ChatAttachment[];\n mine: boolean;\n theme: ChatTheme;\n fileBaseUrl?: string | null;\n audio?: ChatAudioSeam;\n /** Still sending: shows a spinner over photos. */\n uploading?: boolean;\n onOpenImage?: (url: string) => void;\n}) {\n const images = attachments.filter((a) => a.kind === 'image');\n const voices = attachments.filter((a) => a.kind === 'audio');\n const others = attachments.filter((a) => a.kind === 'file');\n const textColor = mine ? theme.bubbleMineText : theme.bubbleTheirsText;\n\n return (\n <View>\n {images.length ? (\n <View style={[styles.imageGrid, { flexDirection: 'row' }]}>\n {images.map((a) => {\n const url = attachmentUrl(a, fileBaseUrl);\n const size = images.length === 1 ? imageSize(a) : { width: 116, height: 116 };\n return (\n <Pressable\n key={a.id}\n onPress={onOpenImage ? () => onOpenImage(url) : undefined}\n accessibilityRole=\"imagebutton\"\n accessibilityLabel=\"Photo\"\n >\n <Image\n source={{ uri: url }}\n style={[styles.image, size, { backgroundColor: theme.surface }]}\n contentFit=\"cover\"\n transition={120}\n />\n {uploading ? (\n <View style={[StyleSheet.absoluteFill, styles.imageOverlay]}>\n <ActivityIndicator color=\"#ffffff\" />\n </View>\n ) : null}\n </Pressable>\n );\n })}\n </View>\n ) : null}\n {voices.map((a) => (\n <VoiceNote\n key={a.id}\n url={attachmentUrl(a, fileBaseUrl)}\n durationMs={a.durationMs}\n audio={audio}\n color={textColor}\n accent={mine ? textColor : theme.accent}\n disabled={uploading}\n />\n ))}\n {others.map((a) => (\n <View key={a.id} style={[styles.fileRow, { flexDirection: 'row' }]}>\n <MaterialCommunityIcons name=\"file-outline\" size={20} color={textColor} />\n <Text numberOfLines={1} style={[styles.fileName, { color: textColor }]}>\n {a.name}\n </Text>\n </View>\n ))}\n </View>\n );\n}\n\nfunction VoiceNote({\n url,\n durationMs,\n audio,\n color,\n accent,\n disabled,\n}: {\n url: string;\n durationMs?: number | null;\n audio?: ChatAudioSeam;\n color: string;\n accent: string;\n disabled?: boolean;\n}) {\n const [playing, setPlaying] = React.useState(false);\n const stopRef = React.useRef<(() => void) | null>(null);\n\n React.useEffect(() => () => stopRef.current?.(), []);\n\n const toggle = React.useCallback(async () => {\n if (!audio) return;\n if (playing) {\n stopRef.current?.();\n stopRef.current = null;\n setPlaying(false);\n return;\n }\n setPlaying(true);\n try {\n stopRef.current = await audio.play(url, () => {\n stopRef.current = null;\n setPlaying(false);\n });\n } catch {\n setPlaying(false);\n }\n }, [audio, playing, url]);\n\n return (\n <View style={[styles.voice, { flexDirection: 'row' }]}>\n <Pressable\n onPress={() => void toggle()}\n disabled={!audio || disabled}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel={playing ? 'Pause voice message' : 'Play voice message'}\n >\n <MaterialCommunityIcons name={playing ? 'pause-circle' : 'play-circle'} size={34} color={accent} />\n </Pressable>\n <View style={[styles.voiceWave, { flexDirection: 'row' }]}>\n {WAVE.map((h, i) => (\n <View key={i} style={[styles.voiceBar, { height: h, backgroundColor: color, opacity: 0.55 }]} />\n ))}\n </View>\n <Text style={[styles.voiceTime, { color }]}>{durationLabel(durationMs)}</Text>\n </View>\n );\n}\n\nconst WAVE = [6, 12, 18, 10, 22, 14, 8, 16, 24, 12, 6, 14, 20, 10, 16, 8, 12, 18];\n\n/** Full-screen photo, tap anywhere or ✕ to close. */\nexport function ImageViewer({ url, onClose }: { url: string | null; onClose: () => void }) {\n return (\n <Modal visible={Boolean(url)} transparent animationType=\"fade\" statusBarTranslucent onRequestClose={onClose}>\n {/* A Modal is a new native root: without its own provider the safe-area insets read 0\n (first open), and the ✕ lands under the status bar where it cannot be tapped. */}\n <SafeAreaProvider>\n <View style={styles.viewer}>\n {url ? (\n <Pressable style={StyleSheet.absoluteFill} onPress={onClose} accessibilityLabel=\"Close photo\">\n <Image source={{ uri: url }} style={StyleSheet.absoluteFill} contentFit=\"contain\" />\n </Pressable>\n ) : null}\n <SafeAreaView edges={['top']} style={styles.viewerTop} pointerEvents=\"box-none\">\n <Pressable\n onPress={onClose}\n hitSlop={16}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Close\"\n style={styles.viewerClose}\n testID=\"chat-image-viewer-close\"\n >\n <MaterialCommunityIcons name=\"close\" size={24} color=\"#ffffff\" />\n </Pressable>\n </SafeAreaView>\n </View>\n </SafeAreaProvider>\n </Modal>\n );\n}\n\n/** The \"+\" menu: Photos and Camera, like iMessage's app drawer rows. */\nexport function AttachSheet({\n visible,\n theme,\n onClose,\n onPhotos,\n onCamera,\n onDismissed,\n}: {\n visible: boolean;\n theme: ChatTheme;\n onClose: () => void;\n onPhotos: () => void;\n onCamera: () => void;\n /** iOS: the sheet is fully gone - only now can the photo picker be presented. */\n onDismissed?: () => void;\n}) {\n const row = (icon: React.ComponentProps<typeof MaterialCommunityIcons>['name'], label: string, run: () => void) => (\n <Pressable\n onPress={run}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n style={({ pressed }) => ({ backgroundColor: pressed ? theme.surface : 'transparent' })}\n >\n <View style={[styles.sheetRow, { flexDirection: 'row' }]}>\n <View style={[styles.sheetIcon, { backgroundColor: theme.surface }]}>\n <MaterialCommunityIcons name={icon} size={22} color={theme.accent} />\n </View>\n <Text style={[styles.sheetLabel, { color: theme.text }]}>{label}</Text>\n </View>\n </Pressable>\n );\n return (\n <Modal visible={visible} transparent animationType=\"slide\" onRequestClose={onClose} onDismiss={onDismissed}>\n <Pressable style={styles.sheetBackdrop} onPress={onClose} accessibilityLabel=\"Close\" />\n <SafeAreaView edges={['bottom']} style={[styles.sheet, { backgroundColor: theme.bg }]}>\n <View style={[styles.sheetHandle, { backgroundColor: theme.border }]} />\n {row('image-multiple-outline', 'Photos', onPhotos)}\n {row('camera-outline', 'Camera', onCamera)}\n </SafeAreaView>\n </Modal>\n );\n}\n\n/** Photos picked for the next message, each removable. */\nexport function StagedAttachments({\n files,\n theme,\n onRemove,\n}: {\n files: ChatPickedImage[];\n theme: ChatTheme;\n onRemove: (index: number) => void;\n}) {\n if (!files.length) return null;\n return (\n <ScrollView\n horizontal\n showsHorizontalScrollIndicator={false}\n // ScrollView grows by default (flexGrow: 1) and would push the transcript up.\n style={[styles.stagedBar, { backgroundColor: theme.composerBar }]}\n contentContainerStyle={styles.staged}\n >\n {files.map((f, i) => (\n <View key={`${f.uri}-${i}`} style={styles.stagedItem}>\n <Image source={{ uri: f.uri }} style={styles.stagedImage} contentFit=\"cover\" />\n <Pressable\n onPress={() => onRemove(i)}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Remove photo\"\n style={styles.stagedRemove}\n >\n <MaterialCommunityIcons name=\"close-circle\" size={20} color=\"#ffffff\" />\n </Pressable>\n </View>\n ))}\n </ScrollView>\n );\n}\n\n/** Replaces the text field while recording: cancel, a live timer, send. */\nexport function RecordingBar({\n theme,\n startedAt,\n onCancel,\n onSend,\n}: {\n theme: ChatTheme;\n startedAt: number;\n onCancel: () => void;\n onSend: () => void;\n}) {\n const [now, setNow] = React.useState(Date.now());\n React.useEffect(() => {\n const t = setInterval(() => setNow(Date.now()), 250);\n return () => clearInterval(t);\n }, []);\n return (\n <View style={[styles.recording, { flexDirection: 'row', backgroundColor: theme.composerBar }]}>\n <Pressable\n onPress={onCancel}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Cancel recording\"\n style={styles.recordingButton}\n >\n <MaterialCommunityIcons name=\"close\" size={26} color={theme.textMuted} />\n </Pressable>\n <View\n style={[\n styles.recordingPill,\n { flexDirection: 'row', backgroundColor: theme.bg, borderColor: theme.border },\n ]}\n >\n <View style={[styles.recordingDot, { backgroundColor: theme.danger }]} />\n <Text style={[styles.recordingTime, { color: theme.text }]}>{durationLabel(now - startedAt)}</Text>\n <Text style={[styles.recordingHint, { color: theme.textMuted }]}>Recording…</Text>\n </View>\n <Pressable\n onPress={onSend}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Send voice message\"\n style={[styles.recordingSend, { backgroundColor: theme.accent }]}\n testID=\"chat-voice-send\"\n >\n <MaterialCommunityIcons name=\"arrow-up\" size={22} color={theme.onAccent} />\n </Pressable>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n imageGrid: { flexWrap: 'wrap', gap: 4, marginVertical: 2 },\n image: { borderRadius: 12 },\n imageOverlay: {\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'rgba(0,0,0,0.25)',\n borderRadius: 12,\n },\n fileRow: { alignItems: 'center', paddingVertical: 4, maxWidth: 220 },\n fileName: { marginLeft: 6, fontSize: 15, flexShrink: 1 },\n voice: { alignItems: 'center', paddingVertical: 2, paddingHorizontal: 6, minWidth: 200 },\n voiceWave: { alignItems: 'center', flex: 1, marginHorizontal: 8, gap: 2 },\n voiceBar: { width: 3, borderRadius: 2 },\n voiceTime: { fontSize: 13, fontVariant: ['tabular-nums'] },\n viewer: { flex: 1, backgroundColor: '#000000' },\n viewerTop: { position: 'absolute', top: 0, left: 0, right: 0, paddingHorizontal: 16, paddingTop: 8, zIndex: 2 },\n viewerClose: {\n width: 36,\n height: 36,\n borderRadius: 18,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'rgba(255,255,255,0.18)',\n },\n sheetBackdrop: { flex: 1, backgroundColor: 'rgba(15, 23, 42, 0.35)' },\n sheet: { borderTopLeftRadius: 16, borderTopRightRadius: 16, paddingTop: 8, paddingBottom: 8 },\n sheetHandle: { alignSelf: 'center', width: 36, height: 5, borderRadius: 3, marginBottom: 8 },\n sheetRow: { alignItems: 'center', paddingHorizontal: 20, paddingVertical: 12 },\n sheetIcon: { width: 40, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center' },\n sheetLabel: { fontSize: 17, marginLeft: 14 },\n stagedBar: { flexGrow: 0, flexShrink: 0 },\n staged: { paddingHorizontal: 12, paddingTop: 8, gap: 8 },\n stagedItem: { width: 72, height: 72 },\n stagedImage: { width: 72, height: 72, borderRadius: 10 },\n stagedRemove: { position: 'absolute', top: 2, right: 2, backgroundColor: 'rgba(0,0,0,0.45)', borderRadius: 10 },\n recording: { alignItems: 'center', paddingHorizontal: 8, paddingVertical: 8 },\n recordingButton: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },\n recordingPill: {\n flex: 1,\n alignItems: 'center',\n minHeight: 38,\n borderRadius: 20,\n borderWidth: StyleSheet.hairlineWidth,\n paddingHorizontal: 12,\n marginHorizontal: 6,\n },\n recordingDot: { width: 10, height: 10, borderRadius: 5 },\n recordingTime: { fontSize: 16, marginLeft: 8, fontVariant: ['tabular-nums'] },\n recordingHint: { fontSize: 15, marginLeft: 10 },\n recordingSend: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center' },\n});\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,View,Text,Pressable}from'react-native';import {Image}from'expo-image';import {MaterialCommunityIcons}from'@expo/vector-icons';import {mobileColors}from'../../theme/mobileTokens.js';import {initialsOf,timeLabel,relativeLabel}from'./chatModel.js';function buildChatTheme(isDark) {
1
+ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,View,Text,Pressable}from'react-native';import {Image}from'expo-image';import {MaterialCommunityIcons}from'@expo/vector-icons';import {mobileColors}from'../../theme/mobileTokens.js';import {initialsOf,timeLabel,relativeLabel}from'./chatModel.js';import {MessageAttachments}from'./ChatAttachments.js';function buildChatTheme(isDark) {
2
2
  const c = mobileColors(isDark);
3
3
  return {
4
4
  bg: c.bg,
@@ -150,7 +150,10 @@ function MessageBubble({
150
150
  groupWithPrevious,
151
151
  groupWithNext,
152
152
  onRetry,
153
- onLongPress
153
+ onLongPress,
154
+ fileBaseUrl,
155
+ audio,
156
+ onOpenImage
154
157
  }) {
155
158
  var _a;
156
159
  const bubbleRef = React.useRef(null);
@@ -198,14 +201,17 @@ function MessageBubble({
198
201
  height
199
202
  }));
200
203
  } : void 0, delayLongPress: 280, disabled: !onLongPress, accessibilityHint: onLongPress ? "Long press for reactions and options" : void 0, style: [styles.bubblePress, reactions.length ? styles.bubbleReacted : null], children: [
201
- /* @__PURE__ */ jsx(View, { ref: bubbleRef, collapsable: false, style: [styles.bubble, styles.bubbleFill, radii, {
204
+ /* @__PURE__ */ jsxs(View, { ref: bubbleRef, collapsable: false, style: [styles.bubble, styles.bubbleFill, radii, {
202
205
  backgroundColor: mine ? theme.bubbleMine : theme.bubbleTheirs
203
206
  }, failed && {
204
207
  borderWidth: 1,
205
208
  borderColor: theme.danger
206
- }, sending && styles.bubbleSending], children: /* @__PURE__ */ jsx(Text, { style: [styles.bubbleText, {
207
- color: mine ? theme.bubbleMineText : theme.bubbleTheirsText
208
- }], children: message.body }) }),
209
+ }, sending && !message.attachments.length && styles.bubbleSending, message.attachments.length ? styles.bubbleWithMedia : null], children: [
210
+ message.attachments.length ? /* @__PURE__ */ jsx(MessageAttachments, { attachments: message.attachments, mine, theme, fileBaseUrl, audio, uploading: sending, onOpenImage }) : null,
211
+ message.body.trim() ? /* @__PURE__ */ jsx(Text, { style: [styles.bubbleText, {
212
+ color: mine ? theme.bubbleMineText : theme.bubbleTheirsText
213
+ }, message.attachments.length ? styles.bubbleCaption : null], children: message.body.trim() }) : null
214
+ ] }),
209
215
  reactions.length ? (
210
216
  // iMessage puts the Tapback on the bubble's top corner facing the other person.
211
217
  /* @__PURE__ */ jsx(View, { style: [styles.reactionBadge, mine ? styles.reactionBadgeMine : styles.reactionBadgeTheirs, {
@@ -449,6 +455,16 @@ const styles = StyleSheet.create({
449
455
  fontSize: 17,
450
456
  lineHeight: 22
451
457
  },
458
+ /** Photos sit close to the bubble edge, like iMessage. */
459
+ bubbleWithMedia: {
460
+ paddingHorizontal: 4,
461
+ paddingVertical: 4
462
+ },
463
+ bubbleCaption: {
464
+ paddingHorizontal: 8,
465
+ paddingTop: 4,
466
+ paddingBottom: 2
467
+ },
452
468
  timestampRow: {
453
469
  flexDirection: "row",
454
470
  alignItems: "center",