[APP-1782] Analytics migration (#9734)
* 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
This commit is contained in:
@@ -20,7 +20,6 @@ import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {logger} from '#/logger'
|
||||
import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
|
||||
import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
@@ -37,18 +36,20 @@ import {ListFooter} from '#/components/Lists'
|
||||
import * as Skele from '#/components/Skeleton'
|
||||
import * as toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_IOS} from '#/env'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'>
|
||||
|
||||
export function BookmarksScreen({}: Props) {
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const ax = useAnalytics()
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setMinimalShellMode(false)
|
||||
logger.metric('bookmarks:view', {})
|
||||
}, [setMinimalShellMode]),
|
||||
ax.metric('bookmarks:view', {})
|
||||
}, [setMinimalShellMode, ax]),
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -144,7 +145,7 @@ function BookmarksInner() {
|
||||
key: bookmark.item.uri,
|
||||
bookmark: {
|
||||
...bookmark,
|
||||
item: bookmark.item as $Typed<AppBskyFeedDefs.NotFoundPost>,
|
||||
item: bookmark.item,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -154,7 +155,7 @@ function BookmarksInner() {
|
||||
key: bookmark.item.uri,
|
||||
bookmark: {
|
||||
...bookmark,
|
||||
item: bookmark.item as $Typed<AppBskyFeedDefs.PostView>,
|
||||
item: bookmark.item,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -270,6 +271,25 @@ function BookmarkNotFound({
|
||||
)
|
||||
}
|
||||
|
||||
function BookmarkItem({
|
||||
item,
|
||||
hideTopBorder,
|
||||
}: {
|
||||
item: Extract<ListItem, {type: 'bookmark'}>
|
||||
hideTopBorder: boolean
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
return (
|
||||
<Post
|
||||
post={item.bookmark.item}
|
||||
hideTopBorder={hideTopBorder}
|
||||
onBeforePress={() => {
|
||||
ax.metric('bookmarks:post-clicked', {})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function BookmarksEmpty() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
@@ -301,15 +321,7 @@ function renderItem({item, index}: {item: ListItem; index: number}) {
|
||||
return <BookmarksEmpty />
|
||||
}
|
||||
case 'bookmark': {
|
||||
return (
|
||||
<Post
|
||||
post={item.bookmark.item}
|
||||
hideTopBorder={index === 0}
|
||||
onBeforePress={() => {
|
||||
logger.metric('bookmarks:post-clicked', {})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return <BookmarkItem item={item} hideTopBorder={index === 0} />
|
||||
}
|
||||
case 'bookmarkNotFound': {
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,6 @@ import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
@@ -12,6 +11,7 @@ import {atoms as a, web} from '#/alf'
|
||||
import {AccountList} from '#/components/AccountList'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {FormContainer} from './FormContainer'
|
||||
|
||||
export const ChooseAccountForm = ({
|
||||
@@ -23,6 +23,7 @@ export const ChooseAccountForm = ({
|
||||
}) => {
|
||||
const [pendingDid, setPendingDid] = React.useState<string | null>(null)
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const {resumeSession} = useSessionApi()
|
||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||
@@ -46,7 +47,7 @@ export const ChooseAccountForm = ({
|
||||
try {
|
||||
setPendingDid(account.did)
|
||||
await resumeSession(account, true)
|
||||
logEvent('account:loggedIn', {
|
||||
ax.metric('account:loggedIn', {
|
||||
logContext: 'ChooseAccountForm',
|
||||
withPassword: false,
|
||||
})
|
||||
|
||||
@@ -3,9 +3,7 @@ import {ActivityIndicator, View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
||||
import {logger} from '#/logger'
|
||||
import {Agent} from '#/state/session/agent'
|
||||
@@ -16,6 +14,7 @@ import * as TextField from '#/components/forms/TextField'
|
||||
import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
|
||||
import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {FormContainer} from './FormContainer'
|
||||
|
||||
export const SetNewPasswordForm = ({
|
||||
@@ -33,6 +32,7 @@ export const SetNewPasswordForm = ({
|
||||
}) => {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
|
||||
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
||||
const [resetCode, setResetCode] = useState<string>('')
|
||||
@@ -49,7 +49,7 @@ export const SetNewPasswordForm = ({
|
||||
msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
|
||||
),
|
||||
)
|
||||
logEvent('signin:passwordResetFailure', {})
|
||||
ax.metric('signin:passwordResetFailure', {})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,11 +69,11 @@ export const SetNewPasswordForm = ({
|
||||
password,
|
||||
})
|
||||
onPasswordSet()
|
||||
logEvent('signin:passwordResetSuccess', {})
|
||||
ax.metric('signin:passwordResetSuccess', {})
|
||||
} catch (e: any) {
|
||||
const errMsg = e.toString()
|
||||
logger.warn('Failed to set new password', {error: e})
|
||||
logEvent('signin:passwordResetFailure', {})
|
||||
ax.metric('signin:passwordResetFailure', {})
|
||||
setIsProcessing(false)
|
||||
if (isNetworkError(e)) {
|
||||
setError(
|
||||
|
||||
@@ -5,7 +5,6 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {DEFAULT_SERVICE} from '#/lib/constants'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {useServiceQuery} from '#/state/queries/service'
|
||||
import {type SessionAccount, useSession} from '#/state/session'
|
||||
@@ -17,6 +16,7 @@ import {PasswordUpdatedForm} from '#/screens/Login/PasswordUpdatedForm'
|
||||
import {SetNewPasswordForm} from '#/screens/Login/SetNewPasswordForm'
|
||||
import {atoms as a, native} from '#/alf'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {ChooseAccountForm} from './ChooseAccountForm'
|
||||
|
||||
enum Forms {
|
||||
@@ -64,6 +64,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
'Forward' | 'Backward'
|
||||
>('Forward')
|
||||
|
||||
const ax = useAnalytics()
|
||||
const {
|
||||
data: serviceDescription,
|
||||
error: serviceError,
|
||||
@@ -96,7 +97,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
logger.warn(`Failed to fetch service description for ${serviceUrl}`, {
|
||||
error: String(serviceError),
|
||||
})
|
||||
logEvent('signin:hostingProviderFailedResolution', {})
|
||||
ax.metric('signin:hostingProviderFailedResolution', {})
|
||||
} else {
|
||||
setError('')
|
||||
}
|
||||
@@ -104,19 +105,19 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
|
||||
const onPressForgotPassword = () => {
|
||||
gotoForm(Forms.ForgotPassword)
|
||||
logEvent('signin:forgotPasswordPressed', {})
|
||||
ax.metric('signin:forgotPasswordPressed', {})
|
||||
}
|
||||
|
||||
const handlePressBack = () => {
|
||||
onPressBack()
|
||||
setScreenTransitionDirection('Backward')
|
||||
logEvent('signin:backPressed', {
|
||||
ax.metric('signin:backPressed', {
|
||||
failedAttemptsCount: failedAttemptCountRef.current,
|
||||
})
|
||||
}
|
||||
|
||||
const onAttemptSuccess = () => {
|
||||
logEvent('signin:success', {
|
||||
ax.metric('signin:success', {
|
||||
isUsingCustomProvider: serviceUrl !== DEFAULT_SERVICE,
|
||||
timeTakenSeconds: Math.round((Date.now() - startTimeRef.current) / 1000),
|
||||
failedAttemptsCount: failedAttemptCountRef.current,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {useAppState} from '#/lib/hooks/useAppState'
|
||||
import {useAppState} from '#/lib/appState'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||
import {type MessagesTabNavigatorParams} from '#/lib/routes/types'
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type UseInfiniteQueryResult,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAppState} from '#/lib/hooks/useAppState'
|
||||
import {useAppState} from '#/lib/appState'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
|
||||
@@ -13,7 +13,6 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
import {GestureActionView} from '#/lib/custom-animations/GestureActionView'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {decrementBadgeCount} from '#/lib/notifications/notifications'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {
|
||||
postUriToRelativePath,
|
||||
@@ -45,6 +44,7 @@ import {createPortalGroup} from '#/components/Portal'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useSimpleVerificationState} from '#/components/verification'
|
||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
@@ -96,6 +96,7 @@ function ChatListItemReady({
|
||||
showMenu?: boolean
|
||||
children?: React.ReactNode
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
@@ -290,10 +291,10 @@ function ChatListItemReady({
|
||||
menuControl.open()
|
||||
return false
|
||||
} else {
|
||||
logEvent('chat:open', {logContext: 'ChatsList'})
|
||||
ax.metric('chat:open', {logContext: 'ChatsList'})
|
||||
}
|
||||
},
|
||||
[isDeletedAccount, menuControl, queryClient, profile, convo],
|
||||
[ax, isDeletedAccount, menuControl, queryClient, profile, convo],
|
||||
)
|
||||
|
||||
const onLongPress = useCallback(() => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {urls} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
usePreferencesQuery,
|
||||
type UsePreferencesQueryResponse,
|
||||
@@ -17,9 +16,11 @@ import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/i
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export function Screen() {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const gutters = useGutters(['base'])
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
|
||||
@@ -51,13 +52,9 @@ export function Screen() {
|
||||
}),
|
||||
)}
|
||||
onPress={() => {
|
||||
logger.metric(
|
||||
'verification:learn-more',
|
||||
{
|
||||
location: 'verificationSettings',
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('verification:learn-more', {
|
||||
location: 'verificationSettings',
|
||||
})
|
||||
}}>
|
||||
Learn more here.
|
||||
</InlineLinkText>
|
||||
|
||||
@@ -2,10 +2,11 @@ import {useCallback, useState} from 'react'
|
||||
import {LayoutAnimationConfig} from 'react-native-reanimated'
|
||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {useCallOnce} from '#/lib/once'
|
||||
import {FindContactsFlow} from '#/components/contacts/FindContactsFlow'
|
||||
import {type Action, type State} from '#/components/contacts/state'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {useOnboardingInternalState} from '../state'
|
||||
|
||||
export function StepFindContacts({
|
||||
@@ -16,6 +17,11 @@ export function StepFindContacts({
|
||||
flowDispatch: React.ActionDispatch<[Action]>
|
||||
}) {
|
||||
const {dispatch} = useOnboardingInternalState()
|
||||
const ax = useAnalytics()
|
||||
|
||||
useCallOnce(() => {
|
||||
ax.metric('onboarding:contacts:begin', {})
|
||||
})()
|
||||
|
||||
const [transitionDirection, setTransitionDirection] = useState<
|
||||
'Forward' | 'Backward'
|
||||
@@ -24,10 +30,10 @@ export function StepFindContacts({
|
||||
const isFinalStep = flowState.step === '4: view matches'
|
||||
const onSkip = useCallback(() => {
|
||||
if (!isFinalStep) {
|
||||
logger.metric('onboarding:contacts:skipPressed', {})
|
||||
ax.metric('onboarding:contacts:skipPressed', {})
|
||||
}
|
||||
dispatch({type: 'next'})
|
||||
}, [dispatch, isFinalStep])
|
||||
}, [dispatch, isFinalStep, ax])
|
||||
|
||||
const canGoBack = flowState.step === '2: verify number'
|
||||
const onBack = useCallback(() => {
|
||||
|
||||
@@ -5,11 +5,13 @@ import {useLingui} from '@lingui/react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {urls} from '#/lib/constants'
|
||||
import {useCallOnce} from '#/lib/once'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {ContactsHeroImage} from '#/components/contacts/components/HeroImage'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {
|
||||
OnboardingControls,
|
||||
OnboardingDescriptionText,
|
||||
@@ -19,9 +21,14 @@ import {
|
||||
import {useOnboardingInternalState} from '../state'
|
||||
|
||||
export function StepFindContactsIntro() {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {dispatch} = useOnboardingInternalState()
|
||||
|
||||
useCallOnce(() => {
|
||||
ax.metric('onboarding:contacts:presented', {})
|
||||
})()
|
||||
|
||||
const {data: isAvailable, isSuccess} = useQuery({
|
||||
queryKey: ['contacts-available'],
|
||||
queryFn: async () => await Contacts.isAvailableAsync(),
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
VIDEO_SAVED_FEED,
|
||||
} from '#/lib/constants'
|
||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||
import {getAllListMembers} from '#/state/queries/list-members'
|
||||
@@ -46,12 +45,14 @@ import {atoms as a, useBreakpoints} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {ValuePropositionPager} from './ValuePropositionPager'
|
||||
|
||||
export function StepFinished() {
|
||||
const {state, dispatch} = useOnboardingInternalState()
|
||||
const ax = useAnalytics()
|
||||
const onboardDispatch = useOnboardingDispatch()
|
||||
const [saving, setSaving] = useState(false)
|
||||
const queryClient = useQueryClient()
|
||||
@@ -165,7 +166,7 @@ export function StepFinished() {
|
||||
return next
|
||||
})
|
||||
|
||||
logEvent('onboarding:finished:avatarResult', {
|
||||
ax.metric('onboarding:finished:avatarResult', {
|
||||
avatarResult: profileStepResults.isCreatedAvatar
|
||||
? 'created'
|
||||
: profileStepResults.image
|
||||
@@ -200,7 +201,7 @@ export function StepFinished() {
|
||||
startProgressGuide('follow-10')
|
||||
dispatch({type: 'finish'})
|
||||
onboardDispatch({type: 'finish'})
|
||||
logEvent('onboarding:finished:nextPressed', {
|
||||
ax.metric('onboarding:finished:nextPressed', {
|
||||
usedStarterPack: Boolean(starterPack),
|
||||
starterPackName:
|
||||
starterPack &&
|
||||
@@ -216,13 +217,14 @@ export function StepFinished() {
|
||||
feedsPinned: starterPack?.feeds?.length ?? 0,
|
||||
})
|
||||
if (starterPack && listItems?.length) {
|
||||
logEvent('starterPack:followAll', {
|
||||
ax.metric('starterPack:followAll', {
|
||||
logContext: 'Onboarding',
|
||||
starterPack: starterPack.uri,
|
||||
count: listItems?.length,
|
||||
})
|
||||
}
|
||||
}, [
|
||||
ax,
|
||||
queryClient,
|
||||
agent,
|
||||
dispatch,
|
||||
@@ -255,6 +257,7 @@ function ValueProposition({
|
||||
}) {
|
||||
const [subStep, setSubStep] = useState<0 | 1 | 2>(0)
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const onPress = () => {
|
||||
@@ -262,10 +265,10 @@ function ValueProposition({
|
||||
finishOnboarding() // has its own metrics
|
||||
} else if (subStep === 1) {
|
||||
setSubStep(2)
|
||||
logger.metric('onboarding:valueProp:stepTwo:nextPressed', {})
|
||||
ax.metric('onboarding:valueProp:stepTwo:nextPressed', {})
|
||||
} else if (subStep === 0) {
|
||||
setSubStep(1)
|
||||
logger.metric('onboarding:valueProp:stepOne:nextPressed', {})
|
||||
ax.metric('onboarding:valueProp:stepOne:nextPressed', {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +283,7 @@ function ValueProposition({
|
||||
size="small"
|
||||
label={_(msg`Skip introduction and start using your account`)}
|
||||
onPress={() => {
|
||||
logger.metric('onboarding:valueProp:skipPressed', {})
|
||||
ax.metric('onboarding:valueProp:skipPressed', {})
|
||||
finishOnboarding()
|
||||
}}
|
||||
style={[a.bg_transparent]}>
|
||||
|
||||
@@ -4,7 +4,6 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {interests, useInterestsDisplayNames} from '#/lib/interests'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {capitalize} from '#/lib/strings/capitalize'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
@@ -19,9 +18,11 @@ import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export function StepInterests() {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const interestsDisplayNames = useInterestsDisplayNames()
|
||||
|
||||
const {state, dispatch} = useOnboardingInternalState()
|
||||
@@ -40,7 +41,7 @@ export function StepInterests() {
|
||||
selectedInterests,
|
||||
})
|
||||
dispatch({type: 'next'})
|
||||
logEvent('onboarding:interests:nextPressed', {
|
||||
ax.metric('onboarding:interests:nextPressed', {
|
||||
selectedInterests,
|
||||
selectedInterestsLength: selectedInterests.length,
|
||||
})
|
||||
@@ -48,7 +49,7 @@ export function StepInterests() {
|
||||
logger.info(`onboading: error saving interests`)
|
||||
logger.error(e)
|
||||
}
|
||||
}, [selectedInterests, setSaving, dispatch])
|
||||
}, [ax, selectedInterests, setSaving, dispatch])
|
||||
|
||||
return (
|
||||
<View style={[a.align_start, a.gap_sm]} testID="onboardingInterests">
|
||||
|
||||
@@ -14,7 +14,6 @@ import {compressIfNeeded} from '#/lib/media/manip'
|
||||
import {openCropper} from '#/lib/media/picker'
|
||||
import {getDataUriSize} from '#/lib/media/util'
|
||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||
import {logEvent, useGate} from '#/lib/statsig/statsig'
|
||||
import {isCancelledError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
@@ -37,6 +36,7 @@ import * as Dialog from '#/components/Dialog'
|
||||
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded} from '#/components/icons/CircleInfo'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE, IS_WEB} from '#/env'
|
||||
import {type AvatarColor, avatarColors, type Emoji, emojiItems} from './types'
|
||||
|
||||
@@ -66,11 +66,11 @@ const randomColor =
|
||||
avatarColors[Math.floor(Math.random() * avatarColors.length)]
|
||||
|
||||
export function StepProfile() {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||
const gate = useGate()
|
||||
const requestNotificationsPermission = useRequestNotificationsPermission()
|
||||
|
||||
const creatorControl = Dialog.useDialogControl()
|
||||
@@ -89,7 +89,7 @@ export function StepProfile() {
|
||||
|
||||
React.useEffect(() => {
|
||||
requestNotificationsPermission('StartOnboarding')
|
||||
}, [gate, requestNotificationsPermission])
|
||||
}, [requestNotificationsPermission])
|
||||
|
||||
const sheetWrapper = useSheetWrapper()
|
||||
const openPicker = React.useCallback(
|
||||
@@ -156,8 +156,8 @@ export function StepProfile() {
|
||||
}
|
||||
|
||||
dispatch({type: 'next'})
|
||||
logEvent('onboarding:profile:nextPressed', {})
|
||||
}, [avatar, dispatch])
|
||||
ax.metric('onboarding:profile:nextPressed', {})
|
||||
}, [ax, avatar, dispatch])
|
||||
|
||||
const onDoneCreating = React.useCallback(() => {
|
||||
setAvatar(prev => ({
|
||||
|
||||
@@ -9,7 +9,6 @@ import * as bcp47Match from 'bcp-47-match'
|
||||
import {wait} from '#/lib/async/wait'
|
||||
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
|
||||
import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
|
||||
import {logger} from '#/logger'
|
||||
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
@@ -30,12 +29,14 @@ import {boostInterests, InterestTabs} from '#/components/InterestTabs'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import * as toast from '#/components/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {bulkWriteFollows} from '../util'
|
||||
|
||||
export function StepSuggestedAccounts() {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -92,7 +93,7 @@ export function StepSuggestedAccounts() {
|
||||
|
||||
const {mutate: followAll, isPending: isFollowingAll} = useMutation({
|
||||
onMutate: () => {
|
||||
logger.metric('onboarding:suggestedAccounts:followAllPressed', {
|
||||
ax.metric('onboarding:suggestedAccounts:followAllPressed', {
|
||||
tab: selectedInterest ?? 'all',
|
||||
numAccounts: followableDids.length,
|
||||
})
|
||||
@@ -132,20 +133,16 @@ export function StepSuggestedAccounts() {
|
||||
(did: string, position: number) => {
|
||||
if (!seenProfilesRef.current.has(did)) {
|
||||
seenProfilesRef.current.add(did)
|
||||
logger.metric(
|
||||
'suggestedUser:seen',
|
||||
{
|
||||
logContext: 'Onboarding',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: did,
|
||||
category: selectedInterest,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('suggestedUser:seen', {
|
||||
logContext: 'Onboarding',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: did,
|
||||
category: selectedInterest,
|
||||
})
|
||||
}
|
||||
},
|
||||
[selectedInterest],
|
||||
[ax, selectedInterest],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -297,6 +294,7 @@ function TabBar({
|
||||
defaultTabLabel?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const interestsDisplayNames = useInterestsDisplayNames()
|
||||
const interests = Object.keys(interestsDisplayNames)
|
||||
.sort(boostInterests(popularInterests))
|
||||
@@ -309,11 +307,7 @@ function TabBar({
|
||||
selectedInterest || (hideDefaultTab ? interests[0] : 'all')
|
||||
}
|
||||
onSelectTab={tab => {
|
||||
logger.metric(
|
||||
'onboarding:suggestedAccounts:tabPressed',
|
||||
{tab: tab},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('onboarding:suggestedAccounts:tabPressed', {tab: tab})
|
||||
onSelectInterest(tab === 'all' ? null : tab)
|
||||
}}
|
||||
interestsDisplayNames={
|
||||
@@ -343,6 +337,7 @@ function SuggestedProfileCard({
|
||||
onSeen: (did: string, position: number) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const cardRef = useRef<View>(null)
|
||||
const hasTrackedRef = useRef(false)
|
||||
|
||||
@@ -403,18 +398,14 @@ function SuggestedProfileCard({
|
||||
withIcon={false}
|
||||
logContext="OnboardingSuggestedAccounts"
|
||||
onFollow={() => {
|
||||
logger.metric(
|
||||
'suggestedUser:follow',
|
||||
{
|
||||
logContext: 'Onboarding',
|
||||
location: 'Card',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('suggestedUser:follow', {
|
||||
logContext: 'Onboarding',
|
||||
location: 'Card',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Che
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp'
|
||||
@@ -30,6 +31,7 @@ export function StarterPackCard({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
const agent = useAgent()
|
||||
@@ -89,7 +91,7 @@ export function StarterPackCard({
|
||||
}
|
||||
})
|
||||
Toast.show(_(msg`All accounts have been followed!`), {type: 'success'})
|
||||
logger.metric('starterPack:followAll', {
|
||||
ax.metric('starterPack:followAll', {
|
||||
logContext: 'Onboarding',
|
||||
starterPack: view.uri,
|
||||
count: dids.length,
|
||||
|
||||
@@ -3,7 +3,6 @@ import {View} from 'react-native'
|
||||
import * as bcp47Match from 'bcp-47-match'
|
||||
|
||||
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {
|
||||
Layout,
|
||||
@@ -23,6 +22,7 @@ import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from '#/components/co
|
||||
import {useFindContactsFlowState} from '#/components/contacts/state'
|
||||
import {Portal} from '#/components/Portal'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {ENV, IS_NATIVE} from '#/env'
|
||||
import {StepFindContacts} from './StepFindContacts'
|
||||
import {StepFindContactsIntro} from './StepFindContactsIntro'
|
||||
@@ -31,7 +31,7 @@ import {StepSuggestedStarterpacks} from './StepSuggestedStarterpacks'
|
||||
|
||||
export function Onboarding() {
|
||||
const t = useTheme()
|
||||
const gate = useGate()
|
||||
const ax = useAnalytics()
|
||||
|
||||
const {contentLanguages} = useLanguagePrefs()
|
||||
const probablySpeaksEnglish = useMemo(() => {
|
||||
@@ -48,7 +48,7 @@ export function Onboarding() {
|
||||
ENV !== 'e2e' &&
|
||||
IS_NATIVE &&
|
||||
findContactsEnabled &&
|
||||
!gate('disable_onboarding_find_contacts')
|
||||
!ax.features.enabled(ax.features.DisableOnboardingFindContacts)
|
||||
|
||||
const [state, dispatch] = useReducer(
|
||||
reducer,
|
||||
|
||||
@@ -172,15 +172,6 @@ export function reducer(
|
||||
}
|
||||
}
|
||||
|
||||
if (a.type === 'next') {
|
||||
if (next.activeStep === 'find-contacts-intro') {
|
||||
logger.metric('onboarding:contacts:presented', {})
|
||||
}
|
||||
if (next.activeStep === 'find-contacts') {
|
||||
logger.metric('onboarding:contacts:begin', {})
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
...next,
|
||||
hasPrev: next.activeStep !== 'profile',
|
||||
|
||||
@@ -2,7 +2,7 @@ import {useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {PrivacySensitive} from 'expo-privacy-sensitive'
|
||||
|
||||
import {useAppState} from '#/lib/hooks/useAppState'
|
||||
import {useAppState} from '#/lib/appState'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {sizes as iconSizes} from '#/components/icons/common'
|
||||
import {Mark as Logo} from '#/components/icons/Logo'
|
||||
|
||||
@@ -2,11 +2,11 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {type ThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export function HeaderDropdown({
|
||||
sort,
|
||||
@@ -17,6 +17,7 @@ export function HeaderDropdown({
|
||||
ThreadPreferences,
|
||||
'sort' | 'setSort' | 'view' | 'setView'
|
||||
>): React.ReactNode {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
return (
|
||||
<Menu.Root>
|
||||
@@ -30,7 +31,7 @@ export function HeaderDropdown({
|
||||
shape="round"
|
||||
hitSlop={HITSLOP_10}
|
||||
onPress={() => {
|
||||
logger.metric('thread:click:headerMenuOpen', {})
|
||||
ax.metric('thread:click:headerMenuOpen', {})
|
||||
onPress()
|
||||
}}
|
||||
{...props}>
|
||||
|
||||
@@ -18,7 +18,6 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {niceDate} from '#/lib/strings/time'
|
||||
import {getTranslatorLink, isPostInLanguage} from '#/locale/helpers'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
@@ -60,6 +59,7 @@ import * as Skele from '#/components/Skeleton'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
|
||||
import {WhoCanReply} from '#/components/WhoCanReply'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export function ThreadItemAnchor({
|
||||
@@ -177,6 +177,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
postSource?: PostSource
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const {currentAccount, hasSession} = useSession()
|
||||
@@ -281,7 +282,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
])
|
||||
|
||||
const onOpenAuthor = () => {
|
||||
logger.metric('post:clickthroughAuthor', {
|
||||
ax.metric('post:clickthroughAuthor', {
|
||||
uri: post.uri,
|
||||
authorDid: post.author.did,
|
||||
logContext: 'PostThreadItem',
|
||||
@@ -298,7 +299,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
}
|
||||
|
||||
const onOpenEmbed = () => {
|
||||
logger.metric('post:clickthroughEmbed', {
|
||||
ax.metric('post:clickthroughEmbed', {
|
||||
uri: post.uri,
|
||||
authorDid: post.author.did,
|
||||
logContext: 'PostThreadItem',
|
||||
@@ -540,6 +541,7 @@ function ExpandedPostDetails({
|
||||
isThreadAuthor: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {_, i18n} = useLingui()
|
||||
const translate = useTranslate()
|
||||
const isRootPost = !('reply' in post.record)
|
||||
@@ -565,7 +567,7 @@ function ExpandedPostDetails({
|
||||
AppBskyFeedPost.isRecord,
|
||||
)
|
||||
) {
|
||||
logger.metric('translate', {
|
||||
ax.metric('translate', {
|
||||
sourceLanguages: post.record.langs ?? [],
|
||||
targetLanguage: langPrefs.primaryLanguage,
|
||||
textLength: post.record.text.length,
|
||||
@@ -574,7 +576,7 @@ function ExpandedPostDetails({
|
||||
|
||||
return false
|
||||
},
|
||||
[translate, langPrefs, post],
|
||||
[ax, translate, langPrefs, post],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,22 +2,23 @@ import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export function ThreadItemShowOtherReplies({onPress}: {onPress: () => void}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const label = _(msg`Show more replies`)
|
||||
|
||||
return (
|
||||
<Button
|
||||
onPress={() => {
|
||||
onPress()
|
||||
logger.metric('thread:click:showOtherReplies', {})
|
||||
ax.metric('thread:click:showOtherReplies', {})
|
||||
}}
|
||||
label={label}>
|
||||
{({hovered, pressed}) => (
|
||||
|
||||
@@ -6,7 +6,6 @@ import {Trans} from '@lingui/macro'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
||||
import {logger} from '#/logger'
|
||||
import {useFeedFeedback} from '#/state/feed-feedback'
|
||||
import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences'
|
||||
import {
|
||||
@@ -44,11 +43,13 @@ import {
|
||||
import {atoms as a, native, platform, useBreakpoints, web} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListFooter} from '#/components/Lists'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
const PARENT_CHUNK_SIZE = 5
|
||||
const CHILDREN_CHUNK_SIZE = 50
|
||||
|
||||
export function PostThread({uri}: {uri: string}) {
|
||||
const ax = useAnalytics()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {hasSession} = useSession()
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
@@ -84,18 +85,14 @@ export function PostThread({uri}: {uri: string}) {
|
||||
const post = anchor.value.post
|
||||
seenPostUriRef.current = post.uri
|
||||
|
||||
logger.metric(
|
||||
'post:view',
|
||||
{
|
||||
uri: post.uri,
|
||||
authorDid: post.author.did,
|
||||
logContext: 'Post',
|
||||
feedDescriptor: feedFeedback.feedDescriptor,
|
||||
},
|
||||
{statsig: false},
|
||||
)
|
||||
ax.metric('post:view', {
|
||||
uri: post.uri,
|
||||
authorDid: post.author.did,
|
||||
logContext: 'Post',
|
||||
feedDescriptor: feedFeedback.feedDescriptor,
|
||||
})
|
||||
}
|
||||
}, [anchor, feedFeedback.feedDescriptor])
|
||||
}, [ax, anchor, feedFeedback.feedDescriptor])
|
||||
|
||||
// Track post:view events for parent posts and replies (non-anchor posts)
|
||||
const trackThreadItemView = usePostViewTracking('PostThreadItem')
|
||||
|
||||
@@ -14,7 +14,6 @@ import {useLingui} from '@lingui/react'
|
||||
import {MAX_LABELERS} from '#/lib/constants'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {isAppLabeler} from '#/lib/moderation'
|
||||
import {logger} from '#/logger'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useLabelerSubscriptionMutation} from '#/state/queries/labeler'
|
||||
@@ -34,6 +33,7 @@ import * as Prompt from '#/components/Prompt'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_IOS} from '#/env'
|
||||
import {ProfileHeaderDisplayName} from './DisplayName'
|
||||
import {EditProfileDialog} from './EditProfileDialog'
|
||||
@@ -61,6 +61,7 @@ let ProfileHeaderLabeler = ({
|
||||
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
|
||||
useProfileShadow(profileUnshadowed)
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {currentAccount, hasSession} = useSession()
|
||||
const playHaptic = useHaptics()
|
||||
@@ -99,9 +100,9 @@ let ProfileHeaderLabeler = ({
|
||||
),
|
||||
{type: 'error'},
|
||||
)
|
||||
logger.error(`Failed to toggle labeler like`, {message: e.message})
|
||||
ax.logger.error(`Failed to toggle labeler like`, {message: e.message})
|
||||
}
|
||||
}, [labeler, playHaptic, likeUri, unlikeMod, likeMod, _])
|
||||
}, [ax, labeler, playHaptic, likeUri, unlikeMod, likeMod, _])
|
||||
|
||||
return (
|
||||
<ProfileHeaderShell
|
||||
@@ -233,8 +234,9 @@ export function HeaderLabelerButtons({
|
||||
/** disable the subscribe button */
|
||||
minimal?: boolean
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const requireAuth = useRequireAuth()
|
||||
const playHaptic = useHaptics()
|
||||
@@ -264,12 +266,11 @@ export function HeaderLabelerButtons({
|
||||
subscribe,
|
||||
})
|
||||
|
||||
logger.metric(
|
||||
ax.metric(
|
||||
subscribe
|
||||
? 'moderation:subscribedToLabeler'
|
||||
: 'moderation:unsubscribedFromLabeler',
|
||||
{},
|
||||
{statsig: true},
|
||||
)
|
||||
} catch (e: any) {
|
||||
reset()
|
||||
@@ -277,7 +278,7 @@ export function HeaderLabelerButtons({
|
||||
cantSubscribePrompt.open()
|
||||
return
|
||||
}
|
||||
logger.error(`Failed to subscribe to labeler`, {message: e.message})
|
||||
ax.logger.error(`Failed to subscribe to labeler`, {message: e.message})
|
||||
}
|
||||
})
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,6 @@ import {useActorStatus} from '#/lib/actor-status'
|
||||
import {BACK_HITSLOP} from '#/lib/constants'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {logger} from '#/logger'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useLightboxControls} from '#/state/lightbox'
|
||||
import {useSession} from '#/state/session'
|
||||
@@ -34,6 +33,7 @@ import {LiveIndicator} from '#/components/live/LiveIndicator'
|
||||
import {LiveStatusDialog} from '#/components/live/LiveStatusDialog'
|
||||
import {LabelsOnMe} from '#/components/moderation/LabelsOnMe'
|
||||
import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_IOS} from '#/env'
|
||||
import {GrowableAvatar} from './GrowableAvatar'
|
||||
import {GrowableBanner} from './GrowableBanner'
|
||||
@@ -54,6 +54,7 @@ let ProfileHeaderShell = ({
|
||||
isPlaceholderProfile,
|
||||
}: React.PropsWithChildren<Props>): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const {_} = useLingui()
|
||||
const {openLightbox} = useLightboxControls()
|
||||
@@ -116,22 +117,14 @@ let ProfileHeaderShell = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (live.isActive) {
|
||||
logger.metric(
|
||||
'live:view:profile',
|
||||
{subject: profile.did},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('live:view:profile', {subject: profile.did})
|
||||
}
|
||||
}, [live.isActive, profile.did])
|
||||
}, [ax, live.isActive, profile.did])
|
||||
|
||||
const onPressAvi = useCallback(() => {
|
||||
if (live.isActive) {
|
||||
playHaptic('Light')
|
||||
logger.metric(
|
||||
'live:card:open',
|
||||
{subject: profile.did, from: 'profile'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('live:card:open', {subject: profile.did, from: 'profile'})
|
||||
liveStatusControl.open()
|
||||
} else {
|
||||
const modui = moderation.ui('avatar')
|
||||
@@ -145,6 +138,7 @@ let ProfileHeaderShell = ({
|
||||
}
|
||||
}
|
||||
}, [
|
||||
ax,
|
||||
profile,
|
||||
moderation,
|
||||
_openLightbox,
|
||||
|
||||
@@ -5,8 +5,7 @@ import {msg, Plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {makeCustomFeedLink} from '#/lib/routes/links'
|
||||
import {makeCustomFeedLink, makeProfileLink} from '#/lib/routes/links'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {toShareUrl} from '#/lib/strings/url-helpers'
|
||||
@@ -51,6 +50,7 @@ import {
|
||||
} from '#/components/moderation/ReportDialog'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
|
||||
export function ProfileFeedHeaderSkeleton() {
|
||||
@@ -86,6 +86,7 @@ export function ProfileFeedHeaderSkeleton() {
|
||||
export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
|
||||
const t = useTheme()
|
||||
const {_, i18n} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {hasSession} = useSession()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const infoControl = Dialog.useDialogControl()
|
||||
@@ -120,7 +121,7 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
|
||||
if (savedFeedConfig) {
|
||||
await removeFeed(savedFeedConfig)
|
||||
Toast.show(_(msg`Removed from your feeds`))
|
||||
logger.metric('feed:unsave', {feedUrl: info.uri})
|
||||
ax.metric('feed:unsave', {feedUrl: info.uri})
|
||||
} else {
|
||||
await addSavedFeeds([
|
||||
{
|
||||
@@ -130,7 +131,7 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
|
||||
},
|
||||
])
|
||||
Toast.show(_(msg`Saved to your feeds`))
|
||||
logger.metric('feed:save', {feedUrl: info.uri})
|
||||
ax.metric('feed:save', {feedUrl: info.uri})
|
||||
}
|
||||
} catch (err) {
|
||||
Toast.show(
|
||||
@@ -158,10 +159,10 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
|
||||
|
||||
if (pinned) {
|
||||
Toast.show(_(msg`Pinned ${info.displayName} to Home`))
|
||||
logger.metric('feed:pin', {feedUrl: info.uri})
|
||||
ax.metric('feed:pin', {feedUrl: info.uri})
|
||||
} else {
|
||||
Toast.show(_(msg`Unpinned ${info.displayName} from Home`))
|
||||
logger.metric('feed:unpin', {feedUrl: info.uri})
|
||||
ax.metric('feed:unpin', {feedUrl: info.uri})
|
||||
}
|
||||
} else {
|
||||
await addSavedFeeds([
|
||||
@@ -172,7 +173,7 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
|
||||
},
|
||||
])
|
||||
Toast.show(_(msg`Pinned ${info.displayName} to Home`))
|
||||
logger.metric('feed:pin', {feedUrl: info.uri})
|
||||
ax.metric('feed:pin', {feedUrl: info.uri})
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||
@@ -388,6 +389,7 @@ function DialogInner({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {hasSession} = useSession()
|
||||
const playHaptic = useHaptics()
|
||||
const control = Dialog.useDialogContext()
|
||||
@@ -407,11 +409,11 @@ function DialogInner({
|
||||
if (isLiked && likeUri) {
|
||||
await unlikeFeed({uri: likeUri})
|
||||
setLikeUri('')
|
||||
logger.metric('feed:unlike', {feedUrl: info.uri})
|
||||
ax.metric('feed:unlike', {feedUrl: info.uri})
|
||||
} else {
|
||||
const res = await likeFeed({uri: info.uri, cid: info.cid})
|
||||
setLikeUri(res.uri)
|
||||
logger.metric('feed:like', {feedUrl: info.uri})
|
||||
ax.metric('feed:like', {feedUrl: info.uri})
|
||||
}
|
||||
} catch (err) {
|
||||
Toast.show(
|
||||
@@ -428,7 +430,7 @@ function DialogInner({
|
||||
playHaptic()
|
||||
const url = toShareUrl(info.route.href)
|
||||
shareUrl(url)
|
||||
logger.metric('feed:share', {feedUrl: info.uri})
|
||||
ax.metric('feed:share', {feedUrl: info.uri})
|
||||
}, [info, playHaptic])
|
||||
|
||||
const onPressReport = React.useCallback(() => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {MoreOptionsMenu} from './MoreOptionsMenu'
|
||||
import {SubscribeMenu} from './SubscribeMenu'
|
||||
|
||||
@@ -34,6 +35,7 @@ export function Header({
|
||||
preferences: UsePreferencesQueryResponse
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST
|
||||
const isModList = list.purpose === AppBskyGraphDefs.MODLIST
|
||||
@@ -96,11 +98,7 @@ export function Header({
|
||||
try {
|
||||
await muteList({uri: list.uri, mute: false})
|
||||
Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
|
||||
logger.metric(
|
||||
'moderation:unsubscribedFromList',
|
||||
{listType: 'mute'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('moderation:unsubscribedFromList', {listType: 'mute'})
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
@@ -114,11 +112,7 @@ export function Header({
|
||||
try {
|
||||
await blockList({uri: list.uri, block: false})
|
||||
Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
|
||||
logger.metric(
|
||||
'moderation:unsubscribedFromList',
|
||||
{listType: 'block'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('moderation:unsubscribedFromList', {listType: 'block'})
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from '#/components/moderation/ReportDialog'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
|
||||
export function MoreOptionsMenu({
|
||||
@@ -43,6 +44,7 @@ export function MoreOptionsMenu({
|
||||
savedFeedConfig?: AppBskyActorDefs.SavedFeed
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const editListDialogControl = useDialogControl()
|
||||
const deleteListPromptControl = useDialogControl()
|
||||
@@ -111,11 +113,7 @@ export function MoreOptionsMenu({
|
||||
try {
|
||||
await muteList({uri: list.uri, mute: false})
|
||||
Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
|
||||
logger.metric(
|
||||
'moderation:unsubscribedFromList',
|
||||
{listType: 'mute'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('moderation:unsubscribedFromList', {listType: 'mute'})
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
@@ -129,11 +127,7 @@ export function MoreOptionsMenu({
|
||||
try {
|
||||
await blockList({uri: list.uri, block: false})
|
||||
Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
|
||||
logger.metric(
|
||||
'moderation:unsubscribedFromList',
|
||||
{listType: 'block'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('moderation:unsubscribedFromList', {listType: 'block'})
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
|
||||
@@ -2,7 +2,6 @@ import {type AppBskyGraphDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
@@ -12,9 +11,11 @@ import {Loader} from '#/components/Loader'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const subscribeMutePromptControl = Prompt.usePromptControl()
|
||||
const subscribeBlockPromptControl = Prompt.usePromptControl()
|
||||
|
||||
@@ -29,11 +30,7 @@ export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) {
|
||||
try {
|
||||
await muteList({uri: list.uri, mute: true})
|
||||
Toast.show(_(msg({message: 'List muted', context: 'toast'})))
|
||||
logger.metric(
|
||||
'moderation:subscribedToList',
|
||||
{listType: 'mute'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('moderation:subscribedToList', {listType: 'mute'})
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
@@ -48,11 +45,7 @@ export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) {
|
||||
try {
|
||||
await blockList({uri: list.uri, block: true})
|
||||
Toast.show(_(msg({message: 'List blocked', context: 'toast'})))
|
||||
logger.metric(
|
||||
'moderation:subscribedToList',
|
||||
{listType: 'block'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('moderation:subscribedToList', {listType: 'block'})
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
|
||||
@@ -13,8 +13,6 @@ import * as bcp47Match from 'bcp-47-match'
|
||||
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {logger} from '#/logger'
|
||||
import {type MetricEvents} from '#/logger/metrics'
|
||||
import {useLanguagePrefs} from '#/state/preferences/languages'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {RQKEY_ROOT as useActorSearchQueryKeyRoot} from '#/state/queries/actor-search'
|
||||
@@ -68,6 +66,7 @@ import {Loader} from '#/components/Loader'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {type Metrics, useAnalytics} from '#/analytics'
|
||||
import {ExploreScreenLiveEventFeedsBanner} from '#/features/liveEvents/components/ExploreScreenLiveEventFeedsBanner'
|
||||
import * as ModuleHeader from './components/ModuleHeader'
|
||||
import {
|
||||
@@ -124,7 +123,7 @@ type ExploreScreenItems =
|
||||
bottomBorder?: boolean
|
||||
searchButton?: {
|
||||
label: string
|
||||
metricsTag: MetricEvents['explore:module:searchButtonPress']['module']
|
||||
metricsTag: Metrics['explore:module:searchButtonPress']['module']
|
||||
tab: 'user' | 'profile' | 'feed'
|
||||
}
|
||||
}
|
||||
@@ -135,7 +134,7 @@ type ExploreScreenItems =
|
||||
icon: React.ComponentType<SVGIconProps>
|
||||
searchButton?: {
|
||||
label: string
|
||||
metricsTag: MetricEvents['explore:module:searchButtonPress']['module']
|
||||
metricsTag: Metrics['explore:module:searchButtonPress']['module']
|
||||
tab: 'user' | 'profile' | 'feed'
|
||||
}
|
||||
hideDefaultTab?: boolean
|
||||
@@ -213,6 +212,7 @@ export function Explore({
|
||||
focusSearchInput: (tab: 'user' | 'profile' | 'feed') => void
|
||||
headerHeight: number
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {data: preferences, error: preferencesError} = usePreferencesQuery()
|
||||
@@ -273,9 +273,10 @@ export function Explore({
|
||||
try {
|
||||
await fetchNextFeedsPage()
|
||||
} catch (err) {
|
||||
logger.error('Failed to load more suggested follows', {message: err})
|
||||
ax.logger.error('Failed to load more suggested follows', {message: err})
|
||||
}
|
||||
}, [
|
||||
ax,
|
||||
isFetchingNextFeedsPage,
|
||||
hasNextFeedsPage,
|
||||
feedsError,
|
||||
@@ -333,9 +334,10 @@ export function Explore({
|
||||
try {
|
||||
await fetchNextPageFeedPreviews()
|
||||
} catch (err) {
|
||||
logger.error('Failed to load more feed previews', {message: err})
|
||||
ax.logger.error('Failed to load more feed previews', {message: err})
|
||||
}
|
||||
}, [
|
||||
ax,
|
||||
isPendingFeedPreviews,
|
||||
isFetchingNextPageFeedPreviews,
|
||||
hasNextPageFeedPreviews,
|
||||
@@ -492,11 +494,7 @@ export function Explore({
|
||||
if (hasPressedLoadMoreFeeds && index < 6) {
|
||||
continue
|
||||
}
|
||||
logger.metric(
|
||||
'feed:suggestion:seen',
|
||||
{feedUrl: item.feed.uri},
|
||||
{statsig: false},
|
||||
)
|
||||
ax.metric('feed:suggestion:seen', {feedUrl: item.feed.uri})
|
||||
}
|
||||
}
|
||||
if (!hasPressedLoadMoreFeeds) {
|
||||
@@ -609,6 +607,7 @@ export function Explore({
|
||||
return i
|
||||
}, [
|
||||
_,
|
||||
ax,
|
||||
useFullExperience,
|
||||
suggestedFeeds,
|
||||
preferences,
|
||||
@@ -729,12 +728,7 @@ export function Explore({
|
||||
<ModuleHeader.SearchButton
|
||||
{...item.searchButton}
|
||||
onPress={() =>
|
||||
focusSearchInput(
|
||||
(item.searchButton?.tab || 'user') as
|
||||
| 'user'
|
||||
| 'profile'
|
||||
| 'feed',
|
||||
)
|
||||
focusSearchInput(item.searchButton?.tab || 'user')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -751,12 +745,7 @@ export function Explore({
|
||||
<ModuleHeader.SearchButton
|
||||
{...item.searchButton}
|
||||
onPress={() =>
|
||||
focusSearchInput(
|
||||
(item.searchButton?.tab || 'user') as
|
||||
| 'user'
|
||||
| 'profile'
|
||||
| 'feed',
|
||||
)
|
||||
focusSearchInput(item.searchButton?.tab || 'user')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -822,7 +811,7 @@ export function Explore({
|
||||
if (!useFullExperience) {
|
||||
return
|
||||
}
|
||||
logger.metric('feed:suggestion:press', {
|
||||
ax.metric('feed:suggestion:press', {
|
||||
feedUrl: item.feed.uri,
|
||||
})
|
||||
}}
|
||||
@@ -1011,6 +1000,7 @@ export function Explore({
|
||||
}
|
||||
},
|
||||
[
|
||||
ax,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg_contrast_25,
|
||||
t.atoms.text_contrast_medium,
|
||||
@@ -1043,7 +1033,7 @@ export function Explore({
|
||||
const seenProfilesRef = useRef<Set<string>>(new Set())
|
||||
const onItemSeen = useCallback(
|
||||
(item: ExploreScreenItems) => {
|
||||
let module: MetricEvents['explore:module:seen']['module']
|
||||
let module: Metrics['explore:module:seen']['module']
|
||||
if (item.type === 'trendingTopics' || item.type === 'trendingVideos') {
|
||||
module = item.type
|
||||
} else if (item.type === 'profile') {
|
||||
@@ -1054,17 +1044,13 @@ export function Explore({
|
||||
const position = suggestedFollowsModule.findIndex(
|
||||
i => i.type === 'profile' && i.profile.did === item.profile.did,
|
||||
)
|
||||
logger.metric(
|
||||
'suggestedUser:seen',
|
||||
{
|
||||
logContext: 'Explore',
|
||||
recId: item.recId,
|
||||
position: position !== -1 ? position - 1 : 0, // -1 to account for header
|
||||
suggestedDid: item.profile.did,
|
||||
category: null,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('suggestedUser:seen', {
|
||||
logContext: 'Explore',
|
||||
recId: item.recId,
|
||||
position: position !== -1 ? position - 1 : 0, // -1 to account for header
|
||||
suggestedDid: item.profile.did,
|
||||
category: null,
|
||||
})
|
||||
}
|
||||
} else if (item.type === 'feed') {
|
||||
module = 'suggestedFeeds'
|
||||
@@ -1077,10 +1063,10 @@ export function Explore({
|
||||
}
|
||||
if (!alreadyReportedRef.current.has(module)) {
|
||||
alreadyReportedRef.current.set(module, module)
|
||||
logger.metric('explore:module:seen', {module}, {statsig: false})
|
||||
ax.metric('explore:module:seen', {module})
|
||||
}
|
||||
},
|
||||
[suggestedFollowsModule],
|
||||
[ax, suggestedFollowsModule],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,6 @@ import {type AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||
|
||||
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||
import {makeCustomFeedLink} from '#/lib/routes/links'
|
||||
import {logger} from '#/logger'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
@@ -13,6 +12,7 @@ import {sizes as iconSizes} from '#/components/icons/common'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass'
|
||||
import {Link} from '#/components/Link'
|
||||
import {Text, type TextProps} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export function Container({
|
||||
style,
|
||||
@@ -126,6 +126,7 @@ export function SearchButton({
|
||||
metricsTag: 'suggestedAccounts' | 'suggestedFeeds'
|
||||
onPress?: () => void
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
return (
|
||||
<Button
|
||||
label={label}
|
||||
@@ -135,11 +136,7 @@ export function SearchButton({
|
||||
shape="round"
|
||||
PressableComponent={native(PressableScale)}
|
||||
onPress={() => {
|
||||
logger.metric(
|
||||
'explore:module:searchButtonPress',
|
||||
{module: metricsTag},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('explore:module:searchButtonPress', {module: metricsTag})
|
||||
onPress?.()
|
||||
}}
|
||||
style={[
|
||||
|
||||
@@ -2,7 +2,6 @@ import {View} from 'react-native'
|
||||
import {type AppBskyUnspeccedDefs} from '@atproto/api'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
DEFAULT_LIMIT as RECOMMENDATIONS_COUNT,
|
||||
useTrendingTopics,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
TrendingTopicSkeleton,
|
||||
} from '#/components/TrendingTopics'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
|
||||
// Note: This module is not currently used and may be removed in the future.
|
||||
@@ -27,6 +27,7 @@ export function ExploreRecommendations() {
|
||||
|
||||
function Inner() {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const gutters = useGutters([0, 'compact'])
|
||||
const {data: trending, error, isLoading} = useTrendingTopics()
|
||||
const noRecs = !isLoading && !error && !trending?.suggested?.length
|
||||
@@ -88,11 +89,7 @@ function Inner() {
|
||||
key={topic.link}
|
||||
topic={topic}
|
||||
onPress={() => {
|
||||
logger.metric(
|
||||
'recommendedTopic:click',
|
||||
{context: 'explore'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('recommendedTopic:click', {context: 'explore'})
|
||||
}}>
|
||||
{({hovered}) => (
|
||||
<TrendingTopic
|
||||
|
||||
@@ -9,11 +9,11 @@ import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
|
||||
import {logger} from '#/logger'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
|
||||
import {useTheme} from '#/alf'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {boostInterests, InterestTabs} from '#/components/InterestTabs'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
export function useLoadEnoughProfiles({
|
||||
@@ -62,6 +62,7 @@ export function SuggestedAccountsTabBar({
|
||||
defaultTabLabel?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const interestsDisplayNames = useInterestsDisplayNames()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const personalizedInterests = preferences?.interests?.tags
|
||||
@@ -77,11 +78,7 @@ export function SuggestedAccountsTabBar({
|
||||
selectedInterest || (hideDefaultTab ? interests[0] : 'all')
|
||||
}
|
||||
onSelectTab={tab => {
|
||||
logger.metric(
|
||||
'explore:suggestedAccounts:tabPressed',
|
||||
{tab: tab},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('explore:suggestedAccounts:tabPressed', {tab: tab})
|
||||
onSelectInterest(tab === 'all' ? null : tab)
|
||||
}}
|
||||
interestsDisplayNames={
|
||||
@@ -112,22 +109,19 @@ let SuggestedProfileCard = ({
|
||||
position: number
|
||||
}): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
return (
|
||||
<ProfileCard.Link
|
||||
profile={profile}
|
||||
style={[a.flex_1]}
|
||||
onPress={() => {
|
||||
logger.metric(
|
||||
'suggestedUser:press',
|
||||
{
|
||||
logContext: 'Explore',
|
||||
recId,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category: null,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('suggestedUser:press', {
|
||||
logContext: 'Explore',
|
||||
recId,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category: null,
|
||||
})
|
||||
}}>
|
||||
{s => (
|
||||
<>
|
||||
@@ -157,18 +151,14 @@ let SuggestedProfileCard = ({
|
||||
withIcon={false}
|
||||
logContext="ExploreSuggestedAccounts"
|
||||
onFollow={() => {
|
||||
logger.metric(
|
||||
'suggestedUser:follow',
|
||||
{
|
||||
logContext: 'Explore',
|
||||
location: 'Card',
|
||||
recId,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category: null,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('suggestedUser:follow', {
|
||||
logContext: 'Explore',
|
||||
location: 'Card',
|
||||
recId,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category: null,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
|
||||
@@ -4,7 +4,6 @@ import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api'
|
||||
import {msg, plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useTrendingSettings} from '#/state/preferences/trending'
|
||||
import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery'
|
||||
@@ -19,6 +18,7 @@ import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/ic
|
||||
import {Link} from '#/components/Link'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
const TOPIC_COUNT = 5
|
||||
|
||||
@@ -29,6 +29,7 @@ export function ExploreTrendingTopics() {
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
const ax = useAnalytics()
|
||||
const {data: trending, error, isLoading, isRefetching} = useGetTrendsQuery()
|
||||
const noTopics = !isLoading && !error && !trending?.trends?.length
|
||||
|
||||
@@ -44,11 +45,7 @@ function Inner() {
|
||||
trend={trend}
|
||||
rank={index + 1}
|
||||
onPress={() => {
|
||||
logger.metric(
|
||||
'trendingTopic:click',
|
||||
{context: 'explore'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('trendingTopic:click', {context: 'explore'})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {VIDEO_FEED_URI} from '#/lib/constants'
|
||||
import {makeCustomFeedLink} from '#/lib/routes/links'
|
||||
import {logger} from '#/logger'
|
||||
import {RQKEY, usePostFeedQuery} from '#/state/queries/post-feed'
|
||||
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
|
||||
import {atoms as a, tokens, useGutters, useTheme} from '#/alf'
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
CompactVideoPostCard,
|
||||
CompactVideoPostCardPlaceholder,
|
||||
} from '#/components/VideoPostCard'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
const CARD_WIDTH = 100
|
||||
|
||||
@@ -151,6 +151,7 @@ function VideoCards({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const items = useMemo(() => {
|
||||
return data.pages
|
||||
.flatMap(page => page.slices)
|
||||
@@ -177,11 +178,7 @@ function VideoCards({
|
||||
sourceInterstitial: 'explore',
|
||||
}}
|
||||
onInteract={() => {
|
||||
logger.metric(
|
||||
'videoCard:click',
|
||||
{context: 'interstitial:explore'},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('videoCard:click', {context: 'interstitial:explore'})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {useMemo} from 'react'
|
||||
import {Platform} from 'react-native'
|
||||
import {setStringAsync} from 'expo-clipboard'
|
||||
import * as FileSystem from 'expo-file-system/legacy'
|
||||
@@ -7,7 +6,6 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
import {Statsig} from 'statsig-react-native-expo'
|
||||
|
||||
import {STATUS_PAGE_URL} from '#/lib/constants'
|
||||
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
||||
@@ -21,6 +19,7 @@ import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/i
|
||||
import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {getDeviceId} from '#/analytics/identifiers'
|
||||
import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env'
|
||||
import * as env from '#/env'
|
||||
import {useDemoMode} from '#/storage/hooks/demo-mode'
|
||||
@@ -32,7 +31,6 @@ export function AboutSettingsScreen({}: Props) {
|
||||
const {_, i18n} = useLingui()
|
||||
const [devModeEnabled, setDevModeEnabled] = useDevMode()
|
||||
const [demoModeEnabled, setDemoModeEnabled] = useDemoMode()
|
||||
const stableID = useMemo(() => Statsig.getStableID(), [])
|
||||
|
||||
const {mutate: onClearImageCache, isPending: isClearingImageCache} =
|
||||
useMutation({
|
||||
@@ -146,7 +144,7 @@ export function AboutSettingsScreen({}: Props) {
|
||||
}}
|
||||
onPress={() => {
|
||||
setStringAsync(
|
||||
`Build version: ${env.APP_VERSION}; Bundle info: ${env.APP_METADATA}; Bundle date: ${env.BUNDLE_DATE}; Platform: ${Platform.OS}; Platform version: ${Platform.Version}; Anonymous ID: ${stableID}`,
|
||||
`Build version: ${env.APP_VERSION}; Bundle info: ${env.APP_METADATA}; Bundle date: ${env.BUNDLE_DATE}; Platform: ${Platform.OS}; Platform version: ${Platform.Version}; Device ID: ${getDeviceId() ?? 'N/A'}`,
|
||||
)
|
||||
Toast.show(_(msg`Copied build version to clipboard`))
|
||||
}}>
|
||||
|
||||
@@ -3,7 +3,6 @@ import {useLingui} from '@lingui/react'
|
||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {useAutoplayDisabled, useSetAutoplayDisabled} from '#/state/preferences'
|
||||
import {
|
||||
useInAppBrowser,
|
||||
@@ -25,6 +24,7 @@ import {Play_Stroke2_Corner2_Rounded as PlayIcon} from '#/components/icons/Play'
|
||||
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending'
|
||||
import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import {LiveEventFeedsSettingsToggle} from '#/features/liveEvents/components/LiveEventFeedsSettingsToggle'
|
||||
|
||||
@@ -34,6 +34,7 @@ type Props = NativeStackScreenProps<
|
||||
>
|
||||
export function ContentAndMediaSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const autoplayDisabledPref = useAutoplayDisabled()
|
||||
const setAutoplayDisabledPref = useSetAutoplayDisabled()
|
||||
const inAppBrowserPref = useInAppBrowser()
|
||||
@@ -135,9 +136,9 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
||||
onChange={value => {
|
||||
const hide = Boolean(!value)
|
||||
if (hide) {
|
||||
logEvent('trendingTopics:hide', {context: 'settings'})
|
||||
ax.metric('trendingTopics:hide', {context: 'settings'})
|
||||
} else {
|
||||
logEvent('trendingTopics:show', {context: 'settings'})
|
||||
ax.metric('trendingTopics:show', {context: 'settings'})
|
||||
}
|
||||
setTrendingDisabled(hide)
|
||||
}}>
|
||||
@@ -157,9 +158,9 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
||||
onChange={value => {
|
||||
const hide = Boolean(!value)
|
||||
if (hide) {
|
||||
logEvent('trendingVideos:hide', {context: 'settings'})
|
||||
ax.metric('trendingVideos:hide', {context: 'settings'})
|
||||
} else {
|
||||
logEvent('trendingVideos:show', {context: 'settings'})
|
||||
ax.metric('trendingVideos:show', {context: 'settings'})
|
||||
}
|
||||
setTrendingVideoDisabled(hide)
|
||||
}}>
|
||||
|
||||
@@ -47,6 +47,7 @@ import {Loader} from '#/components/Loader'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {bulkWriteFollows} from '../Onboarding/util'
|
||||
@@ -54,13 +55,14 @@ import {bulkWriteFollows} from '../Onboarding/util'
|
||||
type Props = NativeStackScreenProps<AllNavigatorParams, 'FindContactsSettings'>
|
||||
export function FindContactsSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
|
||||
const {data, error, refetch} = useContactsSyncStatusQuery()
|
||||
|
||||
const isFocused = useIsFocused()
|
||||
useEffect(() => {
|
||||
if (data && isFocused) {
|
||||
logger.metric('contacts:settings:presented', {
|
||||
ax.metric('contacts:settings:presented', {
|
||||
hasPreviouslySynced: !!data.syncStatus,
|
||||
matchCount: data.syncStatus?.matchesCount,
|
||||
})
|
||||
@@ -169,6 +171,7 @@ function SyncStatus({
|
||||
info: AppBskyContactDefs.SyncStatus
|
||||
refetchStatus: () => Promise<any>
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
const {_} = useLingui()
|
||||
@@ -197,7 +200,7 @@ function SyncStatus({
|
||||
await agent.app.bsky.contact.dismissMatch({subject: did})
|
||||
},
|
||||
onMutate: async (did: string) => {
|
||||
logger.metric('contacts:settings:dismiss', {})
|
||||
ax.metric('contacts:settings:dismiss', {})
|
||||
optimisticRemoveMatch(queryClient, did)
|
||||
},
|
||||
onError: err => {
|
||||
@@ -278,6 +281,7 @@ function MatchItem({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const shadow = useProfileShadow(profile)
|
||||
|
||||
return (
|
||||
@@ -314,7 +318,7 @@ function MatchItem({
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
logContext="FindContacts"
|
||||
onFollow={() => logger.metric('contacts:settings:follow', {})}
|
||||
onFollow={() => ax.metric('contacts:settings:follow', {})}
|
||||
/>
|
||||
{!shadow.viewer?.following && (
|
||||
<Button
|
||||
@@ -343,6 +347,7 @@ function StatusHeader({
|
||||
isAnyUnfollowed: boolean
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
const {currentAccount} = useSession()
|
||||
@@ -374,7 +379,7 @@ function StatusHeader({
|
||||
}
|
||||
} while (cursor)
|
||||
|
||||
logger.metric('contacts:settings:followAll', {
|
||||
ax.metric('contacts:settings:followAll', {
|
||||
followCount: didsToFollow.length,
|
||||
})
|
||||
|
||||
@@ -459,6 +464,7 @@ function StatusHeader({
|
||||
function StatusFooter({syncedAt}: {syncedAt: string}) {
|
||||
const {_, i18n} = useLingui()
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -466,7 +472,7 @@ function StatusFooter({syncedAt}: {syncedAt: string}) {
|
||||
mutationFn: async () => {
|
||||
await agent.app.bsky.contact.removeData({})
|
||||
},
|
||||
onMutate: () => logger.metric('contacts:settings:removeData', {}),
|
||||
onMutate: () => ax.metric('contacts:settings:removeData', {}),
|
||||
onSuccess: () => {
|
||||
Toast.show(_(msg`Contacts removed`))
|
||||
queryClient.setQueryData<AppBskyContactGetSyncStatus.OutputSchema>(
|
||||
@@ -520,7 +526,7 @@ function StatusFooter({syncedAt}: {syncedAt: string}) {
|
||||
(Date.now() - new Date(syncedAt).getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
)
|
||||
logger.metric('contacts:settings:resync', {
|
||||
ax.metric('contacts:settings:resync', {
|
||||
daysSinceLastSync,
|
||||
})
|
||||
}}
|
||||
|
||||
@@ -4,12 +4,12 @@ import {type AppBskyNotificationDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a, platform, useTheme} from '#/alf'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {Divider} from '../../components/SettingsList'
|
||||
|
||||
export function PreferenceControls({
|
||||
@@ -61,6 +61,7 @@ export function Inner({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {mutate} = useNotificationSettingsUpdateMutation()
|
||||
|
||||
const channels = useMemo(() => {
|
||||
@@ -77,7 +78,7 @@ export function Inner({
|
||||
push: change.includes('push'),
|
||||
} satisfies typeof preference
|
||||
|
||||
logger.metric('activityPreference:changeChannels', {
|
||||
ax.metric('activityPreference:changeChannels', {
|
||||
name,
|
||||
push: newPreference.push,
|
||||
list: newPreference.list,
|
||||
@@ -98,7 +99,7 @@ export function Inner({
|
||||
include: change,
|
||||
} satisfies typeof preference
|
||||
|
||||
logger.metric('activityPreference:changeFilter', {name, value: change})
|
||||
ax.metric('activityPreference:changeFilter', {name, value: change})
|
||||
|
||||
mutate({
|
||||
[name]: newPreference,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useAppState} from '#/lib/hooks/useAppState'
|
||||
import {useAppState} from '#/lib/appState'
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
type CommonNavigatorParams,
|
||||
type NavigationProp,
|
||||
} from '#/lib/routes/types'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
@@ -69,12 +68,14 @@ import {
|
||||
shouldShowVerificationCheckButton,
|
||||
VerificationCheckButton,
|
||||
} from '#/components/verification/VerificationCheckButton'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_INTERNAL, IS_IOS, IS_NATIVE} from '#/env'
|
||||
import {device, useStorage} from '#/storage'
|
||||
import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
|
||||
export function SettingsScreen({}: Props) {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const reducedMotion = useReducedMotion()
|
||||
const {logoutEveryAccount} = useSessionApi()
|
||||
@@ -92,7 +93,6 @@ export function SettingsScreen({}: Props) {
|
||||
const [showDevOptions, setShowDevOptions] = useState(false)
|
||||
const findContactsEnabled =
|
||||
useIsFindContactsFeatureEnabledBasedOnGeolocation()
|
||||
const gate = useGate()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
@@ -213,7 +213,7 @@ export function SettingsScreen({}: Props) {
|
||||
</SettingsList.LinkItem>
|
||||
{IS_NATIVE &&
|
||||
findContactsEnabled &&
|
||||
!gate('disable_settings_find_contacts') && (
|
||||
!ax.features.enabled(ax.features.DisableSettingsFindContacts) && (
|
||||
<SettingsList.LinkItem
|
||||
to="/settings/find-contacts"
|
||||
label={_(msg`Find friends from contacts`)}>
|
||||
|
||||
@@ -11,8 +11,8 @@ import {useSignupContext} from '#/screens/Signup/state'
|
||||
import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {FormError} from '#/components/forms/FormError'
|
||||
import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
|
||||
import {GCP_PROJECT_ID} from '#/env'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {GCP_PROJECT_ID, IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
|
||||
import {BackNextButtons} from '../BackNextButtons'
|
||||
|
||||
const CAPTCHA_PATH =
|
||||
@@ -71,6 +71,7 @@ function StepCaptchaInner({
|
||||
payload?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const theme = useTheme()
|
||||
const {state, dispatch} = useSignupContext()
|
||||
|
||||
@@ -109,13 +110,13 @@ function StepCaptchaInner({
|
||||
const onSuccess = React.useCallback(
|
||||
(code: string) => {
|
||||
setCompleted(true)
|
||||
logger.metric('signup:captchaSuccess', {}, {statsig: true})
|
||||
ax.metric('signup:captchaSuccess', {})
|
||||
dispatch({
|
||||
type: 'submit',
|
||||
task: {verificationCode: code, mutableProcessed: false},
|
||||
})
|
||||
},
|
||||
[dispatch],
|
||||
[ax, dispatch],
|
||||
)
|
||||
|
||||
const onError = React.useCallback(
|
||||
@@ -124,13 +125,13 @@ function StepCaptchaInner({
|
||||
type: 'setError',
|
||||
value: _(msg`Error receiving captcha response.`),
|
||||
})
|
||||
logger.metric('signup:captchaFailure', {}, {statsig: true})
|
||||
ax.metric('signup:captchaFailure', {})
|
||||
logger.error('Signup Flow Error', {
|
||||
registrationHandle: state.handle,
|
||||
error,
|
||||
})
|
||||
},
|
||||
[_, dispatch, state.handle],
|
||||
[_, ax, dispatch, state.handle],
|
||||
)
|
||||
|
||||
const onBackPress = React.useCallback(() => {
|
||||
|
||||
@@ -26,11 +26,13 @@ import {useThrottledValue} from '#/components/hooks/useThrottledValue'
|
||||
import {At_Stroke2_Corner0_Rounded as AtIcon} from '#/components/icons/At'
|
||||
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {BackNextButtons} from '../BackNextButtons'
|
||||
import {HandleSuggestions} from './HandleSuggestions'
|
||||
|
||||
export function StepHandle() {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const t = useTheme()
|
||||
const {state, dispatch} = useSignupContext()
|
||||
const [draftValue, setDraftValue] = useState(state.handle)
|
||||
@@ -68,16 +70,19 @@ export function StepHandle() {
|
||||
const {available: handleAvailable} = await checkHandleAvailability(
|
||||
createFullHandle(handle, state.userDomain),
|
||||
state.serviceDescription?.did ?? 'UNKNOWN',
|
||||
{typeahead: false},
|
||||
{},
|
||||
)
|
||||
|
||||
if (!handleAvailable) {
|
||||
ax.metric('signup:handleTaken', {typeahead: false})
|
||||
dispatch({
|
||||
type: 'setError',
|
||||
value: _(msg`That username is already taken`),
|
||||
field: 'handle',
|
||||
})
|
||||
return
|
||||
} else {
|
||||
ax.metric('signup:handleAvailable', {typeahead: false})
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to check handle availability on next press', {
|
||||
@@ -88,15 +93,11 @@ export function StepHandle() {
|
||||
dispatch({type: 'setIsLoading', value: false})
|
||||
}
|
||||
|
||||
logger.metric(
|
||||
'signup:nextPressed',
|
||||
{
|
||||
activeStep: state.activeStep,
|
||||
phoneVerificationRequired:
|
||||
state.serviceDescription?.phoneVerificationRequired,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('signup:nextPressed', {
|
||||
activeStep: state.activeStep,
|
||||
phoneVerificationRequired:
|
||||
state.serviceDescription?.phoneVerificationRequired,
|
||||
})
|
||||
// phoneVerificationRequired is actually whether a captcha is required
|
||||
if (!state.serviceDescription?.phoneVerificationRequired) {
|
||||
dispatch({
|
||||
@@ -115,11 +116,7 @@ export function StepHandle() {
|
||||
value: handle,
|
||||
})
|
||||
dispatch({type: 'prev'})
|
||||
logger.metric(
|
||||
'signup:backPressed',
|
||||
{activeStep: state.activeStep},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('signup:backPressed', {activeStep: state.activeStep})
|
||||
}
|
||||
|
||||
const hasDebounceSettled = draftValue === debouncedDraftValue
|
||||
@@ -202,7 +199,7 @@ export function StepHandle() {
|
||||
state.userDomain.length * -1,
|
||||
),
|
||||
)
|
||||
logger.metric('signup:handleSuggestionSelected', {
|
||||
ax.metric('signup:handleSuggestionSelected', {
|
||||
method: suggestion.method,
|
||||
})
|
||||
}}
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
MIN_ACCESS_AGE,
|
||||
useAgeAssuranceRegionConfigWithFallback,
|
||||
} from '#/ageAssurance/util'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import {
|
||||
useDeviceGeolocationApi,
|
||||
@@ -59,6 +60,7 @@ export function StepInfo({
|
||||
isLoadingStarterPack: boolean
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {state, dispatch} = useSignupContext()
|
||||
const preemptivelyCompleteActivePolicyUpdate =
|
||||
usePreemptivelyCompleteActivePolicyUpdate()
|
||||
@@ -165,13 +167,9 @@ export function StepInfo({
|
||||
dispatch({type: 'setEmail', value: email})
|
||||
dispatch({type: 'setPassword', value: password})
|
||||
dispatch({type: 'next'})
|
||||
logger.metric(
|
||||
'signup:nextPressed',
|
||||
{
|
||||
activeStep: state.activeStep,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('signup:nextPressed', {
|
||||
activeStep: state.activeStep,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,17 +29,28 @@ import {LinearGradientBackground} from '#/components/LinearGradientBackground'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {IS_ANDROID} from '#/env'
|
||||
import {GCP_PROJECT_ID} from '#/env'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {GCP_PROJECT_ID, IS_ANDROID} from '#/env'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export function Signup({onPressBack}: {onPressBack: () => void}) {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
...initialState,
|
||||
analytics: ax,
|
||||
})
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const submit = useSubmitSignup()
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'setAnalytics',
|
||||
value: ax,
|
||||
})
|
||||
}, [ax])
|
||||
|
||||
const activeStarterPack = useActiveStarterPack()
|
||||
const {
|
||||
data: starterPack,
|
||||
|
||||
+27
-23
@@ -12,9 +12,9 @@ import {DEFAULT_SERVICE} from '#/lib/constants'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {createFullHandle} from '#/lib/strings/handles'
|
||||
import {getAge} from '#/lib/strings/time'
|
||||
import {logger} from '#/logger'
|
||||
import {useSessionApi} from '#/state/session'
|
||||
import {useOnboardingDispatch} from '#/state/shell'
|
||||
import {type AnalyticsContextType, useAnalytics} from '#/analytics'
|
||||
|
||||
export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||
|
||||
@@ -39,6 +39,8 @@ type ErrorField =
|
||||
| 'date-of-birth'
|
||||
|
||||
export type SignupState = {
|
||||
analytics?: AnalyticsContextType
|
||||
|
||||
hasPrev: boolean
|
||||
activeStep: SignupStep
|
||||
screenTransitionDirection: 'Forward' | 'Backward'
|
||||
@@ -65,6 +67,7 @@ export type SignupState = {
|
||||
}
|
||||
|
||||
export type SignupAction =
|
||||
| {type: 'setAnalytics'; value: AnalyticsContextType}
|
||||
| {type: 'prev'}
|
||||
| {type: 'next'}
|
||||
| {type: 'finish'}
|
||||
@@ -83,6 +86,8 @@ export type SignupAction =
|
||||
| {type: 'incrementBackgroundCount'}
|
||||
|
||||
export const initialState: SignupState = {
|
||||
analytics: undefined,
|
||||
|
||||
hasPrev: false,
|
||||
activeStep: SignupStep.INFO,
|
||||
screenTransitionDirection: 'Forward',
|
||||
@@ -126,6 +131,10 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
|
||||
let next = {...s}
|
||||
|
||||
switch (a.type) {
|
||||
case 'setAnalytics': {
|
||||
next.analytics = a.value
|
||||
break
|
||||
}
|
||||
case 'prev': {
|
||||
if (s.activeStep !== SignupStep.INFO) {
|
||||
next.screenTransitionDirection = 'Backward'
|
||||
@@ -194,16 +203,12 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
|
||||
next.fieldErrors[a.field] = (next.fieldErrors[a.field] || 0) + 1
|
||||
|
||||
// Log the field error
|
||||
logger.metric(
|
||||
'signup:fieldError',
|
||||
{
|
||||
field: a.field,
|
||||
errorCount: next.fieldErrors[a.field],
|
||||
errorMessage: a.value,
|
||||
activeStep: next.activeStep,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
s.analytics?.metric('signup:fieldError', {
|
||||
field: a.field,
|
||||
errorCount: next.fieldErrors[a.field],
|
||||
errorMessage: a.value,
|
||||
activeStep: next.activeStep,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -220,24 +225,22 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
|
||||
next.backgroundCount = s.backgroundCount + 1
|
||||
|
||||
// Log background/foreground event during signup
|
||||
logger.metric(
|
||||
'signup:backgrounded',
|
||||
{
|
||||
activeStep: next.activeStep,
|
||||
backgroundCount: next.backgroundCount,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
s.analytics?.metric('signup:backgrounded', {
|
||||
activeStep: next.activeStep,
|
||||
backgroundCount: next.backgroundCount,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
next.hasPrev = next.activeStep !== SignupStep.INFO
|
||||
|
||||
logger.debug('signup', next)
|
||||
s.analytics?.logger.debug('signup', next)
|
||||
|
||||
if (s.activeStep !== next.activeStep) {
|
||||
logger.debug('signup: step changed', {activeStep: next.activeStep})
|
||||
s.analytics?.logger.debug('signup: step changed', {
|
||||
activeStep: next.activeStep,
|
||||
})
|
||||
}
|
||||
|
||||
return next
|
||||
@@ -252,6 +255,7 @@ SignupContext.displayName = 'SignupContext'
|
||||
export const useSignupContext = () => React.useContext(SignupContext)
|
||||
|
||||
export function useSubmitSignup() {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {createAccount} = useSessionApi()
|
||||
const onboardingDispatch = useOnboardingDispatch()
|
||||
@@ -295,7 +299,7 @@ export function useSubmitSignup() {
|
||||
!state.pendingSubmit?.verificationCode
|
||||
) {
|
||||
dispatch({type: 'setStep', value: SignupStep.CAPTCHA})
|
||||
logger.error('Signup Flow Error', {
|
||||
ax.logger.error('Signup Flow Error', {
|
||||
errorMessage: 'Verification captcha code was not set.',
|
||||
registrationHandle: state.handle,
|
||||
})
|
||||
@@ -358,7 +362,7 @@ export function useSubmitSignup() {
|
||||
})
|
||||
dispatch({type: 'setStep', value: isHandleError ? 2 : 1})
|
||||
|
||||
logger.error('Signup Flow Error', {
|
||||
ax.logger.error('Signup Flow Error', {
|
||||
errorMessage: error,
|
||||
registrationHandle: state.handle,
|
||||
})
|
||||
|
||||
@@ -13,7 +13,6 @@ import {useLingui} from '@lingui/react'
|
||||
|
||||
import {JOINED_THIS_WEEK} from '#/lib/constants'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {createStarterPackGooglePlayUri} from '#/lib/strings/starter-pack'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useStarterPackQuery} from '#/state/queries/starter-packs'
|
||||
@@ -36,6 +35,7 @@ import {Default as ProfileCard} from '#/components/ProfileCard'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB, IS_WEB_MOBILE_ANDROID} from '#/env'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
@@ -119,6 +119,7 @@ function LandingScreenLoaded({
|
||||
}) {
|
||||
const {creator, listItemsSample, feeds} = starterPack
|
||||
const {_, i18n} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const t = useTheme()
|
||||
const activeStarterPack = useActiveStarterPack()
|
||||
const setActiveStarterPack = useSetActiveStarterPack()
|
||||
@@ -146,7 +147,7 @@ function LandingScreenLoaded({
|
||||
} else {
|
||||
onContinue()
|
||||
}
|
||||
logEvent('starterPack:ctaPress', {
|
||||
ax.metric('starterPack:ctaPress', {
|
||||
starterPack: starterPack.uri,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
type CommonNavigatorParams,
|
||||
type NavigationProp,
|
||||
} from '#/lib/routes/types'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
|
||||
import {logger} from '#/logger'
|
||||
@@ -33,8 +32,10 @@ import {getAllListMembers} from '#/state/queries/list-members'
|
||||
import {useResolvedStarterPackShortLink} from '#/state/queries/resolve-short-link'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {useShortenLink} from '#/state/queries/shorten-link'
|
||||
import {useDeleteStarterPackMutation} from '#/state/queries/starter-packs'
|
||||
import {useStarterPackQuery} from '#/state/queries/starter-packs'
|
||||
import {
|
||||
useDeleteStarterPackMutation,
|
||||
useStarterPackQuery,
|
||||
} from '#/state/queries/starter-packs'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
import {
|
||||
@@ -71,6 +72,7 @@ import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList'
|
||||
import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog'
|
||||
import {ShareDialog} from '#/components/StarterPack/ShareDialog'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
@@ -184,6 +186,7 @@ function StarterPackScreenLoaded({
|
||||
const showFeedsTab = Boolean(starterPack.feeds?.length)
|
||||
const showPostsTab = Boolean(starterPack.list)
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
|
||||
const tabs = [
|
||||
...(showPeopleTab ? [_(msg`People`)] : []),
|
||||
@@ -199,10 +202,10 @@ function StarterPackScreenLoaded({
|
||||
const [imageLoaded, setImageLoaded] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
logEvent('starterPack:opened', {
|
||||
ax.metric('starterPack:opened', {
|
||||
starterPack: starterPack.uri,
|
||||
})
|
||||
}, [starterPack.uri])
|
||||
}, [ax, starterPack.uri])
|
||||
|
||||
const onOpenShareDialog = React.useCallback(() => {
|
||||
const rkey = new AtUri(starterPack.uri).rkey
|
||||
@@ -243,7 +246,7 @@ function StarterPackScreenLoaded({
|
||||
? ({headerHeight, scrollElRef}) => (
|
||||
<ProfilesList
|
||||
// Validated above
|
||||
listUri={starterPack!.list!.uri}
|
||||
listUri={starterPack.list!.uri}
|
||||
headerHeight={headerHeight}
|
||||
// @ts-expect-error
|
||||
scrollElRef={scrollElRef}
|
||||
@@ -266,7 +269,7 @@ function StarterPackScreenLoaded({
|
||||
? ({headerHeight, scrollElRef}) => (
|
||||
<PostsList
|
||||
// Validated above
|
||||
listUri={starterPack!.list!.uri}
|
||||
listUri={starterPack.list!.uri}
|
||||
headerHeight={headerHeight}
|
||||
// @ts-expect-error
|
||||
scrollElRef={scrollElRef}
|
||||
@@ -315,6 +318,7 @@ function Header({
|
||||
const {record, creator} = starterPack
|
||||
const isOwn = creator?.did === currentAccount?.did
|
||||
const joinedAllTimeCount = starterPack.joinedAllTimeCount ?? 0
|
||||
const ax = useAnalytics()
|
||||
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
@@ -385,7 +389,7 @@ function Header({
|
||||
})
|
||||
Toast.show(_(msg`All accounts have been followed!`))
|
||||
captureAction(ProgressGuideAction.Follow, dids.length)
|
||||
logEvent('starterPack:followAll', {
|
||||
ax.metric('starterPack:followAll', {
|
||||
logContext: 'StarterPackProfilesList',
|
||||
starterPack: starterPack.uri,
|
||||
count: dids.length,
|
||||
@@ -516,6 +520,7 @@ function OverflowMenu({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {currentAccount} = useSession()
|
||||
const reportDialogControl = useReportDialogControl()
|
||||
@@ -528,7 +533,7 @@ function OverflowMenu({
|
||||
error: deleteError,
|
||||
} = useDeleteStarterPackMutation({
|
||||
onSuccess: () => {
|
||||
logEvent('starterPack:delete', {})
|
||||
ax.metric('starterPack:delete', {})
|
||||
deleteDialogControl.close(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.popToTop()
|
||||
@@ -554,7 +559,7 @@ function OverflowMenu({
|
||||
rkey: routeParams.rkey,
|
||||
listUri: starterPack.list.uri,
|
||||
})
|
||||
logEvent('starterPack:delete', {})
|
||||
ax.metric('starterPack:delete', {})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
type CommonNavigatorParams,
|
||||
type NavigationProp,
|
||||
} from '#/lib/routes/types'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {enforceLen} from '#/lib/strings/helpers'
|
||||
@@ -58,6 +57,7 @@ import {ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {Provider} from './State'
|
||||
@@ -167,6 +167,7 @@ function WizardInner({
|
||||
onSuccess?: () => void
|
||||
}) {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const [state, dispatch] = useWizardState()
|
||||
@@ -222,7 +223,7 @@ function WizardInner({
|
||||
|
||||
const onSuccessCreate = (data: {uri: string; cid: string}) => {
|
||||
const rkey = new AtUri(data.uri).rkey
|
||||
logEvent('starterPack:create', {
|
||||
ax.metric('starterPack:create', {
|
||||
setName: state.name != null,
|
||||
setDescription: state.description != null,
|
||||
profilesCount: state.profiles.length,
|
||||
@@ -236,7 +237,7 @@ function WizardInner({
|
||||
onSuccess?.()
|
||||
} else {
|
||||
navigation.replace('StarterPack', {
|
||||
name: profile!.handle,
|
||||
name: profile.handle,
|
||||
rkey,
|
||||
new: true,
|
||||
})
|
||||
|
||||
@@ -21,8 +21,7 @@ import {
|
||||
useSafeAreaFrame,
|
||||
useSafeAreaInsets,
|
||||
} from 'react-native-safe-area-context'
|
||||
import {useEvent} from 'expo'
|
||||
import {useEventListener} from 'expo'
|
||||
import {useEvent, useEventListener} from 'expo'
|
||||
import {Image, type ImageStyle} from 'expo-image'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
import {createVideoPlayer, type VideoPlayer, VideoView} from 'expo-video'
|
||||
@@ -66,13 +65,12 @@ import {
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {
|
||||
FeedFeedbackProvider,
|
||||
useFeedFeedback,
|
||||
useFeedFeedbackContext,
|
||||
} from '#/state/feed-feedback'
|
||||
import {useFeedFeedback} from '#/state/feed-feedback'
|
||||
import {useFeedInfo} from '#/state/queries/feed'
|
||||
import {usePostLikeMutationQueue} from '#/state/queries/post'
|
||||
import {
|
||||
type AuthorFilter,
|
||||
type FeedPostSliceItem,
|
||||
usePostFeedQuery,
|
||||
} from '#/state/queries/post-feed'
|
||||
@@ -100,6 +98,7 @@ import * as Hider from '#/components/moderation/Hider'
|
||||
import {PostControls} from '#/components/PostControls'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_ANDROID} from '#/env'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber'
|
||||
@@ -192,11 +191,9 @@ function Feed() {
|
||||
const feedDesc = useMemo(() => {
|
||||
switch (params.type) {
|
||||
case 'feedgen':
|
||||
return `feedgen|${params.uri as string}` as const
|
||||
return `feedgen|${params.uri}` as const
|
||||
case 'author':
|
||||
return `author|${params.did as string}|${
|
||||
params.filter as AuthorFilter
|
||||
}` as const
|
||||
return `author|${params.did}|${params.filter}` as const
|
||||
default:
|
||||
throw new Error(`Invalid video feed params ${JSON.stringify(params)}`)
|
||||
}
|
||||
@@ -490,6 +487,7 @@ let VideoItem = ({
|
||||
feedContext: string | undefined
|
||||
reqId: string | undefined
|
||||
}): React.ReactNode => {
|
||||
const ax = useAnalytics()
|
||||
const postShadow = usePostShadow(post)
|
||||
const {width, height} = useSafeAreaFrame()
|
||||
const {sendInteraction, feedDescriptor} = useFeedFeedbackContext()
|
||||
@@ -507,16 +505,12 @@ let VideoItem = ({
|
||||
// Track post:view event
|
||||
if (!hasTrackedView.current) {
|
||||
hasTrackedView.current = true
|
||||
logger.metric(
|
||||
'post:view',
|
||||
{
|
||||
uri: post.uri,
|
||||
authorDid: post.author.did,
|
||||
logContext: 'ImmersiveVideo',
|
||||
feedDescriptor,
|
||||
},
|
||||
{statsig: false},
|
||||
)
|
||||
ax.metric('post:view', {
|
||||
uri: post.uri,
|
||||
authorDid: post.author.did,
|
||||
logContext: 'ImmersiveVideo',
|
||||
feedDescriptor,
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
||||
Reference in New Issue
Block a user