Add metrics, clean up callbacks

This commit is contained in:
Eric Bailey
2026-05-27 11:04:42 -05:00
parent 04aa71c909
commit 99a48fe0e9
5 changed files with 139 additions and 26 deletions
+11
View File
@@ -987,6 +987,17 @@ export type Events = {
'share:press:recentDm': {} 'share:press:recentDm': {}
'share:press:embed': {} 'share:press:embed': {}
'embed:standardSite:view': {url: string}
'embed:standardSite:article:press': {url: string}
'embed:standardSite:article:longPress': {url: string}
'embed:standardSite:publication:press': {url: string}
'embed:standardSite:publication:longPress': {url: string}
'embed:standardSite:publicationCta:press': {url: string}
'embed:standardSite:publicationCta:longPress': {url: string}
'embed:standardSite:subscribe:press': {url: string}
'embed:standardSite:subscribe:longPress': {url: string}
'embed:standardSite:authorHandle:press': {handle: string}
'thread:click:showOtherReplies': {} 'thread:click:showOtherReplies': {}
'thread:click:hideReplyForMe': {} 'thread:click:hideReplyForMe': {}
'thread:click:hideReplyForEveryone': {} 'thread:click:hideReplyForEveryone': {}
@@ -16,14 +16,18 @@ import {
isStandardSitePublicationUri, isStandardSitePublicationUri,
} from '#/components/Post/Embed/StandardSiteEmbed/utils' } from '#/components/Post/Embed/StandardSiteEmbed/utils'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
export function StandardSiteMetaRow({ export function StandardSiteMetaRow({
preview,
type = 'document', type = 'document',
view, view,
}: { }: {
preview?: boolean
type?: 'document' | 'publication' type?: 'document' | 'publication'
view: AppBskyEmbedExternal.ViewExternal view: AppBskyEmbedExternal.ViewExternal
}) { }) {
const ax = useAnalytics()
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const highlightedPublisher = !!matchStandardSitePublisher(view) const highlightedPublisher = !!matchStandardSitePublisher(view)
@@ -77,7 +81,15 @@ export function StandardSiteMetaRow({
<InlineLinkText <InlineLinkText
label={l`View @${authorProfile.handle}'s profile`} label={l`View @${authorProfile.handle}'s profile`}
to={makeProfileLink(authorProfile)} to={makeProfileLink(authorProfile)}
style={[metaTextStyle, a.pointer_events_auto]}> style={[
metaTextStyle,
preview ? a.pointer_events_none : a.pointer_events_auto,
]}
onPress={() => {
ax.metric('embed:standardSite:authorHandle:press', {
handle: authorProfile.handle,
})
}}>
@{authorProfile.handle} @{authorProfile.handle}
</InlineLinkText> </InlineLinkText>
</Trans> </Trans>
@@ -5,6 +5,7 @@ import {plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {useCallOnce} from '#/lib/once'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {niceDate} from '#/lib/strings/time' import {niceDate} from '#/lib/strings/time'
import {toNiceDomain} from '#/lib/strings/url-helpers' import {toNiceDomain} from '#/lib/strings/url-helpers'
@@ -22,6 +23,7 @@ import {StandardSiteMetaRow} from '#/components/Post/Embed/StandardSiteEmbed/Sta
import {StandardSiteThemeProvider} from '#/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider' import {StandardSiteThemeProvider} from '#/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider'
import {isStandardSitePublicationEmbed} from '#/components/Post/Embed/StandardSiteEmbed/utils' import {isStandardSitePublicationEmbed} from '#/components/Post/Embed/StandardSiteEmbed/utils'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
export type ThemeColors = { export type ThemeColors = {
@@ -35,14 +37,20 @@ const PUBLICATION_AVATAR_STYLE = {
} }
export const StandardSiteEmbed = ({ export const StandardSiteEmbed = ({
preview,
view, view,
onOpen, onEmbedInteractionCallback,
style, style,
}: { }: {
/**
* Indicates the card is showing the composer
*/
preview?: boolean
view: AppBskyEmbedExternal.ViewExternal view: AppBskyEmbedExternal.ViewExternal
onOpen?: () => void onEmbedInteractionCallback?: () => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) => { }) => {
const ax = useAnalytics()
const {t: l, i18n} = useLingui() const {t: l, i18n} = useLingui()
const t = useTheme() const t = useTheme()
const playHaptic = useHaptics() const playHaptic = useHaptics()
@@ -76,27 +84,51 @@ export const StandardSiteEmbed = ({
onIn: onInteract, onIn: onInteract,
onOut: onInteractOut, onOut: onInteractOut,
} = useInteractionState() } = useInteractionState()
const onPress = () => { const onPress = () => {
playHaptic('Light') playHaptic('Light')
onOpen?.() onEmbedInteractionCallback?.()
ax.metric('embed:standardSite:article:press', {url: view.uri})
} }
const onLongPress = () => { const onLongPress = () => {
if (view.uri && IS_NATIVE) { if (view.uri && IS_NATIVE) {
playHaptic('Heavy') playHaptic('Heavy')
shareUrl(view.uri) shareUrl(view.uri)
ax.metric('embed:standardSite:article:longPress', {url: view.uri})
} }
} }
const onPressPublication = () => {
playHaptic('Light')
onEmbedInteractionCallback?.()
ax.metric('embed:standardSite:publication:press', {
url: view.source?.uri || '',
})
}
const onLongPressPublication = () => {
if (view.source?.uri && IS_NATIVE) {
playHaptic('Heavy')
shareUrl(view.source.uri)
ax.metric('embed:standardSite:publication:longPress', {
url: view.source.uri,
})
}
}
useCallOnce(() => {
if (!preview) {
ax.metric('embed:standardSite:view', {url: view.uri})
}
})()
if (isStandardPublication) { if (isStandardPublication) {
return ( return (
<PublicationCard <PublicationCard
preview={preview}
view={view} view={view}
onPress={onPress} onPress={onPressPublication}
onLongPress={onLongPress} onLongPress={onLongPressPublication}
style={style} style={style}
themeColors={themeColors} themeColors={themeColors}
onEmbedInteractionCallback={onEmbedInteractionCallback}
/> />
) )
} }
@@ -111,6 +143,7 @@ export const StandardSiteEmbed = ({
a.border, a.border,
t.atoms.bg, t.atoms.bg,
interacted ? t.atoms.border_contrast_high : t.atoms.border_contrast_low, interacted ? t.atoms.border_contrast_high : t.atoms.border_contrast_low,
preview && a.pointer_events_none,
style, style,
]}> ]}>
<Link <Link
@@ -224,7 +257,7 @@ export const StandardSiteEmbed = ({
<View style={[a.px_md]}> <View style={[a.px_md]}>
<Divider /> <Divider />
<View style={[a.py_sm]}> <View style={[a.py_sm]}>
<StandardSiteMetaRow view={view} /> <StandardSiteMetaRow preview={preview} view={view} />
</View> </View>
</View> </View>
)} )}
@@ -234,11 +267,13 @@ export const StandardSiteEmbed = ({
<View style={[a.z_20]}> <View style={[a.z_20]}>
<Divider /> <Divider />
<PublicationFooter <PublicationFooter
preview={preview}
view={view} view={view}
onPress={onPress} onPress={onPressPublication}
onLongPress={onLongPress} onLongPress={onLongPressPublication}
themeColors={themeColors} themeColors={themeColors}
interactedOuter={interacted} interactedOuter={interacted}
onEmbedInteractionCallback={onEmbedInteractionCallback}
/> />
</View> </View>
)} )}
@@ -247,17 +282,21 @@ export const StandardSiteEmbed = ({
} }
export function PublicationCard({ export function PublicationCard({
preview,
view, view,
onPress, onPress,
onLongPress, onLongPress,
themeColors, themeColors,
style, style,
onEmbedInteractionCallback,
}: { }: {
preview?: boolean
view: AppBskyEmbedExternal.ViewExternal view: AppBskyEmbedExternal.ViewExternal
onPress?: () => void onPress?: () => void
onLongPress?: () => void onLongPress?: () => void
themeColors: ThemeColors themeColors: ThemeColors
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
onEmbedInteractionCallback?: () => void
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -330,16 +369,20 @@ export function PublicationCard({
style={[a.text_md, a.font_semi_bold, t.atoms.text]}> style={[a.text_md, a.font_semi_bold, t.atoms.text]}>
{view.source?.title} {view.source?.title}
</Text> </Text>
<StandardSiteMetaRow type="publication" view={view} /> <StandardSiteMetaRow
preview={preview}
type="publication"
view={view}
/>
</View> </View>
</View> </View>
{gtPhone && ( {gtPhone && (
<SubscribeButton <SubscribeButton
preview={preview}
view={view} view={view}
style={[!gtPhone && [a.w_full, a.justify_center]]} style={[!gtPhone && [a.w_full, a.justify_center]]}
onPress={onPress} onEmbedInteractionCallback={onEmbedInteractionCallback}
onLongPress={onLongPress}
/> />
)} )}
</View> </View>
@@ -356,10 +399,10 @@ export function PublicationCard({
{!gtPhone && ( {!gtPhone && (
<View style={[view.description && a.pt_sm]}> <View style={[view.description && a.pt_sm]}>
<SubscribeButton <SubscribeButton
preview={preview}
view={view} view={view}
style={[!gtPhone && [a.w_full, a.justify_center]]} style={[!gtPhone && [a.w_full, a.justify_center]]}
onPress={onPress} onEmbedInteractionCallback={onEmbedInteractionCallback}
onLongPress={onLongPress}
/> />
</View> </View>
)} )}
@@ -369,17 +412,20 @@ export function PublicationCard({
} }
export function SubscribeButton({ export function SubscribeButton({
preview,
view, view,
onPress,
onLongPress,
style, style,
onEmbedInteractionCallback,
}: { }: {
preview?: boolean
view: AppBskyEmbedExternal.ViewExternal view: AppBskyEmbedExternal.ViewExternal
onPress?: () => void
onLongPress?: () => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
onEmbedInteractionCallback?: () => void
}) { }) {
const ax = useAnalytics()
const {t: l} = useLingui() const {t: l} = useLingui()
const playHaptic = useHaptics()
const highlightedPublisher = matchStandardSitePublisher(view) const highlightedPublisher = matchStandardSitePublisher(view)
const cta = highlightedPublisher const cta = highlightedPublisher
? l`Subscribe on ${highlightedPublisher.name}` ? l`Subscribe on ${highlightedPublisher.name}`
@@ -396,6 +442,36 @@ export function SubscribeButton({
? l`View ${publicationTitle}` ? l`View ${publicationTitle}`
: l`View publication` : l`View publication`
const onPress = () => {
playHaptic('Light')
onEmbedInteractionCallback?.()
if (highlightedPublisher) {
ax.metric('embed:standardSite:subscribe:press', {
url: view.source?.uri || '',
})
} else {
ax.metric('embed:standardSite:publicationCta:press', {
url: view.source?.uri || '',
})
}
}
const onLongPress = () => {
if (view.source?.uri && IS_NATIVE) {
playHaptic('Heavy')
shareUrl(view.source.uri)
if (highlightedPublisher) {
ax.metric('embed:standardSite:subscribe:longPress', {
url: view.source?.uri || '',
})
} else {
ax.metric('embed:standardSite:publicationCta:longPress', {
url: view.source?.uri || '',
})
}
}
}
return ( return (
<StandardSiteThemeProvider view={view}> <StandardSiteThemeProvider view={view}>
<Link <Link
@@ -404,7 +480,11 @@ export function SubscribeButton({
label={label} label={label}
size="small" size="small"
color="secondary_inverted" color="secondary_inverted"
style={[style, a.gap_sm, a.pointer_events_auto]} style={[
style,
a.gap_sm,
preview ? a.pointer_events_none : a.pointer_events_auto,
]}
onPress={onPress} onPress={onPress}
onLongPress={onLongPress}> onLongPress={onLongPress}>
{highlightedPublisher ? ( {highlightedPublisher ? (
@@ -470,17 +550,21 @@ function PublicationIcon({
} }
export function PublicationFooter({ export function PublicationFooter({
preview,
view, view,
themeColors, themeColors,
onPress, onPress,
onLongPress, onLongPress,
interactedOuter, interactedOuter,
onEmbedInteractionCallback,
}: { }: {
preview?: boolean
view: AppBskyEmbedExternal.ViewExternal view: AppBskyEmbedExternal.ViewExternal
themeColors: ThemeColors themeColors: ThemeColors
onPress?: () => void onPress?: () => void
onLongPress?: () => void onLongPress?: () => void
interactedOuter?: boolean interactedOuter?: boolean
onEmbedInteractionCallback?: () => void
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -503,8 +587,8 @@ export function PublicationFooter({
a.gap_md, a.gap_md,
gtPhone && [a.flex_row, a.gap_sm], gtPhone && [a.flex_row, a.gap_sm],
interactedOuter && t.atoms.bg_contrast_25, interactedOuter && t.atoms.bg_contrast_25,
]} preview && a.pointer_events_none,
testID="publication-embed-footer"> ]}>
<Link <Link
shouldProxy shouldProxy
to={view.source.uri} to={view.source.uri}
@@ -549,15 +633,19 @@ export function PublicationFooter({
]}> ]}>
{view.source?.title} {view.source?.title}
</Text> </Text>
<StandardSiteMetaRow type="publication" view={view} /> <StandardSiteMetaRow
preview={preview}
type="publication"
view={view}
/>
</View> </View>
</View> </View>
<SubscribeButton <SubscribeButton
preview={preview}
view={view} view={view}
style={[a.z_10, !gtPhone && [a.w_full, a.justify_center]]} style={[a.z_10, !gtPhone && [a.w_full, a.justify_center]]}
onPress={onPress} onEmbedInteractionCallback={onEmbedInteractionCallback}
onLongPress={onLongPress}
/> />
</View> </View>
) )
+1
View File
@@ -104,6 +104,7 @@ function MediaEmbed({
activeStyle={[a.mt_sm]}> activeStyle={[a.mt_sm]}>
<StandardSiteEmbed <StandardSiteEmbed
view={embed.view.external} view={embed.view.external}
onEmbedInteractionCallback={rest.onOpen}
style={[a.mt_sm, rest.style]} style={[a.mt_sm, rest.style]}
/> />
</ContentHider> </ContentHider>
+1
View File
@@ -91,6 +91,7 @@ export const ExternalEmbedLink = ({
if (data.view && isStandardSiteEmbed(data.view.external)) { if (data.view && isStandardSiteEmbed(data.view.external)) {
return ( return (
<StandardSiteEmbed <StandardSiteEmbed
preview
view={{ view={{
...data.view?.external, ...data.view?.external,
title: data.view?.external?.title || data.title || uri, title: data.view?.external?.title || data.title || uri,