Merge branch 'log-session-events' into session-stress

This commit is contained in:
Dan Abramov
2024-07-09 20:08:08 +01:00
26 changed files with 165 additions and 136 deletions
+4 -4
View File
@@ -10,7 +10,7 @@ Get the app itself:
## Development Resources
This is a [React Native](https://reactnative.dev/) application, written in the TypeScript programming language. It builds on the `atproto` TypeScript packages (like [`@atproto/api`](https://www.npmjs.com/package/@atproto/api)), code for which is also on open source, but in [a different git repository](https://github.com/bluesky-social/atproto).
This is a [React Native](https://reactnative.dev/) application, written in the TypeScript programming language. It builds on the `atproto` TypeScript packages (like [`@atproto/api`](https://www.npmjs.com/package/@atproto/api)), code for which is also open source, but in [a different git repository](https://github.com/bluesky-social/atproto).
There is a small amount of Go language source code (in `./bskyweb/`), for a web service that returns the React Native Web application.
@@ -42,10 +42,10 @@ The Bluesky Social application encompasses a set of schemas and APIs built in th
- Open an issue and give some time for discussion before submitting a PR.
- Stay away from PRs like...
- Changing "Post" to "Skeet."
- Refactoring the codebase, eg to replace mobx with redux or something.
- Refactoring the codebase, e.g., to replace MobX with Redux or something.
- Adding entirely new features without prior discussion.
Remember, we serve a wide community of users. Our day to day involves us constantly asking "which top priority is our top priority." If you submit well-written PRs that solve problems concisely, that's an awesome contribution. Otherwise, as much as we'd love to accept your ideas and contributions, we really don't have the bandwidth. That's what forking is for!
Remember, we serve a wide community of users. Our day-to-day involves us constantly asking "which top priority is our top priority." If you submit well-written PRs that solve problems concisely, that's an awesome contribution. Otherwise, as much as we'd love to accept your ideas and contributions, we really don't have the bandwidth. That's what forking is for!
## Forking guidelines
@@ -63,7 +63,7 @@ If you discover any security issues, please send an email to security@bsky.app.
## Are you a developer interested in building on atproto?
Bluesky is an open social network built on the AT Protocol, a flexible technology that will never lock developers out of the ecosystems that they help build. With atproto, third-party can be as seamless as first-party through custom feeds, federated services, clients, and more.
Bluesky is an open social network built on the AT Protocol, a flexible technology that will never lock developers out of the ecosystems that they help build. With atproto, third-party integration can be as seamless as first-party through custom feeds, federated services, clients, and more.
## License (MIT)
+14 -6
View File
@@ -12,6 +12,7 @@ import {Link, LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
const AVI_SIZE = 30
const AVI_SIZE_SMALL = 20
const AVI_BORDER = 1
/**
@@ -30,10 +31,12 @@ export function KnownFollowers({
profile,
moderationOpts,
onLinkPress,
minimal,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
onLinkPress?: LinkProps['onPress']
minimal?: boolean
}) {
const cache = React.useRef<Map<string, AppBskyActorDefs.KnownFollowers>>(
new Map(),
@@ -59,6 +62,7 @@ export function KnownFollowers({
cachedKnownFollowers={cachedKnownFollowers}
moderationOpts={moderationOpts}
onLinkPress={onLinkPress}
minimal={minimal}
/>
)
}
@@ -71,11 +75,13 @@ function KnownFollowersInner({
moderationOpts,
cachedKnownFollowers,
onLinkPress,
minimal,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
cachedKnownFollowers: AppBskyActorDefs.KnownFollowers
onLinkPress?: LinkProps['onPress']
minimal?: boolean
}) {
const t = useTheme()
const {_} = useLingui()
@@ -110,6 +116,8 @@ function KnownFollowersInner({
*/
if (slice.length === 0) return null
const SIZE = minimal ? AVI_SIZE_SMALL : AVI_SIZE
return (
<Link
label={_(
@@ -120,7 +128,7 @@ function KnownFollowersInner({
style={[
a.flex_1,
a.flex_row,
a.gap_md,
minimal ? a.gap_sm : a.gap_md,
a.align_center,
{marginLeft: -AVI_BORDER},
]}>
@@ -129,8 +137,8 @@ function KnownFollowersInner({
<View
style={[
{
height: AVI_SIZE,
width: AVI_SIZE + (slice.length - 1) * a.gap_md.gap,
height: SIZE,
width: SIZE + (slice.length - 1) * a.gap_md.gap,
},
pressed && {
opacity: 0.5,
@@ -145,14 +153,14 @@ function KnownFollowersInner({
{
borderWidth: AVI_BORDER,
borderColor: t.atoms.bg.backgroundColor,
width: AVI_SIZE + AVI_BORDER * 2,
height: AVI_SIZE + AVI_BORDER * 2,
width: SIZE + AVI_BORDER * 2,
height: SIZE + AVI_BORDER * 2,
left: i * a.gap_md.gap,
zIndex: AVI_BORDER - i,
},
]}>
<UserAvatar
size={AVI_SIZE}
size={SIZE}
avatar={prof.avatar}
moderation={moderation.ui('avatar')}
/>
+3 -1
View File
@@ -166,7 +166,9 @@ export function NameAndHandle({
return (
<View style={[a.flex_1]}>
<Text style={[a.text_md, a.font_bold, a.leading_snug]} numberOfLines={1}>
<Text
style={[a.text_md, a.font_bold, a.leading_snug, a.self_start]}
numberOfLines={1}>
{name}
</Text>
<Text
@@ -462,7 +462,8 @@ function Inner({
<Link to={profileURL} label={_(msg`View profile`)} onPress={hide}>
<View style={[a.pb_sm, a.flex_1]}>
<Text style={[a.pt_md, a.pb_xs, a.text_lg, a.font_bold]}>
<Text
style={[a.pt_md, a.pb_xs, a.text_lg, a.font_bold, a.self_start]}>
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -78,7 +78,13 @@ function WizardListCard({
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
style={[a.flex_1, a.font_bold, a.text_md, a.leading_tight]}
style={[
a.flex_1,
a.font_bold,
a.text_md,
a.leading_tight,
a.self_start,
]}
numberOfLines={1}>
{displayName}
</Text>
+6 -1
View File
@@ -168,7 +168,12 @@ function HeaderReady({
</View>
<View style={a.flex_1}>
<Text
style={[a.text_md, a.font_bold, web(a.leading_normal)]}
style={[
a.text_md,
a.font_bold,
a.self_start,
web(a.leading_normal),
]}
numberOfLines={1}>
{displayName}
</Text>
@@ -395,7 +395,7 @@ function ProfileCard({
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
style={[t.atoms.text, a.font_bold, a.leading_tight]}
style={[t.atoms.text, a.font_bold, a.leading_tight, a.self_start]}
numberOfLines={1}>
{displayName}
</Text>
+1
View File
@@ -1,6 +1,7 @@
export type Gate =
// Keep this alphabetic please.
| 'debug_show_feedcontext'
| 'explore_page_profile_card_social_proof'
| 'native_pwi_disabled'
| 'new_user_guided_tour'
| 'new_user_progress_guide'
+1
View File
@@ -0,0 +1 @@
export const NON_BREAKING_SPACE = '\u00A0'
@@ -386,10 +386,10 @@ export function MessagesList({
data={convoState.items}
renderItem={renderItem}
keyExtractor={keyExtractor}
containWeb={true}
disableFullWindowScroll={true}
// Prevents wrong position in Firefox when sending a message
// as well as scroll getting stuck on Chome when scrolling upwards.
disableContentVisibility={true}
disableContainStyle={true}
disableVirtualization={true}
style={animatedListStyle}
// The extra two items account for the header and the footer components
+1 -1
View File
@@ -20,7 +20,7 @@ export function ProfileHeaderDisplayName({
<View pointerEvents="none">
<Text
testID="profileHeaderDisplayName"
style={[t.atoms.text, a.text_4xl, {fontWeight: '500'}]}>
style={[t.atoms.text, a.text_4xl, a.self_start, {fontWeight: '500'}]}>
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
+1 -1
View File
@@ -101,7 +101,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) {
onEndReachedThreshold={2}
renderScrollComponent={props => <KeyboardAwareScrollView {...props} />}
keyboardShouldPersistTaps="handled"
containWeb={true}
disableFullWindowScroll={true}
sideBorders={false}
style={{flex: 1}}
ListEmptyComponent={
@@ -80,7 +80,7 @@ export function StepProfiles({
keyExtractor={keyExtractor}
renderScrollComponent={props => <KeyboardAwareScrollView {...props} />}
keyboardShouldPersistTaps="handled"
containWeb={true}
disableFullWindowScroll={true}
sideBorders={false}
style={[a.flex_1]}
onEndReached={
+2
View File
@@ -12,6 +12,7 @@ import {tryFetchGates} from '#/lib/statsig/statsig'
import {getAge} from '#/lib/strings/time'
import {logger} from '#/logger'
import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders'
import {addSessionEventLog} from './logging'
import {
configureModerationForAccount,
configureModerationForGuest,
@@ -194,6 +195,7 @@ async function prepareAgent(
const account = agentToSessionAccountOrThrow(agent)
agent.setPersistSessionHandler(event => {
onSessionChange(agent, account.did, event)
addSessionEventLog(account.did, event)
})
return {agent, account}
}
+13 -1
View File
@@ -1,4 +1,4 @@
import {AtpSessionData} from '@atproto/api'
import {AtpSessionData, AtpSessionEvent} from '@atproto/api'
import {sha256} from 'js-sha256'
import {Statsig} from 'statsig-react-native-expo'
@@ -70,6 +70,18 @@ export function wrapSessionReducerForLogging(reducer: Reducer): Reducer {
let nextMessageIndex = 0
const MAX_SLICE_LENGTH = 1000
// Not gated.
export function addSessionEventLog(did: string, event: AtpSessionEvent) {
try {
if (!Statsig.initializeCalled() || !Statsig.getStableID()) {
return
}
Statsig.logEvent('session:event', null, {did, event})
} catch (e) {
console.error(e)
}
}
export function addSessionDebugLog(log: Log) {
console.log(new Date(), log)
try {
+6 -3
View File
@@ -58,6 +58,7 @@ import {useNavigation} from '@react-navigation/native'
import {parseTenorGif} from '#/lib/strings/embed-player'
import {logger} from '#/logger'
import {NavigationProp} from 'lib/routes/types'
import {forceLTR} from 'lib/strings/bidi'
import {DM_SERVICE_HEADERS} from 'state/queries/messages/const'
import {useAgent} from 'state/session'
import {Button, ButtonText} from '#/components/Button'
@@ -274,13 +275,15 @@ let FeedItem = ({
showDmButton={item.type === 'starterpack-joined' || isFollowBack}
/>
<ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
<Text style={styles.meta}>
<Text style={[styles.meta, a.self_start]}>
<TextLink
key={authors[0].href}
style={[pal.text, s.bold]}
href={authors[0].href}
text={sanitizeDisplayName(
authors[0].profile.displayName || authors[0].profile.handle,
text={forceLTR(
sanitizeDisplayName(
authors[0].profile.displayName || authors[0].profile.handle,
),
)}
disableMismatchWarning
/>
+1 -1
View File
@@ -281,7 +281,7 @@ let PostThreadItemLoaded = ({
<Link style={s.flex1} href={authorHref} title={authorTitle}>
<Text
type="xl-bold"
style={[pal.text]}
style={[pal.text, a.self_start]}
numberOfLines={1}
lineHeight={1.2}>
{sanitizeDisplayName(
+38 -67
View File
@@ -5,7 +5,6 @@ import {
moderateProfile,
ModerationDecision,
} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {useQueryClient} from '@tanstack/react-query'
import {useProfileShadow} from '#/state/cache/profile-shadow'
@@ -19,12 +18,16 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {s} from 'lib/styles'
import {precacheProfile} from 'state/queries/profile'
import {atoms as a} from '#/alf'
import {
KnownFollowers,
shouldShowKnownFollowers,
} from '#/components/KnownFollowers'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {FollowButton} from './FollowButton'
import hairlineWidth = StyleSheet.hairlineWidth
import {atoms as a} from '#/alf'
import * as Pills from '#/components/Pills'
export function ProfileCard({
@@ -33,22 +36,22 @@ export function ProfileCard({
noModFilter,
noBg,
noBorder,
followers,
renderButton,
onPress,
style,
showKnownFollowers,
}: {
testID?: string
profile: AppBskyActorDefs.ProfileViewBasic
noModFilter?: boolean
noBg?: boolean
noBorder?: boolean
followers?: AppBskyActorDefs.ProfileView[] | undefined
renderButton?: (
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>,
) => React.ReactNode
onPress?: () => void
style?: StyleProp<ViewStyle>
showKnownFollowers?: boolean
}) {
const queryClient = useQueryClient()
const pal = usePalette('default')
@@ -70,6 +73,11 @@ export function ProfileCard({
return null
}
const knownFollowersVisible =
showKnownFollowers &&
shouldShowKnownFollowers(profile.viewer?.knownFollowers) &&
moderationOpts
return (
<Link
testID={testID}
@@ -97,7 +105,7 @@ export function ProfileCard({
<View style={styles.layoutContent}>
<Text
type="lg"
style={[s.bold, pal.text]}
style={[s.bold, pal.text, a.self_start]}
numberOfLines={1}
lineHeight={1.2}>
{sanitizeDisplayName(
@@ -118,14 +126,30 @@ export function ProfileCard({
<View style={styles.layoutButton}>{renderButton(profile)}</View>
) : undefined}
</View>
{profile.description ? (
{profile.description || knownFollowersVisible ? (
<View style={styles.details}>
<Text style={pal.text} numberOfLines={4}>
{profile.description as string}
</Text>
{profile.description ? (
<Text style={pal.text} numberOfLines={4}>
{profile.description as string}
</Text>
) : null}
{knownFollowersVisible ? (
<View
style={[
a.flex_row,
a.align_center,
a.gap_sm,
!!profile.description && a.mt_md,
]}>
<KnownFollowers
minimal
profile={profile}
moderationOpts={moderationOpts}
/>
</View>
) : null}
</View>
) : null}
<FollowersList followers={followers} />
</Link>
)
}
@@ -155,73 +179,20 @@ export function ProfileCardPills({
)
}
function FollowersList({
followers,
}: {
followers?: AppBskyActorDefs.ProfileView[] | undefined
}) {
const pal = usePalette('default')
const moderationOpts = useModerationOpts()
const followersWithMods = React.useMemo(() => {
if (!followers || !moderationOpts) {
return []
}
return followers
.map(f => ({
f,
mod: moderateProfile(f, moderationOpts),
}))
.filter(({mod}) => !mod.ui('profileList').filter)
}, [followers, moderationOpts])
if (!followersWithMods?.length) {
return null
}
return (
<View style={styles.followedBy}>
<Text
type="sm"
style={[styles.followsByDesc, pal.textLight]}
numberOfLines={2}
lineHeight={1.2}>
<Trans>
Followed by{' '}
{followersWithMods.map(({f}) => f.displayName || f.handle).join(', ')}
</Trans>
</Text>
{followersWithMods.slice(0, 3).map(({f, mod}) => (
<View key={f.did} style={styles.followedByAviContainer}>
<View style={[styles.followedByAvi, pal.view]}>
<PreviewableUserAvatar
size={32}
profile={f}
moderation={mod.ui('avatar')}
type={f.associated?.labeler ? 'labeler' : 'user'}
/>
</View>
</View>
))}
</View>
)
}
export function ProfileCardWithFollowBtn({
profile,
noBg,
noBorder,
followers,
onPress,
logContext = 'ProfileCard',
showKnownFollowers,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
profile: AppBskyActorDefs.ProfileView
noBg?: boolean
noBorder?: boolean
followers?: AppBskyActorDefs.ProfileView[] | undefined
onPress?: () => void
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
showKnownFollowers?: boolean
}) {
const {currentAccount} = useSession()
const isMe = profile.did === currentAccount?.did
@@ -231,7 +202,6 @@ export function ProfileCardWithFollowBtn({
profile={profile}
noBg={noBg}
noBorder={noBorder}
followers={followers}
renderButton={
isMe
? undefined
@@ -240,6 +210,7 @@ export function ProfileCardWithFollowBtn({
)
}
onPress={onPress}
showKnownFollowers={!isMe && showKnownFollowers}
/>
)
}
+3 -2
View File
@@ -24,11 +24,12 @@ export type ListProps<ItemT> = Omit<
refreshing?: boolean
onRefresh?: () => void
onItemSeen?: (item: ItemT) => void
containWeb?: boolean
desktopFixedHeight?: number | boolean
// Web only prop to contain the scroll to the container rather than the window
disableFullWindowScroll?: boolean
sideBorders?: boolean
// Web only prop to disable a perf optimization (which would otherwise be on).
disableContentVisibility?: boolean
disableContainStyle?: boolean
}
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
+25 -22
View File
@@ -23,9 +23,11 @@ export type ListProps<ItemT> = Omit<
onRefresh?: () => void
onItemSeen?: (item: ItemT) => void
desktopFixedHeight?: number | boolean
containWeb?: boolean
// Web only prop to contain the scroll to the container rather than the window
disableFullWindowScroll?: boolean
sideBorders?: boolean
disableContentVisibility?: boolean
// Web only prop to disable a perf optimization (which would otherwise be on).
disableContainStyle?: boolean
}
export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
@@ -39,7 +41,7 @@ function ListImpl<ItemT>(
ListHeaderComponent,
ListFooterComponent,
ListEmptyComponent,
containWeb,
disableFullWindowScroll,
contentContainerStyle,
data,
desktopFixedHeight,
@@ -58,7 +60,7 @@ function ListImpl<ItemT>(
extraData,
style,
sideBorders = true,
disableContentVisibility,
disableContainStyle,
...props
}: ListProps<ItemT>,
ref: React.Ref<ListMethods>,
@@ -112,7 +114,7 @@ function ListImpl<ItemT>(
}
const getScrollableNode = React.useCallback(() => {
if (containWeb) {
if (disableFullWindowScroll) {
const element = nativeRef.current as HTMLDivElement | null
if (!element) return
@@ -182,7 +184,7 @@ function ListImpl<ItemT>(
},
}
}
}, [containWeb])
}, [disableFullWindowScroll])
const nativeRef = React.useRef<HTMLDivElement>(null)
React.useImperativeHandle(
@@ -267,7 +269,12 @@ function ListImpl<ItemT>(
return () => {
element?.removeEventListener('scroll', handleScroll)
}
}, [isInsideVisibleTree, handleScroll, containWeb, getScrollableNode])
}, [
isInsideVisibleTree,
handleScroll,
disableFullWindowScroll,
getScrollableNode,
])
// --- onScrolledDownChange ---
const isScrolledDown = useRef(false)
@@ -308,7 +315,7 @@ function ListImpl<ItemT>(
{...props}
style={[
style,
containWeb && {
disableFullWindowScroll && {
flex: 1,
// @ts-expect-error web only
'overflow-y': 'scroll',
@@ -332,13 +339,13 @@ function ListImpl<ItemT>(
pal.border,
]}>
<Visibility
root={containWeb ? nativeRef : null}
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={handleAboveTheFoldVisibleChange}
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
/>
{onStartReached && !isEmpty && (
<Visibility
root={containWeb ? nativeRef : null}
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={onHeadVisibilityChange}
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
/>
@@ -356,13 +363,13 @@ function ListImpl<ItemT>(
renderItem={renderItem}
extraData={extraData}
onItemSeen={onItemSeen}
disableContentVisibility={disableContentVisibility}
disableContainStyle={disableContainStyle}
/>
)
})}
{onEndReached && !isEmpty && (
<Visibility
root={containWeb ? nativeRef : null}
root={disableFullWindowScroll ? nativeRef : null}
onVisibleChange={onTailVisibilityChange}
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
key={data?.length}
@@ -406,7 +413,7 @@ let Row = function RowImpl<ItemT>({
renderItem,
extraData: _unused,
onItemSeen,
disableContentVisibility,
disableContainStyle,
}: {
item: ItemT
index: number
@@ -416,7 +423,7 @@ let Row = function RowImpl<ItemT>({
| ((data: {index: number; item: any; separators: any}) => React.ReactNode)
extraData: any
onItemSeen: ((item: any) => void) | undefined
disableContentVisibility?: boolean
disableContainStyle?: boolean
}): React.ReactNode {
const rowRef = React.useRef(null)
const intersectionTimeout = React.useRef<NodeJS.Timer | undefined>(undefined)
@@ -465,14 +472,10 @@ let Row = function RowImpl<ItemT>({
return null
}
const shouldDisableContentVisibility = disableContentVisibility || isSafari
const shouldDisableContainStyle = disableContainStyle || isSafari
return (
<View
style={
shouldDisableContentVisibility
? undefined
: styles.contentVisibilityAuto
}
style={shouldDisableContainStyle ? undefined : styles.contain}
ref={rowRef}>
{renderItem({item, index, separators: null as any})}
</View>
@@ -544,9 +547,9 @@ const styles = StyleSheet.create({
marginLeft: 'auto',
marginRight: 'auto',
},
contentVisibilityAuto: {
contain: {
// @ts-ignore web only
contentVisibility: 'auto',
contain: 'layout paint',
},
minHeightViewport: {
// @ts-ignore web only
+8 -10
View File
@@ -6,6 +6,8 @@ import {useQueryClient} from '@tanstack/react-query'
import {precacheProfile} from '#/state/queries/profile'
import {usePalette} from 'lib/hooks/usePalette'
import {makeProfileLink} from 'lib/routes/links'
import {forceLTR} from 'lib/strings/bidi'
import {NON_BREAKING_SPACE} from 'lib/strings/constants'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {niceDate} from 'lib/strings/time'
@@ -32,8 +34,6 @@ interface PostMetaOpts {
style?: StyleProp<ViewStyle>
}
const NON_BREAKING_SPACE = '\u00A0'
let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
const pal = usePalette('default')
const displayName = opts.author.displayName || opts.author.handle
@@ -70,14 +70,12 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
style={[pal.text]}
lineHeight={1.2}
disableMismatchWarning
text={
<>
{sanitizeDisplayName(
displayName,
opts.moderation?.ui('displayName'),
)}
</>
}
text={forceLTR(
sanitizeDisplayName(
displayName,
opts.moderation?.ui('displayName'),
),
)}
href={profileLink}
onBeforePress={onBeforePressAuthor}
/>
+8 -4
View File
@@ -131,7 +131,7 @@ export function PostEmbeds({
const {alt, thumb, aspectRatio} = images[0]
return (
<ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.imagesContainer, style]}>
<View style={[styles.container, style]}>
<AutoSizedImage
alt={alt}
uri={thumb}
@@ -156,7 +156,7 @@ export function PostEmbeds({
return (
<ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.imagesContainer, style]}>
<View style={[styles.container, style]}>
<ImageLayoutGrid
images={embed.images}
onPress={_openLightbox}
@@ -174,7 +174,11 @@ export function PostEmbeds({
const link = embed.external
return (
<ContentHider modui={moderation?.ui('contentMedia')}>
<ExternalLinkEmbed link={link} onOpen={onOpen} style={style} />
<ExternalLinkEmbed
link={link}
onOpen={onOpen}
style={[styles.container, style]}
/>
</ContentHider>
)
}
@@ -183,7 +187,7 @@ export function PostEmbeds({
}
const styles = StyleSheet.create({
imagesContainer: {
container: {
marginTop: 8,
},
altContainer: {
+12 -3
View File
@@ -10,6 +10,7 @@ import {
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -241,7 +242,7 @@ type ExploreScreenItems =
| {
type: 'profile'
key: string
profile: AppBskyActorDefs.ProfileViewBasic
profile: AppBskyActorDefs.ProfileView
}
| {
type: 'feed'
@@ -291,6 +292,7 @@ export function Explore() {
error: feedsError,
fetchNextPage: fetchNextFeedsPage,
} = useGetPopularFeedsQuery({limit: 10})
const gate = useGate()
const isLoadingMoreProfiles = isFetchingNextProfilesPage && !isLoadingProfiles
const onLoadMoreProfiles = React.useCallback(async () => {
@@ -492,7 +494,14 @@ export function Explore() {
case 'profile': {
return (
<View style={[a.border_b, t.atoms.border_contrast_low]}>
<ProfileCardWithFollowBtn profile={item.profile} noBg noBorder />
<ProfileCardWithFollowBtn
profile={item.profile}
noBg
noBorder
showKnownFollowers={gate(
'explore_page_profile_card_social_proof',
)}
/>
</View>
)
}
@@ -555,7 +564,7 @@ export function Explore() {
}
}
},
[t, moderationOpts],
[t, moderationOpts, gate],
)
return (
+2 -1
View File
@@ -68,6 +68,7 @@ import {navigate, resetToTab} from '#/Navigation'
import {Email2FAToggle} from './Email2FAToggle'
import {ExportCarDialog} from './ExportCarDialog'
import hairlineWidth = StyleSheet.hairlineWidth
import {atoms as a} from '#/alf'
function SettingsAccountCard({
account,
@@ -104,7 +105,7 @@ function SettingsAccountCard({
/>
</View>
<View style={[s.flex1]}>
<Text type="md-bold" style={pal.text} numberOfLines={1}>
<Text type="md-bold" style={[pal.text, a.self_start]} numberOfLines={1}>
{profile?.displayName || account.handle}
</Text>
<Text type="sm" style={pal.textLight} numberOfLines={1}>
+1 -1
View File
@@ -47,7 +47,7 @@ export function ListContained() {
)
}}
keyExtractor={item => item.id.toString()}
containWeb={true}
disableFullWindowScroll={true}
style={{flex: 1}}
onStartReached={() => {
console.log('Start Reached')
+2 -1
View File
@@ -30,6 +30,7 @@ import {precacheProfile} from 'state/queries/profile'
import {Link} from '#/view/com/util/Link'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {Text} from 'view/com/util/text/Text'
import {atoms as a} from '#/alf'
let SearchLinkCard = ({
label,
@@ -127,7 +128,7 @@ let SearchProfileCard = ({
<View style={{flex: 1}}>
<Text
type="lg"
style={[s.bold, pal.text]}
style={[s.bold, pal.text, a.self_start]}
numberOfLines={1}
lineHeight={1.2}>
{sanitizeDisplayName(