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 {toShareUrl} from '#/lib/strings/url-helpers'
16import {type Shadow} from '#/state/cache/post-shadow'
17import {useFeedFeedbackContext} from '#/state/feed-feedback'
18import {EventStopper} from '#/view/com/util/EventStopper'
19import {native} from '#/alf'
20import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/components/icons/ArrowShareRight'
21import {useMenuControl} from '#/components/Menu'
22import * as Menu from '#/components/Menu'
23import {useAnalytics} from '#/analytics'
24import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
25import {ShareMenuItems} from './ShareMenuItems'
26
27let ShareMenuButton = ({
28 testID,
29 post,
30 big,
31 record,
32 richText,
33 timestamp,
34 threadgateRecord,
35 onShare,
36 hitSlop,
37 logContext,
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 logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
49}): React.ReactNode => {
50 const ax = useAnalytics()
51 const {_} = useLingui()
52 const {feedDescriptor} = useFeedFeedbackContext()
53
54 const menuControl = useMenuControl()
55 const [hasBeenOpen, setHasBeenOpen] = useState(false)
56 const lazyMenuControl = useMemo(
57 () => ({
58 ...menuControl,
59 open() {
60 setHasBeenOpen(true)
61 // HACK. We need the state update to be flushed by the time
62 // menuControl.open() fires but RN doesn't expose flushSync.
63 setTimeout(menuControl.open)
64
65 ax.metric('post:share', {
66 uri: post.uri,
67 authorDid: post.author.did,
68 logContext,
69 feedDescriptor,
70 postContext: big ? 'thread' : 'feed',
71 })
72 },
73 }),
74 [
75 ax,
76 menuControl,
77 setHasBeenOpen,
78 big,
79 logContext,
80 feedDescriptor,
81 post.uri,
82 post.author.did,
83 ],
84 )
85
86 const onNativeLongPress = () => {
87 ax.metric('share:press:nativeShare', {})
88 const urip = new AtUri(post.uri)
89 const href = makeProfileLink(post.author, 'post', urip.rkey)
90 const url = toShareUrl(href)
91 shareUrl(url)
92 onShare()
93 }
94
95 return (
96 <EventStopper onKeyDown={false}>
97 <Menu.Root control={lazyMenuControl}>
98 <Menu.Trigger label={_(msg`Open share menu`)}>
99 {({props}) => {
100 return (
101 <PostControlButton
102 testID="postShareBtn"
103 big={big}
104 label={props.accessibilityLabel}
105 {...props}
106 onLongPress={native(onNativeLongPress)}
107 hitSlop={hitSlop}>
108 <PostControlButtonIcon icon={ArrowShareRightIcon} />
109 </PostControlButton>
110 )
111 }}
112 </Menu.Trigger>
113 {hasBeenOpen && (
114 // Lazily initialized. Once mounted, they stay mounted.
115 <ShareMenuItems
116 testID={testID}
117 post={post}
118 record={record}
119 richText={richText}
120 timestamp={timestamp}
121 threadgateRecord={threadgateRecord}
122 onShare={onShare}
123 />
124 )}
125 </Menu.Root>
126 </EventStopper>
127 )
128}
129
130ShareMenuButton = memo(ShareMenuButton)
131export {ShareMenuButton}