Skip to content

Commit 2b0b412

Browse files
committed
fix(mobile): avoid duplicate shared links
1 parent c6f9424 commit 2b0b412

7 files changed

Lines changed: 127 additions & 30 deletions

File tree

apps/mobile/src/lib/share.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, it } from "vitest"
2+
3+
import { createLinkShareContent } from "./share"
4+
5+
describe("createLinkShareContent", () => {
6+
it("uses a single URL item on iOS", () => {
7+
const url = "https://example.com/post/1"
8+
9+
const content = createLinkShareContent({
10+
platform: "ios",
11+
title: "Post title",
12+
url,
13+
message: url,
14+
})
15+
16+
expect(content).toEqual({
17+
title: "Post title",
18+
url,
19+
})
20+
expect(content).not.toHaveProperty("message")
21+
})
22+
23+
it("uses a single text item on Android", () => {
24+
const url = "https://example.com/post/1"
25+
const message = `Check out this post: ${url}`
26+
27+
const content = createLinkShareContent({
28+
platform: "android",
29+
title: "Post title",
30+
url,
31+
message,
32+
})
33+
34+
expect(content).toEqual({
35+
title: "Post title",
36+
message,
37+
})
38+
expect(content).not.toHaveProperty("url")
39+
})
40+
41+
it("falls back to the URL as Android share text", () => {
42+
const url = "https://example.com/post/1"
43+
44+
expect(
45+
createLinkShareContent({
46+
platform: "android",
47+
title: "Post title",
48+
url,
49+
}),
50+
).toEqual({
51+
title: "Post title",
52+
message: url,
53+
})
54+
})
55+
})

apps/mobile/src/lib/share.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { ShareContent } from "react-native"
2+
3+
type LinkSharePlatform = "android" | "ios" | "macos" | "native" | "web" | "windows"
4+
5+
interface CreateLinkShareContentOptions {
6+
platform: LinkSharePlatform
7+
title?: string
8+
url: string
9+
message?: string
10+
}
11+
12+
export const createLinkShareContent = ({
13+
platform,
14+
title,
15+
url,
16+
message,
17+
}: CreateLinkShareContentOptions): ShareContent => {
18+
if (platform === "ios") {
19+
return {
20+
title,
21+
url,
22+
}
23+
}
24+
25+
return {
26+
title,
27+
message: message || url,
28+
}
29+
}

apps/mobile/src/modules/context-menu/entry.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import { PortalProvider } from "@gorhom/portal"
99
import type { PropsWithChildren } from "react"
1010
import { useCallback } from "react"
1111
import { useTranslation } from "react-i18next"
12-
import { Share, View } from "react-native"
12+
import { Platform, Share, View } from "react-native"
1313

