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

15
App.tsx
View File

@ -10,14 +10,13 @@ import { persistor, store } from '@root/store'
import { resetLocal } from '@root/utils/slices/instancesSlice' import { resetLocal } from '@root/utils/slices/instancesSlice'
import ThemeManager from '@utils/styles/ThemeManager' import ThemeManager from '@utils/styles/ThemeManager'
// if (__DEV__) { if (__DEV__) {
// const whyDidYouRender = require('@welldone-software/why-did-you-render') const whyDidYouRender = require('@welldone-software/why-did-you-render')
// whyDidYouRender(React, { whyDidYouRender(React, {
// trackAllPureComponents: true, trackHooks: true,
// trackHooks: true, hotReloadBufferMs: 1000
// hotReloadBufferMs: 1000 })
// }) }
// }
const queryClient = new QueryClient() const queryClient = new QueryClient()

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

View File

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

View File

@ -1,10 +1,7 @@
import React, { useState } from 'react' import React from 'react'
import { LayoutAnimation, Pressable, Text, View } from 'react-native' import { View } from 'react-native'
import ParseContent from '@components/ParseContent' import ParseContent from '@components/ParseContent'
import { useTheme } from '@utils/styles/ThemeManager'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { LinearGradient } from 'expo-linear-gradient'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
export interface Props { export interface Props {
status: Mastodon.Status status: Mastodon.Status
@ -17,11 +14,6 @@ const TimelineContent: React.FC<Props> = ({
numberOfLines, numberOfLines,
highlighted = false highlighted = false
}) => { }) => {
layoutAnimation()
const { theme } = useTheme()
const [spoilerCollapsed, setSpoilerCollapsed] = useState(false)
const lineHeight = 28
return ( return (
<> <>
{status.spoiler_text ? ( {status.spoiler_text ? (
@ -32,58 +24,16 @@ const TimelineContent: React.FC<Props> = ({
emojis={status.emojis} emojis={status.emojis}
numberOfLines={999} numberOfLines={999}
/> />
<View <View style={{ marginTop: StyleConstants.Font.Size.M }}>
style={{
flex: 1,
height: spoilerCollapsed ? StyleConstants.Font.Size.M : undefined,
marginTop: StyleConstants.Font.Size.M
}}
>
<ParseContent <ParseContent
content={status.content} content={status.content}
size={highlighted ? 'L' : 'M'} size={highlighted ? 'L' : 'M'}
emojis={status.emojis} emojis={status.emojis}
mentions={status.mentions} mentions={status.mentions}
numberOfLines={999} numberOfLines={1}
expandHint='隐藏内容'
/> />
</View> </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 <ParseContent
@ -91,7 +41,7 @@ const TimelineContent: React.FC<Props> = ({
size={highlighted ? 'L' : 'M'} size={highlighted ? 'L' : 'M'}
emojis={status.emojis} emojis={status.emojis}
mentions={status.mentions} mentions={status.mentions}
{...(numberOfLines && { numberOfLines: numberOfLines })} numberOfLines={numberOfLines}
/> />
)} )}
</> </>

View File

@ -3400,25 +3400,25 @@ gl-react-blurhash@^1.0.0:
blurhash "^1.1.3" blurhash "^1.1.3"
gl-react-expo@^4.0.1: gl-react-expo@^4.0.1:
version "4.0.1" version "4.1.0"
resolved "https://registry.yarnpkg.com/gl-react-expo/-/gl-react-expo-4.0.1.tgz#7512b65fd7e27d84f3405e737f8b334aed6b69fc" resolved "https://registry.yarnpkg.com/gl-react-expo/-/gl-react-expo-4.1.0.tgz#0f3b13fae7fe490bc72fcf59c894605293da642d"
integrity sha512-L4bwpqEolXmdc1YH8okN4qkfOW13/iLvevhJYgf2mHaLCRwhW1WDUJ18EEip8lUxaUmH9NmT9SC5DwMRiP0YSg== integrity sha512-3Q4LOBGYsNNqpLkwUsUlRrFCNLUqjVsVPKE3c6pLABfkNxyBacbDK3mg3T8XOD7jp+Vsp/Ic9McjICMsYHANJw==
dependencies: dependencies:
invariant "^2.2.4" invariant "^2.2.4"
webgltexture-loader-expo "1.0.0" webgltexture-loader-expo "1.0.0"
gl-react@^4.0.1: gl-react@^4.0.1:
version "4.0.1" version "4.1.0"
resolved "https://registry.yarnpkg.com/gl-react/-/gl-react-4.0.1.tgz#6b747084237dc2209eb3ed05162bb5941c5a6e94" resolved "https://registry.yarnpkg.com/gl-react/-/gl-react-4.1.0.tgz#b7623555a4c3ba92a1ccbbb122c28c46231cd863"
integrity sha512-/+wbFHVeh21wOS6g3v7r/zAdXoYMqn7xCB0CwLwMxBUwUsnV0jcvGS+8HDoup9Yd20xGt77p2c3gmrt2XtEEgg== integrity sha512-hTvxQHN2wxLfbA4c6mRcMdXWjA0/8UY2Sxn5eNre0DOrcauBj/+vMeAMbL2+zwE1BfVYnraE4cvKJTIzFIeBLw==
dependencies: dependencies:
gl-shader "^4.2.1" gl-shader "^4.2.1"
invariant "^2.2.4" invariant "^2.2.4"
ndarray "^1.0.18" ndarray "^1.0.19"
prop-types "^15.7.2" prop-types "^15.7.2"
typedarray-pool "^1.1.0" typedarray-pool "^1.2.0"
webgltexture-loader "1.0.0" webgltexture-loader "1.0.0"
webgltexture-loader-ndarray "1.0.0" webgltexture-loader-ndarray "1.1.0"
gl-shader@^4.2.1: gl-shader@^4.2.1:
version "4.2.1" version "4.2.1"
@ -4811,7 +4811,7 @@ ndarray-ops@^1.2.2:
dependencies: dependencies:
cwise-compiler "^1.0.0" cwise-compiler "^1.0.0"
ndarray@^1.0.18: ndarray@^1.0.19:
version "1.0.19" version "1.0.19"
resolved "https://registry.yarnpkg.com/ndarray/-/ndarray-1.0.19.tgz#6785b5f5dfa58b83e31ae5b2a058cfd1ab3f694e" resolved "https://registry.yarnpkg.com/ndarray/-/ndarray-1.0.19.tgz#6785b5f5dfa58b83e31ae5b2a058cfd1ab3f694e"
integrity sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ== integrity sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==
@ -6365,7 +6365,7 @@ type-fest@^0.7.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
typedarray-pool@^1.1.0: typedarray-pool@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/typedarray-pool/-/typedarray-pool-1.2.0.tgz#e7e90720144ba02b9ed660438af6f3aacfe33ac3" resolved "https://registry.yarnpkg.com/typedarray-pool/-/typedarray-pool-1.2.0.tgz#e7e90720144ba02b9ed660438af6f3aacfe33ac3"
integrity sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ== integrity sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ==
@ -6613,14 +6613,14 @@ webgltexture-loader-expo@1.0.0:
dependencies: dependencies:
webgltexture-loader "^1.0.0" webgltexture-loader "^1.0.0"
webgltexture-loader-ndarray@1.0.0: webgltexture-loader-ndarray@1.1.0:
version "1.0.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/webgltexture-loader-ndarray/-/webgltexture-loader-ndarray-1.0.0.tgz#844181c4cbe0cbc066750d66412c11d9205ad79a" resolved "https://registry.yarnpkg.com/webgltexture-loader-ndarray/-/webgltexture-loader-ndarray-1.1.0.tgz#20869150e79dfb6190cbd6bedafacda262e29a5e"
integrity sha512-ocEStCCkihBu+PyMr14ke2vv08c5wVObP5mH9AuPICcBvaS8zfxvMpoGc9dIxOxBC1reI/lzmpYxpYdoY/Ke8g== integrity sha512-t9Q4x5do5feHjMOJLCg8UdQVx1eInnWXvgAqaL9NJJSL5pzI59Ynx3ZZSw3QqGxj15iRbYij7vK8BCIDyp5EZA==
dependencies: dependencies:
ndarray "^1.0.18" ndarray "^1.0.19"
ndarray-ops "^1.2.2" ndarray-ops "^1.2.2"
typedarray-pool "^1.1.0" typedarray-pool "^1.2.0"
webgltexture-loader "^1.0.0" webgltexture-loader "^1.0.0"
webgltexture-loader@1.0.0, webgltexture-loader@^1.0.0: webgltexture-loader@1.0.0, webgltexture-loader@^1.0.0: