1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Rewrite expandable content

This commit is contained in:
Zhiyuan Zheng
2020-12-26 12:59:16 +01:00
parent f192939671
commit 7a15cceb28
5 changed files with 101 additions and 124 deletions

View File

@ -1,4 +1,5 @@
import React, { useCallback, useState } from 'react'
import { LinearGradient } from 'expo-linear-gradient'
import React, { useCallback, useMemo, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import HTMLView from 'react-native-htmlview'
import { useNavigation } from '@react-navigation/native'
@ -6,8 +7,8 @@ import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
import { useTheme } from '@utils/styles/ThemeManager'
import { Feather } from '@expo/vector-icons'
import { StyleConstants } from '@utils/styles/constants'
import { LinearGradient } from 'expo-linear-gradient'
import openLink from '@root/utils/openLink'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
// Prevent going to the same hashtag multiple times
const renderNode = ({
@ -96,7 +97,7 @@ const renderNode = ({
} else {
if (node.name === 'p') {
if (!node.children.length) {
return <View key={index}></View> // bug when the tag is empty
return <View key={index} /> // bug when the tag is empty
}
}
}
@ -109,6 +110,7 @@ export interface Props {
mentions?: Mastodon.Mention[]
showFullLink?: boolean
numberOfLines?: number
expandHint?: string
}
const ParseContent: React.FC<Props> = ({
@ -117,7 +119,8 @@ const ParseContent: React.FC<Props> = ({
emojis,
mentions,
showFullLink = false,
numberOfLines = 10
numberOfLines = 10,
expandHint = '全文'
}) => {
const navigation = useNavigation()
const { theme } = useTheme()
@ -136,55 +139,76 @@ const ParseContent: React.FC<Props> = ({
[]
)
const textComponent = useCallback(({ children }) => {
return emojis && children ? (
<Emojis
content={children.toString()}
emojis={emojis}
size={StyleConstants.Font.Size[size]}
/>
) : (
<Text>{children}</Text>
)
if (children) {
return emojis ? (
<Emojis
content={children.toString()}
emojis={emojis}
size={StyleConstants.Font.Size[size]}
/>
) : (
<Text>{children}</Text>
)
} else {
return null
}
}, [])
const rootComponent = useCallback(({ children }) => {
const { theme } = useTheme()
const [textLoaded, setTextLoaded] = useState(false)
const [totalLines, setTotalLines] = useState<number | undefined>()
const [lineHeight, setLineHeight] = useState<number | undefined>()
const [shownLines, setShownLines] = useState(numberOfLines)
// layoutAnimation()
const lineHeight = StyleConstants.Font.LineHeight[size]
const [heightOriginal, setHeightOriginal] = useState<number>()
const [heightTruncated, setHeightTruncated] = useState<number>()
const [allowExpand, setAllowExpand] = useState(false)
const [showAllText, setShowAllText] = useState(false)
const calNumberOfLines = useMemo(() => {
if (heightOriginal) {
if (!heightTruncated) {
return numberOfLines
} else {
if (allowExpand && !showAllText) {
return numberOfLines
} else {
return undefined
}
}
} else {
return undefined
}
}, [heightOriginal, heightTruncated, allowExpand, showAllText])
return (
<>
<View>
<Text
numberOfLines={
totalLines && totalLines > numberOfLines ? shownLines : totalLines
}
style={{ lineHeight: StyleConstants.Font.LineHeight[size] }}
onTextLayout={({ nativeEvent }) => {
if (!textLoaded) {
setTextLoaded(true)
setTotalLines(nativeEvent.lines.length)
setLineHeight(nativeEvent.lines[0].height)
style={{ lineHeight }}
children={children}
numberOfLines={calNumberOfLines}
onLayout={({ nativeEvent }) => {
if (!heightOriginal) {
setHeightOriginal(nativeEvent.layout.height)
} else {
if (!heightTruncated) {
setHeightTruncated(nativeEvent.layout.height)
} else {
if (heightOriginal > heightTruncated) {
setAllowExpand(true)
}
}
}
}}
>
{children}
</Text>
{totalLines && lineHeight && totalLines > shownLines && (
/>
{allowExpand && (
<Pressable
onPress={() => {
setShownLines(totalLines)
}}
style={{
marginTop: -lineHeight
}}
onPress={() => setShowAllText(!showAllText)}
style={{ marginTop: showAllText ? 0 : -lineHeight * 1.25 }}
>
<LinearGradient
colors={[
theme.backgroundGradientStart,
theme.backgroundGradientEnd
]}
locations={[0, lineHeight / (StyleConstants.Font.Size.S * 5)]}
locations={[0, lineHeight / (StyleConstants.Font.Size.S * 4)]}
style={{
paddingTop: StyleConstants.Font.Size.S * 2,
paddingBottom: StyleConstants.Font.Size.S
@ -197,12 +221,12 @@ const ParseContent: React.FC<Props> = ({
color: theme.primary
}}
>
{`${showAllText ? '折叠' : '展开'}${expandHint}`}
</Text>
</LinearGradient>
</Pressable>
)}
</>
</View>
)
}, [])

View File

@ -55,7 +55,8 @@ const Timeline: React.FC<Props> = ({
fetchPreviousPage,
isFetchingPreviousPage,
hasNextPage,
fetchNextPage
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery(queryKey, timelineFetch, {
getPreviousPageParam: firstPage => {
return firstPage.toots.length
@ -133,7 +134,10 @@ const Timeline: React.FC<Props> = ({
() => <TimelineEmpty status={status} refetch={refetch} />,
[status]
)
const onEndReached = useCallback(() => !disableRefresh && fetchNextPage(), [])
const onEndReached = useCallback(
() => !disableRefresh && !isFetchingNextPage && fetchNextPage(),
[isFetchingNextPage]
)
const ListFooterComponent = useCallback(
() => <TimelineEnd hasNextPage={!disableRefresh ? hasNextPage : false} />,
[hasNextPage]

View File

@ -1,10 +1,7 @@
import React, { useState } from 'react'
import { LayoutAnimation, Pressable, Text, View } from 'react-native'
import React from 'react'
import { View } from 'react-native'
import ParseContent from '@components/ParseContent'
import { useTheme } from '@utils/styles/ThemeManager'
import { StyleConstants } from '@utils/styles/constants'
import { LinearGradient } from 'expo-linear-gradient'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
export interface Props {
status: Mastodon.Status
@ -17,11 +14,6 @@ const TimelineContent: React.FC<Props> = ({
numberOfLines,
highlighted = false
}) => {
layoutAnimation()
const { theme } = useTheme()
const [spoilerCollapsed, setSpoilerCollapsed] = useState(false)
const lineHeight = 28
return (
<>
{status.spoiler_text ? (
@ -32,58 +24,16 @@ const TimelineContent: React.FC<Props> = ({
emojis={status.emojis}
numberOfLines={999}
/>
<View
style={{
flex: 1,
height: spoilerCollapsed ? StyleConstants.Font.Size.M : undefined,
marginTop: StyleConstants.Font.Size.M
}}
>
<View style={{ marginTop: StyleConstants.Font.Size.M }}>
<ParseContent
content={status.content}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
numberOfLines={999}
numberOfLines={1}
expandHint='隐藏内容'
/>
</View>
<Pressable
onPress={() => setSpoilerCollapsed(!spoilerCollapsed)}
style={{
marginTop: spoilerCollapsed ? -lineHeight : 0
}}
>
<LinearGradient
{...(spoilerCollapsed
? {
colors: [
theme.backgroundGradientStart,
theme.backgroundGradientEnd
],
locations: [
0,
lineHeight / (StyleConstants.Font.Size.S * 4)
]
}
: {
colors: ['rgba(0, 0, 0, 0)']
})}
style={{
paddingTop: StyleConstants.Font.Size.S * 2,
paddingBottom: StyleConstants.Font.Size.S
}}
>
<Text
style={{
textAlign: 'center',
fontSize: StyleConstants.Font.Size.S,
color: theme.primary
}}
>
{spoilerCollapsed ? '展开' : '收起'}
</Text>
</LinearGradient>
</Pressable>
</>
) : (
<ParseContent
@ -91,7 +41,7 @@ const TimelineContent: React.FC<Props> = ({
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
{...(numberOfLines && { numberOfLines: numberOfLines })}
numberOfLines={numberOfLines}
/>
)}
</>