mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Rewrite header actions
This commit is contained in:
		| @@ -1,6 +1,6 @@ | |||||||
| import React, { useCallback, useEffect, useMemo, useRef } from 'react' | import React, { useCallback, useEffect, useMemo, useRef } from 'react' | ||||||
| import { StyleSheet } from 'react-native' | import { StyleSheet } from 'react-native' | ||||||
| import { useInfiniteQuery } from 'react-query' | import { InfiniteData, useInfiniteQuery } from 'react-query' | ||||||
|  |  | ||||||
| import TimelineNotifications from '@components/Timelines/Timeline/Notifications' | import TimelineNotifications from '@components/Timelines/Timeline/Notifications' | ||||||
| import TimelineDefault from '@components/Timelines/Timeline/Default' | 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 { useScrollToTop } from '@react-navigation/native' | ||||||
| import { FlatList } from 'react-native-gesture-handler' | import { FlatList } from 'react-native-gesture-handler' | ||||||
|  |  | ||||||
|  | export type TimelineData = | ||||||
|  |   | InfiniteData<{ | ||||||
|  |       toots: Mastodon.Status[] | ||||||
|  |       pointer?: number | undefined | ||||||
|  |       pinnedLength?: number | undefined | ||||||
|  |     }> | ||||||
|  |   | undefined | ||||||
|  |  | ||||||
| export interface Props { | export interface Props { | ||||||
|   page: App.Pages |   page: App.Pages | ||||||
|   hashtag?: string |   hashtag?: string | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ const TimelineDefault: React.FC<Props> = ({ | |||||||
|   const tootOnPress = useCallback( |   const tootOnPress = useCallback( | ||||||
|     () => |     () => | ||||||
|       !isRemotePublic && |       !isRemotePublic && | ||||||
|  |       !highlighted && | ||||||
|       navigation.push('Screen-Shared-Toot', { |       navigation.push('Screen-Shared-Toot', { | ||||||
|         toot: actualStatus |         toot: actualStatus | ||||||
|       }), |       }), | ||||||
| @@ -63,7 +64,11 @@ const TimelineDefault: React.FC<Props> = ({ | |||||||
|           <TimelineContent status={actualStatus} highlighted={highlighted} /> |           <TimelineContent status={actualStatus} highlighted={highlighted} /> | ||||||
|         )} |         )} | ||||||
|         {actualStatus.poll && ( |         {actualStatus.poll && ( | ||||||
|           <TimelinePoll queryKey={queryKey} status={actualStatus} /> |           <TimelinePoll | ||||||
|  |             queryKey={queryKey} | ||||||
|  |             poll={actualStatus.poll} | ||||||
|  |             reblog={item.reblog ? true : false} | ||||||
|  |           /> | ||||||
|         )} |         )} | ||||||
|         {actualStatus.media_attachments.length > 0 && ( |         {actualStatus.media_attachments.length > 0 && ( | ||||||
|           <TimelineAttachment status={actualStatus} width={contentWidth} /> |           <TimelineAttachment status={actualStatus} width={contentWidth} /> | ||||||
| @@ -103,7 +108,11 @@ const TimelineDefault: React.FC<Props> = ({ | |||||||
|               : StyleConstants.Avatar.M + StyleConstants.Spacing.S |               : StyleConstants.Avatar.M + StyleConstants.Spacing.S | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
|           <TimelineActions queryKey={queryKey} status={actualStatus} /> |           <TimelineActions | ||||||
|  |             queryKey={queryKey} | ||||||
|  |             status={actualStatus} | ||||||
|  |             reblog={item.reblog ? true : false} | ||||||
|  |           /> | ||||||
|         </View> |         </View> | ||||||
|       )} |       )} | ||||||
|     </View> |     </View> | ||||||
| @@ -122,16 +131,4 @@ const styles = StyleSheet.create({ | |||||||
|   } |   } | ||||||
| }) | }) | ||||||
|  |  | ||||||
| export default React.memo(TimelineDefault, (prev, next) => { | export default TimelineDefault | ||||||
|   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 |  | ||||||
| }) |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import React, { useCallback, useMemo } from 'react' | import React, { useCallback, useMemo } from 'react' | ||||||
| import { ActionSheetIOS, Pressable, StyleSheet, Text, View } from 'react-native' | 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 { Feather } from '@expo/vector-icons' | ||||||
|  |  | ||||||
| import client from '@api/client' | import client from '@api/client' | ||||||
| @@ -9,6 +9,8 @@ import { toast } from '@components/toast' | |||||||
| import { StyleConstants } from '@utils/styles/constants' | import { StyleConstants } from '@utils/styles/constants' | ||||||
| import { useNavigation } from '@react-navigation/native' | import { useNavigation } from '@react-navigation/native' | ||||||
| import getCurrentTab from '@utils/getCurrentTab' | import getCurrentTab from '@utils/getCurrentTab' | ||||||
|  | import { findIndex } from 'lodash' | ||||||
|  | import { TimelineData } from '../../Timeline' | ||||||
|  |  | ||||||
| const fireMutation = async ({ | const fireMutation = async ({ | ||||||
|   id, |   id, | ||||||
| @@ -46,9 +48,10 @@ const fireMutation = async ({ | |||||||
| export interface Props { | export interface Props { | ||||||
|   queryKey: QueryKey.Timeline |   queryKey: QueryKey.Timeline | ||||||
|   status: Mastodon.Status |   status: Mastodon.Status | ||||||
|  |   reblog: boolean | ||||||
| } | } | ||||||
|  |  | ||||||
| const TimelineActions: React.FC<Props> = ({ queryKey, status }) => { | const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => { | ||||||
|   const navigation = useNavigation() |   const navigation = useNavigation() | ||||||
|   const { theme } = useTheme() |   const { theme } = useTheme() | ||||||
|   const iconColor = theme.secondary |   const iconColor = theme.secondary | ||||||
| @@ -65,18 +68,31 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => { | |||||||
|         case 'favourite': |         case 'favourite': | ||||||
|         case 'reblog': |         case 'reblog': | ||||||
|         case 'bookmark': |         case 'bookmark': | ||||||
|           queryClient.setQueryData(queryKey, (old: any) => { |           queryClient.setQueryData<TimelineData>(queryKey, old => { | ||||||
|             old.pages.map((paging: any) => ({ |             let tootIndex = -1 | ||||||
|               toots: paging.toots.map((toot: any) => { |             const pageIndex = findIndex(old?.pages, page => { | ||||||
|                 if (toot.id === id) { |               const tempIndex = findIndex(page.toots, [ | ||||||
|                   console.log(toot[stateKey]) |                 reblog ? 'reblog.id' : 'id', | ||||||
|                   toot[stateKey] = |                 id | ||||||
|                     typeof prevState === 'boolean' ? !prevState : true |               ]) | ||||||
|                 } |               if (tempIndex >= 0) { | ||||||
|                 return toot |                 tootIndex = tempIndex | ||||||
|               }), |                 return true | ||||||
|               pointer: paging.pointer |               } 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 |             return old | ||||||
|           }) |           }) | ||||||
|           break |           break | ||||||
|   | |||||||
| @@ -36,9 +36,12 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => { | |||||||
|  |  | ||||||
|   // causing full re-render |   // causing full re-render | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setTimeout(() => { |     const timer = setTimeout(() => { | ||||||
|       setSince(relativeTime(status.created_at)) |       setSince(relativeTime(status.created_at)) | ||||||
|     }, 1000) |     }, 1000) | ||||||
|  |     return () => { | ||||||
|  |       clearTimeout(timer) | ||||||
|  |     } | ||||||
|   }, [since]) |   }, [since]) | ||||||
|  |  | ||||||
|   const onPressAction = useCallback(() => setBottomSheetVisible(true), []) |   const onPressAction = useCallback(() => setBottomSheetVisible(true), []) | ||||||
|   | |||||||
| @@ -70,14 +70,8 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({ | |||||||
| }) => { | }) => { | ||||||
|   const queryClient = useQueryClient() |   const queryClient = useQueryClient() | ||||||
|   const { mutate } = useMutation(fireMutation, { |   const { mutate } = useMutation(fireMutation, { | ||||||
|     onMutate: () => { |     onSettled: () => { | ||||||
|       queryClient.cancelQueries(queryKey) |       queryClient.invalidateQueries(queryKey) | ||||||
|       const oldData = queryClient.getQueryData(queryKey) |  | ||||||
|       return oldData |  | ||||||
|     }, |  | ||||||
|     onError: (err, _, oldData) => { |  | ||||||
|       toast({ type: 'error', content: '请重试', autoHide: false }) |  | ||||||
|       queryClient.setQueryData(queryKey, oldData) |  | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,15 +42,6 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({ | |||||||
| }) => { | }) => { | ||||||
|   const queryClient = useQueryClient() |   const queryClient = useQueryClient() | ||||||
|   const { mutate } = useMutation(fireMutation, { |   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: () => { |     onSettled: () => { | ||||||
|       queryClient.invalidateQueries(queryKey) |       queryClient.invalidateQueries(queryKey) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ import client from '@api/client' | |||||||
| import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu' | import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu' | ||||||
| import { toast } from '@components/toast' | import { toast } from '@components/toast' | ||||||
| import getCurrentTab from '@utils/getCurrentTab' | import getCurrentTab from '@utils/getCurrentTab' | ||||||
|  | import { TimelineData } from '@root/components/Timelines/Timeline' | ||||||
|  | import { findIndex } from 'lodash' | ||||||
|  |  | ||||||
| const fireMutation = async ({ | const fireMutation = async ({ | ||||||
|   id, |   id, | ||||||
| @@ -75,25 +77,39 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({ | |||||||
|       switch (type) { |       switch (type) { | ||||||
|         case 'mute': |         case 'mute': | ||||||
|         case 'pin': |         case 'pin': | ||||||
|           queryClient.setQueryData(queryKey, (old: any) => |           queryClient.setQueryData<TimelineData>(queryKey, old => { | ||||||
|             old.pages.map((paging: any) => ({ |             let tootIndex = -1 | ||||||
|               toots: paging.toots.map((toot: any) => { |             const pageIndex = findIndex(old?.pages, page => { | ||||||
|                 if (toot.id === id) { |               const tempIndex = findIndex(page.toots, ['id', id]) | ||||||
|                   toot[stateKey] = |               if (tempIndex >= 0) { | ||||||
|                     typeof prevState === 'boolean' ? !prevState : true |                 tootIndex = tempIndex | ||||||
|                 } |                 return true | ||||||
|                 return toot |               } else { | ||||||
|               }), |                 return false | ||||||
|               pointer: paging.pointer |               } | ||||||
|             })) |             }) | ||||||
|           ) |  | ||||||
|  |             if (pageIndex >= 0 && tootIndex >= 0) { | ||||||
|  |               old!.pages[pageIndex].toots[tootIndex][ | ||||||
|  |                 stateKey as 'muted' | 'pinned' | ||||||
|  |               ] = typeof prevState === 'boolean' ? !prevState : true | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return old | ||||||
|  |           }) | ||||||
|           break |           break | ||||||
|         case 'delete': |         case 'delete': | ||||||
|           queryClient.setQueryData(queryKey, (old: any) => |           console.log('deleting toot') | ||||||
|             old.pages.map((paging: any) => ({ |           queryClient.setQueryData<TimelineData>( | ||||||
|               toots: paging.toots.filter((toot: any) => toot.id !== id), |             queryKey, | ||||||
|               pointer: paging.pointer |             old => | ||||||
|             })) |               old && { | ||||||
|  |                 ...old, | ||||||
|  |                 pages: old?.pages.map(paging => ({ | ||||||
|  |                   ...paging, | ||||||
|  |                   toots: paging.toots.filter(toot => toot.id !== id) | ||||||
|  |                 })) | ||||||
|  |               } | ||||||
|           ) |           ) | ||||||
|           break |           break | ||||||
|       } |       } | ||||||
| @@ -167,7 +183,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({ | |||||||
|           }) |           }) | ||||||
|         }} |         }} | ||||||
|         iconFront='volume-x' |         iconFront='volume-x' | ||||||
|         title={status.muted ? '取消隐藏对话' : '隐藏对话'} |         title={status.muted ? '取消静音对话' : '静音对话'} | ||||||
|       /> |       /> | ||||||
|       {/* Also note that reblogs cannot be pinned. */} |       {/* Also note that reblogs cannot be pinned. */} | ||||||
|       {(status.visibility === 'public' || status.visibility === 'unlisted') && ( |       {(status.visibility === 'public' || status.visibility === 'unlisted') && ( | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ import { StyleConstants } from '@utils/styles/constants' | |||||||
| import { useTheme } from '@utils/styles/ThemeManager' | import { useTheme } from '@utils/styles/ThemeManager' | ||||||
|  |  | ||||||
| import Emojis from './Emojis' | import Emojis from './Emojis' | ||||||
|  | import { TimelineData } from '../../Timeline' | ||||||
|  | import { findIndex } from 'lodash' | ||||||
|  |  | ||||||
| const fireMutation = async ({ | const fireMutation = async ({ | ||||||
|   id, |   id, | ||||||
| @@ -48,10 +50,11 @@ const fireMutation = async ({ | |||||||
|  |  | ||||||
| export interface Props { | export interface Props { | ||||||
|   queryKey: QueryKey.Timeline |   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 { theme } = useTheme() | ||||||
|  |  | ||||||
|   const queryClient = useQueryClient() |   const queryClient = useQueryClient() | ||||||
| @@ -60,39 +63,58 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => { | |||||||
|       queryClient.cancelQueries(queryKey) |       queryClient.cancelQueries(queryKey) | ||||||
|       const oldData = queryClient.getQueryData(queryKey) |       const oldData = queryClient.getQueryData(queryKey) | ||||||
|  |  | ||||||
|       queryClient.setQueryData(queryKey, (old: any) => |       const updatePoll = (poll: Mastodon.Poll): Mastodon.Poll => { | ||||||
|         old.pages.map((paging: any) => ({ |         const myVotes = Object.keys(options).filter( | ||||||
|           toots: paging.toots.map((toot: any) => { |           // @ts-ignore | ||||||
|             if (toot.poll?.id === id) { |           option => options[option] | ||||||
|               const poll = toot.poll |         ) | ||||||
|               const myVotes = Object.keys(options).filter( |         const myVotesInt = myVotes.map(option => parseInt(option)) | ||||||
|                 // @ts-ignore |  | ||||||
|                 option => options[option] |  | ||||||
|               ) |  | ||||||
|               const myVotesInt = myVotes.map(option => parseInt(option)) |  | ||||||
|  |  | ||||||
|               toot.poll = { |         return { | ||||||
|                 ...toot.poll, |           ...poll, | ||||||
|                 votes_count: poll.votes_count |           votes_count: poll.votes_count | ||||||
|                   ? poll.votes_count + myVotes.length |             ? poll.votes_count + myVotes.length | ||||||
|                   : myVotes.length, |             : myVotes.length, | ||||||
|                 voters_count: poll.voters_count ? poll.voters_count + 1 : 1, |           voters_count: poll.voters_count ? poll.voters_count + 1 : 1, | ||||||
|                 voted: true, |           voted: true, | ||||||
|                 own_votes: myVotesInt, |           own_votes: myVotesInt, | ||||||
|                 // @ts-ignore |           options: poll.options.map((o, i) => { | ||||||
|                 options: poll.options.map((o, i) => { |             if (myVotesInt.includes(i)) { | ||||||
|                   if (myVotesInt.includes(i)) { |               o.votes_count = o.votes_count + 1 | ||||||
|                     o.votes_count = o.votes_count + 1 |  | ||||||
|                   } |  | ||||||
|                   return o |  | ||||||
|                 }) |  | ||||||
|               } |  | ||||||
|             } |             } | ||||||
|             return toot |             return o | ||||||
|           }), |           }) | ||||||
|           pointer: paging.pointer |         } | ||||||
|         })) |       } | ||||||
|       ) |  | ||||||
|  |       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 |       return oldData | ||||||
|     }, |     }, | ||||||
| @@ -300,5 +322,5 @@ const styles = StyleSheet.create({ | |||||||
|  |  | ||||||
| export default React.memo( | export default React.memo( | ||||||
|   TimelinePoll, |   TimelinePoll, | ||||||
|   (prev, next) => prev.status.poll.voted === next.status.poll.voted |   (prev, next) => prev.poll.voted === next.poll.voted | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -179,6 +179,7 @@ const composeExistingState = ({ | |||||||
| }): ComposeState => { | }): ComposeState => { | ||||||
|   switch (type) { |   switch (type) { | ||||||
|     case 'edit': |     case 'edit': | ||||||
|  |       console.log(incomingStatus) | ||||||
|       return { |       return { | ||||||
|         ...composeInitialState, |         ...composeInitialState, | ||||||
|         ...(incomingStatus.spoiler_text?.length && { |         ...(incomingStatus.spoiler_text?.length && { | ||||||
| @@ -201,10 +202,10 @@ const composeExistingState = ({ | |||||||
|             active: true, |             active: true, | ||||||
|             total: incomingStatus.poll.options.length, |             total: incomingStatus.poll.options.length, | ||||||
|             options: { |             options: { | ||||||
|               '0': incomingStatus.poll.options[0].title || undefined, |               '0': incomingStatus.poll.options[0]?.title || undefined, | ||||||
|               '1': incomingStatus.poll.options[1].title || undefined, |               '1': incomingStatus.poll.options[1]?.title || undefined, | ||||||
|               '2': incomingStatus.poll.options[2].title || undefined, |               '2': incomingStatus.poll.options[2]?.title || undefined, | ||||||
|               '3': incomingStatus.poll.options[3].title || undefined |               '3': incomingStatus.poll.options[3]?.title || undefined | ||||||
|             }, |             }, | ||||||
|             multiple: incomingStatus.poll.multiple, |             multiple: incomingStatus.poll.multiple, | ||||||
|             expire: '86400' // !!! |             expire: '86400' // !!! | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user