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

Rewrite timeline logic

This commit is contained in:
Zhiyuan Zheng
2021-02-27 16:33:54 +01:00
parent 45681fc1f5
commit f3fa6bc662
67 changed files with 1980 additions and 1395 deletions

View File

@ -15,140 +15,139 @@ export interface Props {
notification?: boolean
}
const TimelineActioned: React.FC<Props> = ({
account,
action,
notification = false
}) => {
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
const navigation = useNavigation<
StackNavigationProp<Nav.TabLocalStackParamList>
>()
const name = account.display_name || account.username
const iconColor = theme.primary
const TimelineActioned = React.memo(
({ account, action, notification = false }: Props) => {
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
const navigation = useNavigation<
StackNavigationProp<Nav.TabLocalStackParamList>
>()
const name = account.display_name || account.username
const iconColor = theme.primary
const content = (content: string) => (
<ParseEmojis content={content} emojis={account.emojis} size='S' />
)
const content = (content: string) => (
<ParseEmojis content={content} emojis={account.emojis} size='S' />
)
const onPress = useCallback(() => {
analytics('timeline_shared_actioned_press', { action })
navigation.push('Tab-Shared-Account', { account })
}, [])
const onPress = useCallback(() => {
analytics('timeline_shared_actioned_press', { action })
navigation.push('Tab-Shared-Account', { account })
}, [])
const children = useMemo(() => {
switch (action) {
case 'pinned':
return (
<>
<Icon
name='Anchor'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
{content(t('shared.actioned.pinned'))}
</>
)
break
case 'favourite':
return (
<>
<Icon
name='Heart'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.favourite', { name }))}
</Pressable>
</>
)
break
case 'follow':
return (
<>
<Icon
name='UserPlus'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.follow', { name }))}
</Pressable>
</>
)
break
case 'follow_request':
return (
<>
<Icon
name='UserPlus'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.follow_request', { name }))}
</Pressable>
</>
)
break
case 'poll':
return (
<>
<Icon
name='BarChart2'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
{content(t('shared.actioned.poll'))}
</>
)
break
case 'reblog':
return (
<>
<Icon
name='Repeat'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(
notification
? t('shared.actioned.reblog.notification', { name })
: t('shared.actioned.reblog.default', { name })
)}
</Pressable>
</>
)
break
case 'status':
return (
<>
<Icon
name='Activity'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.status', { name }))}
</Pressable>
</>
)
break
}
}, [])
const children = useMemo(() => {
switch (action) {
case 'pinned':
return (
<>
<Icon
name='Anchor'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
{content(t('shared.actioned.pinned'))}
</>
)
break
case 'favourite':
return (
<>
<Icon
name='Heart'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.favourite', { name }))}
</Pressable>
</>
)
break
case 'follow':
return (
<>
<Icon
name='UserPlus'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.follow', { name }))}
</Pressable>
</>
)
break
case 'follow_request':
return (
<>
<Icon
name='UserPlus'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.follow_request', { name }))}
</Pressable>
</>
)
break
case 'poll':
return (
<>
<Icon
name='BarChart2'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
{content(t('shared.actioned.poll'))}
</>
)
break
case 'reblog':
return (
<>
<Icon
name='Repeat'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(
notification
? t('shared.actioned.reblog.notification', { name })
: t('shared.actioned.reblog.default', { name })
)}
</Pressable>
</>
)
break
case 'status':
return (
<>
<Icon
name='Activity'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
<Pressable onPress={onPress}>
{content(t('shared.actioned.status', { name }))}
</Pressable>
</>
)
break
}
}, [])
return <View style={styles.actioned} children={children} />
}
return <View style={styles.actioned} children={children} />
},
() => true
)
const styles = StyleSheet.create({
actioned: {
@ -163,4 +162,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(TimelineActioned, () => true)
export default TimelineActioned

View File

@ -56,7 +56,10 @@ const TimelineActions: React.FC<Props> = ({
theParams.payload.currentValue === true)
) {
queryClient.invalidateQueries(queryKey)
} else if (theParams.payload.property === 'reblogged') {
} else if (
theParams.payload.property === 'reblogged' &&
queryKey[1].page !== 'Following'
) {
// When reblogged, update cache of following page
const tempQueryKey: QueryKeyTimeline = [
'Timeline',

View File

@ -16,129 +16,132 @@ export interface Props {
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
}
const TimelineAttachment: React.FC<Props> = ({ status }) => {
const { t } = useTranslation('componentTimeline')
const TimelineAttachment = React.memo(
({ status }: Props) => {
const { t } = useTranslation('componentTimeline')
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
const onPressBlurView = useCallback(() => {
analytics('timeline_shared_attachment_blurview_press_show')
layoutAnimation()
setSensitiveShown(false)
haptics('Light')
}, [])
const onPressShow = useCallback(() => {
analytics('timeline_shared_attachment_blurview_press_hide')
setSensitiveShown(true)
haptics('Light')
}, [])
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
const onPressBlurView = useCallback(() => {
analytics('timeline_shared_attachment_blurview_press_show')
layoutAnimation()
setSensitiveShown(false)
haptics('Light')
}, [])
const onPressShow = useCallback(() => {
analytics('timeline_shared_attachment_blurview_press_hide')
setSensitiveShown(true)
haptics('Light')
}, [])
let imageUrls: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'] = []
const navigation = useNavigation()
const navigateToImagesViewer = (imageIndex: number) =>
navigation.navigate('Screen-ImagesViewer', {
imageUrls,
imageIndex
})
const attachments = useMemo(
() =>
status.media_attachments.map((attachment, index) => {
switch (attachment.type) {
case 'image':
imageUrls.push({
url: attachment.url,
preview_url: attachment.preview_url,
remote_url: attachment.remote_url,
width: attachment.meta?.original?.width,
height: attachment.meta?.original?.height,
imageIndex: index
})
return (
<AttachmentImage
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
image={attachment}
navigateToImagesViewer={navigateToImagesViewer}
/>
)
case 'video':
return (
<AttachmentVideo
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
video={attachment}
/>
)
case 'gifv':
return (
<AttachmentVideo
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
video={attachment}
gifv
/>
)
case 'audio':
return (
<AttachmentAudio
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
audio={attachment}
/>
)
default:
return (
<AttachmentUnsupported
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
attachment={attachment}
/>
)
}
}),
[sensitiveShown]
)
let imageUrls: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'] = []
const navigation = useNavigation()
const navigateToImagesViewer = (imageIndex: number) =>
navigation.navigate('Screen-ImagesViewer', {
imageUrls,
imageIndex
})
const attachments = useMemo(
() =>
status.media_attachments.map((attachment, index) => {
switch (attachment.type) {
case 'image':
imageUrls.push({
url: attachment.url,
preview_url: attachment.preview_url,
remote_url: attachment.remote_url,
width: attachment.meta?.original?.width,
height: attachment.meta?.original?.height,
imageIndex: index
})
return (
<AttachmentImage
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
image={attachment}
navigateToImagesViewer={navigateToImagesViewer}
/>
)
case 'video':
return (
<AttachmentVideo
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
video={attachment}
/>
)
case 'gifv':
return (
<AttachmentVideo
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
video={attachment}
gifv
/>
)
case 'audio':
return (
<AttachmentAudio
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
audio={attachment}
/>
)
default:
return (
<AttachmentUnsupported
key={index}
total={status.media_attachments.length}
index={index}
sensitiveShown={sensitiveShown}
attachment={attachment}
/>
)
}
}),
[sensitiveShown]
)
return (
<View>
<View style={styles.container} children={attachments} />
return (
<View>
<View style={styles.container} children={attachments} />
{status.sensitive &&
(sensitiveShown ? (
<Pressable style={styles.sensitiveBlur}>
{status.sensitive &&
(sensitiveShown ? (
<Pressable style={styles.sensitiveBlur}>
<Button
type='text'
content={t('shared.attachment.sensitive.button')}
overlay
onPress={onPressBlurView}
/>
</Pressable>
) : (
<Button
type='text'
content={t('shared.attachment.sensitive.button')}
type='icon'
content='EyeOff'
round
overlay
onPress={onPressBlurView}
onPress={onPressShow}
style={{
position: 'absolute',
top: StyleConstants.Spacing.S * 2,
left: StyleConstants.Spacing.S
}}
/>
</Pressable>
) : (
<Button
type='icon'
content='EyeOff'
round
overlay
onPress={onPressShow}
style={{
position: 'absolute',
top: StyleConstants.Spacing.S * 2,
left: StyleConstants.Spacing.S
}}
/>
))}
</View>
)
}
))}
</View>
)
},
() => true
)
const styles = StyleSheet.create({
container: {
@ -166,4 +169,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(TimelineAttachment, () => true)
export default TimelineAttachment

View File

@ -13,31 +13,28 @@ export interface Props {
navigateToImagesViewer: (imageIndex: number) => void
}
const AttachmentImage: React.FC<Props> = ({
total,
index,
sensitiveShown,
image,
navigateToImagesViewer
}) => {
const onPress = useCallback(() => {
analytics('timeline_shared_attachment_image_press', { id: image.id })
navigateToImagesViewer(index)
}, [])
const AttachmentImage = React.memo(
({ total, index, sensitiveShown, image, navigateToImagesViewer }: Props) => {
const onPress = useCallback(() => {
analytics('timeline_shared_attachment_image_press', { id: image.id })
navigateToImagesViewer(index)
}, [])
return (
<GracefullyImage
hidden={sensitiveShown}
uri={{ original: image.preview_url, remote: image.remote_url }}
blurhash={image.blurhash}
onPress={onPress}
style={[
styles.base,
{ aspectRatio: attachmentAspectRatio({ total, index }) }
]}
/>
)
}
return (
<GracefullyImage
hidden={sensitiveShown}
uri={{ original: image.preview_url, remote: image.remote_url }}
blurhash={image.blurhash}
onPress={onPress}
style={[
styles.base,
{ aspectRatio: attachmentAspectRatio({ total, index }) }
]}
/>
)
},
(prev, next) => prev.sensitiveShown === next.sensitiveShown
)
const styles = StyleSheet.create({
base: {
@ -47,7 +44,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(
AttachmentImage,
(prev, next) => prev.sensitiveShown === next.sensitiveShown
)
export default AttachmentImage

View File

@ -11,33 +11,36 @@ export interface Props {
account: Mastodon.Account
}
const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => {
const navigation = useNavigation<
StackNavigationProp<Nav.TabLocalStackParamList>
>()
// Need to fix go back root
const onPress = useCallback(() => {
analytics('timeline_shared_avatar_press', {
page: queryKey && queryKey[1].page
})
queryKey && navigation.push('Tab-Shared-Account', { account })
}, [])
const TimelineAvatar = React.memo(
({ queryKey, account }: Props) => {
const navigation = useNavigation<
StackNavigationProp<Nav.TabLocalStackParamList>
>()
// Need to fix go back root
const onPress = useCallback(() => {
analytics('timeline_shared_avatar_press', {
page: queryKey && queryKey[1].page
})
queryKey && navigation.push('Tab-Shared-Account', { account })
}, [])
return (
<GracefullyImage
onPress={onPress}
uri={{ original: account.avatar_static }}
dimension={{
width: StyleConstants.Avatar.M,
height: StyleConstants.Avatar.M
}}
style={{
borderRadius: 4,
overflow: 'hidden',
marginRight: StyleConstants.Spacing.S
}}
/>
)
}
return (
<GracefullyImage
onPress={onPress}
uri={{ original: account.avatar_static }}
dimension={{
width: StyleConstants.Avatar.M,
height: StyleConstants.Avatar.M
}}
style={{
borderRadius: 4,
overflow: 'hidden',
marginRight: StyleConstants.Spacing.S
}}
/>
)
},
() => true
)
export default React.memo(TimelineAvatar, () => true)
export default TimelineAvatar

View File

@ -10,53 +10,56 @@ export interface Props {
card: Mastodon.Card
}
const TimelineCard: React.FC<Props> = ({ card }) => {
const { theme } = useTheme()
const TimelineCard = React.memo(
({ card }: Props) => {
const { theme } = useTheme()
return (
<Pressable
style={[styles.card, { borderColor: theme.border }]}
onPress={async () => {
analytics('timeline_shared_card_press')
await openLink(card.url)
}}
testID='base'
>
{card.image && (
<GracefullyImage
uri={{ original: card.image }}
blurhash={card.blurhash}
style={styles.left}
imageStyle={styles.image}
/>
)}
<View style={styles.right}>
<Text
numberOfLines={2}
style={[styles.rightTitle, { color: theme.primary }]}
testID='title'
>
{card.title}
</Text>
{card.description ? (
return (
<Pressable
style={[styles.card, { borderColor: theme.border }]}
onPress={async () => {
analytics('timeline_shared_card_press')
await openLink(card.url)
}}
testID='base'
>
{card.image && (
<GracefullyImage
uri={{ original: card.image }}
blurhash={card.blurhash}
style={styles.left}
imageStyle={styles.image}
/>
)}
<View style={styles.right}>
<Text
numberOfLines={2}
style={[styles.rightTitle, { color: theme.primary }]}
testID='title'
>
{card.title}
</Text>
{card.description ? (
<Text
numberOfLines={1}
style={[styles.rightDescription, { color: theme.primary }]}
testID='description'
>
{card.description}
</Text>
) : null}
<Text
numberOfLines={1}
style={[styles.rightDescription, { color: theme.primary }]}
testID='description'
style={[styles.rightLink, { color: theme.secondary }]}
>
{card.description}
{card.url}
</Text>
) : null}
<Text
numberOfLines={1}
style={[styles.rightLink, { color: theme.secondary }]}
>
{card.url}
</Text>
</View>
</Pressable>
)
}
</View>
</Pressable>
)
},
() => true
)
const styles = StyleSheet.create({
card: {
@ -93,4 +96,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(TimelineCard, () => true)
export default TimelineCard

View File

@ -11,53 +11,56 @@ export interface Props {
disableDetails?: boolean
}
const TimelineContent: React.FC<Props> = ({
status,
numberOfLines,
highlighted = false,
disableDetails = false
}) => {
const { t } = useTranslation('componentTimeline')
const TimelineContent = React.memo(
({
status,
numberOfLines,
highlighted = false,
disableDetails = false
}: Props) => {
const { t } = useTranslation('componentTimeline')
return (
<>
{status.spoiler_text ? (
<>
<View style={{ marginBottom: StyleConstants.Font.Size.M }}>
return (
<>
{status.spoiler_text ? (
<>
<View style={{ marginBottom: StyleConstants.Font.Size.M }}>
<ParseHTML
content={status.spoiler_text}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
tags={status.tags}
numberOfLines={999}
disableDetails={disableDetails}
/>
</View>
<ParseHTML
content={status.spoiler_text}
content={status.content}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
tags={status.tags}
numberOfLines={999}
numberOfLines={0}
expandHint={t('shared.content.expandHint')}
disableDetails={disableDetails}
/>
</View>
</>
) : (
<ParseHTML
content={status.content}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
tags={status.tags}
numberOfLines={0}
expandHint={t('shared.content.expandHint')}
numberOfLines={highlighted ? 999 : numberOfLines}
disableDetails={disableDetails}
/>
</>
) : (
<ParseHTML
content={status.content}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
tags={status.tags}
numberOfLines={highlighted ? 999 : numberOfLines}
disableDetails={disableDetails}
/>
)}
</>
)
}
)}
</>
)
},
() => true
)
export default React.memo(TimelineContent, () => true)
export default TimelineContent

View File

@ -44,78 +44,81 @@ export interface Props {
conversation: Mastodon.Conversation
}
const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
const { t } = useTranslation('componentTimeline')
const HeaderConversation = React.memo(
({ queryKey, conversation }: Props) => {
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
queryClient,
onMutate: true,
onError: (err: any, _, oldData) => {
haptics('Error')
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(`shared.header.conversation.delete.function`)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
queryClient,
onMutate: true,
onError: (err: any, _, oldData) => {
haptics('Error')
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(`shared.header.conversation.delete.function`)
}),
autoHide: false
})
queryClient.setQueryData(queryKey, oldData)
}
})
const { theme } = useTheme()
const actionOnPress = useCallback(() => {
analytics('timeline_conversation_delete_press')
mutation.mutate({
type: 'deleteItem',
source: 'conversations',
queryKey,
id: conversation.id
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
}),
autoHide: false
})
queryClient.setQueryData(queryKey, oldData)
}
})
}, [])
const actionChildren = useMemo(
() => (
<Icon
name='Trash'
color={theme.secondary}
size={StyleConstants.Font.Size.L}
/>
),
[]
)
const { theme } = useTheme()
return (
<View style={styles.base}>
<View style={styles.nameAndMeta}>
<Names accounts={conversation.accounts} />
<View style={styles.meta}>
{conversation.last_status?.created_at ? (
<HeaderSharedCreated
created_at={conversation.last_status?.created_at}
/>
) : null}
<HeaderSharedMuted muted={conversation.last_status?.muted} />
const actionOnPress = useCallback(() => {
analytics('timeline_conversation_delete_press')
mutation.mutate({
type: 'deleteItem',
source: 'conversations',
queryKey,
id: conversation.id
})
}, [])
const actionChildren = useMemo(
() => (
<Icon
name='Trash'
color={theme.secondary}
size={StyleConstants.Font.Size.L}
/>
),
[]
)
return (
<View style={styles.base}>
<View style={styles.nameAndMeta}>
<Names accounts={conversation.accounts} />
<View style={styles.meta}>
{conversation.last_status?.created_at ? (
<HeaderSharedCreated
created_at={conversation.last_status?.created_at}
/>
) : null}
<HeaderSharedMuted muted={conversation.last_status?.muted} />
</View>
</View>
</View>
<Pressable
style={styles.action}
onPress={actionOnPress}
children={actionChildren}
/>
</View>
)
}
<Pressable
style={styles.action}
onPress={actionOnPress}
children={actionChildren}
/>
</View>
)
},
() => true
)
const styles = StyleSheet.create({
base: {

View File

@ -17,50 +17,49 @@ export interface Props {
status: Mastodon.Status
}
const TimelineHeaderDefault: React.FC<Props> = ({
queryKey,
rootQueryKey,
status
}) => {
const navigation = useNavigation()
const { theme } = useTheme()
const TimelineHeaderDefault = React.memo(
({ queryKey, rootQueryKey, status }: Props) => {
const navigation = useNavigation()
const { theme } = useTheme()
return (
<View style={styles.base}>
<View style={styles.accountAndMeta}>
<HeaderSharedAccount account={status.account} />
<View style={styles.meta}>
<HeaderSharedCreated created_at={status.created_at} />
<HeaderSharedVisibility visibility={status.visibility} />
<HeaderSharedMuted muted={status.muted} />
<HeaderSharedApplication application={status.application} />
return (
<View style={styles.base}>
<View style={styles.accountAndMeta}>
<HeaderSharedAccount account={status.account} />
<View style={styles.meta}>
<HeaderSharedCreated created_at={status.created_at} />
<HeaderSharedVisibility visibility={status.visibility} />
<HeaderSharedMuted muted={status.muted} />
<HeaderSharedApplication application={status.application} />
</View>
</View>
</View>
{queryKey ? (
<Pressable
style={styles.action}
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
rootQueryKey,
status,
url: status.url || status.uri,
type: 'status'
})
}
children={
<Icon
name='MoreHorizontal'
color={theme.secondary}
size={StyleConstants.Font.Size.L}
/>
}
/>
) : null}
</View>
)
}
{queryKey ? (
<Pressable
style={styles.action}
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
rootQueryKey,
status,
url: status.url || status.uri,
type: 'status'
})
}
children={
<Icon
name='MoreHorizontal'
color={theme.secondary}
size={StyleConstants.Font.Size.L}
/>
}
/>
) : null}
</View>
)
},
() => true
)
const styles = StyleSheet.create({
base: {
@ -87,7 +86,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(
TimelineHeaderDefault,
(prev, next) => prev.status.muted !== next.status.muted
)
export default TimelineHeaderDefault

View File

@ -20,98 +20,98 @@ export interface Props {
notification: Mastodon.Notification
}
const TimelineHeaderNotification: React.FC<Props> = ({
queryKey,
notification
}) => {
const navigation = useNavigation()
const { theme } = useTheme()
const TimelineHeaderNotification = React.memo(
({ queryKey, notification }: Props) => {
const navigation = useNavigation()
const { theme } = useTheme()
const actions = useMemo(() => {
switch (notification.type) {
case 'follow':
return <RelationshipOutgoing id={notification.account.id} />
case 'follow_request':
return <RelationshipIncoming id={notification.account.id} />
default:
if (notification.status) {
return (
<Pressable
style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
paddingBottom: StyleConstants.Spacing.S
}}
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
status: notification.status,
url: notification.status?.url || notification.status?.uri,
type: 'status'
})
}
children={
<Icon
name='MoreHorizontal'
color={theme.secondary}
size={StyleConstants.Font.Size.L}
/>
}
const actions = useMemo(() => {
switch (notification.type) {
case 'follow':
return <RelationshipOutgoing id={notification.account.id} />
case 'follow_request':
return <RelationshipIncoming id={notification.account.id} />
default:
if (notification.status) {
return (
<Pressable
style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
paddingBottom: StyleConstants.Spacing.S
}}
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
status: notification.status,
url: notification.status?.url || notification.status?.uri,
type: 'status'
})
}
children={
<Icon
name='MoreHorizontal'
color={theme.secondary}
size={StyleConstants.Font.Size.L}
/>
}
/>
)
}
}
}, [notification.type])
return (
<View style={styles.base}>
<View
style={{
flex:
notification.type === 'follow' ||
notification.type === 'follow_request'
? 1
: 4
}}
>
<HeaderSharedAccount
account={
notification.status
? notification.status.account
: notification.account
}
{...((notification.type === 'follow' ||
notification.type === 'follow_request') && { withoutName: true })}
/>
<View style={styles.meta}>
<HeaderSharedCreated created_at={notification.created_at} />
{notification.status?.visibility ? (
<HeaderSharedVisibility
visibility={notification.status.visibility}
/>
) : null}
<HeaderSharedMuted muted={notification.status?.muted} />
<HeaderSharedApplication
application={notification.status?.application}
/>
)
}
}
}, [notification.type])
</View>
</View>
return (
<View style={styles.base}>
<View
style={{
flex:
<View
style={[
styles.relationship,
notification.type === 'follow' ||
notification.type === 'follow_request'
? 1
: 4
}}
>
<HeaderSharedAccount
account={
notification.status
? notification.status.account
: notification.account
}
{...((notification.type === 'follow' ||
notification.type === 'follow_request') && { withoutName: true })}
/>
<View style={styles.meta}>
<HeaderSharedCreated created_at={notification.created_at} />
{notification.status?.visibility ? (
<HeaderSharedVisibility
visibility={notification.status.visibility}
/>
) : null}
<HeaderSharedMuted muted={notification.status?.muted} />
<HeaderSharedApplication
application={notification.status?.application}
/>
? { flexShrink: 1 }
: { flex: 1 }
]}
>
{actions}
</View>
</View>
<View
style={[
styles.relationship,
notification.type === 'follow' ||
notification.type === 'follow_request'
? { flexShrink: 1 }
: { flex: 1 }
]}
>
{actions}
</View>
</View>
)
}
)
},
() => true
)
const styles = StyleSheet.create({
base: {
@ -129,4 +129,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(TimelineHeaderNotification, () => true)
export default TimelineHeaderNotification