25a1ed1209
* WIP * Clean up growthbook code, integrate into init and sessions * Move everything out of React * Add metrics client * Move to separate file * Shared metadata cache * Ensure we update metadata when session ID changes * Ensure userMetadata is cleared when logging out * WIP revamp * Integrate feature gates into analytics context * Clean up old code * Fix useMeta util * Some comments and cleanup * Add logger to base analytics context * Refactor current route handling * Rip out LogEvent from navigation * Update tracking endpoint * Migrate toClout * Clear out statsig client * Add todo, reset logger readme * Ope fix statsig noop * Refactor logging in feed-feedback, add debug logging to metrics client * Remove LogEvents alias for Metrics * Prefer root package export * Remove Metrics alias from logger * [APP-1782] Migrate to new analytics APIs (#9735) * Migrate logEvent to useAnalytics * Migrate logger.metric to useAnalytics * Migrate tricky spot, fix types * Migrate remaining tricky spot * Missed one * Remove metric() from logger * Migrate useGate to useAnalytics * Remove all other StatSig mentions * Update event payload * Update logger tests * Mock expo method * Fix session ID bug * Add session ID test * Add test for metrics client * Clarify intent * Clean up core analytics file * Clean up the call once utils * Fix TODO * Fix TODO * Fix TODO * Fix TODO * Fix TODO * Remove debug code * Fix navigation context * OK nav context is not working, todo * Checkpoint: works but feels hacky * Fix navigation context issue * Improve feature API * Improve metric logging * Update logger tests
153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
import {memo} from 'react'
|
|
import {type Insets} from 'react-native'
|
|
import {type AppBskyFeedDefs} from '@atproto/api'
|
|
import {msg, Trans} from '@lingui/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import type React from 'react'
|
|
|
|
import {useCleanError} from '#/lib/hooks/useCleanError'
|
|
import {type Shadow} from '#/state/cache/post-shadow'
|
|
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
|
import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
|
|
import {useRequireAuth} from '#/state/session'
|
|
import {useTheme} from '#/alf'
|
|
import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
|
|
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
|
import * as toast from '#/components/Toast'
|
|
import {useAnalytics} from '#/analytics'
|
|
import {PostControlButton, PostControlButtonIcon} from './PostControlButton'
|
|
|
|
export const BookmarkButton = memo(function BookmarkButton({
|
|
post,
|
|
big,
|
|
logContext,
|
|
hitSlop,
|
|
}: {
|
|
post: Shadow<AppBskyFeedDefs.PostView>
|
|
big?: boolean
|
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
|
|
hitSlop?: Insets
|
|
}): React.ReactNode {
|
|
const t = useTheme()
|
|
const ax = useAnalytics()
|
|
const {_} = useLingui()
|
|
const {mutateAsync: bookmark} = useBookmarkMutation()
|
|
const cleanError = useCleanError()
|
|
const requireAuth = useRequireAuth()
|
|
const {feedDescriptor} = useFeedFeedbackContext()
|
|
|
|
const {viewer} = post
|
|
const isBookmarked = !!viewer?.bookmarked
|
|
|
|
const undoLabel = _(
|
|
msg({
|
|
message: `Undo`,
|
|
context: `Button label to undo saving/removing a post from saved posts.`,
|
|
}),
|
|
)
|
|
|
|
const save = async ({disableUndo}: {disableUndo?: boolean} = {}) => {
|
|
try {
|
|
await bookmark({
|
|
action: 'create',
|
|
post,
|
|
})
|
|
|
|
ax.metric('post:bookmark', {
|
|
uri: post.uri,
|
|
authorDid: post.author.did,
|
|
logContext,
|
|
feedDescriptor,
|
|
})
|
|
|
|
toast.show(
|
|
<toast.Outer>
|
|
<toast.Icon />
|
|
<toast.Text>
|
|
<Trans>Post saved</Trans>
|
|
</toast.Text>
|
|
{!disableUndo && (
|
|
<toast.Action
|
|
label={undoLabel}
|
|
onPress={() => remove({disableUndo: true})}>
|
|
{undoLabel}
|
|
</toast.Action>
|
|
)}
|
|
</toast.Outer>,
|
|
{
|
|
type: 'success',
|
|
},
|
|
)
|
|
} catch (e: any) {
|
|
const {raw, clean} = cleanError(e)
|
|
toast.show(clean || raw || e, {
|
|
type: 'error',
|
|
})
|
|
}
|
|
}
|
|
|
|
const remove = async ({disableUndo}: {disableUndo?: boolean} = {}) => {
|
|
try {
|
|
await bookmark({
|
|
action: 'delete',
|
|
uri: post.uri,
|
|
})
|
|
|
|
ax.metric('post:unbookmark', {
|
|
uri: post.uri,
|
|
authorDid: post.author.did,
|
|
logContext,
|
|
feedDescriptor,
|
|
})
|
|
|
|
toast.show(
|
|
<toast.Outer>
|
|
<toast.Icon icon={TrashIcon} />
|
|
<toast.Text>
|
|
<Trans>Removed from saved posts</Trans>
|
|
</toast.Text>
|
|
{!disableUndo && (
|
|
<toast.Action
|
|
label={undoLabel}
|
|
onPress={() => save({disableUndo: true})}>
|
|
{undoLabel}
|
|
</toast.Action>
|
|
)}
|
|
</toast.Outer>,
|
|
)
|
|
} catch (e: any) {
|
|
const {raw, clean} = cleanError(e)
|
|
toast.show(clean || raw || e, {
|
|
type: 'error',
|
|
})
|
|
}
|
|
}
|
|
|
|
const onHandlePress = () =>
|
|
requireAuth(async () => {
|
|
if (isBookmarked) {
|
|
await remove()
|
|
} else {
|
|
await save()
|
|
}
|
|
})
|
|
|
|
return (
|
|
<PostControlButton
|
|
testID="postBookmarkBtn"
|
|
big={big}
|
|
label={
|
|
isBookmarked
|
|
? _(msg`Remove from saved posts`)
|
|
: _(msg`Add to saved posts`)
|
|
}
|
|
onPress={onHandlePress}
|
|
hitSlop={hitSlop}>
|
|
<PostControlButtonIcon
|
|
fill={isBookmarked ? t.palette.primary_500 : undefined}
|
|
icon={isBookmarked ? BookmarkFilled : Bookmark}
|
|
/>
|
|
</PostControlButton>
|
|
)
|
|
})
|