mirror of
https://github.com/tooot-app/app
synced 2025-03-28 09:20:12 +01:00
Switch to shared hooks
This commit is contained in:
parent
fdce172c57
commit
284d6e46e0
App.tsx
src
Index.tsx
components
screens
utils/queryHooks
1
App.tsx
1
App.tsx
@ -7,7 +7,6 @@ import { enableScreens } from 'react-native-screens'
|
||||
import { QueryClient, QueryClientProvider } from 'react-query'
|
||||
import { Provider } from 'react-redux'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
import checkSecureStorageVersion from '@root/startup/checkSecureStorageVersion'
|
||||
import dev from '@root/startup/dev'
|
||||
import sentry from '@root/startup/sentry'
|
||||
import log from '@root/startup/log'
|
||||
|
@ -14,7 +14,7 @@ import ScreenLocal from '@screens/Local'
|
||||
import ScreenMe from '@screens/Me'
|
||||
import ScreenNotifications from '@screens/Notifications'
|
||||
import ScreenPublic from '@screens/Public'
|
||||
import hookTimeline from '@utils/queryHooks/timeline'
|
||||
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
getLocalActiveIndex,
|
||||
getLocalNotification,
|
||||
@ -100,7 +100,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
}, [])
|
||||
|
||||
// On launch check if there is any unread noficiations
|
||||
const queryNotification = hookTimeline({
|
||||
const queryNotification = useTimelineQuery({
|
||||
page: 'Notifications',
|
||||
options: {
|
||||
enabled: localActiveIndex !== null ? true : false,
|
||||
@ -112,9 +112,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
const prevNotification = useSelector(getLocalNotification)
|
||||
useEffect(() => {
|
||||
if (queryNotification.data?.pages) {
|
||||
const flattenData = queryNotification.data.pages.flatMap(d => [
|
||||
...d?.toots
|
||||
])
|
||||
const flattenData = queryNotification.data.pages.flatMap(d => [...d])
|
||||
const latestNotificationTime = flattenData.length
|
||||
? (flattenData[0] as Mastodon.Notification).created_at
|
||||
: undefined
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
ViewStyle
|
||||
} from 'react-native'
|
||||
import { Chase } from 'react-native-animated-spinkit'
|
||||
import Animated from 'react-native-reanimated'
|
||||
|
||||
export interface Props {
|
||||
style?: StyleProp<ViewStyle>
|
||||
@ -156,7 +155,7 @@ const Button: React.FC<Props> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View>
|
||||
<View>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.button,
|
||||
@ -175,7 +174,7 @@ const Button: React.FC<Props> = ({
|
||||
children={children}
|
||||
disabled={disabled || active || loading}
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@ import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import hookApps from '@utils/queryHooks/apps'
|
||||
import hookInstance from '@utils/queryHooks/instance'
|
||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
getLocalInstances,
|
||||
@ -42,11 +42,11 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
const [appData, setApplicationData] = useState<InstanceLocal['appData']>()
|
||||
const localInstances = useSelector(getLocalInstances)
|
||||
|
||||
const instanceQuery = hookInstance({
|
||||
const instanceQuery = useInstanceQuery({
|
||||
instanceDomain,
|
||||
options: { enabled: false, retry: false }
|
||||
})
|
||||
const applicationQuery = hookApps({
|
||||
const applicationQuery = useAppsQuery({
|
||||
instanceDomain,
|
||||
options: { enabled: false, retry: false }
|
||||
})
|
||||
|
@ -1,13 +1,16 @@
|
||||
import client from '@api/client'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { toast } from '@components/toast'
|
||||
import { QueryKeyRelationship } from '@utils/queryHooks/relationship'
|
||||
import {
|
||||
QueryKeyRelationship,
|
||||
useRelationshipMutation
|
||||
} from '@utils/queryHooks/relationship'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
id: Mastodon.Account['id']
|
||||
@ -17,23 +20,18 @@ const RelationshipIncoming: React.FC<Props> = ({ id }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
|
||||
|
||||
const queryKeyNotification: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Notifications' }
|
||||
]
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(
|
||||
({ type }: { type: 'authorize' | 'reject' }) => {
|
||||
return client<Mastodon.Relationship>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `follow_requests/${id}/${type}`
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
const mutation = useMutation(fireMutation, {
|
||||
const mutation = useRelationshipMutation({
|
||||
onSuccess: res => {
|
||||
haptics('Success')
|
||||
queryClient.setQueryData(queryKeyRelationship, res)
|
||||
queryClient.refetchQueries(['Notifications'])
|
||||
queryClient.setQueryData<Mastodon.Relationship[]>(queryKeyRelationship, [
|
||||
res
|
||||
])
|
||||
queryClient.refetchQueries(queryKeyNotification)
|
||||
},
|
||||
onError: (err: any, { type }) => {
|
||||
haptics('Error')
|
||||
@ -60,14 +58,26 @@ const RelationshipIncoming: React.FC<Props> = ({ id }) => {
|
||||
type='icon'
|
||||
content='X'
|
||||
loading={mutation.isLoading}
|
||||
onPress={() => mutation.mutate({ type: 'reject' })}
|
||||
onPress={() =>
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'incoming',
|
||||
payload: { action: 'reject' }
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
round
|
||||
type='icon'
|
||||
content='Check'
|
||||
loading={mutation.isLoading}
|
||||
onPress={() => mutation.mutate({ type: 'authorize' })}
|
||||
onPress={() =>
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'incoming',
|
||||
payload: { action: 'authorize' }
|
||||
})
|
||||
}
|
||||
style={styles.approve}
|
||||
/>
|
||||
</View>
|
||||
|
@ -1,113 +1,127 @@
|
||||
import client from '@api/client'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { toast } from '@components/toast'
|
||||
import hookRelationship, {
|
||||
QueryKeyRelationship
|
||||
import {
|
||||
QueryKeyRelationship,
|
||||
useRelationshipMutation,
|
||||
useRelationshipQuery
|
||||
} from '@utils/queryHooks/relationship'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
id: Mastodon.Account['id']
|
||||
}
|
||||
|
||||
const RelationshipOutgoing: React.FC<Props> = ({ id }) => {
|
||||
const { t } = useTranslation()
|
||||
const RelationshipOutgoing = React.memo(
|
||||
({ id }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
|
||||
const query = hookRelationship({ id })
|
||||
const query = useRelationshipQuery({ id })
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(
|
||||
({ type, state }: { type: 'follow' | 'block'; state: boolean }) => {
|
||||
return client<Mastodon.Relationship>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `accounts/${id}/${state ? 'un' : ''}${type}`
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
const mutation = useMutation(fireMutation, {
|
||||
onSuccess: res => {
|
||||
haptics('Success')
|
||||
queryClient.setQueryData(queryKeyRelationship, res)
|
||||
},
|
||||
onError: (err: any, { type }) => {
|
||||
haptics('Error')
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
function: t(`relationship:${type}.function`)
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
err.data &&
|
||||
err.data.error &&
|
||||
typeof err.data.error === 'string' && {
|
||||
description: err.data.error
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
|
||||
const queryClient = useQueryClient()
|
||||
const mutation = useRelationshipMutation({
|
||||
onSuccess: res => {
|
||||
haptics('Success')
|
||||
queryClient.setQueryData<Mastodon.Relationship[]>(
|
||||
queryKeyRelationship,
|
||||
[res]
|
||||
)
|
||||
},
|
||||
onError: (err: any, { type }) => {
|
||||
haptics('Error')
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
function: t(`relationship:${type}.function`)
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
err.data &&
|
||||
err.data.error &&
|
||||
typeof err.data.error === 'string' && {
|
||||
description: err.data.error
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
let content: string
|
||||
let onPress: () => void
|
||||
let content: string
|
||||
let onPress: () => void
|
||||
|
||||
if (query.isError) {
|
||||
content = t('relationship:button.error')
|
||||
onPress = () => {}
|
||||
} else {
|
||||
if (query.data?.blocked_by) {
|
||||
content = t('relationship:button.blocked_by')
|
||||
onPress = () => null
|
||||
if (query.isError) {
|
||||
content = t('relationship:button.error')
|
||||
onPress = () => {}
|
||||
} else {
|
||||
if (query.data?.blocking) {
|
||||
content = t('relationship:button.blocking')
|
||||
onPress = () =>
|
||||
mutation.mutate({
|
||||
type: 'block',
|
||||
state: query.data?.blocking
|
||||
})
|
||||
if (query.data?.blocked_by) {
|
||||
content = t('relationship:button.blocked_by')
|
||||
onPress = () => null
|
||||
} else {
|
||||
if (query.data?.following) {
|
||||
content = t('relationship:button.following')
|
||||
if (query.data?.blocking) {
|
||||
content = t('relationship:button.blocking')
|
||||
onPress = () =>
|
||||
mutation.mutate({
|
||||
type: 'follow',
|
||||
state: query.data?.following
|
||||
id,
|
||||
type: 'outgoing',
|
||||
payload: {
|
||||
action: 'block',
|
||||
state: query.data?.blocking
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (query.data?.requested) {
|
||||
content = t('relationship:button.requested')
|
||||
if (query.data?.following) {
|
||||
content = t('relationship:button.following')
|
||||
onPress = () =>
|
||||
mutation.mutate({
|
||||
type: 'follow',
|
||||
state: query.data?.requested
|
||||
id,
|
||||
type: 'outgoing',
|
||||
payload: {
|
||||
action: 'follow',
|
||||
state: query.data?.following
|
||||
}
|
||||
})
|
||||
} else {
|
||||
content = t('relationship:button.default')
|
||||
onPress = () =>
|
||||
mutation.mutate({
|
||||
type: 'follow',
|
||||
state: false
|
||||
})
|
||||
if (query.data?.requested) {
|
||||
content = t('relationship:button.requested')
|
||||
onPress = () =>
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'outgoing',
|
||||
payload: {
|
||||
action: 'follow',
|
||||
state: query.data?.requested
|
||||
}
|
||||
})
|
||||
} else {
|
||||
content = t('relationship:button.default')
|
||||
onPress = () =>
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'outgoing',
|
||||
payload: {
|
||||
action: 'follow',
|
||||
state: false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
type='text'
|
||||
content={content}
|
||||
onPress={onPress}
|
||||
loading={query.isLoading || mutation.isLoading}
|
||||
disabled={query.isError || query.data?.blocked_by}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
type='text'
|
||||
content={content}
|
||||
onPress={onPress}
|
||||
loading={query.isLoading || mutation.isLoading}
|
||||
disabled={query.isError || query.data?.blocked_by}
|
||||
/>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
export default RelationshipOutgoing
|
||||
|
@ -13,13 +13,12 @@ import { RefreshControl, StyleSheet } from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import { InfiniteData } from 'react-query'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import hookTimeline, { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { findIndex } from 'lodash'
|
||||
|
||||
export type TimelineData =
|
||||
type TimelineData =
|
||||
| InfiniteData<{
|
||||
toots: Mastodon.Status[]
|
||||
pointer?: number | undefined
|
||||
pinnedLength?: number | undefined
|
||||
}>
|
||||
| undefined
|
||||
|
||||
@ -61,35 +60,33 @@ const Timeline: React.FC<Props> = ({
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage
|
||||
} = hookTimeline({
|
||||
} = useTimelineQuery({
|
||||
...queryKeyParams,
|
||||
options: {
|
||||
getPreviousPageParam: firstPage => {
|
||||
return firstPage.toots.length
|
||||
return firstPage.length
|
||||
? {
|
||||
direction: 'prev',
|
||||
id: firstPage.toots[0].id
|
||||
id: firstPage[0].last_status
|
||||
? firstPage[0].last_status.id
|
||||
: firstPage[0].id
|
||||
}
|
||||
: undefined
|
||||
},
|
||||
getNextPageParam: lastPage => {
|
||||
return lastPage.toots.length
|
||||
return lastPage.length
|
||||
? {
|
||||
direction: 'next',
|
||||
id: lastPage.toots[lastPage.toots.length - 1].id
|
||||
id: lastPage[lastPage.length - 1].last_status
|
||||
? lastPage[lastPage.length - 1].last_status.id
|
||||
: lastPage[lastPage.length - 1].id
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const flattenData = data?.pages ? data.pages.flatMap(d => [...d?.toots]) : []
|
||||
const flattenPointer = data?.pages
|
||||
? data.pages.flatMap(d => [d?.pointer])
|
||||
: []
|
||||
const flattenPinnedLength = data?.pages
|
||||
? data.pages.flatMap(d => [d?.pinnedLength])
|
||||
: []
|
||||
const flattenData = data?.pages ? data.pages.flatMap(d => [...d]) : []
|
||||
|
||||
// Clear unread notification badge
|
||||
const dispatch = useDispatch()
|
||||
@ -107,48 +104,41 @@ const Timeline: React.FC<Props> = ({
|
||||
const flRef = useRef<FlatList<any>>(null)
|
||||
useEffect(() => {
|
||||
if (toot && isSuccess) {
|
||||
const pointer = findIndex(flattenData, ['id', toot])
|
||||
setTimeout(() => {
|
||||
flRef.current?.scrollToIndex({
|
||||
index: flattenPointer[0]!,
|
||||
index: pointer,
|
||||
viewOffset: 100
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
}, [isSuccess])
|
||||
}, [isSuccess, flattenData])
|
||||
|
||||
const keyExtractor = useCallback(({ id }) => id, [])
|
||||
const renderItem = useCallback(
|
||||
({ item, index }) => {
|
||||
switch (page) {
|
||||
case 'Conversations':
|
||||
return (
|
||||
<TimelineConversation conversation={item} queryKey={queryKey} />
|
||||
)
|
||||
case 'Notifications':
|
||||
return (
|
||||
<TimelineNotifications notification={item} queryKey={queryKey} />
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<TimelineDefault
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
index={index}
|
||||
{...(queryKey[1].page === 'RemotePublic' && {
|
||||
disableDetails: true,
|
||||
disableOnPress: true
|
||||
})}
|
||||
{...(flattenPinnedLength &&
|
||||
flattenPinnedLength[0] && {
|
||||
pinnedLength: flattenPinnedLength[0]
|
||||
})}
|
||||
{...(toot === item.id && { highlighted: true })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
[flattenPinnedLength[0]]
|
||||
)
|
||||
const renderItem = useCallback(({ item }) => {
|
||||
switch (page) {
|
||||
case 'Conversations':
|
||||
return <TimelineConversation conversation={item} queryKey={queryKey} />
|
||||
case 'Notifications':
|
||||
return <TimelineNotifications notification={item} queryKey={queryKey} />
|
||||
default:
|
||||
// if (item.poll) {
|
||||
// console.log('Timeline')
|
||||
// console.log(item.poll)
|
||||
// }
|
||||
return (
|
||||
<TimelineDefault
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
{...(queryKey[1].page === 'RemotePublic' && {
|
||||
disableDetails: true,
|
||||
disableOnPress: true
|
||||
})}
|
||||
{...(toot === item.id && { highlighted: true })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
const ItemSeparatorComponent = useCallback(
|
||||
({ leadingItem }) => (
|
||||
<ComponentSeparator
|
||||
|
@ -1,16 +1,18 @@
|
||||
import client from '@api/client'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
import TimelineAvatar from '@components/Timelines/Timeline/Shared/Avatar'
|
||||
import TimelineHeaderConversation from '@components/Timelines/Timeline/Shared/HeaderConversation'
|
||||
import TimelineContent from '@components/Timelines/Timeline/Shared/Content'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
||||
import client from '@root/api/client'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineActions from './Shared/Actions'
|
||||
import TimelineAvatar from './Shared/Avatar'
|
||||
import TimelineContent from './Shared/Content'
|
||||
import TimelineHeaderConversation from './Shared/HeaderConversation'
|
||||
import TimelinePoll from './Shared/Poll'
|
||||
|
||||
export interface Props {
|
||||
conversation: Mastodon.Conversation
|
||||
@ -23,6 +25,7 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
queryKey,
|
||||
highlighted = false
|
||||
}) => {
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const { theme } = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
@ -88,6 +91,15 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
status={conversation.last_status}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
{conversation.last_status.poll && (
|
||||
<TimelinePoll
|
||||
queryKey={queryKey}
|
||||
statusId={conversation.last_status.id}
|
||||
poll={conversation.last_status.poll}
|
||||
reblog={false}
|
||||
sameAccount={conversation.last_status.id === localAccount?.id}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
|
@ -17,8 +17,6 @@ import { useSelector } from 'react-redux'
|
||||
export interface Props {
|
||||
item: Mastodon.Status
|
||||
queryKey?: QueryKeyTimeline
|
||||
index: number
|
||||
pinnedLength?: number
|
||||
highlighted?: boolean
|
||||
disableDetails?: boolean
|
||||
disableOnPress?: boolean
|
||||
@ -28,8 +26,6 @@ export interface Props {
|
||||
const TimelineDefault: React.FC<Props> = ({
|
||||
item,
|
||||
queryKey,
|
||||
index,
|
||||
pinnedLength,
|
||||
highlighted = false,
|
||||
disableDetails = false,
|
||||
disableOnPress = false
|
||||
@ -53,7 +49,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
<Pressable style={styles.statusView} onPress={onPress}>
|
||||
{item.reblog ? (
|
||||
<TimelineActioned action='reblog' account={item.account} />
|
||||
) : pinnedLength && index < pinnedLength ? (
|
||||
) : item.isPinned ? (
|
||||
<TimelineActioned action='pinned' account={item.account} />
|
||||
) : null}
|
||||
|
||||
@ -84,14 +80,15 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
disableDetails={disableDetails}
|
||||
/>
|
||||
)}
|
||||
{queryKey && actualStatus.poll && (
|
||||
{queryKey && actualStatus.poll ? (
|
||||
<TimelinePoll
|
||||
queryKey={queryKey}
|
||||
statusId={actualStatus.id}
|
||||
poll={actualStatus.poll}
|
||||
reblog={item.reblog ? true : false}
|
||||
sameAccount={actualStatus.account.id === localAccount?.id}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
{!disableDetails && actualStatus.media_attachments.length > 0 && (
|
||||
<TimelineAttachment status={actualStatus} />
|
||||
)}
|
||||
|
@ -82,6 +82,7 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
{notification.status.poll && (
|
||||
<TimelinePoll
|
||||
queryKey={queryKey}
|
||||
statusId={notification.status.id}
|
||||
poll={notification.status.poll}
|
||||
reblog={false}
|
||||
sameAccount={notification.account.id === localAccount?.id}
|
||||
|
@ -1,17 +1,18 @@
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { TimelineData } from '@components/Timelines/Timeline'
|
||||
import { toast } from '@components/toast'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ActionSheetIOS, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
@ -28,87 +29,18 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
state ? theme.primary : theme.secondary
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(
|
||||
async ({
|
||||
type,
|
||||
state
|
||||
}: {
|
||||
type: 'favourite' | 'reblog' | 'bookmark'
|
||||
stateKey: 'favourited' | 'reblogged' | 'bookmarked'
|
||||
state?: boolean
|
||||
}) => {
|
||||
return client<Mastodon.Status>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `statuses/${status.id}/${state ? 'un' : ''}${type}`
|
||||
}) // bug in response from Mastodon
|
||||
},
|
||||
[]
|
||||
)
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: ({ type, stateKey, state }) => {
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
haptics('Success')
|
||||
switch (type) {
|
||||
case 'favourite':
|
||||
case 'reblog':
|
||||
case 'bookmark':
|
||||
queryClient.setQueryData<TimelineData>(queryKey, old => {
|
||||
let tootIndex = -1
|
||||
const pageIndex = findIndex(old?.pages, page => {
|
||||
const tempIndex = findIndex(page.toots, [
|
||||
queryKey[1].page === 'Notifications'
|
||||
? 'status.id'
|
||||
: reblog
|
||||
? 'reblog.id'
|
||||
: 'id',
|
||||
status.id
|
||||
])
|
||||
if (tempIndex >= 0) {
|
||||
tootIndex = tempIndex
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (pageIndex >= 0 && tootIndex >= 0) {
|
||||
if (
|
||||
(type === 'favourite' && queryKey[1].page === 'Favourites') ||
|
||||
(type === 'bookmark' && queryKey[1].page === 'Bookmarks')
|
||||
) {
|
||||
old!.pages[pageIndex].toots.splice(tootIndex, 1)
|
||||
} else {
|
||||
if (queryKey[1].page === 'Notifications') {
|
||||
old!.pages[pageIndex].toots[tootIndex].status[stateKey] =
|
||||
typeof state === 'boolean' ? !state : true
|
||||
} else {
|
||||
if (reblog) {
|
||||
old!.pages[pageIndex].toots[tootIndex].reblog![stateKey] =
|
||||
typeof state === 'boolean' ? !state : true
|
||||
} else {
|
||||
old!.pages[pageIndex].toots[tootIndex][stateKey] =
|
||||
typeof state === 'boolean' ? !state : true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return old
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
return oldData
|
||||
},
|
||||
onError: (err: any, { type }, oldData) => {
|
||||
const mutation = useTimelineMutation({
|
||||
queryClient,
|
||||
onMutate: true,
|
||||
onError: (err: any, params, oldData) => {
|
||||
const correctParam = params as MutationVarsTimelineUpdateStatusProperty
|
||||
haptics('Error')
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
function: t(`timeline:shared.actions.${type}.function`)
|
||||
function: t(
|
||||
`timeline:shared.actions.${correctParam.payload.property}.function`
|
||||
)
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
@ -132,28 +64,43 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
)
|
||||
const onPressReblog = useCallback(
|
||||
() =>
|
||||
mutate({
|
||||
type: 'reblog',
|
||||
stateKey: 'reblogged',
|
||||
state: status.reblogged
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'reblogged',
|
||||
currentValue: status.reblogged
|
||||
}
|
||||
}),
|
||||
[status.reblogged]
|
||||
)
|
||||
const onPressFavourite = useCallback(
|
||||
() =>
|
||||
mutate({
|
||||
type: 'favourite',
|
||||
stateKey: 'favourited',
|
||||
state: status.favourited
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'favourited',
|
||||
currentValue: status.favourited
|
||||
}
|
||||
}),
|
||||
[status.favourited]
|
||||
)
|
||||
const onPressBookmark = useCallback(
|
||||
() =>
|
||||
mutate({
|
||||
type: 'bookmark',
|
||||
stateKey: 'bookmarked',
|
||||
state: status.bookmarked
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'bookmarked',
|
||||
currentValue: status.bookmarked
|
||||
}
|
||||
}),
|
||||
[status.bookmarked]
|
||||
)
|
||||
|
@ -1,18 +1,19 @@
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { TimelineData } from '@components/Timelines/Timeline'
|
||||
import { toast } from '@components/toast'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
@ -23,40 +24,9 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(() => {
|
||||
return client<Mastodon.Conversation>({
|
||||
method: 'delete',
|
||||
instance: 'local',
|
||||
url: `conversations/${conversation.id}`
|
||||
})
|
||||
}, [])
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: () => {
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
haptics('Success')
|
||||
queryClient.setQueryData<TimelineData>(queryKey, old => {
|
||||
let tootIndex = -1
|
||||
const pageIndex = findIndex(old?.pages, page => {
|
||||
const tempIndex = findIndex(page.toots, ['id', conversation.id])
|
||||
if (tempIndex >= 0) {
|
||||
tootIndex = tempIndex
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (pageIndex >= 0 && tootIndex >= 0) {
|
||||
old!.pages[pageIndex].toots.splice(tootIndex, 1)
|
||||
}
|
||||
|
||||
return old
|
||||
})
|
||||
|
||||
return oldData
|
||||
},
|
||||
const mutation = useTimelineMutation({
|
||||
queryClient,
|
||||
onMutate: true,
|
||||
onError: (err: any, _, oldData) => {
|
||||
haptics('Error')
|
||||
toast({
|
||||
@ -79,7 +49,16 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
|
||||
const { theme } = useTheme()
|
||||
|
||||
const actionOnPress = useCallback(() => mutate(), [])
|
||||
const actionOnPress = useCallback(
|
||||
() =>
|
||||
mutation.mutate({
|
||||
type: 'deleteItem',
|
||||
source: 'conversations',
|
||||
queryKey,
|
||||
id: conversation.id
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
const actionChildren = useMemo(
|
||||
() => (
|
||||
@ -102,6 +81,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
created_at={conversation.last_status?.created_at}
|
||||
/>
|
||||
) : null}
|
||||
<HeaderSharedMuted muted={conversation.last_status?.muted} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
@ -14,6 +14,7 @@ import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
@ -54,6 +55,7 @@ const TimelineHeaderDefault: React.FC<Props> = ({
|
||||
<View style={styles.meta}>
|
||||
<HeaderSharedCreated created_at={status.created_at} />
|
||||
<HeaderSharedVisibility visibility={status.visibility} />
|
||||
<HeaderSharedMuted muted={status.muted} />
|
||||
<HeaderSharedApplication application={status.application} />
|
||||
</View>
|
||||
</View>
|
||||
@ -125,4 +127,7 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
export default React.memo(TimelineHeaderDefault, () => true)
|
||||
export default React.memo(
|
||||
TimelineHeaderDefault,
|
||||
(prev, next) => prev.status.muted !== next.status.muted
|
||||
)
|
||||
|
@ -1,11 +1,13 @@
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
|
||||
import { toast } from '@components/toast'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
@ -19,34 +21,10 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
setBottomSheetVisible
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(
|
||||
async ({ type }: { type: 'mute' | 'block' | 'reports' }) => {
|
||||
switch (type) {
|
||||
case 'mute':
|
||||
case 'block':
|
||||
return client<Mastodon.Account>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `accounts/${account.id}/${type}`
|
||||
})
|
||||
break
|
||||
case 'reports':
|
||||
return client<Mastodon.Account>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `reports`,
|
||||
params: {
|
||||
account_id: account.id!
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
const mutateion = useTimelineMutation({
|
||||
queryClient,
|
||||
onSuccess: (_, { type }) => {
|
||||
haptics('Success')
|
||||
toast({
|
||||
@ -91,7 +69,12 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutate({ type: 'mute' })
|
||||
mutateion.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id: account.id,
|
||||
payload: { property: 'mute' }
|
||||
})
|
||||
}}
|
||||
iconFront='EyeOff'
|
||||
title={t('timeline:shared.header.default.actions.account.mute.button', {
|
||||
@ -101,7 +84,12 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutate({ type: 'block' })
|
||||
mutateion.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id: account.id,
|
||||
payload: { property: 'block' }
|
||||
})
|
||||
}}
|
||||
iconFront='XCircle'
|
||||
title={t(
|
||||
@ -114,7 +102,12 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutate({ type: 'reports' })
|
||||
mutateion.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id: account.id,
|
||||
payload: { property: 'reports' }
|
||||
})
|
||||
}}
|
||||
iconFront='Flag'
|
||||
title={t(
|
||||
|
@ -1,12 +1,15 @@
|
||||
import client from '@api/client'
|
||||
import MenuContainer from '@components/Menu/Container'
|
||||
import MenuHeader from '@components/Menu/Header'
|
||||
import MenuRow from '@components/Menu/Row'
|
||||
import { toast } from '@components/toast'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
@ -21,17 +24,8 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(() => {
|
||||
return client<{}>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `domain_blocks`,
|
||||
params: {
|
||||
domain: domain!
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
const mutation = useTimelineMutation({
|
||||
queryClient,
|
||||
onSettled: () => {
|
||||
toast({
|
||||
type: 'success',
|
||||
@ -52,8 +46,27 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({
|
||||
/>
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutate()
|
||||
Alert.alert(
|
||||
t('timeline:shared.header.default.actions.domain.alert.title'),
|
||||
t('timeline:shared.header.default.actions.domain.alert.message'),
|
||||
[
|
||||
{ text: t('common:buttons.cancel'), style: 'cancel' },
|
||||
{
|
||||
text: t(
|
||||
'timeline:shared.header.default.actions.domain.alert.confirm'
|
||||
),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
setBottomSheetVisible(false)
|
||||
mutation.mutate({
|
||||
type: 'domainBlock',
|
||||
queryKey,
|
||||
domain: domain
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
}}
|
||||
iconFront='CloudOff'
|
||||
title={t(`timeline:shared.header.default.actions.domain.block.button`, {
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
|
||||
import { TimelineData } from '@components/Timelines/Timeline'
|
||||
import { toast } from '@components/toast'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
@ -26,92 +26,19 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(
|
||||
({ type, state }: { type: 'mute' | 'pin' | 'delete'; state?: boolean }) => {
|
||||
switch (type) {
|
||||
case 'mute':
|
||||
case 'pin':
|
||||
return client<Mastodon.Status>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `statuses/${status.id}/${state ? 'un' : ''}${type}`
|
||||
}) // bug in response from Mastodon, but onMutate ignore the error in response
|
||||
break
|
||||
case 'delete':
|
||||
return client<Mastodon.Status>({
|
||||
method: 'delete',
|
||||
instance: 'local',
|
||||
url: `statuses/${status.id}`
|
||||
})
|
||||
break
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
enum mapTypeToProp {
|
||||
mute = 'muted',
|
||||
pin = 'pinned'
|
||||
}
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: ({ type, state }) => {
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
switch (type) {
|
||||
case 'mute':
|
||||
case 'pin':
|
||||
haptics('Success')
|
||||
toast({
|
||||
type: 'success',
|
||||
message: t('common:toastMessage.success.message', {
|
||||
function: t(
|
||||
`timeline:shared.header.default.actions.status.${type}.function`
|
||||
)
|
||||
})
|
||||
})
|
||||
queryClient.setQueryData<TimelineData>(queryKey, old => {
|
||||
let tootIndex = -1
|
||||
const pageIndex = findIndex(old?.pages, page => {
|
||||
const tempIndex = findIndex(page.toots, ['id', status.id])
|
||||
if (tempIndex >= 0) {
|
||||
tootIndex = tempIndex
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (pageIndex >= 0 && tootIndex >= 0) {
|
||||
old!.pages[pageIndex].toots[tootIndex][mapTypeToProp[type]] =
|
||||
typeof state === 'boolean' ? !state : true // State could be null from response
|
||||
}
|
||||
|
||||
return old
|
||||
})
|
||||
break
|
||||
case 'delete':
|
||||
queryClient.setQueryData<TimelineData>(
|
||||
queryKey,
|
||||
old =>
|
||||
old && {
|
||||
...old,
|
||||
pages: old?.pages.map(paging => ({
|
||||
...paging,
|
||||
toots: paging.toots.filter(toot => toot.id !== status.id)
|
||||
}))
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
return oldData
|
||||
},
|
||||
onError: (err: any, { type }, oldData) => {
|
||||
const mutation = useTimelineMutation({
|
||||
queryClient,
|
||||
onMutate: true,
|
||||
onError: (err: any, params, oldData) => {
|
||||
const theFunction = (params as MutationVarsTimelineUpdateStatusProperty)
|
||||
.payload
|
||||
? (params as MutationVarsTimelineUpdateStatusProperty).payload.property
|
||||
: 'delete'
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
function: t(
|
||||
`timeline:shared.header.default.actions.status.${type}.function`
|
||||
`timeline:shared.header.default.actions.status.${theFunction}.function`
|
||||
)
|
||||
}),
|
||||
...(err.status &&
|
||||
@ -134,7 +61,12 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutate({ type: 'delete' })
|
||||
mutation.mutate({
|
||||
type: 'deleteItem',
|
||||
source: 'statuses',
|
||||
queryKey,
|
||||
id: status.id
|
||||
})
|
||||
}}
|
||||
iconFront='Trash'
|
||||
title={t('timeline:shared.header.default.actions.status.delete.button')}
|
||||
@ -154,29 +86,19 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
),
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
await client<Mastodon.Status>({
|
||||
method: 'delete',
|
||||
instance: 'local',
|
||||
url: `statuses/${status.id}`
|
||||
setBottomSheetVisible(false)
|
||||
const res = await mutation.mutateAsync({
|
||||
type: 'deleteItem',
|
||||
source: 'statuses',
|
||||
queryKey,
|
||||
id: status.id
|
||||
})
|
||||
.then(res => {
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
setBottomSheetVisible(false)
|
||||
navigation.navigate('Screen-Shared-Compose', {
|
||||
type: 'edit',
|
||||
incomingStatus: res
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.success.message', {
|
||||
function: t(
|
||||
`timeline:shared.header.default.actions.status.edit.function`
|
||||
)
|
||||
})
|
||||
})
|
||||
if (res.id) {
|
||||
navigation.navigate('Screen-Shared-Compose', {
|
||||
type: 'edit',
|
||||
incomingStatus: res
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -188,7 +110,12 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutate({ type: 'mute', state: status.muted })
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
payload: { property: 'muted', currentValue: status.muted }
|
||||
})
|
||||
}}
|
||||
iconFront='VolumeX'
|
||||
title={
|
||||
@ -206,7 +133,12 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutate({ type: 'pin', state: status.pinned })
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
payload: { property: 'pinned', currentValue: status.pinned }
|
||||
})
|
||||
}}
|
||||
iconFront='Anchor'
|
||||
title={
|
||||
|
@ -7,6 +7,7 @@ import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
import RelationshipIncoming from '@root/components/Relationship/Incoming'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
|
||||
export interface Props {
|
||||
notification: Mastodon.Notification
|
||||
@ -30,6 +31,7 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
<HeaderSharedVisibility
|
||||
visibility={notification.status?.visibility}
|
||||
/>
|
||||
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||
<HeaderSharedApplication
|
||||
application={notification.status?.application}
|
||||
/>
|
||||
|
@ -0,0 +1,30 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
muted?: Mastodon.Status['muted']
|
||||
}
|
||||
|
||||
const HeaderSharedMuted: React.FC<Props> = ({ muted }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return muted ? (
|
||||
<Icon
|
||||
name='VolumeX'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
||||
style={styles.visibility}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
visibility: {
|
||||
marginLeft: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
export default HeaderSharedMuted
|
@ -1,22 +1,23 @@
|
||||
import client from '@api/client'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { TimelineData } from '@components/Timelines/Timeline'
|
||||
import { ParseEmojis } from '@root/components/Parse'
|
||||
import { toast } from '@root/components/toast'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
statusId: Mastodon.Status['id']
|
||||
poll: NonNullable<Mastodon.Status['poll']>
|
||||
reblog: boolean
|
||||
sameAccount: boolean
|
||||
@ -24,6 +25,7 @@ export interface Props {
|
||||
|
||||
const TimelinePoll: React.FC<Props> = ({
|
||||
queryKey,
|
||||
statusId,
|
||||
poll,
|
||||
reblog,
|
||||
sameAccount
|
||||
@ -36,56 +38,9 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(
|
||||
({ type }: { type: 'vote' | 'refresh' }) => {
|
||||
const formData = new FormData()
|
||||
type === 'vote' &&
|
||||
allOptions.forEach((o, i) => {
|
||||
if (allOptions[i]) {
|
||||
formData.append('choices[]', i.toString())
|
||||
}
|
||||
})
|
||||
|
||||
return client<Mastodon.Poll>({
|
||||
method: type === 'vote' ? 'post' : 'get',
|
||||
instance: 'local',
|
||||
url: type === 'vote' ? `polls/${poll.id}/votes` : `polls/${poll.id}`,
|
||||
...(type === 'vote' && { body: formData })
|
||||
})
|
||||
},
|
||||
[allOptions]
|
||||
)
|
||||
const mutation = useMutation(fireMutation, {
|
||||
onSuccess: (res) => {
|
||||
queryClient.cancelQueries(queryKey)
|
||||
|
||||
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',
|
||||
poll.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 = res
|
||||
} else {
|
||||
old!.pages[pageIndex].toots[tootIndex].poll = res
|
||||
}
|
||||
}
|
||||
return old
|
||||
})
|
||||
|
||||
haptics('Success')
|
||||
},
|
||||
const mutation = useTimelineMutation({
|
||||
queryClient,
|
||||
onSuccess: true,
|
||||
onError: (err: any) => {
|
||||
haptics('Error')
|
||||
toast({
|
||||
@ -110,7 +65,20 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
return (
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
onPress={() => mutation.mutate({ type: 'vote' })}
|
||||
onPress={() =>
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: statusId,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'poll',
|
||||
id: poll.id,
|
||||
type: 'vote',
|
||||
options: allOptions
|
||||
}
|
||||
})
|
||||
}
|
||||
type='text'
|
||||
content={t('shared.poll.meta.button.vote')}
|
||||
loading={mutation.isLoading}
|
||||
@ -122,7 +90,19 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
return (
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
onPress={() => mutation.mutate({ type: 'refresh' })}
|
||||
onPress={() =>
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: statusId,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'poll',
|
||||
id: poll.id,
|
||||
type: 'refresh'
|
||||
}
|
||||
})
|
||||
}
|
||||
type='text'
|
||||
content={t('shared.poll.meta.button.refresh')}
|
||||
loading={mutation.isLoading}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import TimelineEmpty from '@root/components/Timelines/Timeline/Empty'
|
||||
import hookLists from '@utils/queryHooks/lists'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import React, { useMemo } from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
const ScreenMeLists: React.FC = () => {
|
||||
const navigation = useNavigation()
|
||||
const { status, data, refetch } = hookLists({})
|
||||
const { status, data, refetch } = useListsQuery({})
|
||||
|
||||
const children = useMemo(() => {
|
||||
if (status === 'success') {
|
||||
|
@ -3,13 +3,13 @@ import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import hookAnnouncement from '@utils/queryHooks/announcement'
|
||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
|
||||
const Collections: React.FC = () => {
|
||||
const { t } = useTranslation('meRoot')
|
||||
const navigation = useNavigation()
|
||||
|
||||
const { data, isFetching } = hookAnnouncement({ showAll: true })
|
||||
const { data, isFetching } = useAnnouncementQuery({ showAll: true })
|
||||
|
||||
const announcementContent = useMemo(() => {
|
||||
if (data) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import AccountHeader from '@screens/Shared/Account/Header'
|
||||
import AccountInformation from '@screens/Shared/Account/Information'
|
||||
import hookAccount from '@utils/queryHooks/account'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
@ -11,7 +11,7 @@ export interface Props {
|
||||
|
||||
const MyInfo: React.FC<Props> = ({ setData }) => {
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const { data } = hookAccount({ id: localAccount!.id })
|
||||
const { data } = useAccountQuery({ id: localAccount!.id })
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
|
@ -1,13 +1,20 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const { t } = useTranslation('meRoot')
|
||||
const navigation = useNavigation()
|
||||
|
||||
const [loadingState, setLoadingState] = React.useState(false)
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setLoadingState(!loadingState)
|
||||
}, 5000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [loadingState])
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Button from '@components/Button'
|
||||
import ComponentInstance from '@components/Instance'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import hookAccountCheck from '@utils/queryHooks/accountCheck'
|
||||
import { useAccountCheckQuery } from '@utils/queryHooks/accountCheck'
|
||||
import {
|
||||
getLocalActiveIndex,
|
||||
getLocalInstances,
|
||||
@ -31,7 +31,7 @@ const AccountButton: React.FC<Props> = ({
|
||||
const queryClient = useQueryClient()
|
||||
const navigation = useNavigation()
|
||||
const dispatch = useDispatch()
|
||||
const { isLoading, data } = hookAccountCheck({
|
||||
const { isLoading, data } = useAccountCheckQuery({
|
||||
id: instance.account.id,
|
||||
index,
|
||||
options: { retry: false }
|
||||
|
@ -1,7 +1,7 @@
|
||||
import BottomSheet from '@components/BottomSheet'
|
||||
import { HeaderRight } from '@components/Header'
|
||||
import HeaderDefaultActionsAccount from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
|
||||
import hookAccount from '@utils/queryHooks/account'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import React, { useEffect, useReducer, useState } from 'react'
|
||||
import Animated, {
|
||||
@ -28,7 +28,7 @@ const ScreenSharedAccount: React.FC<SharedAccountProp> = ({
|
||||
navigation
|
||||
}) => {
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const { data } = hookAccount({ id: account.id })
|
||||
const { data } = useAccountQuery({ id: account.id })
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const [accountState, accountDispatch] = useReducer(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Button from '@components/Button'
|
||||
import { RelationshipOutgoing } from '@components/Relationship'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import hookRelationship from '@utils/queryHooks/relationship'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
@ -12,7 +12,7 @@ export interface Props {
|
||||
|
||||
const Conversation = ({ account }: { account: Mastodon.Account }) => {
|
||||
const navigation = useNavigation()
|
||||
const query = hookRelationship({ id: account.id })
|
||||
const query = useRelationshipQuery({ id: account.id })
|
||||
|
||||
return query.data && !query.data.blocked_by ? (
|
||||
<Button
|
||||
|
@ -4,7 +4,10 @@ import haptics from '@components/haptics'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
||||
import hookAnnouncement from '@utils/queryHooks/announcement'
|
||||
import {
|
||||
useAnnouncementMutation,
|
||||
useAnnouncementQuery
|
||||
} from '@utils/queryHooks/announcement'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
@ -20,7 +23,6 @@ import {
|
||||
import { Chase } from 'react-native-animated-spinkit'
|
||||
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { useMutation } from 'react-query'
|
||||
import { SharedAnnouncementsProp } from './sharedScreens'
|
||||
|
||||
const fireMutation = async ({
|
||||
@ -61,7 +63,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
const [index, setIndex] = useState(0)
|
||||
const { t, i18n } = useTranslation()
|
||||
|
||||
const { data, refetch } = hookAnnouncement({
|
||||
const query = useAnnouncementQuery({
|
||||
showAll,
|
||||
options: {
|
||||
select: announcements =>
|
||||
@ -70,18 +72,18 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
)
|
||||
}
|
||||
})
|
||||
const queryMutation = useMutation(fireMutation, {
|
||||
const mutation = useAnnouncementMutation({
|
||||
onSettled: () => {
|
||||
haptics('Success')
|
||||
refetch()
|
||||
query.refetch()
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!showAll && data?.length === 0) {
|
||||
if (!showAll && query.data?.length === 0) {
|
||||
navigation.goBack()
|
||||
}
|
||||
}, [data])
|
||||
}, [query.data])
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: Mastodon.Announcement; index: number }) => (
|
||||
@ -132,8 +134,8 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
}
|
||||
]}
|
||||
onPress={() =>
|
||||
queryMutation.mutate({
|
||||
announcementId: item.id,
|
||||
mutation.mutate({
|
||||
id: item.id,
|
||||
type: 'reaction',
|
||||
name: reaction.name,
|
||||
me: reaction.me
|
||||
@ -172,13 +174,13 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
<Button
|
||||
type='text'
|
||||
content={item.read ? '已读' : '标记阅读'}
|
||||
loading={queryMutation.isLoading}
|
||||
loading={mutation.isLoading}
|
||||
disabled={item.read}
|
||||
onPress={() =>
|
||||
!item.read &&
|
||||
queryMutation.mutate({
|
||||
type: 'dismiss',
|
||||
announcementId: item.id
|
||||
mutation.mutate({
|
||||
id: item.id,
|
||||
type: 'dismiss'
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -221,7 +223,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
</View>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={data}
|
||||
data={query.data}
|
||||
pagingEnabled
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
@ -229,9 +231,9 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
<View style={[styles.indicators, { height: bottomTabBarHeight }]}>
|
||||
{data && data.length > 1 ? (
|
||||
{query.data && query.data.length > 1 ? (
|
||||
<>
|
||||
{data.map((d, i) => (
|
||||
{query.data.map((d, i) => (
|
||||
<View
|
||||
key={i}
|
||||
style={[
|
||||
@ -239,7 +241,8 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
{
|
||||
borderColor: theme.primary,
|
||||
backgroundColor: i === index ? theme.primary : undefined,
|
||||
marginLeft: i === data.length ? 0 : StyleConstants.Spacing.S
|
||||
marginLeft:
|
||||
i === query.data.length ? 0 : StyleConstants.Spacing.S
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
@ -4,6 +4,7 @@ import { store } from '@root/store'
|
||||
import layoutAnimation from '@root/utils/styles/layoutAnimation'
|
||||
import formatText from '@screens/Shared/Compose/formatText'
|
||||
import ComposeRoot from '@screens/Shared/Compose/Root'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -166,7 +167,11 @@ const Compose: React.FC<SharedComposeProp> = ({
|
||||
composePost(params, composeState)
|
||||
.then(() => {
|
||||
haptics('Success')
|
||||
queryClient.invalidateQueries(['Following', {}])
|
||||
const queryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Following' }
|
||||
]
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
if (
|
||||
params?.type &&
|
||||
(params.type === 'reply' || params.type === 'conversation')
|
||||
|
@ -1,5 +1,5 @@
|
||||
import hookEmojis from '@utils/queryHooks/emojis'
|
||||
import hookSearch from '@utils/queryHooks/search'
|
||||
import { useEmojisQuery } from '@utils/queryHooks/emojis'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { forEach, groupBy, sortBy } from 'lodash'
|
||||
@ -17,7 +17,7 @@ const ComposeRoot: React.FC = () => {
|
||||
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
|
||||
const { isFetching, data, refetch } = hookSearch({
|
||||
const { isFetching, data, refetch } = useSearchQuery({
|
||||
type:
|
||||
composeState.tag?.type === 'accounts' ||
|
||||
composeState.tag?.type === 'hashtags'
|
||||
@ -37,7 +37,7 @@ const ComposeRoot: React.FC = () => {
|
||||
}
|
||||
}, [composeState.tag])
|
||||
|
||||
const { data: emojisData } = hookEmojis({})
|
||||
const { data: emojisData } = useEmojisQuery({})
|
||||
useEffect(() => {
|
||||
if (emojisData && emojisData.length) {
|
||||
let sortedEmojis: { title: string; data: Mastodon.Emoji[] }[] = []
|
||||
|
@ -1,12 +1,12 @@
|
||||
import ComponentAccount from '@components/Account'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
||||
import TimelineEnd from '@root/components/Timelines/Timeline/End'
|
||||
import TimelineEnd from '@components/Timelines/Timeline/End'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { useRelationshipsQuery } from '@utils/queryHooks/relationships'
|
||||
import React, { useCallback, useMemo, useRef } from 'react'
|
||||
import { RefreshControl, StyleSheet } from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import hookRelationships from '@utils/queryHooks/relationships'
|
||||
|
||||
export interface Props {
|
||||
id: Mastodon.Account['id']
|
||||
@ -22,7 +22,7 @@ const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage
|
||||
} = hookRelationships({
|
||||
} = useRelationshipsQuery({
|
||||
type,
|
||||
id,
|
||||
options: {
|
||||
|
@ -2,7 +2,7 @@ import { useNavigation } from '@react-navigation/native'
|
||||
import ComponentAccount from '@root/components/Account'
|
||||
import ComponentSeparator from '@root/components/Separator'
|
||||
import TimelineDefault from '@root/components/Timelines/Timeline/Default'
|
||||
import hookSearch from '@utils/queryHooks/search'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
@ -23,7 +23,7 @@ export interface Props {
|
||||
const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
||||
const navigation = useNavigation()
|
||||
const { theme } = useTheme()
|
||||
const { status, data, refetch } = hookSearch({
|
||||
const { status, data, refetch } = useSearchQuery({
|
||||
term: searchTerm,
|
||||
options: { enabled: false }
|
||||
})
|
||||
|
@ -14,7 +14,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookAccount = <TData = Mastodon.Account>({
|
||||
const useAccountQuery = <TData = Mastodon.Account>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
@ -24,4 +24,4 @@ const hookAccount = <TData = Mastodon.Account>({
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookAccount
|
||||
export { useAccountQuery }
|
||||
|
@ -22,7 +22,7 @@ const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookAccountCheck = <TData = Mastodon.Account>({
|
||||
const useAccountCheckQuery = <TData = Mastodon.Account>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
@ -32,4 +32,4 @@ const hookAccountCheck = <TData = Mastodon.Account>({
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookAccountCheck
|
||||
export { useAccountCheckQuery }
|
||||
|
@ -1,10 +1,15 @@
|
||||
import client from '@api/client'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
import {
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
UseQueryOptions
|
||||
} from 'react-query'
|
||||
|
||||
type QueryKey = ['Announcements', { showAll?: boolean }]
|
||||
type QueryKeyAnnouncement = ['Announcements', { showAll?: boolean }]
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKeyAnnouncement }) => {
|
||||
const { showAll } = queryKey[1]
|
||||
|
||||
return client<Mastodon.Announcement[]>({
|
||||
@ -19,14 +24,52 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookAnnouncement = <TData = Mastodon.Announcement[]>({
|
||||
const useAnnouncementQuery = <TData = Mastodon.Announcement[]>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
}: QueryKeyAnnouncement[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Announcement[], AxiosError, TData>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['Announcements', { ...queryKeyParams }]
|
||||
const queryKey: QueryKeyAnnouncement = [
|
||||
'Announcements',
|
||||
{ ...queryKeyParams }
|
||||
]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookAnnouncement
|
||||
type MutationVarsAnnouncement = {
|
||||
id: Mastodon.Announcement['id']
|
||||
type: 'reaction' | 'dismiss'
|
||||
name?: Mastodon.AnnouncementReaction['name']
|
||||
me?: boolean
|
||||
}
|
||||
|
||||
const mutationFunction = async ({
|
||||
id,
|
||||
type,
|
||||
name,
|
||||
me
|
||||
}: MutationVarsAnnouncement) => {
|
||||
switch (type) {
|
||||
case 'reaction':
|
||||
return client<{}>({
|
||||
method: me ? 'delete' : 'put',
|
||||
instance: 'local',
|
||||
url: `announcements/${id}/reactions/${name}`
|
||||
})
|
||||
case 'dismiss':
|
||||
return client<{}>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `announcements/${id}/dismiss`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const useAnnouncementMutation = (
|
||||
options: UseMutationOptions<{}, AxiosError, MutationVarsAnnouncement>
|
||||
) => {
|
||||
return useMutation(mutationFunction, options)
|
||||
}
|
||||
|
||||
export { useAnnouncementQuery, useAnnouncementMutation }
|
||||
|
@ -21,7 +21,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookApps = <TData = Mastodon.Apps>({
|
||||
const useAppsQuery = <TData = Mastodon.Apps>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
@ -31,4 +31,4 @@ const hookApps = <TData = Mastodon.Apps>({
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookApps
|
||||
export { useAppsQuery }
|
||||
|
@ -12,7 +12,7 @@ const queryFunction = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookEmojis = <TData = Mastodon.Emoji[]>({
|
||||
const useEmojisQuery = <TData = Mastodon.Emoji[]>({
|
||||
options
|
||||
}: {
|
||||
options?: UseQueryOptions<Mastodon.Emoji[], AxiosError, TData>
|
||||
@ -21,4 +21,4 @@ const hookEmojis = <TData = Mastodon.Emoji[]>({
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookEmojis
|
||||
export { useEmojisQuery }
|
||||
|
@ -15,7 +15,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookInstance = <TData = Mastodon.Instance>({
|
||||
const useInstanceQuery = <TData = Mastodon.Instance>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
@ -25,4 +25,4 @@ const hookInstance = <TData = Mastodon.Instance>({
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookInstance
|
||||
export { useInstanceQuery }
|
||||
|
@ -12,7 +12,7 @@ const queryFunction = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookLists = <TData = Mastodon.List[]>({
|
||||
const useListsQuery = <TData = Mastodon.List[]>({
|
||||
options
|
||||
}: {
|
||||
options?: UseQueryOptions<Mastodon.List[], AxiosError, TData>
|
||||
@ -21,4 +21,4 @@ const hookLists = <TData = Mastodon.List[]>({
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookLists
|
||||
export { useListsQuery }
|
||||
|
@ -1,6 +1,11 @@
|
||||
import client from '@api/client'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
import {
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
UseQueryOptions
|
||||
} from 'react-query'
|
||||
|
||||
export type QueryKeyRelationship = [
|
||||
'Relationship',
|
||||
@ -20,7 +25,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKeyRelationship }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookRelationship = ({
|
||||
const useRelationshipQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyRelationship[1] & {
|
||||
@ -37,4 +42,45 @@ const hookRelationship = ({
|
||||
})
|
||||
}
|
||||
|
||||
export default hookRelationship
|
||||
type MutationVarsRelationship =
|
||||
| {
|
||||
id: Mastodon.Account['id']
|
||||
type: 'incoming'
|
||||
payload: { action: 'authorize' | 'reject' }
|
||||
}
|
||||
| {
|
||||
id: Mastodon.Account['id']
|
||||
type: 'outgoing'
|
||||
payload: { action: 'follow' | 'block'; state: boolean }
|
||||
}
|
||||
|
||||
const mutationFunction = async (params: MutationVarsRelationship) => {
|
||||
switch (params.type) {
|
||||
case 'incoming':
|
||||
return client<Mastodon.Relationship>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `follow_requests/${params.id}/${params.payload.action}`
|
||||
})
|
||||
case 'outgoing':
|
||||
return client<Mastodon.Relationship>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `accounts/${params.id}/${params.payload.state ? 'un' : ''}${
|
||||
params.payload.action
|
||||
}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const useRelationshipMutation = (
|
||||
options: UseMutationOptions<
|
||||
Mastodon.Relationship,
|
||||
AxiosError,
|
||||
MutationVarsRelationship
|
||||
>
|
||||
) => {
|
||||
return useMutation(mutationFunction, options)
|
||||
}
|
||||
|
||||
export { useRelationshipQuery, useRelationshipMutation }
|
||||
|
@ -33,7 +33,7 @@ const queryFunction = ({
|
||||
})
|
||||
}
|
||||
|
||||
const hookRelationships = <TData = Mastodon.Account[]>({
|
||||
const useRelationshipsQuery = <TData = Mastodon.Account[]>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
@ -43,4 +43,4 @@ const hookRelationships = <TData = Mastodon.Account[]>({
|
||||
return useInfiniteQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookRelationships
|
||||
export { useRelationshipsQuery }
|
||||
|
@ -28,7 +28,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const hookSearch = <TData = SearchResult>({
|
||||
const useSearchQuery = <TData = SearchResult>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
@ -38,4 +38,4 @@ const hookSearch = <TData = SearchResult>({
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookSearch
|
||||
export { useSearchQuery }
|
||||
|
@ -1,7 +1,16 @@
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import { AxiosError } from 'axios'
|
||||
import { uniqBy } from 'lodash'
|
||||
import { useInfiniteQuery, UseInfiniteQueryOptions } from 'react-query'
|
||||
import {
|
||||
MutationOptions,
|
||||
QueryClient,
|
||||
useInfiniteQuery,
|
||||
UseInfiniteQueryOptions,
|
||||
useMutation
|
||||
} from 'react-query'
|
||||
import deleteItem from './timeline/deleteItem'
|
||||
import updateStatusProperty from './timeline/updateStatusProperty'
|
||||
|
||||
export type QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
@ -14,7 +23,7 @@ export type QueryKeyTimeline = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryFunction = async ({
|
||||
const queryFunction = ({
|
||||
queryKey,
|
||||
pageParam
|
||||
}: {
|
||||
@ -22,7 +31,6 @@ const queryFunction = async ({
|
||||
pageParam?: { direction: 'prev' | 'next'; id: Mastodon.Status['id'] }
|
||||
}) => {
|
||||
const { page, account, hashtag, list, toot } = queryKey[1]
|
||||
let res
|
||||
let params: { [key: string]: string } = {}
|
||||
|
||||
if (pageParam) {
|
||||
@ -46,74 +54,51 @@ const queryFunction = async ({
|
||||
|
||||
switch (page) {
|
||||
case 'Following':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'timelines/home',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Local':
|
||||
params.local = 'true'
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'timelines/public',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
params: {
|
||||
...params,
|
||||
local: 'true'
|
||||
}
|
||||
})
|
||||
|
||||
case 'LocalPublic':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'timelines/public',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'RemotePublic':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'remote',
|
||||
url: 'timelines/public',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Notifications':
|
||||
res = await client<Mastodon.Notification[]>({
|
||||
return client<Mastodon.Notification[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'notifications',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Account_Default':
|
||||
if (pageParam && pageParam.direction === 'next') {
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
@ -122,53 +107,41 @@ const queryFunction = async ({
|
||||
...params
|
||||
}
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
} else {
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<(Mastodon.Status & { isPinned: boolean })[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
pinned: 'true'
|
||||
}
|
||||
})
|
||||
const pinnedLength = res.length
|
||||
let toots = res
|
||||
res = await client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true'
|
||||
}
|
||||
})
|
||||
toots = uniqBy([...toots, ...res], 'id')
|
||||
return Promise.resolve({
|
||||
toots: toots,
|
||||
pointer: undefined,
|
||||
pinnedLength
|
||||
}).then(async res1 => {
|
||||
let toots = res1.map(status => {
|
||||
status.isPinned = true
|
||||
return status
|
||||
})
|
||||
const res2 = await client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true'
|
||||
}
|
||||
})
|
||||
return uniqBy([...toots, ...res2], 'id')
|
||||
})
|
||||
}
|
||||
|
||||
case 'Account_All':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Account_Media':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
@ -177,100 +150,62 @@ const queryFunction = async ({
|
||||
...params
|
||||
}
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Hashtag':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `timelines/tag/${hashtag}`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Conversations':
|
||||
res = await client<Mastodon.Conversation[]>({
|
||||
return client<Mastodon.Conversation[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `conversations`,
|
||||
params
|
||||
})
|
||||
if (pageParam) {
|
||||
// Bug in pull to refresh in conversations
|
||||
res = res.filter(b => b.id !== pageParam.id)
|
||||
}
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Bookmarks':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `bookmarks`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Favourites':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `favourites`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'List':
|
||||
res = await client<Mastodon.Status[]>({
|
||||
return client<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `timelines/list/${list}`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: res,
|
||||
pointer: undefined,
|
||||
pinnedLength: undefined
|
||||
})
|
||||
|
||||
case 'Toot':
|
||||
res = await client<Mastodon.Status>({
|
||||
return client<Mastodon.Status>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `statuses/${toot}`
|
||||
})
|
||||
const theToot = res
|
||||
res = await client<{
|
||||
ancestors: Mastodon.Status[]
|
||||
descendants: Mastodon.Status[]
|
||||
}>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `statuses/${toot}/context`
|
||||
})
|
||||
return Promise.resolve({
|
||||
toots: [...res.ancestors, theToot, ...res.descendants],
|
||||
pointer: res.ancestors.length,
|
||||
pinnedLength: undefined
|
||||
}).then(async res1 => {
|
||||
const res2 = await client<{
|
||||
ancestors: Mastodon.Status[]
|
||||
descendants: Mastodon.Status[]
|
||||
}>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `statuses/${toot}/context`
|
||||
})
|
||||
return [...res2.ancestors, res1, ...res2.descendants]
|
||||
})
|
||||
default:
|
||||
return Promise.reject()
|
||||
@ -278,7 +213,8 @@ const queryFunction = async ({
|
||||
}
|
||||
|
||||
type Unpromise<T extends Promise<any>> = T extends Promise<infer U> ? U : never
|
||||
const hookTimeline = <TData = Unpromise<ReturnType<typeof queryFunction>>>({
|
||||
export type TimelineData = Unpromise<ReturnType<typeof queryFunction>>
|
||||
const useTimelineQuery = <TData = TimelineData>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyTimeline[1] & {
|
||||
@ -288,4 +224,199 @@ const hookTimeline = <TData = Unpromise<ReturnType<typeof queryFunction>>>({
|
||||
return useInfiniteQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export default hookTimeline
|
||||
// --- Separator ---
|
||||
|
||||
enum MapPropertyToUrl {
|
||||
bookmarked = 'bookmark',
|
||||
favourited = 'favourite',
|
||||
muted = 'mute',
|
||||
pinned = 'pin',
|
||||
reblogged = 'reblog'
|
||||
}
|
||||
|
||||
export type MutationVarsTimelineUpdateStatusProperty = {
|
||||
// This is status in general, including "status" inside conversation and notification
|
||||
type: 'updateStatusProperty'
|
||||
queryKey: QueryKeyTimeline
|
||||
id: Mastodon.Status['id'] | Mastodon.Poll['id']
|
||||
reblog?: boolean
|
||||
payload:
|
||||
| {
|
||||
property: 'bookmarked' | 'favourited' | 'muted' | 'pinned' | 'reblogged'
|
||||
currentValue: boolean
|
||||
}
|
||||
| {
|
||||
property: 'poll'
|
||||
id: Mastodon.Poll['id']
|
||||
type: 'vote' | 'refresh'
|
||||
options?: boolean[]
|
||||
data?: Mastodon.Poll
|
||||
}
|
||||
}
|
||||
|
||||
export type MutationVarsTimelineUpdateAccountProperty = {
|
||||
// This is status in general, including "status" inside conversation and notification
|
||||
type: 'updateAccountProperty'
|
||||
queryKey?: QueryKeyTimeline
|
||||
id: Mastodon.Account['id']
|
||||
payload: {
|
||||
property: 'mute' | 'block' | 'reports'
|
||||
}
|
||||
}
|
||||
|
||||
export type MutationVarsTimelineDeleteItem = {
|
||||
// This is for deleting status and conversation
|
||||
type: 'deleteItem'
|
||||
source: 'statuses' | 'conversations'
|
||||
queryKey: QueryKeyTimeline
|
||||
id: Mastodon.Conversation['id']
|
||||
}
|
||||
|
||||
export type MutationVarsTimelineDomainBlock = {
|
||||
// This is for deleting status and conversation
|
||||
type: 'domainBlock'
|
||||
queryKey: QueryKeyTimeline
|
||||
domain: string
|
||||
}
|
||||
|
||||
export type MutationVarsTimeline =
|
||||
| MutationVarsTimelineUpdateStatusProperty
|
||||
| MutationVarsTimelineUpdateAccountProperty
|
||||
| MutationVarsTimelineDeleteItem
|
||||
| MutationVarsTimelineDomainBlock
|
||||
|
||||
const mutationFunction = async (params: MutationVarsTimeline) => {
|
||||
switch (params.type) {
|
||||
case 'updateStatusProperty':
|
||||
switch (params.payload.property) {
|
||||
case 'poll':
|
||||
const formData = new FormData()
|
||||
params.payload.type === 'vote' &&
|
||||
params.payload.options!.forEach((option, index) => {
|
||||
if (option) {
|
||||
formData.append('choices[]', index.toString())
|
||||
}
|
||||
})
|
||||
|
||||
return client<Mastodon.Poll>({
|
||||
method: params.payload.type === 'vote' ? 'post' : 'get',
|
||||
instance: 'local',
|
||||
url:
|
||||
params.payload.type === 'vote'
|
||||
? `polls/${params.payload.id}/votes`
|
||||
: `polls/${params.payload.id}`,
|
||||
...(params.payload.type === 'vote' && { body: formData })
|
||||
})
|
||||
default:
|
||||
return client<Mastodon.Status>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `statuses/${params.id}/${
|
||||
params.payload.currentValue ? 'un' : ''
|
||||
}${MapPropertyToUrl[params.payload.property]}`
|
||||
})
|
||||
}
|
||||
case 'updateAccountProperty':
|
||||
switch (params.payload.property) {
|
||||
case 'block':
|
||||
case 'mute':
|
||||
return client<Mastodon.Account>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `accounts/${params.id}/${params.payload.property}`
|
||||
})
|
||||
case 'reports':
|
||||
return client<Mastodon.Account>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `reports`,
|
||||
params: {
|
||||
account_id: params.id
|
||||
}
|
||||
})
|
||||
}
|
||||
case 'deleteItem':
|
||||
return client<Mastodon.Conversation>({
|
||||
method: 'delete',
|
||||
instance: 'local',
|
||||
url: `${params.source}/${params.id}`
|
||||
})
|
||||
case 'domainBlock':
|
||||
return client<any>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `domain_blocks`,
|
||||
params: {
|
||||
domain: params.domain
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MutationOptionsTimeline = MutationOptions<
|
||||
Mastodon.Conversation | Mastodon.Notification | Mastodon.Status,
|
||||
AxiosError,
|
||||
MutationVarsTimeline
|
||||
>
|
||||
|
||||
const useTimelineMutation = ({
|
||||
queryClient,
|
||||
onError,
|
||||
onMutate,
|
||||
onSettled,
|
||||
onSuccess
|
||||
}: {
|
||||
queryClient: QueryClient
|
||||
onError?: MutationOptionsTimeline['onError']
|
||||
onMutate?: boolean
|
||||
onSettled?: MutationOptionsTimeline['onSettled']
|
||||
onSuccess?: MutationOptionsTimeline['onSuccess'] | boolean
|
||||
}) => {
|
||||
return useMutation<
|
||||
Mastodon.Conversation | Mastodon.Notification | Mastodon.Status,
|
||||
AxiosError,
|
||||
MutationVarsTimeline
|
||||
>(mutationFunction, {
|
||||
onError,
|
||||
onSettled,
|
||||
...(typeof onSuccess === 'function'
|
||||
? { onSuccess }
|
||||
: {
|
||||
onSuccess: (data, params) => {
|
||||
queryClient.cancelQueries(params.queryKey)
|
||||
|
||||
haptics('Success')
|
||||
switch (params.type) {
|
||||
case 'updateStatusProperty':
|
||||
switch (params.payload.property) {
|
||||
case 'poll':
|
||||
params.payload.data = (data as unknown) as Mastodon.Poll
|
||||
updateStatusProperty({ queryClient, ...params })
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}),
|
||||
...(onMutate && {
|
||||
onMutate: params => {
|
||||
queryClient.cancelQueries(params.queryKey)
|
||||
let oldData
|
||||
params.queryKey && (oldData = queryClient.getQueryData(params.queryKey))
|
||||
|
||||
haptics('Success')
|
||||
switch (params.type) {
|
||||
case 'updateStatusProperty':
|
||||
updateStatusProperty({ queryClient, ...params })
|
||||
break
|
||||
case 'deleteItem':
|
||||
deleteItem({ queryClient, ...params })
|
||||
break
|
||||
}
|
||||
return oldData
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export { useTimelineQuery, useTimelineMutation }
|
||||
|
25
src/utils/queryHooks/timeline/deleteItem.ts
Normal file
25
src/utils/queryHooks/timeline/deleteItem.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { InfiniteData, QueryClient } from 'react-query'
|
||||
import { QueryKeyTimeline } from '../timeline'
|
||||
|
||||
const deleteItem = ({
|
||||
queryClient,
|
||||
queryKey,
|
||||
id
|
||||
}: {
|
||||
queryClient: QueryClient
|
||||
queryKey: QueryKeyTimeline
|
||||
id: Mastodon.Status['id']
|
||||
}) => {
|
||||
queryClient.setQueryData<InfiniteData<Mastodon.Conversation[]> | undefined>(
|
||||
queryKey,
|
||||
old => {
|
||||
if (old) {
|
||||
old.pages = old.pages.map(page => page.filter(item => item.id !== id))
|
||||
}
|
||||
|
||||
return old
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default deleteItem
|
27
src/utils/queryHooks/timeline/update/conversation.ts
Normal file
27
src/utils/queryHooks/timeline/update/conversation.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { MutationVarsTimelineUpdateStatusProperty } from '@utils/queryHooks/timeline'
|
||||
|
||||
const updateConversation = ({
|
||||
item,
|
||||
payload
|
||||
}: {
|
||||
item: Mastodon.Conversation
|
||||
payload: MutationVarsTimelineUpdateStatusProperty['payload']
|
||||
}) => {
|
||||
switch (payload.property) {
|
||||
case 'poll':
|
||||
if (item.last_status) {
|
||||
item.last_status[payload.property] = payload.data
|
||||
}
|
||||
return item
|
||||
default:
|
||||
if (item.last_status) {
|
||||
item.last_status[payload.property] =
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
export default updateConversation
|
24
src/utils/queryHooks/timeline/update/notification.ts
Normal file
24
src/utils/queryHooks/timeline/update/notification.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { MutationVarsTimelineUpdateStatusProperty } from '@utils/queryHooks/timeline'
|
||||
|
||||
const updateNotification = ({
|
||||
item,
|
||||
payload
|
||||
}: {
|
||||
item: Mastodon.Notification
|
||||
payload: MutationVarsTimelineUpdateStatusProperty['payload']
|
||||
}) => {
|
||||
switch (payload.property) {
|
||||
case 'poll':
|
||||
return item
|
||||
default:
|
||||
if (item.status) {
|
||||
item.status[payload.property] =
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
export default updateNotification
|
37
src/utils/queryHooks/timeline/update/status.ts
Normal file
37
src/utils/queryHooks/timeline/update/status.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { MutationVarsTimelineUpdateStatusProperty } from '@utils/queryHooks/timeline'
|
||||
|
||||
const updateStatus = ({
|
||||
item,
|
||||
reblog,
|
||||
payload
|
||||
}: {
|
||||
item: Mastodon.Status
|
||||
reblog?: boolean
|
||||
payload: MutationVarsTimelineUpdateStatusProperty['payload']
|
||||
}) => {
|
||||
switch (payload.property) {
|
||||
case 'poll':
|
||||
console.log(payload.data)
|
||||
if (reblog) {
|
||||
item.reblog!.poll = payload.data
|
||||
} else {
|
||||
item.poll = payload.data
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (reblog) {
|
||||
item.reblog![payload.property] =
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
} else {
|
||||
item[payload.property] =
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
export default updateStatus
|
76
src/utils/queryHooks/timeline/updateStatusProperty.ts
Normal file
76
src/utils/queryHooks/timeline/updateStatusProperty.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { findIndex } from 'lodash'
|
||||
import { InfiniteData, QueryClient } from 'react-query'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
TimelineData
|
||||
} from '../timeline'
|
||||
import updateConversation from './update/conversation'
|
||||
import updateNotification from './update/notification'
|
||||
import updateStatus from './update/status'
|
||||
|
||||
const updateStatusProperty = ({
|
||||
queryClient,
|
||||
queryKey,
|
||||
id,
|
||||
reblog,
|
||||
payload
|
||||
}: {
|
||||
queryClient: QueryClient
|
||||
queryKey: MutationVarsTimelineUpdateStatusProperty['queryKey']
|
||||
id: MutationVarsTimelineUpdateStatusProperty['id']
|
||||
reblog?: MutationVarsTimelineUpdateStatusProperty['reblog']
|
||||
payload: MutationVarsTimelineUpdateStatusProperty['payload']
|
||||
}) => {
|
||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
||||
queryKey,
|
||||
old => {
|
||||
if (old) {
|
||||
let foundToot = false
|
||||
old.pages = old.pages.map(page => {
|
||||
// Skip rest of the pages if any toot is found
|
||||
if (foundToot) {
|
||||
return page
|
||||
} else {
|
||||
if (
|
||||
typeof (page as Mastodon.Conversation[])[0].unread === 'boolean'
|
||||
) {
|
||||
const items = page as Mastodon.Conversation[]
|
||||
const tootIndex = findIndex(items, ['last_status.id', id])
|
||||
if (tootIndex >= 0) {
|
||||
foundToot = true
|
||||
updateConversation({ item: items[tootIndex], payload })
|
||||
}
|
||||
return page
|
||||
} else if (
|
||||
typeof (page as Mastodon.Notification[])[0].type === 'string'
|
||||
) {
|
||||
const items = page as Mastodon.Notification[]
|
||||
const tootIndex = findIndex(items, ['status.id', id])
|
||||
if (tootIndex >= 0) {
|
||||
foundToot = true
|
||||
updateNotification({ item: items[tootIndex], payload })
|
||||
}
|
||||
} else {
|
||||
const items = page as Mastodon.Status[]
|
||||
const tootIndex = findIndex(items, [
|
||||
reblog ? 'reblog.id' : 'id',
|
||||
id
|
||||
])
|
||||
// if favouriets page and notifications page, remove the item instead
|
||||
if (tootIndex >= 0) {
|
||||
foundToot = true
|
||||
updateStatus({ item: items[tootIndex], reblog, payload })
|
||||
}
|
||||
}
|
||||
|
||||
return page
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return old
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default updateStatusProperty
|
Loading…
x
Reference in New Issue
Block a user