collapsible post text
This commit is contained in:
committed by
Eric Bailey
parent
6b10ccf3da
commit
c9953c72b0
@@ -19,7 +19,7 @@ import {Text, TextProps} from '#/components/Typography'
|
||||
const WORD_WRAP = {wordWrap: 1}
|
||||
|
||||
export type RichTextProps = TextStyleProp &
|
||||
Pick<TextProps, 'selectable'> & {
|
||||
Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & {
|
||||
value: RichTextAPI | string
|
||||
testID?: string
|
||||
numberOfLines?: number
|
||||
@@ -43,6 +43,8 @@ export function RichText({
|
||||
onLinkPress,
|
||||
interactiveStyle,
|
||||
emojiMultiplier = 1.85,
|
||||
onLayout,
|
||||
onTextLayout,
|
||||
}: RichTextProps) {
|
||||
const richText = React.useMemo(
|
||||
() =>
|
||||
@@ -70,6 +72,8 @@ export function RichText({
|
||||
selectable={selectable}
|
||||
testID={testID}
|
||||
style={[plainStyles, {fontSize}]}
|
||||
onLayout={onLayout}
|
||||
onTextLayout={onTextLayout}
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={WORD_WRAP}>
|
||||
{text}
|
||||
@@ -83,6 +87,8 @@ export function RichText({
|
||||
testID={testID}
|
||||
style={plainStyles}
|
||||
numberOfLines={numberOfLines}
|
||||
onLayout={onLayout}
|
||||
onTextLayout={onTextLayout}
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={WORD_WRAP}>
|
||||
{text}
|
||||
@@ -163,6 +169,8 @@ export function RichText({
|
||||
testID={testID}
|
||||
style={plainStyles}
|
||||
numberOfLines={numberOfLines}
|
||||
onLayout={onLayout}
|
||||
onTextLayout={onTextLayout}
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={WORD_WRAP}>
|
||||
{els}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||
import {
|
||||
LayoutAnimation,
|
||||
ListRenderItem,
|
||||
ScrollView,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
ViewToken,
|
||||
@@ -19,7 +21,8 @@ import {
|
||||
AtUri,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {
|
||||
useFocusEffect,
|
||||
useIsFocused,
|
||||
@@ -27,7 +30,7 @@ import {
|
||||
} from '@react-navigation/native'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {VIBES_FEED_URI} from '#/lib/constants'
|
||||
import {HITSLOP_20, VIBES_FEED_URI} from '#/lib/constants'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
@@ -40,7 +43,7 @@ import {List} from '#/view/com/util/List'
|
||||
import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
|
||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, ThemeProvider, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListFooter} from '#/components/Lists'
|
||||
import {RichText} from '#/components/RichText'
|
||||
@@ -272,6 +275,8 @@ function VibeOverlay({
|
||||
const insets = useSafeAreaInsets()
|
||||
const t = useTheme()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
const pushToProfile = useNonReactiveCallback(() => {
|
||||
navigation.navigate('Profile', {name: post.author.did})
|
||||
})
|
||||
@@ -288,7 +293,7 @@ function VibeOverlay({
|
||||
const dragLeftGesture = Gesture.Pan()
|
||||
.activeOffsetX([0, 10])
|
||||
.failOffsetX([-10, 0])
|
||||
.failOffsetY([-10, 10])
|
||||
.failOffsetY([-5, 5])
|
||||
.maxPointers(1)
|
||||
.onEnd(evt => {
|
||||
'worklet'
|
||||
@@ -317,12 +322,16 @@ function VibeOverlay({
|
||||
<View />
|
||||
</Button>
|
||||
<LinearGradient
|
||||
colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.8)']}
|
||||
colors={
|
||||
expanded
|
||||
? ['rgba(0,0,0,0)', 'rgba(0,0,0,0.6)', 'rgba(0,0,0,0.8)']
|
||||
: ['rgba(0,0,0,0)', 'rgba(0,0,0,0.8)']
|
||||
}
|
||||
style={[a.w_full, a.px_xl, a.py_sm, a.gap_sm]}>
|
||||
<View style={[a.flex_row, a.gap_md, a.align_center]}>
|
||||
<PreviewableUserAvatar profile={post.author} size={32} />
|
||||
<View>
|
||||
<Text style={[a.text_md, a.font_heavy]} numberOfLines={1}>
|
||||
<Text style={[a.text_md, a.font_heavy]} emoji numberOfLines={1}>
|
||||
{sanitizeDisplayName(
|
||||
post.author.displayName || post.author.handle,
|
||||
)}
|
||||
@@ -334,12 +343,14 @@ function VibeOverlay({
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<RichText
|
||||
value={richText}
|
||||
style={[a.text_sm]}
|
||||
authorHandle={post.author.handle}
|
||||
enableTags
|
||||
/>
|
||||
{record?.text?.trim() && (
|
||||
<ExpandableRichTextView
|
||||
expanded={expanded}
|
||||
setExpanded={setExpanded}
|
||||
value={richText}
|
||||
authorHandle={post.author.handle}
|
||||
/>
|
||||
)}
|
||||
{postShadow !== POST_TOMBSTONE && record && (
|
||||
<PostCtrls
|
||||
richText={richText}
|
||||
@@ -357,3 +368,63 @@ function VibeOverlay({
|
||||
</GestureDetector>
|
||||
)
|
||||
}
|
||||
|
||||
function ExpandableRichTextView({
|
||||
value,
|
||||
authorHandle,
|
||||
expanded,
|
||||
setExpanded,
|
||||
}: {
|
||||
value: RichTextAPI
|
||||
authorHandle?: string
|
||||
expanded: boolean
|
||||
setExpanded: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}) {
|
||||
const {height: screenHeight} = useWindowDimensions()
|
||||
const [constrained, setConstrained] = useState(false)
|
||||
const [contentHeight, setContentHeight] = useState(0)
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
scrollEnabled={expanded}
|
||||
onContentSizeChange={(_w, h) => {
|
||||
LayoutAnimation.configureNext({
|
||||
duration: 500,
|
||||
update: {type: 'spring', springDamping: 0.6},
|
||||
})
|
||||
setContentHeight(h)
|
||||
}}
|
||||
style={{height: Math.min(contentHeight, screenHeight * 0.5)}}
|
||||
contentContainerStyle={[
|
||||
a.gap_xs,
|
||||
expanded ? [a.align_start] : a.flex_row,
|
||||
]}>
|
||||
<RichText
|
||||
value={value}
|
||||
style={[a.text_sm, a.flex_1]}
|
||||
authorHandle={authorHandle}
|
||||
enableTags
|
||||
numberOfLines={expanded ? undefined : constrained ? 1 : 2}
|
||||
onTextLayout={evt => {
|
||||
if (!constrained && evt.nativeEvent.lines.length > 1) {
|
||||
setConstrained(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{constrained && (
|
||||
<Button
|
||||
label={expanded ? _(msg`Read less`) : _(msg`Read more`)}
|
||||
hitSlop={HITSLOP_20}
|
||||
onPress={() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setExpanded(prev => !prev)
|
||||
}}>
|
||||
<ButtonText>
|
||||
{expanded ? <Trans>Read less</Trans> : <Trans>Read more</Trans>}
|
||||
</ButtonText>
|
||||
</Button>
|
||||
)}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user