mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Remove most React memorization
Though added memo for timeline components making them (almost) pure
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { AccessibilityProps, Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
||||
import { Flow } from 'react-native-animated-spinkit'
|
||||
import CustomText from './Text'
|
||||
@ -48,18 +48,16 @@ const Button: React.FC<Props> = ({
|
||||
overlay = false,
|
||||
onPress
|
||||
}) => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
|
||||
const loadingSpinkit = useMemo(
|
||||
() => (
|
||||
const loadingSpinkit = () =>
|
||||
loading ? (
|
||||
<View style={{ position: 'absolute' }}>
|
||||
<Flow size={StyleConstants.Font.Size[size]} color={colors.secondary} />
|
||||
</View>
|
||||
),
|
||||
[theme]
|
||||
)
|
||||
) : null
|
||||
|
||||
const mainColor = useMemo(() => {
|
||||
const mainColor = () => {
|
||||
if (selected) {
|
||||
return colors.blue
|
||||
} else if (overlay) {
|
||||
@ -73,29 +71,21 @@ const Button: React.FC<Props> = ({
|
||||
return colors.primaryDefault
|
||||
}
|
||||
}
|
||||
}, [theme, disabled, loading, selected])
|
||||
}
|
||||
|
||||
const colorBackground = useMemo(() => {
|
||||
if (overlay) {
|
||||
return colors.backgroundOverlayInvert
|
||||
} else {
|
||||
return colors.backgroundDefault
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
const children = useMemo(() => {
|
||||
const children = () => {
|
||||
switch (type) {
|
||||
case 'icon':
|
||||
return (
|
||||
<>
|
||||
<Icon
|
||||
name={content}
|
||||
color={mainColor}
|
||||
color={mainColor()}
|
||||
strokeWidth={strokeWidth}
|
||||
style={{ opacity: loading ? 0 : 1 }}
|
||||
size={StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1)}
|
||||
/>
|
||||
{loading ? loadingSpinkit : null}
|
||||
{loadingSpinkit()}
|
||||
</>
|
||||
)
|
||||
case 'text':
|
||||
@ -103,7 +93,7 @@ const Button: React.FC<Props> = ({
|
||||
<>
|
||||
<CustomText
|
||||
style={{
|
||||
color: mainColor,
|
||||
color: mainColor(),
|
||||
fontSize: StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1),
|
||||
opacity: loading ? 0 : 1
|
||||
}}
|
||||
@ -111,11 +101,11 @@ const Button: React.FC<Props> = ({
|
||||
children={content}
|
||||
testID='text'
|
||||
/>
|
||||
{loading ? loadingSpinkit : null}
|
||||
{loadingSpinkit()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}, [theme, content, loading, disabled])
|
||||
}
|
||||
|
||||
const [layoutHeight, setLayoutHeight] = useState<number | undefined>()
|
||||
|
||||
@ -136,8 +126,8 @@ const Button: React.FC<Props> = ({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: overlay ? 0 : 1,
|
||||
borderColor: mainColor,
|
||||
backgroundColor: colorBackground,
|
||||
borderColor: mainColor(),
|
||||
backgroundColor: overlay ? colors.backgroundOverlayInvert : colors.backgroundDefault,
|
||||
paddingVertical: StyleConstants.Spacing[spacing],
|
||||
paddingHorizontal: StyleConstants.Spacing[spacing] + StyleConstants.Spacing.XS,
|
||||
width: round && layoutHeight ? layoutHeight : undefined
|
||||
@ -149,7 +139,7 @@ const Button: React.FC<Props> = ({
|
||||
})}
|
||||
testID='base'
|
||||
onPress={onPress}
|
||||
children={children}
|
||||
children={children()}
|
||||
disabled={selected || disabled || loading}
|
||||
/>
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
AccessibilityProps,
|
||||
Image,
|
||||
@ -65,7 +65,7 @@ const GracefullyImage = ({
|
||||
}
|
||||
}
|
||||
|
||||
const blurhashView = useMemo(() => {
|
||||
const blurhashView = () => {
|
||||
if (hidden || !imageLoaded) {
|
||||
if (blurhash) {
|
||||
return <Blurhash decodeAsync blurhash={blurhash} style={styles.placeholder} />
|
||||
@ -75,7 +75,7 @@ const GracefullyImage = ({
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}, [hidden, imageLoaded])
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
@ -98,7 +98,7 @@ const GracefullyImage = ({
|
||||
style={[{ flex: 1 }, imageStyle]}
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
{blurhashView}
|
||||
{blurhashView()}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { PropsWithChildren, useCallback, useState } from 'react'
|
||||
import React, { PropsWithChildren, useState } from 'react'
|
||||
import { Dimensions, Pressable, View } from 'react-native'
|
||||
import Sparkline from './Sparkline'
|
||||
import CustomText from './Text'
|
||||
@ -21,9 +21,9 @@ const ComponentHashtag: React.FC<PropsWithChildren & Props> = ({
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
const onPress = () => {
|
||||
navigation.push('Tab-Shared-Hashtag', { hashtag: hashtag.name })
|
||||
}, [])
|
||||
}
|
||||
|
||||
const padding = StyleConstants.Spacing.Global.PagePadding
|
||||
const width = Dimensions.get('window').width / 4
|
||||
|
@ -2,7 +2,7 @@ import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { Pressable } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
@ -21,9 +21,9 @@ const HeaderLeft: React.FC<Props> = ({
|
||||
background = false,
|
||||
onPress
|
||||
}) => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
|
||||
const children = useMemo(() => {
|
||||
const children = () => {
|
||||
switch (type) {
|
||||
case 'icon':
|
||||
return (
|
||||
@ -35,31 +35,23 @@ const HeaderLeft: React.FC<Props> = ({
|
||||
)
|
||||
case 'text':
|
||||
return (
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
style={{ color: colors.primaryDefault }}
|
||||
children={content}
|
||||
/>
|
||||
<CustomText fontStyle='M' style={{ color: colors.primaryDefault }} children={content} />
|
||||
)
|
||||
}
|
||||
}, [theme])
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
children={children}
|
||||
children={children()}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: background
|
||||
? colors.backgroundOverlayDefault
|
||||
: undefined,
|
||||
backgroundColor: background ? colors.backgroundOverlayDefault : undefined,
|
||||
minHeight: 44,
|
||||
minWidth: 44,
|
||||
marginLeft: native
|
||||
? -StyleConstants.Spacing.S
|
||||
: StyleConstants.Spacing.S,
|
||||
marginLeft: native ? -StyleConstants.Spacing.S : StyleConstants.Spacing.S,
|
||||
...(type === 'icon' && {
|
||||
borderRadius: 100
|
||||
}),
|
||||
|
@ -2,7 +2,7 @@ import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { AccessibilityProps, Pressable, View } from 'react-native'
|
||||
import { Flow } from 'react-native-animated-spinkit'
|
||||
|
||||
@ -40,16 +40,14 @@ const HeaderRight: React.FC<Props> = ({
|
||||
}) => {
|
||||
const { colors, theme } = useTheme()
|
||||
|
||||
const loadingSpinkit = useMemo(
|
||||
() => (
|
||||
const loadingSpinkit = () =>
|
||||
loading ? (
|
||||
<View style={{ position: 'absolute' }}>
|
||||
<Flow size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
||||
</View>
|
||||
),
|
||||
[theme]
|
||||
)
|
||||
) : null
|
||||
|
||||
const children = useMemo(() => {
|
||||
const children = () => {
|
||||
switch (type) {
|
||||
case 'icon':
|
||||
return (
|
||||
@ -60,7 +58,7 @@ const HeaderRight: React.FC<Props> = ({
|
||||
size={StyleConstants.Spacing.M * 1.25}
|
||||
color={disabled ? colors.secondary : destructive ? colors.red : colors.primaryDefault}
|
||||
/>
|
||||
{loading && loadingSpinkit}
|
||||
{loadingSpinkit()}
|
||||
</>
|
||||
)
|
||||
case 'text':
|
||||
@ -79,11 +77,11 @@ const HeaderRight: React.FC<Props> = ({
|
||||
}}
|
||||
children={content}
|
||||
/>
|
||||
{loading && loadingSpinkit}
|
||||
{loadingSpinkit()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}, [theme, loading, disabled])
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
@ -92,7 +90,7 @@ const HeaderRight: React.FC<Props> = ({
|
||||
accessibilityRole='button'
|
||||
accessibilityState={accessibilityState}
|
||||
onPress={onPress}
|
||||
children={children}
|
||||
children={children()}
|
||||
disabled={disabled || loading}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
|
@ -4,7 +4,7 @@ import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { ColorDefinitions } from '@utils/styles/themes'
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { Flow } from 'react-native-animated-spinkit'
|
||||
import { State, Switch, TapGestureHandler } from 'react-native-gesture-handler'
|
||||
@ -47,15 +47,6 @@ const MenuRow: React.FC<Props> = ({
|
||||
const { colors, theme } = useTheme()
|
||||
const { screenReaderEnabled } = useAccessibility()
|
||||
|
||||
const loadingSpinkit = useMemo(
|
||||
() => (
|
||||
<View style={{ position: 'absolute' }}>
|
||||
<Flow size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
||||
</View>
|
||||
),
|
||||
[theme]
|
||||
)
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{ minHeight: 50 }}
|
||||
@ -157,7 +148,11 @@ const MenuRow: React.FC<Props> = ({
|
||||
style={{ marginLeft: 8, opacity: loading ? 0 : 1 }}
|
||||
/>
|
||||
) : null}
|
||||
{loading && loadingSpinkit}
|
||||
{loading ? (
|
||||
<View style={{ position: 'absolute' }}>
|
||||
<Flow size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
@ -20,81 +20,85 @@ export interface Props {
|
||||
style?: TextStyle
|
||||
}
|
||||
|
||||
const ParseEmojis = React.memo(
|
||||
({ content, emojis, size = 'M', adaptiveSize = false, fontBold = false, style }: Props) => {
|
||||
if (!content) return null
|
||||
const ParseEmojis: React.FC<Props> = ({
|
||||
content,
|
||||
emojis,
|
||||
size = 'M',
|
||||
adaptiveSize = false,
|
||||
fontBold = false,
|
||||
style
|
||||
}) => {
|
||||
if (!content) return null
|
||||
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
const adaptedLineheight = adaptiveScale(
|
||||
StyleConstants.Font.LineHeight[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
const adaptedLineheight = adaptiveScale(
|
||||
StyleConstants.Font.LineHeight[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors, theme } = useTheme()
|
||||
|
||||
return (
|
||||
<CustomText
|
||||
style={[
|
||||
{
|
||||
color: colors.primaryDefault,
|
||||
fontSize: adaptedFontsize,
|
||||
lineHeight: adaptedLineheight
|
||||
},
|
||||
style
|
||||
]}
|
||||
fontWeight={fontBold ? 'Bold' : undefined}
|
||||
>
|
||||
{emojis ? (
|
||||
content
|
||||
.split(regexEmoji)
|
||||
.filter(f => f)
|
||||
.map((str, i) => {
|
||||
if (str.match(regexEmoji)) {
|
||||
const emojiShortcode = str.split(regexEmoji)[1]
|
||||
const emojiIndex = emojis.findIndex(emoji => {
|
||||
return emojiShortcode === `:${emoji.shortcode}:`
|
||||
})
|
||||
if (emojiIndex === -1) {
|
||||
return <CustomText key={emojiShortcode + i}>{emojiShortcode}</CustomText>
|
||||
} else {
|
||||
const uri = reduceMotionEnabled
|
||||
? emojis[emojiIndex].static_url
|
||||
: emojis[emojiIndex].url
|
||||
if (validUrl.isHttpsUri(uri)) {
|
||||
return (
|
||||
<CustomText key={emojiShortcode + i}>
|
||||
{i === 0 ? ' ' : undefined}
|
||||
<FastImage
|
||||
source={{ uri }}
|
||||
style={{
|
||||
width: adaptedFontsize,
|
||||
height: adaptedFontsize,
|
||||
transform: [{ translateY: Platform.OS === 'ios' ? -1 : 2 }]
|
||||
}}
|
||||
/>
|
||||
</CustomText>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return (
|
||||
<CustomText
|
||||
style={[
|
||||
{
|
||||
color: colors.primaryDefault,
|
||||
fontSize: adaptedFontsize,
|
||||
lineHeight: adaptedLineheight
|
||||
},
|
||||
style
|
||||
]}
|
||||
fontWeight={fontBold ? 'Bold' : undefined}
|
||||
>
|
||||
{emojis ? (
|
||||
content
|
||||
.split(regexEmoji)
|
||||
.filter(f => f)
|
||||
.map((str, i) => {
|
||||
if (str.match(regexEmoji)) {
|
||||
const emojiShortcode = str.split(regexEmoji)[1]
|
||||
const emojiIndex = emojis.findIndex(emoji => {
|
||||
return emojiShortcode === `:${emoji.shortcode}:`
|
||||
})
|
||||
if (emojiIndex === -1) {
|
||||
return <CustomText key={emojiShortcode + i}>{emojiShortcode}</CustomText>
|
||||
} else {
|
||||
return <CustomText key={i}>{str}</CustomText>
|
||||
const uri = reduceMotionEnabled
|
||||
? emojis[emojiIndex].static_url
|
||||
: emojis[emojiIndex].url
|
||||
if (validUrl.isHttpsUri(uri)) {
|
||||
return (
|
||||
<CustomText key={emojiShortcode + i}>
|
||||
{i === 0 ? ' ' : undefined}
|
||||
<FastImage
|
||||
source={{ uri }}
|
||||
style={{
|
||||
width: adaptedFontsize,
|
||||
height: adaptedFontsize,
|
||||
transform: [{ translateY: Platform.OS === 'ios' ? -1 : 2 }]
|
||||
}}
|
||||
/>
|
||||
</CustomText>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
) : (
|
||||
<CustomText>{content}</CustomText>
|
||||
)}
|
||||
</CustomText>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.content === next.content && prev.style?.color === next.style?.color
|
||||
)
|
||||
} else {
|
||||
return <CustomText key={i}>{str}</CustomText>
|
||||
}
|
||||
})
|
||||
) : (
|
||||
<CustomText>{content}</CustomText>
|
||||
)}
|
||||
</CustomText>
|
||||
)
|
||||
}
|
||||
|
||||
export default ParseEmojis
|
||||
|
@ -34,237 +34,233 @@ export interface Props {
|
||||
setSpoilerExpanded?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
const ParseHTML = React.memo(
|
||||
({
|
||||
content,
|
||||
size = 'M',
|
||||
textStyles,
|
||||
adaptiveSize = false,
|
||||
emojis,
|
||||
mentions,
|
||||
tags,
|
||||
showFullLink = false,
|
||||
numberOfLines = 10,
|
||||
expandHint,
|
||||
highlighted = false,
|
||||
disableDetails = false,
|
||||
selectable = false,
|
||||
setSpoilerExpanded
|
||||
}: Props) => {
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
const adaptedLineheight = adaptiveScale(
|
||||
StyleConstants.Font.LineHeight[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
const ParseHTML: React.FC<Props> = ({
|
||||
content,
|
||||
size = 'M',
|
||||
textStyles,
|
||||
adaptiveSize = false,
|
||||
emojis,
|
||||
mentions,
|
||||
tags,
|
||||
showFullLink = false,
|
||||
numberOfLines = 10,
|
||||
expandHint,
|
||||
highlighted = false,
|
||||
disableDetails = false,
|
||||
selectable = false,
|
||||
setSpoilerExpanded
|
||||
}) => {
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
const adaptedLineheight = adaptiveScale(
|
||||
StyleConstants.Font.LineHeight[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
const { params } = useRoute()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('componentParse')
|
||||
if (!expandHint) {
|
||||
expandHint = t('HTML.defaultHint')
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
const { params } = useRoute()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('componentParse')
|
||||
if (!expandHint) {
|
||||
expandHint = t('HTML.defaultHint')
|
||||
}
|
||||
|
||||
if (disableDetails) {
|
||||
numberOfLines = 4
|
||||
}
|
||||
|
||||
const followedTags = useFollowedTagsQuery()
|
||||
|
||||
const [totalLines, setTotalLines] = useState<number>()
|
||||
const [expanded, setExpanded] = useState(highlighted)
|
||||
|
||||
const document = parseDocument(content)
|
||||
const unwrapNode = (node: ChildNode): string => {
|
||||
switch (node.type) {
|
||||
case ElementType.Text:
|
||||
return node.data
|
||||
case ElementType.Tag:
|
||||
if (node.name === 'span') {
|
||||
if (node.attribs.class?.includes('invisible')) return ''
|
||||
if (node.attribs.class?.includes('ellipsis'))
|
||||
return node.children.map(child => unwrapNode(child)).join('') + '...'
|
||||
}
|
||||
return node.children.map(child => unwrapNode(child)).join('')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
|
||||
if (disableDetails) {
|
||||
numberOfLines = 4
|
||||
}
|
||||
|
||||
const followedTags = useFollowedTagsQuery()
|
||||
|
||||
const [totalLines, setTotalLines] = useState<number>()
|
||||
const [expanded, setExpanded] = useState(highlighted)
|
||||
|
||||
const document = parseDocument(content)
|
||||
const unwrapNode = (node: ChildNode): string => {
|
||||
switch (node.type) {
|
||||
case ElementType.Text:
|
||||
return node.data
|
||||
case ElementType.Tag:
|
||||
if (node.name === 'span') {
|
||||
if (node.attribs.class?.includes('invisible')) return ''
|
||||
if (node.attribs.class?.includes('ellipsis'))
|
||||
return node.children.map(child => unwrapNode(child)).join('') + '...'
|
||||
}
|
||||
return node.children.map(child => unwrapNode(child)).join('')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
const renderNode = (node: ChildNode, index: number) => {
|
||||
switch (node.type) {
|
||||
case ElementType.Text:
|
||||
return (
|
||||
<ParseEmojis
|
||||
key={index}
|
||||
content={node.data}
|
||||
emojis={emojis}
|
||||
size={size}
|
||||
adaptiveSize={adaptiveSize}
|
||||
/>
|
||||
)
|
||||
case ElementType.Tag:
|
||||
switch (node.name) {
|
||||
case 'a':
|
||||
const classes = node.attribs.class
|
||||
const href = node.attribs.href
|
||||
if (classes) {
|
||||
if (classes.includes('hashtag')) {
|
||||
const tag = href.match(new RegExp(/\/tags?\/(.*)/, 'i'))?.[1]
|
||||
const paramsHashtag = (params as { hashtag: Mastodon.Tag['name'] } | undefined)
|
||||
?.hashtag
|
||||
const sameHashtag = paramsHashtag === tag
|
||||
const isFollowing = followedTags.data?.pages[0]?.body.find(t => t.name === tag)
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={[
|
||||
{ color: tag?.length ? colors.blue : colors.red },
|
||||
isFollowing
|
||||
? {
|
||||
textDecorationColor: tag?.length ? colors.blue : colors.red,
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationStyle: 'dotted'
|
||||
}
|
||||
: null
|
||||
]}
|
||||
onPress={() =>
|
||||
tag?.length &&
|
||||
!disableDetails &&
|
||||
!sameHashtag &&
|
||||
navigation.push('Tab-Shared-Hashtag', { hashtag: tag })
|
||||
}
|
||||
children={node.children.map(unwrapNode).join('')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (classes.includes('mention') && mentions?.length) {
|
||||
const mentionIndex = mentions.findIndex(mention => mention.url === href)
|
||||
const paramsAccount = (params as { account: Mastodon.Account } | undefined)
|
||||
?.account
|
||||
const sameAccount = paramsAccount?.id === mentions[mentionIndex]?.id
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={{ color: mentionIndex > -1 ? colors.blue : undefined }}
|
||||
onPress={() =>
|
||||
mentionIndex > -1 &&
|
||||
!disableDetails &&
|
||||
!sameAccount &&
|
||||
navigation.push('Tab-Shared-Account', { account: mentions[mentionIndex] })
|
||||
}
|
||||
children={node.children.map(unwrapNode).join('')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const content = node.children.map(child => unwrapNode(child)).join('')
|
||||
const shouldBeTag = tags && tags.find(tag => `#${tag.name}` === content)
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={{ color: colors.blue }}
|
||||
onPress={async () => {
|
||||
if (!disableDetails) {
|
||||
if (shouldBeTag) {
|
||||
navigation.push('Tab-Shared-Hashtag', {
|
||||
hashtag: content.substring(1)
|
||||
})
|
||||
} else {
|
||||
await openLink(href, navigation)
|
||||
}
|
||||
}
|
||||
}}
|
||||
children={content !== href ? content : showFullLink ? href : content}
|
||||
/>
|
||||
)
|
||||
break
|
||||
case 'p':
|
||||
if (index < document.children.length - 1) {
|
||||
}
|
||||
const renderNode = (node: ChildNode, index: number) => {
|
||||
switch (node.type) {
|
||||
case ElementType.Text:
|
||||
return (
|
||||
<ParseEmojis
|
||||
key={index}
|
||||
content={node.data}
|
||||
emojis={emojis}
|
||||
size={size}
|
||||
adaptiveSize={adaptiveSize}
|
||||
/>
|
||||
)
|
||||
case ElementType.Tag:
|
||||
switch (node.name) {
|
||||
case 'a':
|
||||
const classes = node.attribs.class
|
||||
const href = node.attribs.href
|
||||
if (classes) {
|
||||
if (classes.includes('hashtag')) {
|
||||
const tag = href.match(new RegExp(/\/tags?\/(.*)/, 'i'))?.[1]
|
||||
const paramsHashtag = (params as { hashtag: Mastodon.Tag['name'] } | undefined)
|
||||
?.hashtag
|
||||
const sameHashtag = paramsHashtag === tag
|
||||
const isFollowing = followedTags.data?.pages[0]?.body.find(t => t.name === tag)
|
||||
return (
|
||||
<Text key={index}>
|
||||
{node.children.map((c, i) => renderNode(c, i))}
|
||||
<Text style={{ lineHeight: adaptedLineheight / 2 }}>{'\n\n'}</Text>
|
||||
</Text>
|
||||
<Text
|
||||
key={index}
|
||||
style={[
|
||||
{ color: tag?.length ? colors.blue : colors.red },
|
||||
isFollowing
|
||||
? {
|
||||
textDecorationColor: tag?.length ? colors.blue : colors.red,
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationStyle: 'dotted'
|
||||
}
|
||||
: null
|
||||
]}
|
||||
onPress={() =>
|
||||
tag?.length &&
|
||||
!disableDetails &&
|
||||
!sameHashtag &&
|
||||
navigation.push('Tab-Shared-Hashtag', { hashtag: tag })
|
||||
}
|
||||
children={node.children.map(unwrapNode).join('')}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return <Text key={index} children={node.children.map((c, i) => renderNode(c, i))} />
|
||||
}
|
||||
default:
|
||||
if (classes.includes('mention') && mentions?.length) {
|
||||
const mentionIndex = mentions.findIndex(mention => mention.url === href)
|
||||
const paramsAccount = (params as { account: Mastodon.Account } | undefined)?.account
|
||||
const sameAccount = paramsAccount?.id === mentions[mentionIndex]?.id
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={{ color: mentionIndex > -1 ? colors.blue : undefined }}
|
||||
onPress={() =>
|
||||
mentionIndex > -1 &&
|
||||
!disableDetails &&
|
||||
!sameAccount &&
|
||||
navigation.push('Tab-Shared-Account', { account: mentions[mentionIndex] })
|
||||
}
|
||||
children={node.children.map(unwrapNode).join('')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const content = node.children.map(child => unwrapNode(child)).join('')
|
||||
const shouldBeTag = tags && tags.find(tag => `#${tag.name}` === content)
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={{ color: colors.blue }}
|
||||
onPress={async () => {
|
||||
if (!disableDetails) {
|
||||
if (shouldBeTag) {
|
||||
navigation.push('Tab-Shared-Hashtag', {
|
||||
hashtag: content.substring(1)
|
||||
})
|
||||
} else {
|
||||
await openLink(href, navigation)
|
||||
}
|
||||
}
|
||||
}}
|
||||
children={content !== href ? content : showFullLink ? href : content}
|
||||
/>
|
||||
)
|
||||
break
|
||||
case 'p':
|
||||
if (index < document.children.length - 1) {
|
||||
return (
|
||||
<Text key={index}>
|
||||
{node.children.map((c, i) => renderNode(c, i))}
|
||||
<Text style={{ lineHeight: adaptedLineheight / 2 }}>{'\n\n'}</Text>
|
||||
</Text>
|
||||
)
|
||||
} else {
|
||||
return <Text key={index} children={node.children.map((c, i) => renderNode(c, i))} />
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
default:
|
||||
return <Text key={index} children={node.children.map((c, i) => renderNode(c, i))} />
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View style={{ overflow: 'hidden' }}>
|
||||
{(!disableDetails && typeof totalLines === 'number') || numberOfLines === 1 ? (
|
||||
<Pressable
|
||||
accessibilityLabel={t('HTML.accessibilityHint')}
|
||||
onPress={() => {
|
||||
layoutAnimation()
|
||||
setExpanded(!expanded)
|
||||
if (setSpoilerExpanded) {
|
||||
setSpoilerExpanded(!expanded)
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: 44,
|
||||
backgroundColor: colors.backgroundDefault
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
...StyleConstants.FontStyle.S,
|
||||
color: colors.primaryDefault,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
children={t('HTML.expanded', {
|
||||
hint: expandHint,
|
||||
moreLines:
|
||||
numberOfLines > 1 && typeof totalLines === 'number'
|
||||
? t('HTML.moreLines', { count: totalLines - numberOfLines })
|
||||
: ''
|
||||
})}
|
||||
/>
|
||||
<Icon
|
||||
name={expanded ? 'Minimize2' : 'Maximize2'}
|
||||
color={colors.primaryDefault}
|
||||
strokeWidth={2}
|
||||
size={StyleConstants.Font.Size[size]}
|
||||
/>
|
||||
</Pressable>
|
||||
) : null}
|
||||
<Text
|
||||
children={document.children.map(renderNode)}
|
||||
onTextLayout={({ nativeEvent }) => {
|
||||
if (numberOfLines === 1 || nativeEvent.lines.length >= numberOfLines + 5) {
|
||||
setTotalLines(nativeEvent.lines.length)
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<View style={{ overflow: 'hidden' }}>
|
||||
{(!disableDetails && typeof totalLines === 'number') || numberOfLines === 1 ? (
|
||||
<Pressable
|
||||
accessibilityLabel={t('HTML.accessibilityHint')}
|
||||
onPress={() => {
|
||||
layoutAnimation()
|
||||
setExpanded(!expanded)
|
||||
if (setSpoilerExpanded) {
|
||||
setSpoilerExpanded(!expanded)
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
fontSize: adaptedFontsize,
|
||||
lineHeight: adaptedLineheight,
|
||||
...textStyles,
|
||||
height: numberOfLines === 1 && !expanded ? 0 : undefined
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: 44,
|
||||
backgroundColor: colors.backgroundDefault
|
||||
}}
|
||||
numberOfLines={
|
||||
typeof totalLines === 'number' ? (expanded ? 999 : numberOfLines) : undefined
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
...StyleConstants.FontStyle.S,
|
||||
color: colors.primaryDefault,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
children={t('HTML.expanded', {
|
||||
hint: expandHint,
|
||||
moreLines:
|
||||
numberOfLines > 1 && typeof totalLines === 'number'
|
||||
? t('HTML.moreLines', { count: totalLines - numberOfLines })
|
||||
: ''
|
||||
})}
|
||||
/>
|
||||
<Icon
|
||||
name={expanded ? 'Minimize2' : 'Maximize2'}
|
||||
color={colors.primaryDefault}
|
||||
strokeWidth={2}
|
||||
size={StyleConstants.Font.Size[size]}
|
||||
/>
|
||||
</Pressable>
|
||||
) : null}
|
||||
<Text
|
||||
children={document.children.map(renderNode)}
|
||||
onTextLayout={({ nativeEvent }) => {
|
||||
if (numberOfLines === 1 || nativeEvent.lines.length >= numberOfLines + 5) {
|
||||
setTotalLines(nativeEvent.lines.length)
|
||||
}
|
||||
selectable={selectable}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.content === next.content && isEqual(prev.emojis, next.emojis)
|
||||
)
|
||||
}}
|
||||
style={{
|
||||
fontSize: adaptedFontsize,
|
||||
lineHeight: adaptedLineheight,
|
||||
...textStyles,
|
||||
height: numberOfLines === 1 && !expanded ? 0 : undefined
|
||||
}}
|
||||
numberOfLines={
|
||||
typeof totalLines === 'number' ? (expanded ? 999 : numberOfLines) : undefined
|
||||
}
|
||||
selectable={selectable}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default ParseHTML
|
||||
|
@ -115,4 +115,4 @@ const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlig
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineConversation
|
||||
export default React.memo(TimelineConversation, () => true)
|
||||
|
@ -221,4 +221,4 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineDefault
|
||||
export default React.memo(TimelineDefault, () => true)
|
||||
|
@ -18,7 +18,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import * as ContextMenu from 'zeego/context-menu'
|
||||
import StatusContext from './Shared/Context'
|
||||
@ -53,14 +53,6 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
notification.status &&
|
||||
navigation.push('Tab-Shared-Toot', {
|
||||
toot: notification.status,
|
||||
rootQueryKey: queryKey
|
||||
})
|
||||
}, [])
|
||||
|
||||
const main = () => {
|
||||
return (
|
||||
<>
|
||||
@ -159,7 +151,13 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
backgroundColor: colors.backgroundDefault,
|
||||
paddingBottom: notification.status ? 0 : StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
onPress={onPress}
|
||||
onPress={() =>
|
||||
notification.status &&
|
||||
navigation.push('Tab-Shared-Toot', {
|
||||
toot: notification.status,
|
||||
rootQueryKey: queryKey
|
||||
})
|
||||
}
|
||||
onLongPress={() => {}}
|
||||
children={main()}
|
||||
/>
|
||||
@ -187,4 +185,4 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineNotifications
|
||||
export default React.memo(TimelineNotifications, () => true)
|
||||
|
@ -16,7 +16,7 @@ import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { uniqBy } from 'lodash'
|
||||
import React, { useCallback, useContext, useMemo } from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import StatusContext from './Context'
|
||||
@ -76,7 +76,7 @@ const TimelineActions: React.FC = () => {
|
||||
})
|
||||
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const onPressReply = useCallback(() => {
|
||||
const onPressReply = () => {
|
||||
const accts = uniqBy(
|
||||
([status.account] as Mastodon.Account[] & Mastodon.Mention[])
|
||||
.concat(status.mentions)
|
||||
@ -89,9 +89,9 @@ const TimelineActions: React.FC = () => {
|
||||
accts,
|
||||
queryKey
|
||||
})
|
||||
}, [status.replies_count])
|
||||
}
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const onPressReblog = useCallback(() => {
|
||||
const onPressReblog = () => {
|
||||
if (!status.reblogged) {
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
@ -157,8 +157,8 @@ const TimelineActions: React.FC = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [status.reblogged, status.reblogs_count])
|
||||
const onPressFavourite = useCallback(() => {
|
||||
}
|
||||
const onPressFavourite = () => {
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
@ -172,8 +172,8 @@ const TimelineActions: React.FC = () => {
|
||||
countValue: status.favourites_count
|
||||
}
|
||||
})
|
||||
}, [status.favourited, status.favourites_count])
|
||||
const onPressBookmark = useCallback(() => {
|
||||
}
|
||||
const onPressBookmark = () => {
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
@ -187,28 +187,25 @@ const TimelineActions: React.FC = () => {
|
||||
countValue: undefined
|
||||
}
|
||||
})
|
||||
}, [status.bookmarked])
|
||||
}
|
||||
|
||||
const childrenReply = useMemo(
|
||||
() => (
|
||||
<>
|
||||
<Icon name='MessageCircle' color={iconColor} size={StyleConstants.Font.Size.L} />
|
||||
{status.replies_count > 0 ? (
|
||||
<CustomText
|
||||
style={{
|
||||
color: colors.secondary,
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
}}
|
||||
>
|
||||
{status.replies_count}
|
||||
</CustomText>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
[status.replies_count]
|
||||
const childrenReply = () => (
|
||||
<>
|
||||
<Icon name='MessageCircle' color={iconColor} size={StyleConstants.Font.Size.L} />
|
||||
{status.replies_count > 0 ? (
|
||||
<CustomText
|
||||
style={{
|
||||
color: colors.secondary,
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
}}
|
||||
>
|
||||
{status.replies_count}
|
||||
</CustomText>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
const childrenReblog = useMemo(() => {
|
||||
const childrenReblog = () => {
|
||||
const color = (state: boolean) => (state ? colors.green : colors.secondary)
|
||||
const disabled =
|
||||
status.visibility === 'direct' || (status.visibility === 'private' && !ownAccount)
|
||||
@ -236,8 +233,8 @@ const TimelineActions: React.FC = () => {
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}, [status.reblogged, status.reblogs_count])
|
||||
const childrenFavourite = useMemo(() => {
|
||||
}
|
||||
const childrenFavourite = () => {
|
||||
const color = (state: boolean) => (state ? colors.red : colors.secondary)
|
||||
return (
|
||||
<>
|
||||
@ -256,13 +253,13 @@ const TimelineActions: React.FC = () => {
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}, [status.favourited, status.favourites_count])
|
||||
const childrenBookmark = useMemo(() => {
|
||||
}
|
||||
const childrenBookmark = () => {
|
||||
const color = (state: boolean) => (state ? colors.yellow : colors.secondary)
|
||||
return (
|
||||
<Icon name='Bookmark' color={color(status.bookmarked)} size={StyleConstants.Font.Size.L} />
|
||||
)
|
||||
}, [status.bookmarked])
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
@ -275,7 +272,7 @@ const TimelineActions: React.FC = () => {
|
||||
: { accessibilityLabel: '' })}
|
||||
style={styles.action}
|
||||
onPress={onPressReply}
|
||||
children={childrenReply}
|
||||
children={childrenReply()}
|
||||
/>
|
||||
|
||||
<Pressable
|
||||
@ -289,7 +286,7 @@ const TimelineActions: React.FC = () => {
|
||||
: { accessibilityLabel: '' })}
|
||||
style={styles.action}
|
||||
onPress={onPressReblog}
|
||||
children={childrenReblog}
|
||||
children={childrenReblog()}
|
||||
disabled={
|
||||
status.visibility === 'direct' || (status.visibility === 'private' && !ownAccount)
|
||||
}
|
||||
@ -306,7 +303,7 @@ const TimelineActions: React.FC = () => {
|
||||
: { accessibilityLabel: '' })}
|
||||
style={styles.action}
|
||||
onPress={onPressFavourite}
|
||||
children={childrenFavourite}
|
||||
children={childrenFavourite()}
|
||||
/>
|
||||
|
||||
<Pressable
|
||||
@ -320,7 +317,7 @@ const TimelineActions: React.FC = () => {
|
||||
: { accessibilityLabel: '' })}
|
||||
style={styles.action}
|
||||
onPress={onPressBookmark}
|
||||
children={childrenBookmark}
|
||||
children={childrenBookmark()}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ import updateStatusProperty from '@utils/queryHooks/timeline/updateStatusPropert
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { maxBy } from 'lodash'
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import StatusContext from './Context'
|
||||
@ -73,7 +73,7 @@ const TimelinePoll: React.FC = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const pollButton = useMemo(() => {
|
||||
const pollButton = () => {
|
||||
if (!poll.expired) {
|
||||
if (!ownAccount && !poll.voted) {
|
||||
return (
|
||||
@ -127,17 +127,14 @@ const TimelinePoll: React.FC = () => {
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [theme, poll.expired, poll.voted, allOptions, mutation.isLoading])
|
||||
}
|
||||
|
||||
const isSelected = useCallback(
|
||||
(index: number): string =>
|
||||
allOptions[index]
|
||||
? `Check${poll.multiple ? 'Square' : 'Circle'}`
|
||||
: `${poll.multiple ? 'Square' : 'Circle'}`,
|
||||
[allOptions]
|
||||
)
|
||||
const isSelected = (index: number): string =>
|
||||
allOptions[index]
|
||||
? `Check${poll.multiple ? 'Square' : 'Circle'}`
|
||||
: `${poll.multiple ? 'Square' : 'Circle'}`
|
||||
|
||||
const pollBodyDisallow = useMemo(() => {
|
||||
const pollBodyDisallow = () => {
|
||||
const maxValue = maxBy(poll.options, option => option.votes_count)?.votes_count
|
||||
return poll.options.map((option, index) => (
|
||||
<View key={index} style={{ flex: 1, paddingVertical: StyleConstants.Spacing.S }}>
|
||||
@ -191,8 +188,8 @@ const TimelinePoll: React.FC = () => {
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
}, [theme, poll.options])
|
||||
const pollBodyAllow = useMemo(() => {
|
||||
}
|
||||
const pollBodyAllow = () => {
|
||||
return poll.options.map((option, index) => (
|
||||
<Pressable
|
||||
key={index}
|
||||
@ -229,7 +226,7 @@ const TimelinePoll: React.FC = () => {
|
||||
</View>
|
||||
</Pressable>
|
||||
))
|
||||
}, [theme, allOptions])
|
||||
}
|
||||
|
||||
const pollVoteCounts = () => {
|
||||
if (poll.voters_count !== null) {
|
||||
@ -263,7 +260,7 @@ const TimelinePoll: React.FC = () => {
|
||||
|
||||
return (
|
||||
<View style={{ marginTop: StyleConstants.Spacing.M }}>
|
||||
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
|
||||
{poll.expired || poll.voted ? pollBodyDisallow() : pollBodyAllow()}
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
@ -272,7 +269,7 @@ const TimelinePoll: React.FC = () => {
|
||||
marginTop: StyleConstants.Spacing.XS
|
||||
}}
|
||||
>
|
||||
{pollButton}
|
||||
{pollButton()}
|
||||
<CustomText fontStyle='S' style={{ flexShrink: 1, color: colors.secondary }}>
|
||||
{pollVoteCounts()}
|
||||
{pollExpiration()}
|
||||
|
@ -5,7 +5,7 @@ import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { useGlobalStorageListener } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback, useRef } from 'react'
|
||||
import React, { RefObject, useRef } from 'react'
|
||||
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
|
||||
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||
import TimelineEmpty from './Empty'
|
||||
@ -56,11 +56,6 @@ const Timeline: React.FC<Props> = ({
|
||||
|
||||
const flattenData = data?.pages ? data.pages?.flatMap(page => [...page.body]) : []
|
||||
|
||||
const onEndReached = useCallback(
|
||||
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
||||
[isFetchingNextPage]
|
||||
)
|
||||
|
||||
const flRef = useRef<FlatList>(null)
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
@ -120,7 +115,7 @@ const Timeline: React.FC<Props> = ({
|
||||
data={flattenData}
|
||||
initialNumToRender={6}
|
||||
maxToRenderPerBatch={3}
|
||||
onEndReached={onEndReached}
|
||||
onEndReached={() => !disableInfinity && !isFetchingNextPage && fetchNextPage()}
|
||||
onEndReachedThreshold={0.75}
|
||||
ListFooterComponent={
|
||||
<TimelineFooter queryKey={queryKey} disableInfinity={disableInfinity} />
|
||||
|
Reference in New Issue
Block a user