Rewrite header actions

This commit is contained in:
Zhiyuan Zheng 2020-12-19 18:21:37 +01:00
parent 54799aabb8
commit 98a60df9d1
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
9 changed files with 152 additions and 104 deletions

View File

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { StyleSheet } from 'react-native'
import { useInfiniteQuery } from 'react-query'
import { InfiniteData, useInfiniteQuery } from 'react-query'
import TimelineNotifications from '@components/Timelines/Timeline/Notifications'
import TimelineDefault from '@components/Timelines/Timeline/Default'
@ -12,6 +12,14 @@ import TimelineEnd from '@components/Timelines/Timeline/Shared/End'
import { useScrollToTop } from '@react-navigation/native'
import { FlatList } from 'react-native-gesture-handler'
export type TimelineData =
| InfiniteData<{
toots: Mastodon.Status[]
pointer?: number | undefined
pinnedLength?: number | undefined
}>
| undefined
export interface Props {
page: App.Pages
hashtag?: string

View File

@ -44,6 +44,7 @@ const TimelineDefault: React.FC<Props> = ({
const tootOnPress = useCallback(
() =>
!isRemotePublic &&
!highlighted &&
navigation.push('Screen-Shared-Toot', {
toot: actualStatus
}),
@ -63,7 +64,11 @@ const TimelineDefault: React.FC<Props> = ({
<TimelineContent status={actualStatus} highlighted={highlighted} />
)}
{actualStatus.poll && (
<TimelinePoll queryKey={queryKey} status={actualStatus} />
<TimelinePoll
queryKey={queryKey}
poll={actualStatus.poll}
reblog={item.reblog ? true : false}
/>
)}
{actualStatus.media_attachments.length > 0 && (
<TimelineAttachment status={actualStatus} width={contentWidth} />
@ -103,7 +108,11 @@ const TimelineDefault: React.FC<Props> = ({
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
}}
>
<TimelineActions queryKey={queryKey} status={actualStatus} />
<TimelineActions
queryKey={queryKey}
status={actualStatus}
reblog={item.reblog ? true : false}
/>
</View>
)}
</View>
@ -122,16 +131,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(TimelineDefault, (prev, next) => {
let skipUpdate = true
skipUpdate =
prev.item.id === next.item.id &&
prev.item.replies_count === next.item.replies_count &&
prev.item.favourited === next.item.favourited &&
prev.item.reblogged === next.item.reblogged &&
prev.item.bookmarked === next.item.bookmarked &&
prev.item.poll?.voted === next.item.poll?.voted &&
prev.item.reblog?.poll?.voted === next.item.reblog?.poll?.voted
return skipUpdate
})
export default TimelineDefault

View File

@ -1,6 +1,6 @@
import React, { useCallback, useMemo } from 'react'
import { ActionSheetIOS, Pressable, StyleSheet, Text, View } from 'react-native'
import { useMutation, useQueryClient } from 'react-query'
import { InfiniteData, useMutation, useQueryClient } from 'react-query'
import { Feather } from '@expo/vector-icons'
import client from '@api/client'
@ -9,6 +9,8 @@ import { toast } from '@components/toast'
import { StyleConstants } from '@utils/styles/constants'
import { useNavigation } from '@react-navigation/native'
import getCurrentTab from '@utils/getCurrentTab'
import { findIndex } from 'lodash'
import { TimelineData } from '../../Timeline'
const fireMutation = async ({
id,
@ -46,9 +48,10 @@ const fireMutation = async ({
export interface Props {
queryKey: QueryKey.Timeline
status: Mastodon.Status
reblog: boolean
}
const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
const navigation = useNavigation()
const { theme } = useTheme()
const iconColor = theme.secondary
@ -65,18 +68,31 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
case 'favourite':
case 'reblog':
case 'bookmark':
queryClient.setQueryData(queryKey, (old: any) => {
old.pages.map((paging: any) => ({
toots: paging.toots.map((toot: any) => {
if (toot.id === id) {
console.log(toot[stateKey])
toot[stateKey] =
typeof prevState === 'boolean' ? !prevState : true
}
return toot
}),
pointer: paging.pointer
}))
queryClient.setQueryData<TimelineData>(queryKey, old => {
let tootIndex = -1
const pageIndex = findIndex(old?.pages, page => {
const tempIndex = findIndex(page.toots, [
reblog ? 'reblog.id' : 'id',
id
])
if (tempIndex >= 0) {
tootIndex = tempIndex
return true
} else {
return false
}
})
if (pageIndex >= 0 && tootIndex >= 0) {
if (reblog) {
old!.pages[pageIndex].toots[tootIndex].reblog![stateKey] =
typeof prevState === 'boolean' ? !prevState : true
} else {
old!.pages[pageIndex].toots[tootIndex][stateKey] =
typeof prevState === 'boolean' ? !prevState : true
}
}
return old
})
break

View File

@ -36,9 +36,12 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
// causing full re-render
useEffect(() => {
setTimeout(() => {
const timer = setTimeout(() => {
setSince(relativeTime(status.created_at))
}, 1000)
return () => {
clearTimeout(timer)
}
}, [since])
const onPressAction = useCallback(() => setBottomSheetVisible(true), [])

View File

@ -70,14 +70,8 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
}) => {
const queryClient = useQueryClient()
const { mutate } = useMutation(fireMutation, {
onMutate: () => {
queryClient.cancelQueries(queryKey)
const oldData = queryClient.getQueryData(queryKey)
return oldData
},
onError: (err, _, oldData) => {
toast({ type: 'error', content: '请重试', autoHide: false })
queryClient.setQueryData(queryKey, oldData)
onSettled: () => {
queryClient.invalidateQueries(queryKey)
}
})

View File

@ -42,15 +42,6 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({
}) => {
const queryClient = useQueryClient()
const { mutate } = useMutation(fireMutation, {
onMutate: () => {
queryClient.cancelQueries(queryKey)
const oldData = queryClient.getQueryData(queryKey)
return oldData
},
onError: (err, _, oldData) => {
toast({ type: 'error', content: '请重试', autoHide: false })
queryClient.setQueryData(queryKey, oldData)
},
onSettled: () => {
queryClient.invalidateQueries(queryKey)
}

View File

@ -6,6 +6,8 @@ import client from '@api/client'
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
import { toast } from '@components/toast'
import getCurrentTab from '@utils/getCurrentTab'
import { TimelineData } from '@root/components/Timelines/Timeline'
import { findIndex } from 'lodash'
const fireMutation = async ({
id,
@ -75,25 +77,39 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
switch (type) {
case 'mute':
case 'pin':
queryClient.setQueryData(queryKey, (old: any) =>
old.pages.map((paging: any) => ({
toots: paging.toots.map((toot: any) => {
if (toot.id === id) {
toot[stateKey] =
typeof prevState === 'boolean' ? !prevState : true
}
return toot
}),
pointer: paging.pointer
}))
)
queryClient.setQueryData<TimelineData>(queryKey, old => {
let tootIndex = -1
const pageIndex = findIndex(old?.pages, page => {
const tempIndex = findIndex(page.toots, ['id', id])
if (tempIndex >= 0) {
tootIndex = tempIndex
return true
} else {
return false
}
})
if (pageIndex >= 0 && tootIndex >= 0) {
old!.pages[pageIndex].toots[tootIndex][
stateKey as 'muted' | 'pinned'
] = typeof prevState === 'boolean' ? !prevState : true
}
return old
})
break
case 'delete':
queryClient.setQueryData(queryKey, (old: any) =>
old.pages.map((paging: any) => ({
toots: paging.toots.filter((toot: any) => toot.id !== id),
pointer: paging.pointer
}))
console.log('deleting toot')
queryClient.setQueryData<TimelineData>(
queryKey,
old =>
old && {
...old,
pages: old?.pages.map(paging => ({
...paging,
toots: paging.toots.filter(toot => toot.id !== id)
}))
}
)
break
}
@ -167,7 +183,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
})
}}
iconFront='volume-x'
title={status.muted ? '取消隐藏对话' : '隐藏对话'}
title={status.muted ? '取消静音对话' : '静音对话'}
/>
{/* Also note that reblogs cannot be pinned. */}
{(status.visibility === 'public' || status.visibility === 'unlisted') && (

View File

@ -10,6 +10,8 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import Emojis from './Emojis'
import { TimelineData } from '../../Timeline'
import { findIndex } from 'lodash'
const fireMutation = async ({
id,
@ -48,10 +50,11 @@ const fireMutation = async ({
export interface Props {
queryKey: QueryKey.Timeline
status: Required<Mastodon.Status, 'poll'>
poll: NonNullable<Mastodon.Status['poll']>
reblog: boolean
}
const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
const TimelinePoll: React.FC<Props> = ({ queryKey, poll, reblog }) => {
const { theme } = useTheme()
const queryClient = useQueryClient()
@ -60,39 +63,58 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
queryClient.cancelQueries(queryKey)
const oldData = queryClient.getQueryData(queryKey)
queryClient.setQueryData(queryKey, (old: any) =>
old.pages.map((paging: any) => ({
toots: paging.toots.map((toot: any) => {
if (toot.poll?.id === id) {
const poll = toot.poll
const myVotes = Object.keys(options).filter(
// @ts-ignore
option => options[option]
)
const myVotesInt = myVotes.map(option => parseInt(option))
const updatePoll = (poll: Mastodon.Poll): Mastodon.Poll => {
const myVotes = Object.keys(options).filter(
// @ts-ignore
option => options[option]
)
const myVotesInt = myVotes.map(option => parseInt(option))
toot.poll = {
...toot.poll,
votes_count: poll.votes_count
? poll.votes_count + myVotes.length
: myVotes.length,
voters_count: poll.voters_count ? poll.voters_count + 1 : 1,
voted: true,
own_votes: myVotesInt,
// @ts-ignore
options: poll.options.map((o, i) => {
if (myVotesInt.includes(i)) {
o.votes_count = o.votes_count + 1
}
return o
})
}
return {
...poll,
votes_count: poll.votes_count
? poll.votes_count + myVotes.length
: myVotes.length,
voters_count: poll.voters_count ? poll.voters_count + 1 : 1,
voted: true,
own_votes: myVotesInt,
options: poll.options.map((o, i) => {
if (myVotesInt.includes(i)) {
o.votes_count = o.votes_count + 1
}
return toot
}),
pointer: paging.pointer
}))
)
return o
})
}
}
queryClient.setQueryData<TimelineData>(queryKey, old => {
let tootIndex = -1
const pageIndex = findIndex(old?.pages, page => {
const tempIndex = findIndex(page.toots, [
reblog ? 'reblog.poll.id' : 'poll.id',
id
])
if (tempIndex >= 0) {
tootIndex = tempIndex
return true
} else {
return false
}
})
if (pageIndex >= 0 && tootIndex >= 0) {
if (reblog) {
old!.pages[pageIndex].toots[tootIndex].reblog!.poll = updatePoll(
old!.pages[pageIndex].toots[tootIndex].reblog!.poll!
)
} else {
old!.pages[pageIndex].toots[tootIndex].poll = updatePoll(
old!.pages[pageIndex].toots[tootIndex].poll!
)
}
}
return old
})
return oldData
},
@ -300,5 +322,5 @@ const styles = StyleSheet.create({
export default React.memo(
TimelinePoll,
(prev, next) => prev.status.poll.voted === next.status.poll.voted
(prev, next) => prev.poll.voted === next.poll.voted
)

View File

@ -179,6 +179,7 @@ const composeExistingState = ({
}): ComposeState => {
switch (type) {
case 'edit':
console.log(incomingStatus)
return {
...composeInitialState,
...(incomingStatus.spoiler_text?.length && {
@ -201,10 +202,10 @@ const composeExistingState = ({
active: true,
total: incomingStatus.poll.options.length,
options: {
'0': incomingStatus.poll.options[0].title || undefined,
'1': incomingStatus.poll.options[1].title || undefined,
'2': incomingStatus.poll.options[2].title || undefined,
'3': incomingStatus.poll.options[3].title || undefined
'0': incomingStatus.poll.options[0]?.title || undefined,
'1': incomingStatus.poll.options[1]?.title || undefined,
'2': incomingStatus.poll.options[2]?.title || undefined,
'3': incomingStatus.poll.options[3]?.title || undefined
},
multiple: incomingStatus.poll.multiple,
expire: '86400' // !!!