diff --git a/src/components/Timelines/Timeline.tsx b/src/components/Timelines/Timeline.tsx index 94bf1cf4..b6bf48a3 100644 --- a/src/components/Timelines/Timeline.tsx +++ b/src/components/Timelines/Timeline.tsx @@ -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 diff --git a/src/components/Timelines/Timeline/Default.tsx b/src/components/Timelines/Timeline/Default.tsx index 8aed403e..499796f5 100644 --- a/src/components/Timelines/Timeline/Default.tsx +++ b/src/components/Timelines/Timeline/Default.tsx @@ -44,6 +44,7 @@ const TimelineDefault: React.FC = ({ const tootOnPress = useCallback( () => !isRemotePublic && + !highlighted && navigation.push('Screen-Shared-Toot', { toot: actualStatus }), @@ -63,7 +64,11 @@ const TimelineDefault: React.FC = ({ )} {actualStatus.poll && ( - + )} {actualStatus.media_attachments.length > 0 && ( @@ -103,7 +108,11 @@ const TimelineDefault: React.FC = ({ : StyleConstants.Avatar.M + StyleConstants.Spacing.S }} > - + )} @@ -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 diff --git a/src/components/Timelines/Timeline/Shared/Actions.tsx b/src/components/Timelines/Timeline/Shared/Actions.tsx index d5c20ab5..cb98a58c 100644 --- a/src/components/Timelines/Timeline/Shared/Actions.tsx +++ b/src/components/Timelines/Timeline/Shared/Actions.tsx @@ -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 = ({ queryKey, status }) => { +const TimelineActions: React.FC = ({ queryKey, status, reblog }) => { const navigation = useNavigation() const { theme } = useTheme() const iconColor = theme.secondary @@ -65,18 +68,31 @@ const TimelineActions: React.FC = ({ 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(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 diff --git a/src/components/Timelines/Timeline/Shared/HeaderDefault.tsx b/src/components/Timelines/Timeline/Shared/HeaderDefault.tsx index c59c19c0..6108b69e 100644 --- a/src/components/Timelines/Timeline/Shared/HeaderDefault.tsx +++ b/src/components/Timelines/Timeline/Shared/HeaderDefault.tsx @@ -36,9 +36,12 @@ const TimelineHeaderDefault: React.FC = ({ 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), []) diff --git a/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount.tsx b/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount.tsx index 14118dff..b7eee216 100644 --- a/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount.tsx +++ b/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount.tsx @@ -70,14 +70,8 @@ const HeaderDefaultActionsAccount: React.FC = ({ }) => { 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) } }) diff --git a/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain.tsx b/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain.tsx index 15ae45f9..c4c655a5 100644 --- a/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain.tsx +++ b/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain.tsx @@ -42,15 +42,6 @@ const HeaderDefaultActionsDomain: React.FC = ({ }) => { 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) } diff --git a/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus.tsx b/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus.tsx index 1f6c3f85..59a809a8 100644 --- a/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus.tsx +++ b/src/components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus.tsx @@ -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 = ({ 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(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( + 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 = ({ }) }} iconFront='volume-x' - title={status.muted ? '取消隐藏对话' : '隐藏对话'} + title={status.muted ? '取消静音对话' : '静音对话'} /> {/* Also note that reblogs cannot be pinned. */} {(status.visibility === 'public' || status.visibility === 'unlisted') && ( diff --git a/src/components/Timelines/Timeline/Shared/Poll.tsx b/src/components/Timelines/Timeline/Shared/Poll.tsx index b8d6db08..2a16924c 100644 --- a/src/components/Timelines/Timeline/Shared/Poll.tsx +++ b/src/components/Timelines/Timeline/Shared/Poll.tsx @@ -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 + poll: NonNullable + reblog: boolean } -const TimelinePoll: React.FC = ({ queryKey, status: { poll } }) => { +const TimelinePoll: React.FC = ({ queryKey, poll, reblog }) => { const { theme } = useTheme() const queryClient = useQueryClient() @@ -60,39 +63,58 @@ const TimelinePoll: React.FC = ({ 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(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 ) diff --git a/src/screens/Shared/Compose.tsx b/src/screens/Shared/Compose.tsx index 25fd5426..b8dffa52 100644 --- a/src/screens/Shared/Compose.tsx +++ b/src/screens/Shared/Compose.tsx @@ -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' // !!!