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