Merge remote-tracking branch 'origin/main' into samuel/alf-login
commit
f71ec52517
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.72.0",
|
||||
"version": "1.73.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<E extends keyof Events>(
|
||||
export function logEvent<E extends keyof LogEvents>(
|
||||
eventName: E & string,
|
||||
rawMetadata?: Events[E] & FlatJSONRecord,
|
||||
rawMetadata: LogEvents[E] & FlatJSONRecord,
|
||||
) {
|
||||
const fullMetadata = {
|
||||
...rawMetadata,
|
||||
|
|
|
@ -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<AppBskyFeedDefs.PostView>,
|
||||
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<void, Error, {postUri: string; likeUri: string}>({
|
||||
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<AppBskyFeedDefs.PostView>,
|
||||
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<void, Error, {postUri: string; repostUri: string}>({
|
||||
mutationFn: ({repostUri}) => getAgent().deleteRepost(repostUri),
|
||||
mutationFn: ({repostUri}) => {
|
||||
logEvent('post:unrepost', {logContext})
|
||||
return getAgent().deleteRepost(repostUri)
|
||||
},
|
||||
onSuccess() {
|
||||
track('Post:Unrepost')
|
||||
},
|
||||
|
|
|
@ -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<AppBskyActorDefs.ProfileViewDetailed>,
|
||||
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<void, Error, {did: string; followUri: string}>({
|
||||
mutationFn: async ({followUri}) => {
|
||||
logEvent('profile:unfollow', {logContext})
|
||||
track('Profile:Unfollow', {username: followUri})
|
||||
return await getAgent().deleteFollow(followUri)
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -182,7 +182,6 @@ let FeedItem = ({
|
|||
testID={`feedItem-by-${item.notification.author.handle}`}
|
||||
style={[
|
||||
styles.outer,
|
||||
pal.view,
|
||||
pal.border,
|
||||
item.notification.isRead
|
||||
? undefined
|
||||
|
|
|
@ -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<typeof TouchableHighlight>) {
|
||||
const _touchActivatePositionRef = useRef<Position | null>(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 (
|
||||
<TouchableHighlight onPressIn={_onPressIn} onPress={_onPress} {...props}>
|
||||
{props.children}
|
||||
</TouchableHighlight>
|
||||
)
|
||||
}
|
|
@ -42,7 +42,10 @@ function PostThreadFollowBtnLoaded({
|
|||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> =
|
||||
useProfileShadow(profileUnshadowed)
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||
profile,
|
||||
'PostThreadItem',
|
||||
)
|
||||
const requireAuth = useRequireAuth()
|
||||
|
||||
const isFollowing = !!profile.viewer?.following
|
||||
|
|
|
@ -407,6 +407,7 @@ let PostThreadItemLoaded = ({
|
|||
record={record}
|
||||
richText={richText}
|
||||
onPressReply={onPressReply}
|
||||
logContext="PostThreadItem"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -431,7 +432,6 @@ let PostThreadItemLoaded = ({
|
|||
<PostHider
|
||||
testID={`postThreadItem-by-${post.author.handle}`}
|
||||
href={postHref}
|
||||
style={[pal.view]}
|
||||
moderation={moderation.content}
|
||||
iconSize={isThreadedChild ? 26 : 38}
|
||||
iconStyles={
|
||||
|
@ -560,6 +560,7 @@ let PostThreadItemLoaded = ({
|
|||
record={record}
|
||||
richText={richText}
|
||||
onPressReply={onPressReply}
|
||||
logContext="PostThreadItem"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -620,7 +621,6 @@ function PostOuterWrapper({
|
|||
return (
|
||||
<View
|
||||
style={[
|
||||
pal.view,
|
||||
pal.border,
|
||||
styles.cursor,
|
||||
{
|
||||
|
@ -648,7 +648,6 @@ function PostOuterWrapper({
|
|||
<View
|
||||
style={[
|
||||
styles.outer,
|
||||
pal.view,
|
||||
pal.border,
|
||||
showParentReplyLine && hasPrecedingItem && styles.noTopBorder,
|
||||
styles.cursor,
|
||||
|
|
|
@ -133,7 +133,7 @@ function PostInner({
|
|||
}, [setLimitLines])
|
||||
|
||||
return (
|
||||
<Link href={itemHref} style={[styles.outer, pal.view, pal.border, style]}>
|
||||
<Link href={itemHref} style={[styles.outer, pal.border, style]}>
|
||||
{showReplyLine && <View style={styles.replyLine} />}
|
||||
<View style={styles.layout}>
|
||||
<View style={styles.layoutAvi}>
|
||||
|
@ -220,6 +220,7 @@ function PostInner({
|
|||
record={record}
|
||||
richText={richText}
|
||||
onPressReply={onPressReply}
|
||||
logContext="Post"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -78,11 +78,7 @@ function ViewFullThread({slice}: {slice: FeedPostSlice}) {
|
|||
}, [slice.rootUri])
|
||||
|
||||
return (
|
||||
<Link
|
||||
style={[pal.view, styles.viewFullThread]}
|
||||
href={itemHref}
|
||||
asAnchor
|
||||
noFeedback>
|
||||
<Link style={[styles.viewFullThread]} href={itemHref} asAnchor noFeedback>
|
||||
<View style={styles.viewFullThreadDots}>
|
||||
<Svg width="4" height="40">
|
||||
<Line
|
||||
|
|
|
@ -13,13 +13,18 @@ export function FollowButton({
|
|||
followedType = 'default',
|
||||
profile,
|
||||
labelStyle,
|
||||
logContext,
|
||||
}: {
|
||||
unfollowedType?: ButtonType
|
||||
followedType?: ButtonType
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
||||
labelStyle?: StyleProp<TextStyle>
|
||||
logContext: 'ProfileCard'
|
||||
}) {
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||
profile,
|
||||
logContext,
|
||||
)
|
||||
const {_} = useLingui()
|
||||
|
||||
const onPressFollow = async () => {
|
||||
|
|
|
@ -230,7 +230,9 @@ export function ProfileCardWithFollowBtn({
|
|||
renderButton={
|
||||
isMe
|
||||
? undefined
|
||||
: profileShadow => <FollowButton profile={profileShadow} />
|
||||
: profileShadow => (
|
||||
<FollowButton profile={profileShadow} logContext="ProfileCard" />
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = ({
|
|||
<Menu.Item
|
||||
testID="profileHeaderDropdownShareBtn"
|
||||
label={_(msg`Share`)}
|
||||
onPress={onPressShare}>
|
||||
onPress={() => {
|
||||
if (showLoggedOutWarning) {
|
||||
loggedOutWarningPromptControl.open()
|
||||
} else {
|
||||
onPressShare()
|
||||
}
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Share</Trans>
|
||||
</Menu.ItemText>
|
||||
|
@ -307,6 +321,16 @@ let ProfileMenu = ({
|
|||
}
|
||||
confirmButtonColor={profile.viewer?.blocking ? undefined : 'negative'}
|
||||
/>
|
||||
|
||||
<Prompt.Basic
|
||||
control={loggedOutWarningPromptControl}
|
||||
title={_(msg`Note about sharing`)}
|
||||
description={_(
|
||||
msg`This profile is only visible to logged-in users. It won't be visible to people who aren't logged in.`,
|
||||
)}
|
||||
onConfirm={onPressShare}
|
||||
confirmButtonCta={_(msg`Share anyway`)}
|
||||
/>
|
||||
</EventStopper>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<HTMLAnchorElement, 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 (
|
||||
<FixedTouchableHighlight
|
||||
testID={testID}
|
||||
onPress={onPress}
|
||||
// @ts-ignore web only -prf
|
||||
href={asAnchor ? sanitizeUrl(href) : undefined}
|
||||
accessible={accessible}
|
||||
accessibilityRole="link"
|
||||
{...props}>
|
||||
<View style={style}>
|
||||
{children ? children : <Text>{title || 'link'}</Text>}
|
||||
</View>
|
||||
</FixedTouchableHighlight>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<WebAuxClickWrapper>
|
||||
<TouchableWithoutFeedback
|
||||
<Pressable
|
||||
testID={testID}
|
||||
onPress={onPress}
|
||||
accessible={accessible}
|
||||
accessibilityRole="link"
|
||||
{...props}>
|
||||
{...props}
|
||||
android_ripple={{
|
||||
color: t.atoms.bg_contrast_25.backgroundColor,
|
||||
}}
|
||||
unstable_pressDelay={isAndroid ? 90 : undefined}>
|
||||
{/* @ts-ignore web only -prf */}
|
||||
<View style={style} href={anchorHref}>
|
||||
{children ? children : <Text>{title || 'link'}</Text>}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</Pressable>
|
||||
</WebAuxClickWrapper>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<EventStopper onKeyDown={false}>
|
||||
<Menu.Root>
|
||||
|
@ -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()
|
||||
}
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
{isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
|
||||
|
@ -342,6 +358,16 @@ let PostDropdownBtn = ({
|
|||
onConfirm={onHidePost}
|
||||
confirmButtonCta={_(msg`Hide`)}
|
||||
/>
|
||||
|
||||
<Prompt.Basic
|
||||
control={loggedOutWarningPromptControl}
|
||||
title={_(msg`Note about sharing`)}
|
||||
description={_(
|
||||
msg`This post is only visible to logged-in users. It won't be visible to people who aren't logged in.`,
|
||||
)}
|
||||
onConfirm={onSharePost}
|
||||
confirmButtonCta={_(msg`Share anyway`)}
|
||||
/>
|
||||
</EventStopper>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ let PostCtrls = ({
|
|||
showAppealLabelItem,
|
||||
style,
|
||||
onPressReply,
|
||||
logContext,
|
||||
}: {
|
||||
big?: boolean
|
||||
post: Shadow<AppBskyFeedDefs.PostView>
|
||||
|
@ -52,13 +53,17 @@ let PostCtrls = ({
|
|||
showAppealLabelItem?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
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(
|
||||
|
|
Loading…
Reference in New Issue