forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {memo, useMemo, useState} from 'react'
2import {type Insets} from 'react-native'
3import {
4 type AppBskyFeedDefs,
5 type AppBskyFeedPost,
6 type AppBskyFeedThreadgate,
7 AtUri,
8 type RichText as RichTextAPI,
9} from '@atproto/api'
10import {msg} from '@lingui/macro'
11import {useLingui} from '@lingui/react'
12
13import {makeProfileLink} from '#/lib/routes/links'
14import {shareUrl} from '#/lib/sharing'
15import {useGate} from '#/lib/statsig/statsig'
16import {toShareUrl} from '#/lib/strings/url-helpers'
17import {logger} from '#/logger'
18import {type Shadow} from '#/state/cache/post-shadow'
19import {EventStopper} from '#/view/com/util/EventStopper'
20import {native} from '#/alf'
21import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
22import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/components/icons/ArrowShareRight'
23import {useMenuControl} from '#/components/Menu'
24import * as Menu from '#/components/Menu'
25import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
26import {ShareMenuItems} from './ShareMenuItems'
27
28let ShareMenuButton = ({
29 testID,
30 post,
31 big,
32 record,
33 richText,
34 timestamp,
35 threadgateRecord,
36 onShare,
37 hitSlop,
38}: {
39 testID: string
40 post: Shadow<AppBskyFeedDefs.PostView>
41 big?: boolean
42 record: AppBskyFeedPost.Record
43 richText: RichTextAPI
44 timestamp: string
45 threadgateRecord?: AppBskyFeedThreadgate.Record
46 onShare: () => void
47 hitSlop?: Insets
48}): React.ReactNode => {
49 const {_} = useLingui()
50 const gate = useGate()
51
52 const ShareIcon = gate('alt_share_icon')
53 ? ArrowShareRightIcon
54 : ArrowOutOfBoxIcon
55
56 const menuControl = useMenuControl()
57 const [hasBeenOpen, setHasBeenOpen] = useState(false)
58 const lazyMenuControl = useMemo(
59 () => ({
60 ...menuControl,
61 open() {
62 setHasBeenOpen(true)
63 // HACK. We need the state update to be flushed by the time
64 // menuControl.open() fires but RN doesn't expose flushSync.
65 setTimeout(menuControl.open)
66
67 logger.metric(
68 'share:open',
69 {context: big ? 'thread' : 'feed'},
70 {statsig: true},
71 )
72 },
73 }),
74 [menuControl, setHasBeenOpen, big],
75 )
76
77 const onNativeLongPress = () => {
78 logger.metric('share:press:nativeShare', {}, {statsig: true})
79 const urip = new AtUri(post.uri)
80 const href = makeProfileLink(post.author, 'post', urip.rkey)
81 const url = toShareUrl(href)
82 shareUrl(url)
83 onShare()
84 }
85
86 return (
87 <EventStopper onKeyDown={false}>
88 <Menu.Root control={lazyMenuControl}>
89 <Menu.Trigger label={_(msg`Open share menu`)}>
90 {({props}) => {
91 return (
92 <PostControlButton
93 testID="postShareBtn"
94 big={big}
95 label={props.accessibilityLabel}
96 {...props}
97 onLongPress={native(onNativeLongPress)}
98 hitSlop={hitSlop}>
99 <PostControlButtonIcon icon={ShareIcon} />
100 </PostControlButton>
101 )
102 }}
103 </Menu.Trigger>
104 {hasBeenOpen && (
105 // Lazily initialized. Once mounted, they stay mounted.
106 <ShareMenuItems
107 testID={testID}
108 post={post}
109 record={record}
110 richText={richText}
111 timestamp={timestamp}
112 threadgateRecord={threadgateRecord}
113 onShare={onShare}
114 />
115 )}
116 </Menu.Root>
117 </EventStopper>
118 )
119}
120
121ShareMenuButton = memo(ShareMenuButton)
122export {ShareMenuButton}