Constrain image heights in feeds and threads (#5129)

* Limit height of images within posts

* Add some future-proofness

* Comments, improve a11y

* Adjust ALT, add crop icon

* Fix disableCrop in record-with-media posts

* Clean up aspect ratios, handle very tall images

* Handle record-with-media separately, clarify intent using enums

* Adjust spacing

* Adjust rwm embed image size on mobile

* Only do reduced layout if images embed

* Adjust gap in small embed variant

* Clean up grid layout

* Hide badge on small variant with one image

* Remove crop icon from image grid, leave on single image

* Fix sizing in Firefox

* Fix fullBleed variant
This commit is contained in:
Eric Bailey 2024-09-05 13:45:13 -05:00 committed by GitHub
parent 117926357d
commit 2265fedd2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 397 additions and 207 deletions

View file

@ -33,7 +33,7 @@ import {InfoCircleIcon} from 'lib/icons'
import {makeProfileLink} from 'lib/routes/links'
import {precacheProfile} from 'state/queries/profile'
import {ComposerOptsQuote} from 'state/shell/composer'
import {atoms as a} from '#/alf'
import {atoms as a, useBreakpoints} from '#/alf'
import {RichText} from '#/components/RichText'
import {ContentHider} from '../../../../components/moderation/ContentHider'
import {PostAlerts} from '../../../../components/moderation/PostAlerts'
@ -41,17 +41,20 @@ import {Link} from '../Link'
import {PostMeta} from '../PostMeta'
import {Text} from '../text/Text'
import {PostEmbeds} from '.'
import {PostEmbedViewContext, QuoteEmbedViewContext} from './types'
export function MaybeQuoteEmbed({
embed,
onOpen,
style,
allowNestedQuotes,
viewContext,
}: {
embed: AppBskyEmbedRecord.View
onOpen?: () => void
style?: StyleProp<ViewStyle>
allowNestedQuotes?: boolean
viewContext?: QuoteEmbedViewContext
}) {
const pal = usePalette('default')
const {currentAccount} = useSession()
@ -67,6 +70,7 @@ export function MaybeQuoteEmbed({
onOpen={onOpen}
style={style}
allowNestedQuotes={allowNestedQuotes}
viewContext={viewContext}
/>
)
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
@ -113,12 +117,14 @@ function QuoteEmbedModerated({
onOpen,
style,
allowNestedQuotes,
viewContext,
}: {
viewRecord: AppBskyEmbedRecord.ViewRecord
postRecord: AppBskyFeedPost.Record
onOpen?: () => void
style?: StyleProp<ViewStyle>
allowNestedQuotes?: boolean
viewContext?: QuoteEmbedViewContext
}) {
const moderationOpts = useModerationOpts()
const moderation = React.useMemo(() => {
@ -144,6 +150,7 @@ function QuoteEmbedModerated({
onOpen={onOpen}
style={style}
allowNestedQuotes={allowNestedQuotes}
viewContext={viewContext}
/>
)
}
@ -154,18 +161,21 @@ export function QuoteEmbed({
onOpen,
style,
allowNestedQuotes,
viewContext,
}: {
quote: ComposerOptsQuote
moderation?: ModerationDecision
onOpen?: () => void
style?: StyleProp<ViewStyle>
allowNestedQuotes?: boolean
viewContext?: QuoteEmbedViewContext
}) {
const queryClient = useQueryClient()
const pal = usePalette('default')
const itemUrip = new AtUri(quote.uri)
const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey)
const itemTitle = `Post by ${quote.author.handle}`
const {gtMobile} = useBreakpoints()
const richText = React.useMemo(
() =>
@ -197,6 +207,7 @@ export function QuoteEmbed({
}
}
}, [quote.embeds, allowNestedQuotes])
const isImagesEmbed = AppBskyEmbedImages.isView(embed)
const onBeforePress = React.useCallback(() => {
precacheProfile(queryClient, quote.author)
@ -226,15 +237,43 @@ export function QuoteEmbed({
{moderation ? (
<PostAlerts modui={moderation.ui('contentView')} style={[a.py_xs]} />
) : null}
{richText ? (
<RichText
value={richText}
style={a.text_md}
numberOfLines={20}
disableLinks
/>
) : null}
{embed && <PostEmbeds embed={embed} moderation={moderation} />}
{viewContext === QuoteEmbedViewContext.FeedEmbedRecordWithMedia &&
isImagesEmbed ? (
<View style={[a.flex_row, a.gap_md]}>
{embed && (
<View style={[{width: gtMobile ? 100 : 80}]}>
<PostEmbeds
embed={embed}
moderation={moderation}
viewContext={PostEmbedViewContext.FeedEmbedRecordWithMedia}
/>
</View>
)}
{richText ? (
<View style={[a.flex_1, a.pt_xs]}>
<RichText
value={richText}
style={a.text_md}
numberOfLines={20}
disableLinks
/>
</View>
) : null}
</View>
) : (
<>
{richText ? (
<RichText
value={richText}
style={a.text_md}
numberOfLines={20}
disableLinks
/>
) : null}
{embed && <PostEmbeds embed={embed} moderation={moderation} />}
</>
)}
</Link>
</ContentHider>
)

View file

@ -3,7 +3,6 @@ import {
InteractionManager,
StyleProp,
StyleSheet,
Text,
View,
ViewStyle,
} from 'react-native'
@ -22,7 +21,6 @@ import {
} from '@atproto/api'
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {usePalette} from 'lib/hooks/usePalette'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
@ -34,8 +32,11 @@ import {AutoSizedImage} from '../images/AutoSizedImage'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
import {MaybeQuoteEmbed} from './QuoteEmbed'
import {PostEmbedViewContext, QuoteEmbedViewContext} from './types'
import {VideoEmbed} from './VideoEmbed'
export * from './types'
type Embed =
| AppBskyEmbedRecord.View
| AppBskyEmbedImages.View
@ -50,15 +51,16 @@ export function PostEmbeds({
onOpen,
style,
allowNestedQuotes,
viewContext,
}: {
embed?: Embed
moderation?: ModerationDecision
onOpen?: () => void
style?: StyleProp<ViewStyle>
allowNestedQuotes?: boolean
viewContext?: PostEmbedViewContext
}) {
const {openLightbox} = useLightboxControls()
const largeAltBadge = useLargeAltBadgeEnabled()
// quote post with media
// =
@ -69,8 +71,17 @@ export function PostEmbeds({
embed={embed.media}
moderation={moderation}
onOpen={onOpen}
viewContext={viewContext}
/>
<MaybeQuoteEmbed
embed={embed.record}
onOpen={onOpen}
viewContext={
viewContext === PostEmbedViewContext.Feed
? QuoteEmbedViewContext.FeedEmbedRecordWithMedia
: undefined
}
/>
<MaybeQuoteEmbed embed={embed.record} onOpen={onOpen} />
</View>
)
}
@ -124,27 +135,26 @@ export function PostEmbeds({
}
if (images.length === 1) {
const {alt, thumb, aspectRatio} = images[0]
const image = images[0]
return (
<ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.container, style]}>
<AutoSizedImage
alt={alt}
uri={thumb}
dimensionsHint={aspectRatio}
crop={
viewContext === PostEmbedViewContext.ThreadHighlighted
? 'none'
: viewContext ===
PostEmbedViewContext.FeedEmbedRecordWithMedia
? 'square'
: 'constrained'
}
image={image}
onPress={() => _openLightbox(0)}
onPressIn={() => onPressIn(0)}
style={a.rounded_sm}>
{alt === '' ? null : (
<View style={styles.altContainer}>
<Text
style={[styles.alt, largeAltBadge && a.text_xs]}
accessible={false}>
ALT
</Text>
</View>
)}
</AutoSizedImage>
hideBadge={
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
}
/>
</View>
</ContentHider>
)
@ -157,6 +167,7 @@ export function PostEmbeds({
images={embed.images}
onPress={_openLightbox}
onPressIn={onPressIn}
viewContext={viewContext}
/>
</View>
</ContentHider>

View file

@ -0,0 +1,9 @@
export enum PostEmbedViewContext {
ThreadHighlighted = 'ThreadHighlighted',
Feed = 'Feed',
FeedEmbedRecordWithMedia = 'FeedEmbedRecordWithMedia',
}
export enum QuoteEmbedViewContext {
FeedEmbedRecordWithMedia = PostEmbedViewContext.FeedEmbedRecordWithMedia,
}