fix(mobile): avoid duplicate shared links
This commit is contained in:
parent
c6f942436d
commit
2b0b412dbe
|
|
@ -0,0 +1,55 @@
|
|||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import { createLinkShareContent } from "./share"
|
||||
|
||||
describe("createLinkShareContent", () => {
|
||||
it("uses a single URL item on iOS", () => {
|
||||
const url = "https://example.com/post/1"
|
||||
|
||||
const content = createLinkShareContent({
|
||||
platform: "ios",
|
||||
title: "Post title",
|
||||
url,
|
||||
message: url,
|
||||
})
|
||||
|
||||
expect(content).toEqual({
|
||||
title: "Post title",
|
||||
url,
|
||||
})
|
||||
expect(content).not.toHaveProperty("message")
|
||||
})
|
||||
|
||||
it("uses a single text item on Android", () => {
|
||||
const url = "https://example.com/post/1"
|
||||
const message = `Check out this post: ${url}`
|
||||
|
||||
const content = createLinkShareContent({
|
||||
platform: "android",
|
||||
title: "Post title",
|
||||
url,
|
||||
message,
|
||||
})
|
||||
|
||||
expect(content).toEqual({
|
||||
title: "Post title",
|
||||
message,
|
||||
})
|
||||
expect(content).not.toHaveProperty("url")
|
||||
})
|
||||
|
||||
it("falls back to the URL as Android share text", () => {
|
||||
const url = "https://example.com/post/1"
|
||||
|
||||
expect(
|
||||
createLinkShareContent({
|
||||
platform: "android",
|
||||
title: "Post title",
|
||||
url,
|
||||
}),
|
||||
).toEqual({
|
||||
title: "Post title",
|
||||
message: url,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import type { ShareContent } from "react-native"
|
||||
|
||||
type LinkSharePlatform = "android" | "ios" | "macos" | "native" | "web" | "windows"
|
||||
|
||||
interface CreateLinkShareContentOptions {
|
||||
platform: LinkSharePlatform
|
||||
title?: string
|
||||
url: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export const createLinkShareContent = ({
|
||||
platform,
|
||||
title,
|
||||
url,
|
||||
message,
|
||||
}: CreateLinkShareContentOptions): ShareContent => {
|
||||
if (platform === "ios") {
|
||||
return {
|
||||
title,
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
message: message || url,
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import { PortalProvider } from "@gorhom/portal"
|
|||
import type { PropsWithChildren } from "react"
|
||||
import { useCallback } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Share, View } from "react-native"
|
||||
import { Platform, Share, View } from "react-native"
|
||||
|
||||
import { getHideAllReadSubscriptions } from "@/src/atoms/settings/general"
|
||||
import { EntryContentWebView } from "@/src/components/native/webview/EntryContentWebView"
|
||||
|
|
@ -17,6 +17,7 @@ import { WebViewManager } from "@/src/components/native/webview/webview-manager"
|
|||
import { ContextMenu } from "@/src/components/ui/context-menu"
|
||||
import { Text } from "@/src/components/ui/typography/Text"
|
||||
import { useNavigation } from "@/src/lib/navigation/hooks"
|
||||
import { createLinkShareContent } from "@/src/lib/share"
|
||||
import { toast } from "@/src/lib/toast"
|
||||
import { playEntryTts } from "@/src/modules/player/entry-tts"
|
||||
import { EntryDetailScreen } from "@/src/screens/(stack)/entries/[entryId]/EntryDetailScreen"
|
||||
|
|
@ -204,11 +205,13 @@ export const EntryItemContextMenu = ({
|
|||
key="Share"
|
||||
onSelect={async () => {
|
||||
if (!entry.url) return
|
||||
await Share.share({
|
||||
message: entry.url,
|
||||
url: entry.url,
|
||||
title: entry.title || "Shared Link",
|
||||
})
|
||||
await Share.share(
|
||||
createLinkShareContent({
|
||||
platform: Platform.OS,
|
||||
title: entry.title || "Shared Link",
|
||||
url: entry.url,
|
||||
}),
|
||||
)
|
||||
}}
|
||||
>
|
||||
<ContextMenu.ItemIcon
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import { unreadSyncService } from "@follow/store/unread/store"
|
|||
import { useIsLoggedIn } from "@follow/store/user/hooks"
|
||||
import type { PropsWithChildren } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Share } from "react-native"
|
||||
import { Platform, Share } from "react-native"
|
||||
|
||||
import { ContextMenu } from "@/src/components/ui/context-menu"
|
||||
import { createLinkShareContent } from "@/src/lib/share"
|
||||
import { toast } from "@/src/lib/toast"
|
||||
|
||||
type VideoContextMenuProps = PropsWithChildren<{
|
||||
|
|
@ -87,11 +88,14 @@ export const VideoContextMenu = ({ entryId, children }: VideoContextMenuProps) =
|
|||
key="Share"
|
||||
onSelect={async () => {
|
||||
if (!entry.url) return
|
||||
await Share.share({
|
||||
message: [entry.title, entry.url].filter(Boolean).join("\n"),
|
||||
url: entry.url,
|
||||
title: entry.title || "Shared Video",
|
||||
})
|
||||
await Share.share(
|
||||
createLinkShareContent({
|
||||
platform: Platform.OS,
|
||||
title: entry.title || "Shared Video",
|
||||
url: entry.url,
|
||||
message: [entry.title, entry.url].filter(Boolean).join("\n"),
|
||||
}),
|
||||
)
|
||||
return
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { setStringAsync } from "expo-clipboard"
|
|||
import { useAtom } from "jotai"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Pressable, Share, View } from "react-native"
|
||||
import { Platform, Pressable, Share, View } from "react-native"
|
||||
import type { SharedValue } from "react-native-reanimated"
|
||||
import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated"
|
||||
import { useColor } from "react-native-uikit-colors"
|
||||
|
|
@ -27,6 +27,7 @@ import { StarCuteReIcon } from "@/src/icons/star_cute_re"
|
|||
import { Translate2CuteReIcon } from "@/src/icons/translate_2_cute_re"
|
||||
import { VoiceCuteReIcon } from "@/src/icons/voice_cute_re"
|
||||
import { hideIntelligenceGlowEffect, openLink } from "@/src/lib/native"
|
||||
import { createLinkShareContent } from "@/src/lib/share"
|
||||
import { toast } from "@/src/lib/toast"
|
||||
import { playEntryTts } from "@/src/modules/player/entry-tts"
|
||||
|
||||
|
|
@ -100,11 +101,9 @@ const HeaderRightActionsImpl = ({
|
|||
|
||||
const handleShare = () => {
|
||||
if (!entry?.title || !entry?.url) return
|
||||
Share.share({
|
||||
message: entry.url,
|
||||
title: entry.title,
|
||||
url: entry.url,
|
||||
})
|
||||
Share.share(
|
||||
createLinkShareContent({ platform: Platform.OS, title: entry.title, url: entry.url }),
|
||||
)
|
||||
}
|
||||
|
||||
const toggleAITranslation = () => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import * as Haptics from "expo-haptics"
|
|||
import type { PropsWithChildren } from "react"
|
||||
import { useCallback } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Pressable, Share, View } from "react-native"
|
||||
import { Platform, Pressable, Share, View } from "react-native"
|
||||
|
||||
import { setGeneralSetting, useGeneralSettingKey } from "@/src/atoms/settings/general"
|
||||
import { UserAvatar } from "@/src/components/ui/avatar/UserAvatar"
|
||||
|
|
@ -17,6 +17,7 @@ import { ShareForwardCuteReIcon } from "@/src/icons/share_forward_cute_re"
|
|||
import { Dialog } from "@/src/lib/dialog"
|
||||
import { useNavigation } from "@/src/lib/navigation/hooks"
|
||||
import { proxyEnv } from "@/src/lib/proxy-env"
|
||||
import { createLinkShareContent } from "@/src/lib/share"
|
||||
import { toast } from "@/src/lib/toast"
|
||||
import { LoginScreen } from "@/src/screens/(modal)/LoginScreen"
|
||||
import { ProfileScreen } from "@/src/screens/(modal)/ProfileScreen"
|
||||
|
|
@ -124,11 +125,14 @@ export const FeedShareActionButton = ({
|
|||
const feed = getFeedById(feedId)
|
||||
if (!feed) return
|
||||
const url = `${proxyEnv.WEB_URL}/share/feeds/${feedId}`
|
||||
Share.share({
|
||||
message: `Check out ${feed.title} on Folo: ${url}`,
|
||||
title: feed.title!,
|
||||
url,
|
||||
})
|
||||
Share.share(
|
||||
createLinkShareContent({
|
||||
platform: Platform.OS,
|
||||
title: feed.title!,
|
||||
url,
|
||||
message: `Check out ${feed.title} on Folo: ${url}`,
|
||||
}),
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { usePrefetchUser, useUserById, useWhoami } from "@follow/store/user/hook
|
|||
import { Image as ExpoImage } from "expo-image"
|
||||
import { createContext, Fragment, use, useCallback, useEffect, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Alert, FlatList, Pressable, Share, View } from "react-native"
|
||||
import { Alert, FlatList, Platform, Pressable, Share, View } from "react-native"
|
||||
import Animated, {
|
||||
interpolate,
|
||||
useAnimatedScrollHandler,
|
||||
|
|
@ -42,6 +42,7 @@ import { ShareForwardCuteReIcon } from "@/src/icons/share_forward_cute_re"
|
|||
import type { followClient } from "@/src/lib/api-client"
|
||||
import { Navigation } from "@/src/lib/navigation/Navigation"
|
||||
import type { NavigationControllerView } from "@/src/lib/navigation/types"
|
||||
import { createLinkShareContent } from "@/src/lib/share"
|
||||
import { toast } from "@/src/lib/toast"
|
||||
import { useShareSubscription } from "@/src/modules/settings/hooks/useShareSubscription"
|
||||
import { UserHeaderBanner } from "@/src/modules/settings/UserHeaderBanner"
|
||||
|
|
@ -95,11 +96,13 @@ function ProfileScreenImpl(props: { userId: string }) {
|
|||
const openShareUrl = useCallback(() => {
|
||||
if (!user?.id) return
|
||||
const shareUrl = `https://app.folo.is/share/users/${user.id}`
|
||||
Share.share({
|
||||
message: shareUrl,
|
||||
url: shareUrl,
|
||||
title: `Folo | ${user.name}'s Profile`,
|
||||
})
|
||||
Share.share(
|
||||
createLinkShareContent({
|
||||
platform: Platform.OS,
|
||||
title: `Folo | ${user.name}'s Profile`,
|
||||
url: shareUrl,
|
||||
}),
|
||||
)
|
||||
}, [user?.id, user?.name])
|
||||
|
||||
const whoami = useWhoami()
|
||||
|
|
|
|||
Loading…
Reference in New Issue