mirror of
https://github.com/tooot-app/app
synced 2025-02-14 19:00:50 +01:00
Rewrite header actions
This commit is contained in:
parent
54799aabb8
commit
98a60df9d1
@ -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
|
||||||
|
])
|
||||||
|
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
|
typeof prevState === 'boolean' ? !prevState : true
|
||||||
}
|
}
|
||||||
return toot
|
}
|
||||||
}),
|
|
||||||
pointer: paging.pointer
|
|
||||||
}))
|
|
||||||
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
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return toot
|
})
|
||||||
}),
|
|
||||||
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,26 +63,21 @@ 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) => ({
|
|
||||||
toots: paging.toots.map((toot: any) => {
|
|
||||||
if (toot.poll?.id === id) {
|
|
||||||
const poll = toot.poll
|
|
||||||
const myVotes = Object.keys(options).filter(
|
const myVotes = Object.keys(options).filter(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
option => options[option]
|
option => options[option]
|
||||||
)
|
)
|
||||||
const myVotesInt = myVotes.map(option => parseInt(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
|
||||||
@ -88,11 +86,35 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toot
|
|
||||||
}),
|
queryClient.setQueryData<TimelineData>(queryKey, old => {
|
||||||
pointer: paging.pointer
|
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' // !!!
|
||||||
|
Loading…
x
Reference in New Issue
Block a user