1414
import { getHideAllReadSubscriptions } from "@/src/atoms/settings/general"
1515
import { EntryContentWebView } from "@/src/components/native/webview/EntryContentWebView"
1616
import { WebViewManager } from "@/src/components/native/webview/webview-manager"
1717
import { ContextMenu } from "@/src/components/ui/context-menu"
1818
import { Text } from "@/src/components/ui/typography/Text"
1919
import { useNavigation } from "@/src/lib/navigation/hooks"
20+
import { createLinkShareContent } from "@/src/lib/share"
2021
import { toast } from "@/src/lib/toast"
2122
import { playEntryTts } from "@/src/modules/player/entry-tts"
2223
import { EntryDetailScreen } from "@/src/screens/(stack)/entries/[entryId]/EntryDetailScreen"
@@ -204,11 +205,13 @@ export const EntryItemContextMenu = ({
204205
key="Share"
205206
onSelect={async () => {
206207
if (!entry.url) return
207-
await Share.share({
208-
message: entry.url,
209-
url: entry.url,
210-
title: entry.title || "Shared Link",
211-
})
208+
await Share.share(
209+
createLinkShareContent({
210+
platform: Platform.OS,
211+
title: entry.title || "Shared Link",
212+
url: entry.url,
213+
}),
214+
)
212215
}}
213216
>
214217
<ContextMenu.ItemIcon

apps/mobile/src/modules/context-menu/video.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { unreadSyncService } from "@follow/store/unread/store"
66
import { useIsLoggedIn } from "@follow/store/user/hooks"
77
import type { PropsWithChildren } from "react"
88
import { useTranslation } from "react-i18next"
9-
import { Share } from "react-native"
9+
import { Platform, Share } from "react-native"
1010

1111
import { ContextMenu } from "@/src/components/ui/context-menu"
12+
import { createLinkShareContent } from "@/src/lib/share"
1213
import { toast } from "@/src/lib/toast"
1314

1415
type VideoContextMenuProps = PropsWithChildren<{
@@ -87,11 +88,14 @@ export const VideoContextMenu = ({ entryId, children }: VideoContextMenuProps) =
8788
key="Share"
8889
onSelect={async () => {
8990
if (!entry.url) return
90-
await Share.share({
91-
message: [entry.title, entry.url].filter(Boolean).join("\n"),
92-
url: entry.url,
93-
title: entry.title || "Shared Video",
94-
})
91+
await Share.share(
92+
createLinkShareContent({
93+
platform: Platform.OS,
94+
title: entry.title || "Shared Video",
95+
url: entry.url,
96+
message: [entry.title, entry.url].filter(Boolean).join("\n"),
97+
}),
98+
)
9599
return
96100
}}
97101
>

apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { setStringAsync } from "expo-clipboard"
1010
import { useAtom } from "jotai"
1111
import { useCallback, useEffect, useState } from "react"
1212
import { useTranslation } from "react-i18next"
13-
import { Pressable, Share, View } from "react-native"
13+
import { Platform, Pressable, Share, View } from "react-native"
1414
import type { SharedValue } from "react-native-reanimated"
1515
import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated"
1616
import { useColor } from "react-native-uikit-colors"
@@ -27,6 +27,7 @@ import { StarCuteReIcon } from "@/src/icons/star_cute_re"
2727
import { Translate2CuteReIcon } from "@/src/icons/translate_2_cute_re"
2828
import { VoiceCuteReIcon } from "@/src/icons/voice_cute_re"
2929
import { hideIntelligenceGlowEffect, openLink } from "@/src/lib/native"
30+
import { createLinkShareContent } from "@/src/lib/share"
3031
import { toast } from "@/src/lib/toast"
3132
import { playEntryTts } from "@/src/modules/player/entry-tts"
3233

@@ -100,11 +101,9 @@ const HeaderRightActionsImpl = ({
100101

101102
const handleShare = () => {
102103
if (!entry?.title || !entry?.url) return
103-
Share.share({
104-
message: entry.url,
105-
title: entry.title,
106-
url: entry.url,
107-
})
104+
Share.share(
105+
createLinkShareContent({ platform: Platform.OS, title: entry.title, url: entry.url }),
106+
)
108107
}
109108

110109
const toggleAITranslation = () => {

apps/mobile/src/modules/screen/action.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as Haptics from "expo-haptics"
55
import type { PropsWithChildren } from "react"
66
import { useCallback } from "react"
77
import { useTranslation } from "react-i18next"
8-
import { Pressable, Share, View } from "react-native"
8+
import { Platform, Pressable, Share, View } from "react-native"
99

1010
import { setGeneralSetting, useGeneralSettingKey } from "@/src/atoms/settings/general"
1111
import { UserAvatar } from "@/src/components/ui/avatar/UserAvatar"
@@ -17,6 +17,7 @@ import { ShareForwardCuteReIcon } from "@/src/icons/share_forward_cute_re"
1717
import { Dialog } from "@/src/lib/dialog"
1818
import { useNavigation } from "@/src/lib/navigation/hooks"
1919
import { proxyEnv } from "@/src/lib/proxy-env"
20+
import { createLinkShareContent } from "@/src/lib/share"
2021
import { toast } from "@/src/lib/toast"
2122
import { LoginScreen } from "@/src/screens/(modal)/LoginScreen"
2223
import { ProfileScreen } from "@/src/screens/(modal)/ProfileScreen"
@@ -124,11 +125,14 @@ export const FeedShareActionButton = ({
124125
const feed = getFeedById(feedId)
125126
if (!feed) return
126127
const url = `${proxyEnv.WEB_URL}/share/feeds/${feedId}`
127-
Share.share({
128-
message: `Check out ${feed.title} on Folo: ${url}`,
129-
title: feed.title!,
130-
url,
131-
})
128+
Share.share(
129+
createLinkShareContent({
130+
platform: Platform.OS,
131+
title: feed.title!,
132+
url,
133+
message: `Check out ${feed.title} on Folo: ${url}`,
134+
}),
135+
)
132136
}}
133137
/>
134138
)

apps/mobile/src/screens/(modal)/ProfileScreen.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { usePrefetchUser, useUserById, useWhoami } from "@follow/store/user/hook
77
import { Image as ExpoImage } from "expo-image"
88
import { createContext, Fragment, use, useCallback, useEffect, useMemo } from "react"
99
import { useTranslation } from "react-i18next"
10-
import { Alert, FlatList, Pressable, Share, View } from "react-native"
10+
import { Alert, FlatList, Platform, Pressable, Share, View } from "react-native"
1111
import Animated, {
1212
interpolate,
1313
useAnimatedScrollHandler,
@@ -42,6 +42,7 @@ import { ShareForwardCuteReIcon } from "@/src/icons/share_forward_cute_re"
4242
import type { followClient } from "@/src/lib/api-client"
4343
import { Navigation } from "@/src/lib/navigation/Navigation"
4444
import type { NavigationControllerView } from "@/src/lib/navigation/types"
45+
import { createLinkShareContent } from "@/src/lib/share"
4546
import { toast } from "@/src/lib/toast"
4647
import { useShareSubscription } from "@/src/modules/settings/hooks/useShareSubscription"
4748
import { UserHeaderBanner } from "@/src/modules/settings/UserHeaderBanner"
@@ -95,11 +96,13 @@ function ProfileScreenImpl(props: { userId: string }) {
9596
const openShareUrl = useCallback(() => {
9697
if (!user?.id) return
9798
const shareUrl = `https://app.folo.is/share/users/${user.id}`
98-
Share.share({
99-
message: shareUrl,
100-
url: shareUrl,
101-
title: `Folo | ${user.name}'s Profile`,
102-
})
99+
Share.share(
100+
createLinkShareContent({
101+
platform: Platform.OS,
102+
title: `Folo | ${user.name}'s Profile`,
103+
url: shareUrl,
104+
}),
105+
)
103106
}, [user?.id, user?.name])
104107

105108
const whoami = useWhoami()

0 commit comments

Comments
 (0)