3606c48f02
react-native-svg materialises three native views per icon (SvgView + Group + Path). Feed screens render around a thousand icons, so that is ~2400 views and ~2400 shadow nodes of pure overhead per profile load. Move the 28 icons that repeat on feed screens - the post action bar, feed row reason, post embeds, the loading skeleton and the bottom tab bar - to react-native-nano-icons, which draws each icon as a single native text glyph. The SVG sources are generated from the existing icon path data so the geometry is unchanged, and `createNanoIcon` mirrors the `useCommonSVGProps` contract so call sites are one-line import swaps. Measured on a Samsung Galaxy A16 (SM-A165F, Android 16) with release builds, 10 runs per condition: native views -20.6%, icon construction -45.3%, JS-thread CPU -6.1%, main-thread CPU -3.3%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
import {memo, useMemo, useState} from 'react'
|
|
import {type Insets} from 'react-native'
|
|
import {AtUri} from '@atproto/syntax'
|
|
import {type RichText as RichTextAPI} from '@bsky/sdk/richtext'
|
|
import {msg} from '@lingui/core/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
|
|
import {makeProfileLink} from '#/lib/routes/links'
|
|
import {shareUrl} from '#/lib/sharing'
|
|
import {toShareUrl} from '#/lib/strings/url-helpers'
|
|
import {type Shadow} from '#/state/cache/post-shadow'
|
|
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
|
import {EventStopper} from '#/view/com/util/EventStopper'
|
|
import {native} from '#/alf'
|
|
import {ShareIcon as ArrowShareRightIcon} from '#/components/icons/nano'
|
|
import * as Menu from '#/components/Menu'
|
|
import {useMenuControl} from '#/components/Menu'
|
|
import {useAnalytics} from '#/analytics'
|
|
import {type app} from '#/lexicons'
|
|
import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
|
|
import {ShareMenuItems} from './ShareMenuItems'
|
|
|
|
let ShareMenuButton = ({
|
|
testID,
|
|
post,
|
|
big,
|
|
record,
|
|
richText,
|
|
timestamp,
|
|
threadgateRecord,
|
|
onShare,
|
|
hitSlop,
|
|
logContext,
|
|
}: {
|
|
testID: string
|
|
post: Shadow<app.bsky.feed.defs.PostView>
|
|
big?: boolean
|
|
record: app.bsky.feed.post.Main
|
|
richText: RichTextAPI
|
|
timestamp: string
|
|
threadgateRecord?: app.bsky.feed.threadgate.Main
|
|
onShare: () => void
|
|
hitSlop?: Insets
|
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
|
|
}): React.ReactNode => {
|
|
const ax = useAnalytics()
|
|
const {_} = useLingui()
|
|
const {feedDescriptor} = useFeedFeedbackContext()
|
|
|
|
const menuControl = useMenuControl()
|
|
const [hasBeenOpen, setHasBeenOpen] = useState(false)
|
|
const lazyMenuControl = useMemo(
|
|
() => ({
|
|
...menuControl,
|
|
open() {
|
|
setHasBeenOpen(true)
|
|
// HACK. We need the state update to be flushed by the time
|
|
// menuControl.open() fires but RN doesn't expose flushSync.
|
|
setTimeout(menuControl.open)
|
|
|
|
ax.metric('post:share', {
|
|
uri: post.uri,
|
|
authorDid: post.author.did,
|
|
logContext,
|
|
feedDescriptor,
|
|
postContext: big ? 'thread' : 'feed',
|
|
})
|
|
},
|
|
}),
|
|
[
|
|
ax,
|
|
menuControl,
|
|
setHasBeenOpen,
|
|
big,
|
|
logContext,
|
|
feedDescriptor,
|
|
post.uri,
|
|
post.author.did,
|
|
],
|
|
)
|
|
|
|
const onNativeLongPress = () => {
|
|
ax.metric('share:press:nativeShare', {})
|
|
const urip = new AtUri(post.uri)
|
|
const href = makeProfileLink(post.author, 'post', urip.rkey)
|
|
const url = toShareUrl(href)
|
|
shareUrl(url)
|
|
onShare()
|
|
}
|
|
|
|
return (
|
|
<EventStopper onKeyDown={false}>
|
|
<Menu.Root control={lazyMenuControl}>
|
|
<Menu.Trigger label={_(msg`Open share menu`)}>
|
|
{({props}) => {
|
|
return (
|
|
<PostControlButton
|
|
testID="postShareBtn"
|
|
big={big}
|
|
label={props.accessibilityLabel}
|
|
{...props}
|
|
onLongPress={native(onNativeLongPress)}
|
|
hitSlop={hitSlop}>
|
|
<PostControlButtonIcon icon={ArrowShareRightIcon} />
|
|
</PostControlButton>
|
|
)
|
|
}}
|
|
</Menu.Trigger>
|
|
{hasBeenOpen && (
|
|
// Lazily initialized. Once mounted, they stay mounted.
|
|
<ShareMenuItems
|
|
testID={testID}
|
|
post={post}
|
|
record={record}
|
|
richText={richText}
|
|
timestamp={timestamp}
|
|
threadgateRecord={threadgateRecord}
|
|
onShare={onShare}
|
|
/>
|
|
)}
|
|
</Menu.Root>
|
|
</EventStopper>
|
|
)
|
|
}
|
|
|
|
ShareMenuButton = memo(ShareMenuButton)
|
|
export {ShareMenuButton}
|