diff --git a/package.json b/package.json index 6e17fcb0..89620182 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky.app", - "version": "1.72.0", + "version": "1.73.0", "private": true, "engines": { "node": ">=18" diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index bc647710..fa7e597f 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -1,5 +1,47 @@ -export type Events = { +export type LogEvents = { init: { initMs: number } + 'feed:endReached': { + feedType: string + itemCount: number + } + 'post:create': { + imageCount: number + isReply: boolean + hasLink: boolean + hasQuote: boolean + langs: string + logContext: 'Composer' + } + 'post:like': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:repost': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:unlike': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:unrepost': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'profile:follow': { + logContext: + | 'RecommendedFollowsItem' + | 'PostThreadItem' + | 'ProfileCard' + | 'ProfileHeader' + | 'ProfileHeaderSuggestedFollows' + | 'ProfileMenu' + } + 'profile:unfollow': { + logContext: + | 'RecommendedFollowsItem' + | 'PostThreadItem' + | 'ProfileCard' + | 'ProfileHeader' + | 'ProfileHeaderSuggestedFollows' + | 'ProfileMenu' + } } diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index a46cef4d..5745d204 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -6,7 +6,9 @@ import { } from 'statsig-react-native-expo' import {useSession} from '../../state/session' import {sha256} from 'js-sha256' -import {Events} from './events' +import {LogEvents} from './events' + +export type {LogEvents} const statsigOptions = { environment: { @@ -31,9 +33,9 @@ export function attachRouteToLogEvents( getCurrentRouteName = getRouteName } -export function logEvent( +export function logEvent( eventName: E & string, - rawMetadata?: Events[E] & FlatJSONRecord, + rawMetadata: LogEvents[E] & FlatJSONRecord, ) { const fullMetadata = { ...rawMetadata, diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts index eb59f7da..e3682e30 100644 --- a/src/state/queries/post.ts +++ b/src/state/queries/post.ts @@ -5,6 +5,7 @@ import {Shadow} from '#/state/cache/types' import {getAgent} from '#/state/session' import {updatePostShadow} from '#/state/cache/post-shadow' import {track} from '#/lib/analytics/analytics' +import {logEvent, LogEvents} from '#/lib/statsig/statsig' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' export const RQKEY = (postUri: string) => ['post', postUri] @@ -58,12 +59,14 @@ export function useGetPost() { export function usePostLikeMutationQueue( post: Shadow, + logContext: LogEvents['post:like']['logContext'] & + LogEvents['post:unlike']['logContext'], ) { const postUri = post.uri const postCid = post.cid const initialLikeUri = post.viewer?.like - const likeMutation = usePostLikeMutation() - const unlikeMutation = usePostUnlikeMutation() + const likeMutation = usePostLikeMutation(logContext) + const unlikeMutation = usePostUnlikeMutation(logContext) const queueToggle = useToggleMutationQueue({ initialState: initialLikeUri, @@ -111,22 +114,30 @@ export function usePostLikeMutationQueue( return [queueLike, queueUnlike] } -function usePostLikeMutation() { +function usePostLikeMutation(logContext: LogEvents['post:like']['logContext']) { return useMutation< {uri: string}, // responds with the uri of the like Error, {uri: string; cid: string} // the post's uri and cid >({ - mutationFn: post => getAgent().like(post.uri, post.cid), + mutationFn: post => { + logEvent('post:like', {logContext}) + return getAgent().like(post.uri, post.cid) + }, onSuccess() { track('Post:Like') }, }) } -function usePostUnlikeMutation() { +function usePostUnlikeMutation( + logContext: LogEvents['post:unlike']['logContext'], +) { return useMutation({ - mutationFn: ({likeUri}) => getAgent().deleteLike(likeUri), + mutationFn: ({likeUri}) => { + logEvent('post:unlike', {logContext}) + return getAgent().deleteLike(likeUri) + }, onSuccess() { track('Post:Unlike') }, @@ -135,12 +146,14 @@ function usePostUnlikeMutation() { export function usePostRepostMutationQueue( post: Shadow, + logContext: LogEvents['post:repost']['logContext'] & + LogEvents['post:unrepost']['logContext'], ) { const postUri = post.uri const postCid = post.cid const initialRepostUri = post.viewer?.repost - const repostMutation = usePostRepostMutation() - const unrepostMutation = usePostUnrepostMutation() + const repostMutation = usePostRepostMutation(logContext) + const unrepostMutation = usePostUnrepostMutation(logContext) const queueToggle = useToggleMutationQueue({ initialState: initialRepostUri, @@ -188,22 +201,32 @@ export function usePostRepostMutationQueue( return [queueRepost, queueUnrepost] } -function usePostRepostMutation() { +function usePostRepostMutation( + logContext: LogEvents['post:repost']['logContext'], +) { return useMutation< {uri: string}, // responds with the uri of the repost Error, {uri: string; cid: string} // the post's uri and cid >({ - mutationFn: post => getAgent().repost(post.uri, post.cid), + mutationFn: post => { + logEvent('post:repost', {logContext}) + return getAgent().repost(post.uri, post.cid) + }, onSuccess() { track('Post:Repost') }, }) } -function usePostUnrepostMutation() { +function usePostUnrepostMutation( + logContext: LogEvents['post:unrepost']['logContext'], +) { return useMutation({ - mutationFn: ({repostUri}) => getAgent().deleteRepost(repostUri), + mutationFn: ({repostUri}) => { + logEvent('post:unrepost', {logContext}) + return getAgent().deleteRepost(repostUri) + }, onSuccess() { track('Post:Unrepost') }, diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index e81ea0f3..3c9e3e41 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -26,6 +26,7 @@ import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts' import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts' import {STALE} from '#/state/queries' import {track} from '#/lib/analytics/analytics' +import {logEvent, LogEvents} from '#/lib/statsig/statsig' import {ThreadNode} from './post-thread' export const RQKEY = (did: string) => ['profile', did] @@ -186,11 +187,13 @@ export function useProfileUpdateMutation() { export function useProfileFollowMutationQueue( profile: Shadow, + logContext: LogEvents['profile:follow']['logContext'] & + LogEvents['profile:unfollow']['logContext'], ) { const did = profile.did const initialFollowingUri = profile.viewer?.following - const followMutation = useProfileFollowMutation() - const unfollowMutation = useProfileUnfollowMutation() + const followMutation = useProfileFollowMutation(logContext) + const unfollowMutation = useProfileUnfollowMutation(logContext) const queueToggle = useToggleMutationQueue({ initialState: initialFollowingUri, @@ -237,9 +240,12 @@ export function useProfileFollowMutationQueue( return [queueFollow, queueUnfollow] } -function useProfileFollowMutation() { +function useProfileFollowMutation( + logContext: LogEvents['profile:follow']['logContext'], +) { return useMutation<{uri: string; cid: string}, Error, {did: string}>({ mutationFn: async ({did}) => { + logEvent('profile:follow', {logContext}) return await getAgent().follow(did) }, onSuccess(data, variables) { @@ -248,9 +254,12 @@ function useProfileFollowMutation() { }) } -function useProfileUnfollowMutation() { +function useProfileUnfollowMutation( + logContext: LogEvents['profile:unfollow']['logContext'], +) { return useMutation({ mutationFn: async ({followUri}) => { + logEvent('profile:unfollow', {logContext}) track('Profile:Unfollow', {username: followUri}) return await getAgent().deleteFollow(followUri) }, diff --git a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx index 07001068..5f81a4d6 100644 --- a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx @@ -56,7 +56,7 @@ export function RecommendedFollowsItem({ ) } -export function ProfileCard({ +function ProfileCard({ profile, onFollowStateChange, moderation, @@ -72,7 +72,10 @@ export function ProfileCard({ const pal = usePalette('default') const [addingMoreSuggestions, setAddingMoreSuggestions] = React.useState(false) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'RecommendedFollowsItem', + ) const onToggleFollow = React.useCallback(async () => { try { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index ef965b27..97f8e519 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -65,6 +65,7 @@ import {logger} from '#/logger' import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo' import * as Prompt from '#/components/Prompt' import {useDialogStateControlContext} from 'state/dialogs' +import {logEvent} from '#/lib/statsig/statsig' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -255,6 +256,16 @@ export const ComposePost = observer(function ComposePost({ setIsProcessing(false) return } finally { + if (postUri) { + logEvent('post:create', { + imageCount: gallery.size, + isReply: replyTo != null, + hasLink: extLink != null, + hasQuote: quote != null, + langs: langPrefs.postLanguage, + logContext: 'Composer', + }) + } track('Create Post', { imageCount: gallery.size, }) diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 45166fe3..a4687026 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -182,7 +182,6 @@ let FeedItem = ({ testID={`feedItem-by-${item.notification.author.handle}`} style={[ styles.outer, - pal.view, pal.border, item.notification.isRead ? undefined diff --git a/src/view/com/pager/FixedTouchableHighlight.tsx b/src/view/com/pager/FixedTouchableHighlight.tsx deleted file mode 100644 index d0719697..00000000 --- a/src/view/com/pager/FixedTouchableHighlight.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// FixedTouchableHighlight.tsx -import React, {ComponentProps, useRef} from 'react' -import {GestureResponderEvent, TouchableHighlight} from 'react-native' - -type Position = {pageX: number; pageY: number} - -export default function FixedTouchableHighlight({ - onPress, - onPressIn, - ...props -}: ComponentProps) { - const _touchActivatePositionRef = useRef(null) - - function _onPressIn(e: GestureResponderEvent) { - const {pageX, pageY} = e.nativeEvent - - _touchActivatePositionRef.current = { - pageX, - pageY, - } - - onPressIn?.(e) - } - - function _onPress(e: GestureResponderEvent) { - const {pageX, pageY} = e.nativeEvent - - const absX = Math.abs(_touchActivatePositionRef.current?.pageX! - pageX) - const absY = Math.abs(_touchActivatePositionRef.current?.pageY! - pageY) - - const dragged = absX > 2 || absY > 2 - if (!dragged) { - onPress?.(e) - } - } - - return ( - - {props.children} - - ) -} diff --git a/src/view/com/post-thread/PostThreadFollowBtn.tsx b/src/view/com/post-thread/PostThreadFollowBtn.tsx index e5b747cc..45c3771f 100644 --- a/src/view/com/post-thread/PostThreadFollowBtn.tsx +++ b/src/view/com/post-thread/PostThreadFollowBtn.tsx @@ -42,7 +42,10 @@ function PostThreadFollowBtnLoaded({ const {isTabletOrDesktop} = useWebMediaQueries() const profile: Shadow = useProfileShadow(profileUnshadowed) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'PostThreadItem', + ) const requireAuth = useRequireAuth() const isFollowing = !!profile.viewer?.following diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 9522ea6a..cd746f9a 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -407,6 +407,7 @@ let PostThreadItemLoaded = ({ record={record} richText={richText} onPressReply={onPressReply} + logContext="PostThreadItem" /> @@ -431,7 +432,6 @@ let PostThreadItemLoaded = ({ @@ -620,7 +621,6 @@ function PostOuterWrapper({ return ( + {showReplyLine && } @@ -220,6 +220,7 @@ function PostInner({ record={record} richText={richText} onPressReply={onPressReply} + logContext="Post" /> diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index cd3e9878..b86646a4 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -33,6 +33,7 @@ import {useLingui} from '@lingui/react' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {logEvent} from '#/lib/statsig/statsig' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -223,16 +224,29 @@ let Feed = ({ setIsPTRing(false) }, [refetch, track, setIsPTRing, onHasNew]) + const feedType = feed.split('|')[0] const onEndReached = React.useCallback(async () => { if (isFetching || !hasNextPage || isError) return + logEvent('feed:endReached', { + feedType: feedType, + itemCount: feedItems.length, + }) track('Feed:onEndReached') try { await fetchNextPage() } catch (err) { logger.error('Failed to load more posts', {message: err}) } - }, [isFetching, hasNextPage, isError, fetchNextPage, track]) + }, [ + isFetching, + hasNextPage, + isError, + fetchNextPage, + track, + feedType, + feedItems.length, + ]) const onPressTryAgain = React.useCallback(() => { refetch() diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 7d29703e..f3911da6 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -144,7 +144,6 @@ let FeedItemInner = ({ const outerStyles = [ styles.outer, - pal.view, { borderColor: pal.colors.border, paddingBottom: @@ -310,6 +309,7 @@ let FeedItemInner = ({ showAppealLabelItem={ post.author.did === currentAccount?.did && isModeratedPost } + logContext="FeedItem" /> diff --git a/src/view/com/posts/FeedSlice.tsx b/src/view/com/posts/FeedSlice.tsx index 84edee4a..49e48aa2 100644 --- a/src/view/com/posts/FeedSlice.tsx +++ b/src/view/com/posts/FeedSlice.tsx @@ -78,11 +78,7 @@ function ViewFullThread({slice}: {slice: FeedPostSlice}) { }, [slice.rootUri]) return ( - + labelStyle?: StyleProp + logContext: 'ProfileCard' }) { - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + logContext, + ) const {_} = useLingui() const onPressFollow = async () => { diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 266adc51..019e6c10 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -230,7 +230,9 @@ export function ProfileCardWithFollowBtn({ renderButton={ isMe ? undefined - : profileShadow => + : profileShadow => ( + + ) } /> ) diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 75e06eb9..17dc5ce1 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -103,7 +103,10 @@ let ProfileHeader = ({ const invalidHandle = isInvalidHandle(profile.handle) const {isDesktop} = useWebMediaQueries() const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'ProfileHeader', + ) const [__, queueUnblock] = useProfileBlockMutationQueue(profile) const unblockPromptControl = Prompt.usePromptControl() const moderation = useMemo( diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index 6edc61fc..8f2c8949 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -170,7 +170,10 @@ function SuggestedFollow({ const pal = usePalette('default') const moderationOpts = useModerationOpts() const profile = useProfileShadow(profileUnshadowed) - const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'ProfileHeaderSuggestedFollows', + ) const onPressFollow = React.useCallback(async () => { try { diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 4153b819..0baa4f39 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -52,9 +52,17 @@ let ProfileMenu = ({ const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) - const [, queueUnfollow] = useProfileFollowMutationQueue(profile) + const [, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'ProfileMenu', + ) const blockPromptControl = Prompt.usePromptControl() + const loggedOutWarningPromptControl = Prompt.usePromptControl() + + const showLoggedOutWarning = React.useMemo(() => { + return !!profile.labels?.find(label => label.val === '!no-unauthenticated') + }, [profile.labels]) const invalidateProfileQuery = React.useCallback(() => { queryClient.invalidateQueries({ @@ -189,7 +197,13 @@ let ProfileMenu = ({ + onPress={() => { + if (showLoggedOutWarning) { + loggedOutWarningPromptControl.open() + } else { + onPressShare() + } + }}> Share @@ -307,6 +321,16 @@ let ProfileMenu = ({ } confirmButtonColor={profile.viewer?.blocking ? undefined : 'negative'} /> + + ) } diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index f4562248..7468111b 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -8,7 +8,6 @@ import { View, ViewStyle, Pressable, - TouchableWithoutFeedback, TouchableOpacity, } from 'react-native' import {useLinkProps, StackActions} from '@react-navigation/native' @@ -23,7 +22,6 @@ import { import {isAndroid, isWeb} from 'platform/detection' import {sanitizeUrl} from '@braintree/sanitize-url' import {PressableWithHover} from './PressableWithHover' -import FixedTouchableHighlight from '../pager/FixedTouchableHighlight' import {useModalControls} from '#/state/modals' import {useOpenLink} from '#/state/preferences/in-app-browser' import {WebAuxClickWrapper} from 'view/com/util/WebAuxClickWrapper' @@ -31,6 +29,7 @@ import { DebouncedNavigationProp, useNavigationDeduped, } from 'lib/hooks/useNavigationDeduped' +import {useTheme} from '#/alf' type Event = | React.MouseEvent @@ -63,6 +62,7 @@ export const Link = memo(function Link({ navigationAction, ...props }: Props) { + const t = useTheme() const {closeModal} = useModalControls() const navigation = useNavigationDeduped() const anchorHref = asAnchor ? sanitizeUrl(href) : undefined @@ -85,37 +85,23 @@ export const Link = memo(function Link({ ) if (noFeedback) { - if (isAndroid) { - // workaround for Android not working well with left/right swipe gestures and TouchableWithoutFeedback - // https://github.com/callstack/react-native-pager-view/issues/424 - return ( - - - {children ? children : {title || 'link'}} - - - ) - } return ( - + {...props} + android_ripple={{ + color: t.atoms.bg_contrast_25.backgroundColor, + }} + unstable_pressDelay={isAndroid ? 90 : undefined}> {/* @ts-ignore web only -prf */} {children ? children : {title || 'link'}} - + ) } diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 84a047c4..8fc3d9ea 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -85,11 +85,13 @@ let PostDropdownBtn = ({ const {mutedWordsDialogControl} = useGlobalDialogsControlContext() const deletePromptControl = useDialogControl() const hidePromptControl = useDialogControl() + const loggedOutWarningPromptControl = useDialogControl() const rootUri = record.reply?.root?.uri || postUri const isThreadMuted = mutedThreads.includes(rootUri) const isPostHidden = hiddenPosts && hiddenPosts.includes(postUri) const isAuthor = postAuthor.did === currentAccount?.did + const href = React.useMemo(() => { const urip = new AtUri(postUri) return makeProfileLink(postAuthor, 'post', urip.rkey) @@ -167,6 +169,17 @@ let PostDropdownBtn = ({ hidePost({uri: postUri}) }, [postUri, hidePost]) + const shouldShowLoggedOutWarning = React.useMemo(() => { + return !!postAuthor.labels?.find( + label => label.val === '!no-unauthenticated', + ) + }, [postAuthor]) + + const onSharePost = React.useCallback(() => { + const url = toShareUrl(href) + shareUrl(url) + }, [href]) + return ( @@ -217,8 +230,11 @@ let PostDropdownBtn = ({ testID="postDropdownShareBtn" label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} onPress={() => { - const url = toShareUrl(href) - shareUrl(url) + if (shouldShowLoggedOutWarning) { + loggedOutWarningPromptControl.open() + } else { + onSharePost() + } }}> {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} @@ -342,6 +358,16 @@ let PostDropdownBtn = ({ onConfirm={onHidePost} confirmButtonCta={_(msg`Hide`)} /> + + ) } diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 1e26eecc..c96954a1 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -44,6 +44,7 @@ let PostCtrls = ({ showAppealLabelItem, style, onPressReply, + logContext, }: { big?: boolean post: Shadow @@ -52,13 +53,17 @@ let PostCtrls = ({ showAppealLabelItem?: boolean style?: StyleProp onPressReply: () => void + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' }): React.ReactNode => { const theme = useTheme() const {_} = useLingui() const {openComposer} = useComposerControls() const {closeModal} = useModalControls() - const [queueLike, queueUnlike] = usePostLikeMutationQueue(post) - const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(post) + const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext) + const [queueRepost, queueUnrepost] = usePostRepostMutationQueue( + post, + logContext, + ) const requireAuth = useRequireAuth() const defaultCtrlColor = React.useMemo(