@poolse/react-native 2.19.0 → 2.20.0

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.
package/dist/index.js CHANGED
@@ -86,7 +86,7 @@ var radii = {
86
86
  lg: 16,
87
87
  xl: 24,
88
88
  pill: 999,
89
- bubble: 16
89
+ bubble: 18
90
90
  };
91
91
  var spacing = {
92
92
  xs: 4,
@@ -2318,7 +2318,12 @@ var styles12 = StyleSheet.create({
2318
2318
  fontWeight: "500"
2319
2319
  }
2320
2320
  });
2321
- function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
2321
+ function AttachmentPreview({
2322
+ attachment,
2323
+ onPress,
2324
+ maxWidth = 280,
2325
+ tone = "default"
2326
+ }) {
2322
2327
  const theme = usePoolseTheme();
2323
2328
  const isImage = (attachment.content_type ?? "").startsWith("image/");
2324
2329
  const { url } = useAttachmentUrl(attachment.id);
@@ -2352,6 +2357,9 @@ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
2352
2357
  }
2353
2358
  ) });
2354
2359
  }
2360
+ const onBrand = tone === "onBrand";
2361
+ const ink = onBrand ? theme.colors.onBrand : theme.colors.ink;
2362
+ const inkSoft = onBrand ? theme.colors.onBrand : theme.colors.ink3;
2355
2363
  return /* @__PURE__ */ jsxs(
2356
2364
  Pressable,
2357
2365
  {
@@ -2360,23 +2368,23 @@ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
2360
2368
  style: [
2361
2369
  styles13.fileCard,
2362
2370
  {
2363
- backgroundColor: theme.colors.surface2,
2364
- borderColor: theme.colors.border,
2371
+ backgroundColor: onBrand ? "rgba(255,255,255,0.18)" : theme.colors.surface2,
2372
+ borderColor: onBrand ? "transparent" : theme.colors.border,
2365
2373
  borderRadius: theme.radii.md
2366
2374
  }
2367
2375
  ],
2368
2376
  children: [
2369
- /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.ink2 }),
2377
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 18, color: onBrand ? ink : theme.colors.ink2 }),
2370
2378
  /* @__PURE__ */ jsxs(View, { style: { flex: 1, marginLeft: 8 }, children: [
2371
2379
  /* @__PURE__ */ jsx(
2372
2380
  Text,
2373
2381
  {
2374
- style: [styles13.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
2382
+ style: [styles13.fileName, { color: ink, fontFamily: theme.type.fontBody }],
2375
2383
  numberOfLines: 1,
2376
2384
  children: attachment.original_filename ?? "file"
2377
2385
  }
2378
2386
  ),
2379
- /* @__PURE__ */ jsx(Text, { style: [styles13.fileSize, { color: theme.colors.ink3 }], children: formatBytes2(attachment.byte_size) })
2387
+ /* @__PURE__ */ jsx(Text, { style: [styles13.fileSize, { color: inkSoft, opacity: onBrand ? 0.8 : 1 }], children: formatBytes2(attachment.byte_size) })
2380
2388
  ] })
2381
2389
  ]
2382
2390
  }
@@ -2404,9 +2412,10 @@ var styles13 = StyleSheet.create({
2404
2412
  fontSize: 11
2405
2413
  }
2406
2414
  });
