@poolse/react-native 2.18.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/CHANGELOG.md +65 -0
- package/dist/index.cjs +324 -192
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -28
- package/dist/index.d.ts +22 -28
- package/dist/index.js +324 -192
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -113,7 +113,7 @@ var radii = {
|
|
|
113
113
|
lg: 16,
|
|
114
114
|
xl: 24,
|
|
115
115
|
pill: 999,
|
|
116
|
-
bubble:
|
|
116
|
+
bubble: 18
|
|
117
117
|
};
|
|
118
118
|
var spacing = {
|
|
119
119
|
xs: 4,
|
|
@@ -2345,7 +2345,12 @@ var styles12 = reactNative.StyleSheet.create({
|
|
|
2345
2345
|
fontWeight: "500"
|
|
2346
2346
|
}
|
|
2347
2347
|
});
|
|
2348
|
-
function AttachmentPreview({
|
|
2348
|
+
function AttachmentPreview({
|
|
2349
|
+
attachment,
|
|
2350
|
+
onPress,
|
|
2351
|
+
maxWidth = 280,
|
|
2352
|
+
tone = "default"
|
|
2353
|
+
}) {
|
|
2349
2354
|
const theme = usePoolseTheme();
|
|
2350
2355
|
const isImage = (attachment.content_type ?? "").startsWith("image/");
|
|
2351
2356
|
const { url } = react$1.useAttachmentUrl(attachment.id);
|
|
@@ -2379,6 +2384,9 @@ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
|
|
|
2379
2384
|
}
|
|
2380
2385
|
) });
|
|
2381
2386
|
}
|
|
2387
|
+
const onBrand = tone === "onBrand";
|
|
2388
|
+
const ink = onBrand ? theme.colors.onBrand : theme.colors.ink;
|
|
2389
|
+
const inkSoft = onBrand ? theme.colors.onBrand : theme.colors.ink3;
|
|
2382
2390
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2383
2391
|
reactNative.Pressable,
|
|
2384
2392
|
{
|
|
@@ -2387,23 +2395,23 @@ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
|
|
|
2387
2395
|
style: [
|
|
2388
2396
|
styles13.fileCard,
|
|
2389
2397
|
{
|
|
2390
|
-
backgroundColor: theme.colors.surface2,
|
|
2391
|
-
borderColor: theme.colors.border,
|
|
2398
|
+
backgroundColor: onBrand ? "rgba(255,255,255,0.18)" : theme.colors.surface2,
|
|
2399
|
+
borderColor: onBrand ? "transparent" : theme.colors.border,
|
|
2392
2400
|
borderRadius: theme.radii.md
|
|
2393
2401
|
}
|
|
2394
2402
|
],
|
|
2395
2403
|
children: [
|
|
2396
|
-
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.ink2 }),
|
|
2404
|
+
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: onBrand ? ink : theme.colors.ink2 }),
|
|
2397
2405
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, marginLeft: 8 }, children: [
|
|
2398
2406
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2399
2407
|
reactNative.Text,
|
|
2400
2408
|
{
|
|
2401
|
-
style: [styles13.fileName, { color:
|
|
2409
|
+
style: [styles13.fileName, { color: ink, fontFamily: theme.type.fontBody }],
|
|
2402
2410
|
numberOfLines: 1,
|
|
2403
2411
|
children: attachment.original_filename ?? "file"
|
|
2404
2412
|
}
|
|
2405
2413
|
),
|
|
2406
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles13.fileSize, { color:
|
|
2414
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles13.fileSize, { color: inkSoft, opacity: onBrand ? 0.8 : 1 }], children: formatBytes2(attachment.byte_size) })
|
|
2407
2415
|
] })
|
|
2408
2416
|
]
|
|
2409
2417
|
}
|
|
@@ -2431,9 +2439,10 @@ var styles13 = reactNative.StyleSheet.create({
|
|
|
2431
2439
|
fontSize: 11
|
|
2432
2440
|
}
|
|
2433
2441
|
});
|
|
2434
|
-
function ImageMosaic({ images, maxWidth, radius }) {
|
|
2442
|
+
function ImageMosaic({ images, maxWidth, radius, squareBottom = false }) {
|
|
2435
2443
|
const theme = usePoolseTheme();
|
|
2436
2444
|
const r = radius ?? theme.radii.md;
|
|
2445
|
+
const rBottom = squareBottom ? 5 : r;
|
|
2437
2446
|
const [viewerIndex, setViewerIndex] = react.useState(null);
|
|
2438
2447
|
if (images.length === 0) return null;
|
|
2439
2448
|
const visible = images.slice(0, 4);
|
|
@@ -2446,8 +2455,17 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2446
2455
|
style: [
|
|
2447
2456
|
styles14.root,
|
|
2448
2457
|
{
|
|
2449
|
-
|
|
2450
|
-
|
|
2458
|
+
borderTopLeftRadius: r,
|
|
2459
|
+
borderTopRightRadius: r,
|
|
2460
|
+
borderBottomLeftRadius: rBottom,
|
|
2461
|
+
borderBottomRightRadius: rBottom,
|
|
2462
|
+
// Explicit pixel width, not just a cap: the mosaic sits
|
|
2463
|
+
// inside an auto-sized bubble, and a percentage width
|
|
2464
|
+
// against an indefinite parent collapses (or, worse,
|
|
2465
|
+
// resolves against the image's intrinsic size and blows
|
|
2466
|
+
// the row open). Media bubbles are fixed-width — the
|
|
2467
|
+
// WhatsApp convention anyway.
|
|
2468
|
+
...maxWidth ? { width: maxWidth } : {}
|
|
2451
2469
|
}
|
|
2452
2470
|
],
|
|
2453
2471
|
children: [
|
|
@@ -2684,6 +2702,27 @@ var styles14 = reactNative.StyleSheet.create({
|
|
|
2684
2702
|
borderRadius: 20
|
|
2685
2703
|
}
|
|
2686
2704
|
});
|
|
2705
|
+
|
|
2706
|
+
// src/internal/bubbleText.ts
|
|
2707
|
+
function hasMarkdown(body) {
|
|
2708
|
+
return /[*_~`#>|\\]|\[.*\]|^\s*(?:[-+]|\d+[.)])\s|https?:\/\/|www\./ms.test(body);
|
|
2709
|
+
}
|
|
2710
|
+
function emojiOnlyCount(body) {
|
|
2711
|
+
const text = body.trim();
|
|
2712
|
+
if (!text || text.length > 32) return 0;
|
|
2713
|
+
try {
|
|
2714
|
+
const unit = "(?:\\p{Regional_Indicator}{2}|\\p{Extended_Pictographic})[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?(?:\\u200D\\p{Extended_Pictographic}[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?)*";
|
|
2715
|
+
const whole = new RegExp(`^(?:${unit}){1,3}$`, "u");
|
|
2716
|
+
if (!whole.test(text)) return 0;
|
|
2717
|
+
const each = new RegExp(unit, "gu");
|
|
2718
|
+
return (text.match(each) ?? []).length;
|
|
2719
|
+
} catch {
|
|
2720
|
+
return 0;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
function emojiFontSize(count) {
|
|
2724
|
+
return count === 1 ? 44 : count === 2 ? 38 : 32;
|
|
2725
|
+
}
|
|
2687
2726
|
var markdownit = Markdown.MarkdownIt({ typographer: false, linkify: true });
|
|
2688
2727
|
function renderMarkdown(body, args) {
|
|
2689
2728
|
const baseText = {
|
|
@@ -2750,100 +2789,154 @@ function MessageBubble({
|
|
|
2750
2789
|
const isSelf = currentUserId !== null && message.sender_id === currentUserId;
|
|
2751
2790
|
const [expanded, setExpanded] = react.useState(false);
|
|
2752
2791
|
const bubbleMaxWidth = Math.floor(screenWidth * 0.78);
|
|
2753
|
-
const
|
|
2792
|
+
const mediaWidth = Math.min(bubbleMaxWidth - 6, 420);
|
|
2754
2793
|
const sender = react$1.useUser(!isSelf ? message.sender_external_id : null);
|
|
2755
2794
|
const senderFallbackName = message.sender_external_id ?? "Unknown";
|
|
2756
2795
|
const senderLabel = sender.profile?.displayName ?? (message.sender_external_id ? labelFor?.(message.sender_external_id) ?? senderFallbackName : senderFallbackName);
|
|
2757
2796
|
const senderTint = userColor(message.sender_external_id ?? message.sender_id ?? message.id);
|
|
2758
2797
|
const tailCorner = groupPosition === "last" || groupPosition === "standalone";
|
|
2759
|
-
const
|
|
2798
|
+
const bubbleRadius = theme.radii.bubble;
|
|
2760
2799
|
const textColor = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
|
|
2800
|
+
const mutedOnBubble = isSelf ? theme.colors.onBrand : theme.colors.ink3;
|
|
2761
2801
|
const radii2 = {
|
|
2762
|
-
topLeft:
|
|
2763
|
-
topRight:
|
|
2764
|
-
bottomLeft:
|
|
2765
|
-
bottomRight:
|
|
2802
|
+
topLeft: bubbleRadius,
|
|
2803
|
+
topRight: bubbleRadius,
|
|
2804
|
+
bottomLeft: bubbleRadius,
|
|
2805
|
+
bottomRight: bubbleRadius
|
|
2766
2806
|
};
|
|
2767
2807
|
if (tailCorner) {
|
|
2768
|
-
if (isSelf) radii2.bottomRight =
|
|
2769
|
-
else radii2.bottomLeft =
|
|
2808
|
+
if (isSelf) radii2.bottomRight = 5;
|
|
2809
|
+
else radii2.bottomLeft = 5;
|
|
2770
2810
|
}
|
|
2771
2811
|
const body = message.body ?? "";
|
|
2772
|
-
const truncated = maxBodyLength > 0 && body.length > maxBodyLength && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
|
|
2773
|
-
const time = formatTime(message.inserted_at);
|
|
2774
2812
|
const isDeleted = !!message.deleted_at;
|
|
2775
2813
|
const isEdited = !!message.edited_at && !isDeleted;
|
|
2776
|
-
const
|
|
2777
|
-
const
|
|
2778
|
-
const
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2814
|
+
const trimmable = maxBodyLength > 0 && body.length > maxBodyLength;
|
|
2815
|
+
const truncated = trimmable && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
|
|
2816
|
+
const time = formatTime(message.inserted_at);
|
|
2817
|
+
const images = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => isImageContentType(a.content_type)) : [];
|
|
2818
|
+
const files = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => !isImageContentType(a.content_type)) : [];
|
|
2819
|
+
const hasMedia = images.length > 0;
|
|
2820
|
+
const hasFiles = files.length > 0;
|
|
2821
|
+
const hasBody = !isDeleted && body.length > 0;
|
|
2822
|
+
const metaOverlay = hasMedia && !hasBody && !hasFiles;
|
|
2823
|
+
const jumboCount = hasBody && !hasMedia && !hasFiles && !message.quoted_message_id ? emojiOnlyCount(body) : 0;
|
|
2824
|
+
const inlineMeta = hasBody && jumboCount === 0 && !trimmable && (!markdown || !hasMarkdown(truncated));
|
|
2825
|
+
const sectionAbove = hasMedia || hasFiles || !!message.quoted_message || isDeleted;
|
|
2826
|
+
const ghost = ` ${isEdited ? "edited " : ""}${time}${isSelf && readState ? " \u2713\u2713" : ""}`;
|
|
2827
|
+
const metaRow = (variant) => {
|
|
2828
|
+
const color = variant === "overlay" ? "#FFFFFF" : variant === "jumbo" ? theme.colors.ink3 : mutedOnBubble;
|
|
2829
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2830
|
+
reactNative.View,
|
|
2831
|
+
{
|
|
2832
|
+
style: [
|
|
2833
|
+
styles15.metaRow,
|
|
2834
|
+
variant === "inline" && styles15.metaInline,
|
|
2835
|
+
variant === "overlay" && styles15.metaOverlay,
|
|
2836
|
+
variant === "block" && styles15.metaBlock,
|
|
2837
|
+
// File-only bubble: the meta row follows the cards directly —
|
|
2838
|
+
// give it air so the time doesn't sit glued to the card.
|
|
2839
|
+
variant === "block" && hasFiles && !hasBody && styles15.metaBlockAfterFiles,
|
|
2840
|
+
variant === "jumbo" && styles15.metaJumbo
|
|
2841
|
+
],
|
|
2842
|
+
pointerEvents: "none",
|
|
2843
|
+
children: [
|
|
2844
|
+
isEdited ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.editedTag, { color, opacity: 0.8 }], children: "edited" }) : null,
|
|
2845
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.meta, { color, opacity: variant === "overlay" ? 1 : 0.75 }], children: time }),
|
|
2846
|
+
isSelf && readState ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.readTick, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2847
|
+
PoolseIcon,
|
|
2798
2848
|
{
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2849
|
+
name: readState === "read" ? "check-double" : "check",
|
|
2850
|
+
size: 13,
|
|
2851
|
+
color
|
|
2852
|
+
}
|
|
2853
|
+
) }) : null
|
|
2854
|
+
]
|
|
2855
|
+
}
|
|
2856
|
+
);
|
|
2857
|
+
};
|
|
2858
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.wrap, { alignSelf: isSelf ? "flex-end" : "flex-start", maxWidth: bubbleMaxWidth }], children: [
|
|
2859
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2860
|
+
reactNative.View,
|
|
2861
|
+
{
|
|
2862
|
+
style: [
|
|
2863
|
+
jumboCount === 0 && {
|
|
2864
|
+
backgroundColor: isSelf ? theme.colors.selfBubble : theme.colors.otherBubble,
|
|
2865
|
+
borderColor: isSelf ? "transparent" : theme.colors.border,
|
|
2866
|
+
borderWidth: isSelf ? 0 : reactNative.StyleSheet.hairlineWidth,
|
|
2867
|
+
borderTopLeftRadius: radii2.topLeft,
|
|
2868
|
+
borderTopRightRadius: radii2.topRight,
|
|
2869
|
+
borderBottomLeftRadius: radii2.bottomLeft,
|
|
2870
|
+
borderBottomRightRadius: radii2.bottomRight
|
|
2871
|
+
},
|
|
2872
|
+
jumboCount === 0 && theme.shadows.sm
|
|
2873
|
+
],
|
|
2874
|
+
children: [
|
|
2875
|
+
showSenderName && !isSelf && jumboCount === 0 && (groupPosition === "first" || groupPosition === "standalone") ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2876
|
+
reactNative.Text,
|
|
2877
|
+
{
|
|
2878
|
+
style: [styles15.senderName, { color: senderTint, fontFamily: theme.type.fontBody }],
|
|
2879
|
+
children: senderLabel
|
|
2880
|
+
}
|
|
2881
|
+
) : null,
|
|
2882
|
+
message.quoted_message ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2883
|
+
QuotedCard,
|
|
2884
|
+
{
|
|
2885
|
+
quoted: message.quoted_message,
|
|
2886
|
+
isSelf,
|
|
2887
|
+
...labelFor ? { labelFor } : {},
|
|
2888
|
+
...onQuotedClick ? { onPress: () => onQuotedClick(message.quoted_message.id) } : {}
|
|
2889
|
+
}
|
|
2890
|
+
) : null,
|
|
2891
|
+
hasMedia ? (
|
|
2892
|
+
// 3px of bubble color framing the picture; the mosaic clips
|
|
2893
|
+
// itself 3px tighter than the bubble so the curves stay
|
|
2894
|
+
// concentric.
|
|
2895
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles15.mediaFrame, children: [
|
|
2896
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2897
|
+
ImageMosaic,
|
|
2898
|
+
{
|
|
2899
|
+
images,
|
|
2900
|
+
maxWidth: mediaWidth,
|
|
2901
|
+
radius: Math.max(bubbleRadius - 3, 8),
|
|
2902
|
+
squareBottom: !metaOverlay
|
|
2903
|
+
}
|
|
2904
|
+
),
|
|
2905
|
+
metaOverlay ? metaRow("overlay") : null
|
|
2906
|
+
] })
|
|
2907
|
+
) : null,
|
|
2908
|
+
hasFiles ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.filesBlock, children: files.map((att) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2909
|
+
AttachmentPreview,
|
|
2910
|
+
{
|
|
2911
|
+
attachment: att,
|
|
2912
|
+
maxWidth: bubbleMaxWidth - 12,
|
|
2913
|
+
tone: isSelf ? "onBrand" : "default"
|
|
2815
2914
|
},
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
children:
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
fontStyle: "italic"
|
|
2842
|
-
}
|
|
2843
|
-
],
|
|
2844
|
-
children: "Message deleted"
|
|
2845
|
-
}
|
|
2846
|
-
) : markdown ? renderMarkdown(truncated, {
|
|
2915
|
+
att.id
|
|
2916
|
+
)) }) : null,
|
|
2917
|
+
isDeleted ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2918
|
+
reactNative.Text,
|
|
2919
|
+
{
|
|
2920
|
+
style: {
|
|
2921
|
+
color: mutedOnBubble,
|
|
2922
|
+
opacity: 0.7,
|
|
2923
|
+
fontFamily: theme.type.fontBody,
|
|
2924
|
+
fontSize: theme.type.bodySize,
|
|
2925
|
+
lineHeight: theme.type.lineHeight,
|
|
2926
|
+
fontStyle: "italic"
|
|
2927
|
+
},
|
|
2928
|
+
children: "Message deleted"
|
|
2929
|
+
}
|
|
2930
|
+
) }) : null,
|
|
2931
|
+
hasBody && jumboCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.jumboBlock, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2932
|
+
reactNative.Text,
|
|
2933
|
+
{
|
|
2934
|
+
style: { fontSize: emojiFontSize(jumboCount), lineHeight: emojiFontSize(jumboCount) + 8 },
|
|
2935
|
+
children: body
|
|
2936
|
+
}
|
|
2937
|
+
) }) : null,
|
|
2938
|
+
hasBody && jumboCount === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: [
|
|
2939
|
+
markdown && !inlineMeta ? renderMarkdown(truncated, {
|
|
2847
2940
|
textColor,
|
|
2848
2941
|
linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
|
|
2849
2942
|
fontFamily: theme.type.fontBody,
|
|
@@ -2853,22 +2946,29 @@ function MessageBubble({
|
|
|
2853
2946
|
void reactNative.Linking.openURL(url).catch(() => void 0);
|
|
2854
2947
|
return false;
|
|
2855
2948
|
}
|
|
2856
|
-
}) : /* @__PURE__ */ jsxRuntime.
|
|
2949
|
+
}) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2857
2950
|
reactNative.Text,
|
|
2858
2951
|
{
|
|
2859
|
-
style:
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2952
|
+
style: {
|
|
2953
|
+
color: textColor,
|
|
2954
|
+
fontFamily: theme.type.fontBody,
|
|
2955
|
+
fontSize: theme.type.bodySize,
|
|
2956
|
+
lineHeight: theme.type.lineHeight
|
|
2957
|
+
},
|
|
2958
|
+
children: [
|
|
2959
|
+
truncated,
|
|
2960
|
+
inlineMeta ? (
|
|
2961
|
+
// The invisible twin: reserves the meta's footprint
|
|
2962
|
+
// at the end of the last line so the absolutely-
|
|
2963
|
+
// positioned real meta never covers a glyph — and
|
|
2964
|
+
// when the line is full, the reservation wraps and
|
|
2965
|
+
// the meta drops down, flush right.
|
|
2966
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles15.ghost, children: ghost })
|
|
2967
|
+
) : null
|
|
2968
|
+
]
|
|
2869
2969
|
}
|
|
2870
|
-
)
|
|
2871
|
-
|
|
2970
|
+
),
|
|
2971
|
+
trimmable ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2872
2972
|
reactNative.Text,
|
|
2873
2973
|
{
|
|
2874
2974
|
style: [
|
|
@@ -2878,48 +2978,15 @@ function MessageBubble({
|
|
|
2878
2978
|
children: expanded ? "Show less" : "Read more"
|
|
2879
2979
|
}
|
|
2880
2980
|
) }) : null,
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
}
|
|
2891
|
-
],
|
|
2892
|
-
children: "edited"
|
|
2893
|
-
}
|
|
2894
|
-
) : null,
|
|
2895
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2896
|
-
reactNative.Text,
|
|
2897
|
-
{
|
|
2898
|
-
style: [
|
|
2899
|
-
styles15.meta,
|
|
2900
|
-
{
|
|
2901
|
-
color: isSelf ? theme.colors.onBrand : theme.colors.ink3,
|
|
2902
|
-
opacity: isSelf ? 0.85 : 1
|
|
2903
|
-
}
|
|
2904
|
-
],
|
|
2905
|
-
children: time
|
|
2906
|
-
}
|
|
2907
|
-
),
|
|
2908
|
-
isSelf && readState ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.readTick, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2909
|
-
PoolseIcon,
|
|
2910
|
-
{
|
|
2911
|
-
name: readState === "read" ? "check-double" : "check",
|
|
2912
|
-
size: 13,
|
|
2913
|
-
color: theme.colors.onBrand
|
|
2914
|
-
}
|
|
2915
|
-
) }) : null
|
|
2916
|
-
] }),
|
|
2917
|
-
actionsTrigger ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.actionsSlot, children: actionsTrigger }) : null
|
|
2918
|
-
]
|
|
2919
|
-
}
|
|
2920
|
-
)
|
|
2921
|
-
}
|
|
2922
|
-
);
|
|
2981
|
+
inlineMeta ? metaRow("inline") : null
|
|
2982
|
+
] }) : null,
|
|
2983
|
+
!inlineMeta && !metaOverlay && jumboCount === 0 ? metaRow("block") : null,
|
|
2984
|
+
actionsTrigger ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.actionsSlot, children: actionsTrigger }) : null
|
|
2985
|
+
]
|
|
2986
|
+
}
|
|
2987
|
+
),
|
|
2988
|
+
jumboCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { alignSelf: isSelf ? "flex-end" : "flex-start" }, children: metaRow("jumbo") }) : null
|
|
2989
|
+
] });
|
|
2923
2990
|
}
|
|
2924
2991
|
function QuotedCard({
|
|
2925
2992
|
quoted,
|
|
@@ -2930,7 +2997,7 @@ function QuotedCard({
|
|
|
2930
2997
|
const theme = usePoolseTheme();
|
|
2931
2998
|
const resolved = react$1.useUser(quoted.sender_external_id);
|
|
2932
2999
|
const senderName = quoted.sender_external_id ? labelFor?.(quoted.sender_external_id) ?? resolved.profile?.displayName ?? quoted.sender_external_id : "Unknown";
|
|
2933
|
-
const bg = isSelf ? "rgba(
|
|
3000
|
+
const bg = isSelf ? "rgba(0,0,0,0.16)" : theme.colors.surface2;
|
|
2934
3001
|
const accent = isSelf ? theme.colors.onBrand : theme.colors.brand;
|
|
2935
3002
|
const txt = isSelf ? theme.colors.onBrand : theme.colors.ink;
|
|
2936
3003
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2946,8 +3013,8 @@ function QuotedCard({
|
|
|
2946
3013
|
}
|
|
2947
3014
|
],
|
|
2948
3015
|
children: [
|
|
2949
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedSender, { color: accent }], children: senderName }),
|
|
2950
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.body ?? "\u2026" })
|
|
3016
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedSender, { color: accent }], numberOfLines: 1, children: senderName }),
|
|
3017
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.deleted_at ? "Original message deleted" : quoted.body ?? "\u2026" })
|
|
2951
3018
|
]
|
|
2952
3019
|
}
|
|
2953
3020
|
);
|
|
@@ -2983,16 +3050,34 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
2983
3050
|
wrap: {
|
|
2984
3051
|
marginBottom: 2
|
|
2985
3052
|
},
|
|
2986
|
-
bubble: {
|
|
2987
|
-
position: "relative"
|
|
2988
|
-
},
|
|
2989
3053
|
senderName: {
|
|
2990
3054
|
fontSize: 12,
|
|
2991
|
-
fontWeight: "
|
|
2992
|
-
|
|
3055
|
+
fontWeight: "700",
|
|
3056
|
+
paddingTop: 7,
|
|
3057
|
+
paddingHorizontal: 12
|
|
2993
3058
|
},
|
|
2994
|
-
|
|
2995
|
-
|
|
3059
|
+
mediaFrame: {
|
|
3060
|
+
margin: 3
|
|
3061
|
+
},
|
|
3062
|
+
filesBlock: {
|
|
3063
|
+
paddingHorizontal: 6,
|
|
3064
|
+
paddingTop: 6,
|
|
3065
|
+
gap: 4
|
|
3066
|
+
},
|
|
3067
|
+
textBlock: {
|
|
3068
|
+
paddingHorizontal: 12,
|
|
3069
|
+
paddingVertical: 7
|
|
3070
|
+
},
|
|
3071
|
+
// A hair less air when media / files / a quote already sit above.
|
|
3072
|
+
textBlockAfter: {
|
|
3073
|
+
paddingTop: 6
|
|
3074
|
+
},
|
|
3075
|
+
jumboBlock: {
|
|
3076
|
+
paddingVertical: 2
|
|
3077
|
+
},
|
|
3078
|
+
ghost: {
|
|
3079
|
+
color: "transparent",
|
|
3080
|
+
fontSize: 11
|
|
2996
3081
|
},
|
|
2997
3082
|
readMore: {
|
|
2998
3083
|
fontSize: 12,
|
|
@@ -3002,10 +3087,36 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3002
3087
|
metaRow: {
|
|
3003
3088
|
flexDirection: "row",
|
|
3004
3089
|
alignItems: "center",
|
|
3090
|
+
gap: 4
|
|
3091
|
+
},
|
|
3092
|
+
metaInline: {
|
|
3093
|
+
position: "absolute",
|
|
3094
|
+
right: 12,
|
|
3095
|
+
// Optically centers the meta against the body's last line (22px
|
|
3096
|
+
// line over the 7px bottom padding): 7 + (22 - 14) / 2. At 8 it
|
|
3097
|
+
// sat visibly low beside a one-line message.
|
|
3098
|
+
bottom: 11
|
|
3099
|
+
},
|
|
3100
|
+
metaOverlay: {
|
|
3101
|
+
position: "absolute",
|
|
3102
|
+
right: 6,
|
|
3103
|
+
bottom: 6,
|
|
3104
|
+
paddingHorizontal: 8,
|
|
3105
|
+
paddingVertical: 3,
|
|
3106
|
+
borderRadius: 999,
|
|
3107
|
+
backgroundColor: "rgba(0,0,0,0.42)"
|
|
3108
|
+
},
|
|
3109
|
+
metaBlock: {
|
|
3005
3110
|
justifyContent: "flex-end",
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3111
|
+
paddingHorizontal: 12,
|
|
3112
|
+
paddingBottom: 6,
|
|
3113
|
+
marginTop: -2
|
|
3114
|
+
},
|
|
3115
|
+
metaBlockAfterFiles: {
|
|
3116
|
+
marginTop: 6
|
|
3117
|
+
},
|
|
3118
|
+
metaJumbo: {
|
|
3119
|
+
paddingTop: 2
|
|
3009
3120
|
},
|
|
3010
3121
|
meta: {
|
|
3011
3122
|
fontSize: 11,
|
|
@@ -3018,7 +3129,7 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3018
3129
|
letterSpacing: 0.1
|
|
3019
3130
|
},
|
|
3020
3131
|
readTick: {
|
|
3021
|
-
marginLeft:
|
|
3132
|
+
marginLeft: 1,
|
|
3022
3133
|
marginTop: 1
|
|
3023
3134
|
},
|
|
3024
3135
|
actionsSlot: {
|
|
@@ -3028,20 +3139,21 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3028
3139
|
},
|
|
3029
3140
|
quoted: {
|
|
3030
3141
|
borderLeftWidth: 3,
|
|
3031
|
-
paddingLeft:
|
|
3032
|
-
paddingVertical:
|
|
3033
|
-
paddingRight:
|
|
3034
|
-
|
|
3035
|
-
|
|
3142
|
+
paddingLeft: 9,
|
|
3143
|
+
paddingVertical: 5,
|
|
3144
|
+
paddingRight: 8,
|
|
3145
|
+
margin: 4,
|
|
3146
|
+
marginBottom: 0,
|
|
3147
|
+
borderRadius: 10
|
|
3036
3148
|
},
|
|
3037
3149
|
quotedSender: {
|
|
3038
3150
|
fontSize: 12,
|
|
3039
|
-
fontWeight: "
|
|
3040
|
-
marginBottom:
|
|
3151
|
+
fontWeight: "700",
|
|
3152
|
+
marginBottom: 1
|
|
3041
3153
|
},
|
|
3042
3154
|
quotedBody: {
|
|
3043
|
-
fontSize: 12,
|
|
3044
|
-
opacity: 0.
|
|
3155
|
+
fontSize: 12.5,
|
|
3156
|
+
opacity: 0.8
|
|
3045
3157
|
}
|
|
3046
3158
|
});
|
|
3047
3159
|
var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
|
|
@@ -3357,15 +3469,7 @@ function MessageRow({
|
|
|
3357
3469
|
maxBodyLength,
|
|
3358
3470
|
showSenderName,
|
|
3359
3471
|
showAttachments: attachments,
|
|
3360
|
-
markdown
|
|
3361
|
-
actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3362
|
-
PoolseIcon,
|
|
3363
|
-
{
|
|
3364
|
-
name: "chevron-down",
|
|
3365
|
-
size: 14,
|
|
3366
|
-
color: isSelf ? theme.colors.onBrand : theme.colors.ink3
|
|
3367
|
-
}
|
|
3368
|
-
) }) : null
|
|
3472
|
+
markdown
|
|
3369
3473
|
}
|
|
3370
3474
|
)
|
|
3371
3475
|
}
|
|
@@ -5836,6 +5940,10 @@ function format(total) {
|
|
|
5836
5940
|
return `${hours > 0 ? `${hours}:` : ""}${mm}:${String(seconds).padStart(2, "0")}`;
|
|
5837
5941
|
}
|
|
5838
5942
|
var CHROME_IDLE_MS = 4500;
|
|
5943
|
+
var ENDED_LINGER_MS = 2500;
|
|
5944
|
+
function isOver(phase) {
|
|
5945
|
+
return phase === "declined" || phase === "busy" || phase === "timed-out";
|
|
5946
|
+
}
|
|
5839
5947
|
function CallScreen({
|
|
5840
5948
|
userId,
|
|
5841
5949
|
labelFor,
|
|
@@ -5951,6 +6059,11 @@ function CallScreen({
|
|
|
5951
6059
|
endedOnEmpty.current = true;
|
|
5952
6060
|
hangUp();
|
|
5953
6061
|
}, [alone, calls.phase, hangUp]);
|
|
6062
|
+
react.useEffect(() => {
|
|
6063
|
+
if (!isOver(calls.phase)) return;
|
|
6064
|
+
const timer = setTimeout(calls.reset, ENDED_LINGER_MS);
|
|
6065
|
+
return () => clearTimeout(timer);
|
|
6066
|
+
}, [calls.phase, calls.reset]);
|
|
5954
6067
|
const micOn = usingSfu ? sfu.micEnabled : !mesh.muted;
|
|
5955
6068
|
const toggleMic = react.useCallback(() => {
|
|
5956
6069
|
if (usingSfu) void sfu.toggleMic();
|
|
@@ -5964,7 +6077,7 @@ function CallScreen({
|
|
|
5964
6077
|
const label = react.useMemo(() => labelFor ?? ((id) => `User ${id.slice(0, 6)}`), [labelFor]);
|
|
5965
6078
|
const ringingIn = calls.phase === "ringing-in";
|
|
5966
6079
|
const ringingOut = calls.phase === "ringing-out";
|
|
5967
|
-
const ended = calls.phase
|
|
6080
|
+
const ended = isOver(calls.phase);
|
|
5968
6081
|
const visible = calls.phase !== "idle" && !(nativeIncomingUi && ringingIn);
|
|
5969
6082
|
const duration = useCallDuration(connected);
|
|
5970
6083
|
const participants = usingSfu ? sfu.participants : [];
|
|
@@ -6206,6 +6319,26 @@ function isStalePush(sentAt, now, staleAfterMs) {
|
|
|
6206
6319
|
if (age < 0) return false;
|
|
6207
6320
|
return age > staleAfterMs;
|
|
6208
6321
|
}
|
|
6322
|
+
function readPush(payload) {
|
|
6323
|
+
const callId = payload.call_id;
|
|
6324
|
+
if (!callId) return { kind: "ignore" };
|
|
6325
|
+
if (payload.event === "ended") return { kind: "teardown", callId };
|
|
6326
|
+
const conversationId = payload.conversation_id;
|
|
6327
|
+
if (!conversationId) return { kind: "ignore" };
|
|
6328
|
+
return {
|
|
6329
|
+
kind: "ring",
|
|
6330
|
+
sentAt: payload.sent_at,
|
|
6331
|
+
call: {
|
|
6332
|
+
callId,
|
|
6333
|
+
conversationId,
|
|
6334
|
+
callerUserId: payload.caller_user_id ?? "",
|
|
6335
|
+
callerName: payload.caller_name ?? "Incoming call",
|
|
6336
|
+
// `has_video` is what older servers sent; `media` supersedes it.
|
|
6337
|
+
// Either saying video means the system UI offers video.
|
|
6338
|
+
media: payload.media === "video" || payload.has_video ? "video" : "audio"
|
|
6339
|
+
}
|
|
6340
|
+
};
|
|
6341
|
+
}
|
|
6209
6342
|
function useVoipCalls({
|
|
6210
6343
|
voipPush,
|
|
6211
6344
|
callKeep,
|
|
@@ -6277,21 +6410,20 @@ function useVoipCalls({
|
|
|
6277
6410
|
react.useEffect(() => {
|
|
6278
6411
|
let cancelled = false;
|
|
6279
6412
|
const fromPush = (payload) => {
|
|
6280
|
-
const
|
|
6281
|
-
|
|
6282
|
-
if (
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
);
|
|
6413
|
+
const intent = readPush(payload);
|
|
6414
|
+
if (intent.kind === "ignore") return;
|
|
6415
|
+
if (intent.kind === "teardown") {
|
|
6416
|
+
dismiss.current(intent.callId);
|
|
6417
|
+
handlers.current.calls?.dropped(intent.callId);
|
|
6418
|
+
return;
|
|
6419
|
+
}
|
|
6420
|
+
present.current(intent.call, intent.sentAt);
|
|
6421
|
+
handlers.current.calls?.ring({
|
|
6422
|
+
callId: intent.call.callId,
|
|
6423
|
+
conversationId: intent.call.conversationId,
|
|
6424
|
+
callerUserId: intent.call.callerUserId,
|
|
6425
|
+
media: intent.call.media
|
|
6426
|
+
});
|
|
6295
6427
|
};
|
|
6296
6428
|
ready.current = callKeep.setup({
|
|
6297
6429
|
ios: { appName },
|