tooot/src/components/Timelines/Timeline/Shared/Card.tsx

148 lines
3.6 KiB
TypeScript
Raw Normal View History

2020-12-25 21:24:07 +01:00
import React, { useEffect, useMemo, useState } from 'react'
2020-10-29 14:52:28 +01:00
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
2021-01-01 16:48:16 +01:00
import openLink from '@components/openLink'
2020-12-13 14:04:25 +01:00
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
2020-12-25 21:24:07 +01:00
import { Surface } from 'gl-react-expo'
import { Blurhash } from 'gl-react-blurhash'
2020-10-29 14:52:28 +01:00
2020-10-31 21:04:46 +01:00
export interface Props {
card: Mastodon.Card
2020-10-31 21:04:46 +01:00
}
2021-01-14 22:53:01 +01:00
type CancelPromise = ((reason?: Error) => void) | undefined
type ImageSize = { width: number; height: number }
interface ImageSizeOperation {
start: () => Promise<ImageSize>
cancel: CancelPromise
}
const getImageSize = (uri: string): ImageSizeOperation => {
let cancel: CancelPromise
const start = (): Promise<ImageSize> =>
new Promise<{ width: number; height: number }>((resolve, reject) => {
cancel = reject
Image.getSize(
uri,
(width, height) => {
cancel = undefined
resolve({ width, height })
},
error => {
reject(error)
}
)
})
return { start, cancel }
}
2020-12-03 01:28:56 +01:00
const TimelineCard: React.FC<Props> = ({ card }) => {
2020-12-01 00:44:28 +01:00
const { theme } = useTheme()
2020-11-28 17:07:30 +01:00
2021-01-10 02:12:14 +01:00
const [imageLoaded, setImageLoaded] = useState(false)
useEffect(() => {
2021-01-10 02:12:14 +01:00
if (card.image) {
2021-01-12 00:12:44 +01:00
Image.getSize(card.image, () => setImageLoaded(true))
}
2021-01-10 02:12:14 +01:00
}, [])
2021-01-14 22:53:01 +01:00
useEffect(() => {
let cancel: CancelPromise
const sideEffect = async (): Promise<void> => {
try {
const operation = getImageSize(card.image)
cancel = operation.cancel
await operation.start()
} catch (error) {
if (__DEV__) console.warn(error)
}
}
if (card.image) {
sideEffect()
}
return () => {
if (cancel) {
cancel()
}
}
})
2020-12-25 21:24:07 +01:00
const cardVisual = useMemo(() => {
if (imageLoaded) {
return <Image source={{ uri: card.image }} style={styles.image} />
} else {
2020-12-30 11:55:51 +01:00
return card.blurhash ? (
2020-12-25 21:24:07 +01:00
<Surface style={styles.image}>
<Blurhash hash={card.blurhash} />
</Surface>
2020-12-30 11:55:51 +01:00
) : null
2020-12-25 21:24:07 +01:00
}
}, [imageLoaded])
2020-10-29 14:52:28 +01:00
return (
2020-12-01 00:44:28 +01:00
<Pressable
style={[styles.card, { borderColor: theme.border }]}
2020-12-20 18:41:28 +01:00
onPress={async () => await openLink(card.url)}
2021-01-10 02:12:14 +01:00
testID='base'
2020-12-01 00:44:28 +01:00
>
2021-01-10 02:12:14 +01:00
{card.image && (
<View style={styles.left} testID='image'>
{cardVisual}
</View>
)}
2020-11-28 17:07:30 +01:00
<View style={styles.right}>
2020-12-01 00:44:28 +01:00
<Text
numberOfLines={2}
style={[styles.rightTitle, { color: theme.primary }]}
2021-01-10 02:12:14 +01:00
testID='title'
2020-12-01 00:44:28 +01:00
>
{card.title}
</Text>
2020-11-28 17:07:30 +01:00
{card.description ? (
2020-12-01 00:44:28 +01:00
<Text
numberOfLines={1}
style={[styles.rightDescription, { color: theme.primary }]}
2021-01-10 02:12:14 +01:00
testID='description'
2020-12-01 00:44:28 +01:00
>
{card.description}
</Text>
2020-12-13 23:38:37 +01:00
) : null}
2020-12-01 00:44:28 +01:00
<Text numberOfLines={1} style={{ color: theme.secondary }}>
{card.url}
</Text>
2020-11-28 17:07:30 +01:00
</View>
</Pressable>
2020-10-29 14:52:28 +01:00
)
}
const styles = StyleSheet.create({
card: {
flex: 1,
flexDirection: 'row',
2020-12-01 00:44:28 +01:00
height: StyleConstants.Avatar.L,
marginTop: StyleConstants.Spacing.M,
2020-12-03 22:03:06 +01:00
borderWidth: StyleSheet.hairlineWidth,
2020-12-01 00:44:28 +01:00
borderRadius: 6
2020-10-29 14:52:28 +01:00
},
left: {
2020-12-01 00:44:28 +01:00
width: StyleConstants.Avatar.L
2020-10-29 14:52:28 +01:00
},
image: {
width: '100%',
2020-12-01 00:44:28 +01:00
height: '100%',
borderTopLeftRadius: 6,
borderBottomLeftRadius: 6
2020-10-29 14:52:28 +01:00
},
right: {
2020-12-01 00:44:28 +01:00
flex: 1,
padding: StyleConstants.Spacing.S
},
rightTitle: {
marginBottom: StyleConstants.Spacing.XS,
fontWeight: StyleConstants.Font.Weight.Bold
},
rightDescription: {
marginBottom: StyleConstants.Spacing.XS
2020-10-29 14:52:28 +01:00
}
})
2020-12-03 01:28:56 +01:00
export default React.memo(TimelineCard, () => true)