2407
- function ImageMosaic({ images, maxWidth, radius }) {
2415
+ function ImageMosaic({ images, maxWidth, radius, squareBottom = false }) {
2408
2416
  const theme = usePoolseTheme();
2409
2417
  const r = radius ?? theme.radii.md;
2418
+ const rBottom = squareBottom ? 5 : r;
2410
2419
  const [viewerIndex, setViewerIndex] = useState(null);
2411
2420
  if (images.length === 0) return null;
2412
2421
  const visible = images.slice(0, 4);
@@ -2419,8 +2428,17 @@ function ImageMosaic({ images, maxWidth, radius }) {
2419
2428
  style: [
2420
2429
  styles14.root,
2421
2430
  {
2422
- borderRadius: r,
2423
- ...maxWidth ? { maxWidth } : {}
2431
+ borderTopLeftRadius: r,
2432
+ borderTopRightRadius: r,
2433
+ borderBottomLeftRadius: rBottom,
2434
+ borderBottomRightRadius: rBottom,
2435
+ // Explicit pixel width, not just a cap: the mosaic sits
2436
+ // inside an auto-sized bubble, and a percentage width
2437
+ // against an indefinite parent collapses (or, worse,
2438
+ // resolves against the image's intrinsic size and blows
2439
+ // the row open). Media bubbles are fixed-width — the
2440
+ // WhatsApp convention anyway.
2441
+ ...maxWidth ? { width: maxWidth } : {}
2424
2442
  }
2425
2443
  ],
2426
2444
  children: [
@@ -2657,6 +2675,27 @@ var styles14 = StyleSheet.create({
2657
2675
  borderRadius: 20
2658
2676
  }
2659
2677
  });
2678
+
2679
+ // src/internal/bubbleText.ts
2680
+ function hasMarkdown(body) {
2681
+ return /[*_~`#>|\\]|\[.*\]|^\s*(?:[-+]|\d+[.)])\s|https?:\/\/|www\./ms.test(body);
2682
+ }
2683
+ function emojiOnlyCount(body) {
2684
+ const text = body.trim();
2685
+ if (!text || text.length > 32) return 0;
2686
+ try {
2687
+ const unit = "(?:\\p{Regional_Indicator}{2}|\\p{Extended_Pictographic})[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?(?:\\u200D\\p{Extended_Pictographic}[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?)*";
2688
+ const whole = new RegExp(`^(?:${unit}){1,3}$`, "u");
2689
+ if (!whole.test(text)) return 0;
2690
+ const each = new RegExp(unit, "gu");
2691
+ return (text.match(each) ?? []).length;
2692
+ } catch {
2693
+ return 0;
2694
+ }
2695
+ }
2696
+ function emojiFontSize(count) {
2697
+ return count === 1 ? 44 : count === 2 ? 38 : 32;
2698
+ }
2660
2699
  var markdownit = MarkdownIt({ typographer: false, linkify: true });
2661
2700
  function renderMarkdown(body, args) {
2662
2701
  const baseText = {
@@ -2723,100 +2762,154 @@ function MessageBubble({
2723
2762
  const isSelf = currentUserId !== null && message.sender_id === currentUserId;
2724
2763
  const [expanded, setExpanded] = useState(false);
2725
2764
  const bubbleMaxWidth = Math.floor(screenWidth * 0.78);
2726
- const contentMaxWidth = bubbleMaxWidth - 28;
2765
+ const mediaWidth = Math.min(bubbleMaxWidth - 6, 420);
2727
2766
  const sender = useUser(!isSelf ? message.sender_external_id : null);
2728
2767
  const senderFallbackName = message.sender_external_id ?? "Unknown";
2729
2768
  const senderLabel = sender.profile?.displayName ?? (message.sender_external_id ? labelFor?.(message.sender_external_id) ?? senderFallbackName : senderFallbackName);
2730
2769
  const senderTint = userColor(message.sender_external_id ?? message.sender_id ?? message.id);
2731
2770
  const tailCorner = groupPosition === "last" || groupPosition === "standalone";
2732
- const bubbleColor = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
2771
+ const bubbleRadius = theme.radii.bubble;
2733
2772
  const textColor = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
2773
+ const mutedOnBubble = isSelf ? theme.colors.onBrand : theme.colors.ink3;
2734
2774
  const radii2 = {
2735
- topLeft: theme.radii.bubble,
2736
- topRight: theme.radii.bubble,
2737
- bottomLeft: theme.radii.bubble,
2738
- bottomRight: theme.radii.bubble
2775
+ topLeft: bubbleRadius,
2776
+ topRight: bubbleRadius,
2777
+ bottomLeft: bubbleRadius,
2778
+ bottomRight: bubbleRadius
2739
2779
  };
2740
2780
  if (tailCorner) {
2741
- if (isSelf) radii2.bottomRight = 4;
2742
- else radii2.bottomLeft = 4;
2781
+ if (isSelf) radii2.bottomRight = 5;
2782
+ else radii2.bottomLeft = 5;
2743
2783
  }
2744
2784
  const body = message.body ?? "";
2745
- const truncated = maxBodyLength > 0 && body.length > maxBodyLength && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
2746
- const time = formatTime(message.inserted_at);
2747
2785
  const isDeleted = !!message.deleted_at;
2748
2786
  const isEdited = !!message.edited_at && !isDeleted;
2749
- const images = (message.attachments ?? []).filter((a) => isImageContentType(a.content_type));
2750
- const files = (message.attachments ?? []).filter((a) => !isImageContentType(a.content_type));
2751
- const renderAttachments = showAttachments && (images.length > 0 || files.length > 0);
2752
- return /* @__PURE__ */ jsx(
2753
- View,
2754
- {
2755
- style: [
2756
- styles15.wrap,
2757
- {
2758
- alignSelf: isSelf ? "flex-end" : "flex-start",
2759
- // Pixel maxWidth (not percentage) so single-line and
2760
- // multi-line bubbles land at the same right edge — was
2761
- // 88% of the bubbleCol which produced inconsistent right
2762
- // padding when the row padding changed.
2763
- maxWidth: bubbleMaxWidth
2764
- }
2765
- ],
2766
- children: /* @__PURE__ */ jsxs(
2767
- View,
2768
- {
2769
- style: [
2770
- styles15.bubble,
2787
+ const trimmable = maxBodyLength > 0 && body.length > maxBodyLength;
2788
+ const truncated = trimmable && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
2789
+ const time = formatTime(message.inserted_at);
2790
+ const images = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => isImageContentType(a.content_type)) : [];
2791
+ const files = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => !isImageContentType(a.content_type)) : [];
2792
+ const hasMedia = images.length > 0;
2793
+ const hasFiles = files.length > 0;
2794
+ const hasBody = !isDeleted && body.length > 0;
2795
+ const metaOverlay = hasMedia && !hasBody && !hasFiles;
2796
+ const jumboCount = hasBody && !hasMedia && !hasFiles && !message.quoted_message_id ? emojiOnlyCount(body) : 0;
2797
+ const inlineMeta = hasBody && jumboCount === 0 && !trimmable && (!markdown || !hasMarkdown(truncated));
2798
+ const sectionAbove = hasMedia || hasFiles || !!message.quoted_message || isDeleted;
2799
+ const ghost = ` ${isEdited ? "edited " : ""}${time}${isSelf && readState ? " \u2713\u2713" : ""}`;
2800
+ const metaRow = (variant) => {
2801
+ const color = variant === "overlay" ? "#FFFFFF" : variant === "jumbo" ? theme.colors.ink3 : mutedOnBubble;
2802
+ return /* @__PURE__ */ jsxs(
2803
+ View,
2804
+ {
2805
+ style: [
2806
+ styles15.metaRow,
2807
+ variant === "inline" && styles15.metaInline,
2808
+ variant === "overlay" && styles15.metaOverlay,
2809
+ variant === "block" && styles15.metaBlock,
2810
+ // File-only bubble: the meta row follows the cards directly —
2811
+ // give it air so the time doesn't sit glued to the card.
2812
+ variant === "block" && hasFiles && !hasBody && styles15.metaBlockAfterFiles,
2813
+ variant === "jumbo" && styles15.metaJumbo
2814
+ ],
2815
+ pointerEvents: "none",
2816
+ children: [
2817
+ isEdited ? /* @__PURE__ */ jsx(Text, { style: [styles15.editedTag, { color, opacity: 0.8 }], children: "edited" }) : null,
2818
+ /* @__PURE__ */ jsx(Text, { style: [styles15.meta, { color, opacity: variant === "overlay" ? 1 : 0.75 }], children: time }),
2819
+ isSelf && readState ? /* @__PURE__ */ jsx(View, { style: styles15.readTick, children: /* @__PURE__ */ jsx(
2820
+ PoolseIcon,
2821
+ {
2822
+ name: readState === "read" ? "check-double" : "check",
2823
+ size: 13,
2824
+ color
2825
+ }
2826
+ ) }) : null
2827
+ ]
2828
+ }
2829
+ );
2830
+ };
2831
+ return /* @__PURE__ */ jsxs(View, { style: [styles15.wrap, { alignSelf: isSelf ? "flex-end" : "flex-start", maxWidth: bubbleMaxWidth }], children: [
2832
+ /* @__PURE__ */ jsxs(
2833
+ View,
2834
+ {
2835
+ style: [
2836
+ jumboCount === 0 && {
2837
+ backgroundColor: isSelf ? theme.colors.selfBubble : theme.colors.otherBubble,
2838
+ borderColor: isSelf ? "transparent" : theme.colors.border,
2839
+ borderWidth: isSelf ? 0 : StyleSheet.hairlineWidth,
2840
+ borderTopLeftRadius: radii2.topLeft,
2841
+ borderTopRightRadius: radii2.topRight,
2842
+ borderBottomLeftRadius: radii2.bottomLeft,
2843
+ borderBottomRightRadius: radii2.bottomRight
2844
+ },
2845
+ jumboCount === 0 && theme.shadows.sm
2846
+ ],
2847
+ children: [
2848
+ showSenderName && !isSelf && jumboCount === 0 && (groupPosition === "first" || groupPosition === "standalone") ? /* @__PURE__ */ jsx(
2849
+ Text,
2850
+ {
2851
+ style: [styles15.senderName, { color: senderTint, fontFamily: theme.type.fontBody }],
2852
+ children: senderLabel
2853
+ }
2854
+ ) : null,
2855
+ message.quoted_message ? /* @__PURE__ */ jsx(
2856
+ QuotedCard,
2857
+ {
2858
+ quoted: message.quoted_message,
2859
+ isSelf,
2860
+ ...labelFor ? { labelFor } : {},
2861
+ ...onQuotedClick ? { onPress: () => onQuotedClick(message.quoted_message.id) } : {}
2862
+ }
2863
+ ) : null,
2864
+ hasMedia ? (
2865
+ // 3px of bubble color framing the picture; the mosaic clips
2866
+ // itself 3px tighter than the bubble so the curves stay
2867
+ // concentric.
2868
+ /* @__PURE__ */ jsxs(View, { style: styles15.mediaFrame, children: [
2869
+ /* @__PURE__ */ jsx(
2870
+ ImageMosaic,
2871
+ {
2872
+ images,
2873
+ maxWidth: mediaWidth,
2874
+ radius: Math.max(bubbleRadius - 3, 8),
2875
+ squareBottom: !metaOverlay
2876
+ }
2877
+ ),
2878
+ metaOverlay ? metaRow("overlay") : null
2879
+ ] })
2880
+ ) : null,
2881
+ hasFiles ? /* @__PURE__ */ jsx(View, { style: styles15.filesBlock, children: files.map((att) => /* @__PURE__ */ jsx(
2882
+ AttachmentPreview,
2771
2883
  {
2772
- backgroundColor: bubbleColor,
2773
- borderColor: isSelf ? "transparent" : theme.colors.border,
2774
- borderWidth: isSelf ? 0 : StyleSheet.hairlineWidth,
2775
- borderTopLeftRadius: radii2.topLeft,
2776
- borderTopRightRadius: radii2.topRight,
2777
- borderBottomLeftRadius: radii2.bottomLeft,
2778
- borderBottomRightRadius: radii2.bottomRight,
2779
- // Slightly more breathing room — 14/10 reads tighter and
2780
- // matches the iMessage / WhatsApp bubble density.
2781
- paddingHorizontal: 14,
2782
- paddingVertical: 10,
2783
- // Floor the bubble at a width that always fits the meta row
2784
- // ("edited HH:MM ✓✓" → ~110px). Without this, short bodies
2785
- // like "ok" produce a bubble too narrow for the meta to lay
2786
- // out, and `edited` or the time gets clipped.
2787
- minWidth: isSelf && isEdited ? 130 : 90
2884
+ attachment: att,
2885
+ maxWidth: bubbleMaxWidth - 12,
2886
+ tone: isSelf ? "onBrand" : "default"
2788
2887
  },
2789
- theme.shadows.sm
2790
- ],
2791
- children: [
2792
- showSenderName && !isSelf && (groupPosition === "first" || groupPosition === "standalone") ? /* @__PURE__ */ jsx(Text, { style: [styles15.senderName, { color: senderTint, fontFamily: theme.type.fontBody }], children: senderLabel }) : null,
2793
- message.quoted_message ? /* @__PURE__ */ jsx(
2794
- QuotedCard,
2795
- {
2796
- quoted: message.quoted_message,
2797
- isSelf,
2798
- ...labelFor ? { labelFor } : {},
2799
- ...onQuotedClick ? { onPress: () => onQuotedClick(message.quoted_message.id) } : {}
2800
- }
2801
- ) : null,
2802
- renderAttachments && images.length > 0 ? /* @__PURE__ */ jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
2803
- renderAttachments && files.length > 0 ? /* @__PURE__ */ jsx(View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: files.map((att) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment: att, maxWidth: contentMaxWidth }, att.id)) }) : null,
2804
- body.length > 0 ? isDeleted ? /* @__PURE__ */ jsx(
2805
- Text,
2806
- {
2807
- style: [
2808
- styles15.body,
2809
- {
2810
- color: theme.colors.ink3,
2811
- fontFamily: theme.type.fontBody,
2812
- fontSize: theme.type.bodySize,
2813
- lineHeight: theme.type.lineHeight,
2814
- fontStyle: "italic"
2815
- }
2816
- ],
2817
- children: "Message deleted"
2818
- }
2819
- ) : markdown ? renderMarkdown(truncated, {
2888
+ att.id
2889
+ )) }) : null,
2890
+ isDeleted ? /* @__PURE__ */ jsx(View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: /* @__PURE__ */ jsx(
2891
+ Text,
2892
+ {
2893
+ style: {
2894
+ color: mutedOnBubble,
2895
+ opacity: 0.7,
2896
+ fontFamily: theme.type.fontBody,
2897
+ fontSize: theme.type.bodySize,
2898
+ lineHeight: theme.type.lineHeight,
2899
+ fontStyle: "italic"
2900
+ },
2901
+ children: "Message deleted"
2902
+ }
2903
+ ) }) : null,
2904
+ hasBody && jumboCount > 0 ? /* @__PURE__ */ jsx(View, { style: styles15.jumboBlock, children: /* @__PURE__ */ jsx(
2905
+ Text,
2906
+ {
2907
+ style: { fontSize: emojiFontSize(jumboCount), lineHeight: emojiFontSize(jumboCount) + 8 },
2908
+ children: body
2909
+ }
2910
+ ) }) : null,
2911
+ hasBody && jumboCount === 0 ? /* @__PURE__ */ jsxs(View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: [
2912
+ markdown && !inlineMeta ? renderMarkdown(truncated, {
2820
2913
  textColor,
2821
2914
  linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
2822
2915
  fontFamily: theme.type.fontBody,
@@ -2826,22 +2919,29 @@ function MessageBubble({
2826
2919
  void Linking.openURL(url).catch(() => void 0);
2827
2920
  return false;
2828
2921
  }
2829
- }) : /* @__PURE__ */ jsx(
2922
+ }) : /* @__PURE__ */ jsxs(
2830
2923
  Text,
2831
2924
  {
2832
- style: [
2833
- styles15.body,
2834
- {
2835
- color: textColor,
2836
- fontFamily: theme.type.fontBody,
2837
- fontSize: theme.type.bodySize,
2838
- lineHeight: theme.type.lineHeight
2839
- }
2840
- ],
2841
- children: truncated
2925
+ style: {
2926
+ color: textColor,
2927
+ fontFamily: theme.type.fontBody,
2928
+ fontSize: theme.type.bodySize,
2929
+ lineHeight: theme.type.lineHeight
2930
+ },
2931
+ children: [
2932
+ truncated,
2933
+ inlineMeta ? (
2934
+ // The invisible twin: reserves the meta's footprint
2935
+ // at the end of the last line so the absolutely-
2936
+ // positioned real meta never covers a glyph — and
2937
+ // when the line is full, the reservation wraps and
2938
+ // the meta drops down, flush right.
2939
+ /* @__PURE__ */ jsx(Text, { style: styles15.ghost, children: ghost })
2940
+ ) : null
2941
+ ]
2842
2942
  }
2843
- ) : null,
2844
- maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsx(
2943
+ ),
2944
+ trimmable ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsx(
2845
2945
  Text,
2846
2946
  {
2847
2947
  style: [
@@ -2851,48 +2951,15 @@ function MessageBubble({
2851
2951
  children: expanded ? "Show less" : "Read more"
2852
2952
  }
2853
2953
  ) }) : null,
2854
- /* @__PURE__ */ jsxs(View, { style: styles15.metaRow, children: [
2855
- isEdited ? /* @__PURE__ */ jsx(
2856
- Text,
2857
- {
2858
- style: [
2859
- styles15.editedTag,
2860
- {
2861
- color: isSelf ? theme.colors.onBrand : theme.colors.ink3,
2862
- opacity: 0.7
2863
- }
2864
- ],
2865
- children: "edited"
2866
- }
2867
- ) : null,
2868
- /* @__PURE__ */ jsx(
2869
- Text,
2870
- {
2871
- style: [
2872
- styles15.meta,
2873
- {
2874
- color: isSelf ? theme.colors.onBrand : theme.colors.ink3,
2875
- opacity: isSelf ? 0.85 : 1
2876
- }
2877
- ],
2878
- children: time
2879
- }
2880
- ),
2881
- isSelf && readState ? /* @__PURE__ */ jsx(View, { style: styles15.readTick, children: /* @__PURE__ */ jsx(
2882
- PoolseIcon,
2883
- {
2884
- name: readState === "read" ? "check-double" : "check",
2885
- size: 13,
2886
- color: theme.colors.onBrand
2887
- }
2888
- ) }) : null
2889
- ] }),
2890
- actionsTrigger ? /* @__PURE__ */ jsx(View, { style: styles15.actionsSlot, children: actionsTrigger }) : null
2891
- ]
2892
- }
2893
- )
2894
- }
2895
- );
2954
+ inlineMeta ? metaRow("inline") : null
2955
+ ] }) : null,
2956
+ !inlineMeta && !metaOverlay && jumboCount === 0 ? metaRow("block") : null,
2957
+ actionsTrigger ? /* @__PURE__ */ jsx(View, { style: styles15.actionsSlot, children: actionsTrigger }) : null
2958
+ ]
2959
+ }
2960
+ ),
2961
+ jumboCount > 0 ? /* @__PURE__ */ jsx(View, { style: { alignSelf: isSelf ? "flex-end" : "flex-start" }, children: metaRow("jumbo") }) : null
2962
+ ] });
2896
2963
  }
2897
2964
  function QuotedCard({
2898
2965
  quoted,
@@ -2903,7 +2970,7 @@ function QuotedCard({
2903
2970
  const theme = usePoolseTheme();
2904
2971
  const resolved = useUser(quoted.sender_external_id);
2905
2972
  const senderName = quoted.sender_external_id ? labelFor?.(quoted.sender_external_id) ?? resolved.profile?.displayName ?? quoted.sender_external_id : "Unknown";
2906
- const bg = isSelf ? "rgba(255,255,255,0.18)" : theme.colors.surface2;
2973
+ const bg = isSelf ? "rgba(0,0,0,0.16)" : theme.colors.surface2;
2907
2974
  const accent = isSelf ? theme.colors.onBrand : theme.colors.brand;
2908
2975
  const txt = isSelf ? theme.colors.onBrand : theme.colors.ink;
2909
2976
  return /* @__PURE__ */ jsxs(
@@ -2919,8 +2986,8 @@ function QuotedCard({
2919
2986
  }
2920
2987
  ],
2921
2988
  children: [
2922
- /* @__PURE__ */ jsx(Text, { style: [styles15.quotedSender, { color: accent }], children: senderName }),
2923
- /* @__PURE__ */ jsx(Text, { style: [styles15.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.body ?? "\u2026" })
2989
+ /* @__PURE__ */ jsx(Text, { style: [styles15.quotedSender, { color: accent }], numberOfLines: 1, children: senderName }),
2990
+ /* @__PURE__ */ jsx(Text, { style: [styles15.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.deleted_at ? "Original message deleted" : quoted.body ?? "\u2026" })
2924
2991
  ]
2925
2992
  }
2926
2993
  );
@@ -2956,16 +3023,34 @@ var styles15 = StyleSheet.create({
2956
3023
  wrap: {
2957
3024
  marginBottom: 2
2958
3025
  },
2959
- bubble: {
2960
- position: "relative"
2961
- },
2962
3026
  senderName: {
2963
3027
  fontSize: 12,
2964
- fontWeight: "600",
2965
- marginBottom: 2
3028
+ fontWeight: "700",
3029
+ paddingTop: 7,
3030
+ paddingHorizontal: 12
2966
3031
  },
2967
- body: {
2968
- flexShrink: 1
3032
+ mediaFrame: {
3033
+ margin: 3
3034
+ },
3035
+ filesBlock: {
3036
+ paddingHorizontal: 6,
3037
+ paddingTop: 6,
3038
+ gap: 4
3039
+ },
3040
+ textBlock: {
3041
+ paddingHorizontal: 12,
3042
+ paddingVertical: 7
3043
+ },
3044
+ // A hair less air when media / files / a quote already sit above.
3045
+ textBlockAfter: {
3046
+ paddingTop: 6
3047
+ },
3048
+ jumboBlock: {
3049
+ paddingVertical: 2
3050
+ },
3051
+ ghost: {
3052
+ color: "transparent",
3053
+ fontSize: 11
2969
3054
  },
2970
3055
  readMore: {
2971
3056
  fontSize: 12,
@@ -2975,10 +3060,36 @@ var styles15 = StyleSheet.create({
2975
3060
  metaRow: {
2976
3061
  flexDirection: "row",
2977
3062
  alignItems: "center",
3063
+ gap: 4
3064
+ },
3065
+ metaInline: {
3066
+ position: "absolute",
3067
+ right: 12,
3068
+ // Optically centers the meta against the body's last line (22px
3069
+ // line over the 7px bottom padding): 7 + (22 - 14) / 2. At 8 it
3070
+ // sat visibly low beside a one-line message.
3071
+ bottom: 11
3072
+ },
3073
+ metaOverlay: {
3074
+ position: "absolute",
3075
+ right: 6,
3076
+ bottom: 6,
3077
+ paddingHorizontal: 8,
3078
+ paddingVertical: 3,
3079
+ borderRadius: 999,
3080
+ backgroundColor: "rgba(0,0,0,0.42)"
3081
+ },
3082
+ metaBlock: {
2978
3083
  justifyContent: "flex-end",
2979
- marginTop: 6,
2980
- gap: 5,
2981
- flexShrink: 0
3084
+ paddingHorizontal: 12,
3085
+ paddingBottom: 6,
3086
+ marginTop: -2
3087
+ },
3088
+ metaBlockAfterFiles: {
3089
+ marginTop: 6
3090
+ },
3091
+ metaJumbo: {
3092
+ paddingTop: 2
2982
3093
  },
2983
3094
  meta: {
2984
3095
  fontSize: 11,
@@ -2991,7 +3102,7 @@ var styles15 = StyleSheet.create({
2991
3102
  letterSpacing: 0.1
2992
3103
  },
2993
3104
  readTick: {
2994
- marginLeft: 2,
3105
+ marginLeft: 1,
2995
3106
  marginTop: 1
2996
3107
  },
2997
3108
  actionsSlot: {
@@ -3001,20 +3112,21 @@ var styles15 = StyleSheet.create({
3001
3112
  },
3002
3113
  quoted: {
3003
3114
  borderLeftWidth: 3,
3004
- paddingLeft: 8,
3005
- paddingVertical: 4,
3006
- paddingRight: 6,
3007
- marginBottom: 6,
3008
- borderRadius: 4
3115
+ paddingLeft: 9,
3116
+ paddingVertical: 5,
3117
+ paddingRight: 8,
3118
+ margin: 4,
3119
+ marginBottom: 0,
3120
+ borderRadius: 10
3009
3121
  },
3010
3122
  quotedSender: {
3011
3123
  fontSize: 12,
3012
- fontWeight: "600",
3013
- marginBottom: 2
3124
+ fontWeight: "700",
3125
+ marginBottom: 1
3014
3126
  },
3015
3127
  quotedBody: {
3016
- fontSize: 12,
3017
- opacity: 0.85
3128
+ fontSize: 12.5,
3129
+ opacity: 0.8
3018
3130
  }
3019
3131
  });
3020
3132
  var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
@@ -3330,15 +3442,7 @@ function MessageRow({
3330
3442
  maxBodyLength,
3331
3443
  showSenderName,
3332
3444
  showAttachments: attachments,
3333
- markdown,
3334
- actionsTrigger: actions ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsx(
3335
- PoolseIcon,
3336
- {
3337
- name: "chevron-down",
3338
- size: 14,
3339
- color: isSelf ? theme.colors.onBrand : theme.colors.ink3
3340
- }
3341
- ) }) : null
3445
+ markdown
3342
3446
  }
3343
3447
  )
3344
3448
  }