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