mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Using react-query 3
This commit is contained in:
41
src/@types/app.d.ts
vendored
41
src/@types/app.d.ts
vendored
@ -14,8 +14,47 @@ declare namespace App {
|
||||
| 'Conversations'
|
||||
| 'Bookmarks'
|
||||
| 'Favourites'
|
||||
}
|
||||
|
||||
type QueryKey = [
|
||||
declare namespace QueryKey {
|
||||
type Account = [
|
||||
'Account',
|
||||
{
|
||||
id: Mastodon.Account['id']
|
||||
}
|
||||
]
|
||||
|
||||
type Application = [
|
||||
'Application',
|
||||
{
|
||||
instanceDomain: string
|
||||
}
|
||||
]
|
||||
|
||||
type Instance = [
|
||||
'Instance',
|
||||
{
|
||||
instanceDomain: string
|
||||
}
|
||||
]
|
||||
|
||||
type Relationship = [
|
||||
'Relationship',
|
||||
{
|
||||
id: Mastodon.Account['id']
|
||||
}
|
||||
]
|
||||
|
||||
type Search = [
|
||||
'Search',
|
||||
{
|
||||
type?: 'accounts' | 'hashtags' | 'statuses'
|
||||
term: string
|
||||
limit?: number
|
||||
}
|
||||
]
|
||||
|
||||
type Timeline = [
|
||||
Pages,
|
||||
{
|
||||
page: Pages
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { AppState, StyleSheet } from 'react-native'
|
||||
import { setFocusHandler, useInfiniteQuery } from 'react-query'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { useInfiniteQuery } from 'react-query'
|
||||
|
||||
import TimelineNotifications from '@components/Timelines/Timeline/Notifications'
|
||||
import TimelineDefault from '@components/Timelines/Timeline/Default'
|
||||
@ -29,17 +29,7 @@ const Timeline: React.FC<Props> = ({
|
||||
account,
|
||||
disableRefresh = false
|
||||
}) => {
|
||||
setFocusHandler(handleFocus => {
|
||||
const handleAppStateChange = (appState: string) => {
|
||||
if (appState === 'active') {
|
||||
handleFocus()
|
||||
}
|
||||
}
|
||||
AppState.addEventListener('change', handleAppStateChange)
|
||||
return () => AppState.removeEventListener('change', handleAppStateChange)
|
||||
})
|
||||
|
||||
const queryKey: App.QueryKey = [
|
||||
const queryKey: QueryKey.Timeline = [
|
||||
page,
|
||||
{
|
||||
page,
|
||||
@ -51,24 +41,38 @@ const Timeline: React.FC<Props> = ({
|
||||
]
|
||||
const {
|
||||
status,
|
||||
isSuccess,
|
||||
isLoading,
|
||||
isError,
|
||||
isFetchingMore,
|
||||
canFetchMore,
|
||||
data,
|
||||
fetchMore,
|
||||
refetch
|
||||
refetch,
|
||||
hasPreviousPage,
|
||||
fetchPreviousPage,
|
||||
isFetchingPreviousPage,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage
|
||||
} = useInfiniteQuery(queryKey, timelineFetch, {
|
||||
getFetchMore: last => last?.toots.length > 0
|
||||
getPreviousPageParam: firstPage => ({
|
||||
direction: 'prev',
|
||||
id: firstPage.toots[0].id
|
||||
}),
|
||||
getNextPageParam: lastPage =>
|
||||
lastPage.toots.length
|
||||
? {
|
||||
direction: 'next',
|
||||
id: lastPage.toots[lastPage.toots.length - 1].id
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
const flattenData = data ? data.flatMap(d => [...d?.toots]) : []
|
||||
const flattenPointer = data ? data.flatMap(d => [d?.pointer]) : []
|
||||
const flattenPinnedLength = data ? data.flatMap(d => [d?.pinnedLength]) : []
|
||||
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 flRef = useRef<FlatList<any>>(null)
|
||||
useEffect(() => {
|
||||
if (toot && isSuccess) {
|
||||
if (toot && status === 'success') {
|
||||
setTimeout(() => {
|
||||
flRef.current?.scrollToIndex({
|
||||
index: flattenPointer[0]!,
|
||||
@ -76,7 +80,7 @@ const Timeline: React.FC<Props> = ({
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
}, [isSuccess])
|
||||
}, [status])
|
||||
|
||||
const flKeyExtrator = useCallback(({ id }) => id, [])
|
||||
const flRenderItem = useCallback(({ item, index }) => {
|
||||
@ -110,33 +114,16 @@ const Timeline: React.FC<Props> = ({
|
||||
)
|
||||
const flItemEmptyComponent = useMemo(
|
||||
() => <TimelineEmpty status={status} refetch={refetch} />,
|
||||
[isLoading, isError, isSuccess]
|
||||
[status]
|
||||
)
|
||||
const flOnRefresh = useCallback(
|
||||
() =>
|
||||
!disableRefresh &&
|
||||
fetchMore(
|
||||
{
|
||||
direction: 'prev',
|
||||
id: flattenData.length ? flattenData[0].id : null
|
||||
},
|
||||
{ previous: true }
|
||||
),
|
||||
[flattenData]
|
||||
)
|
||||
const flOnEndReach = useCallback(
|
||||
() =>
|
||||
!disableRefresh &&
|
||||
canFetchMore &&
|
||||
fetchMore({
|
||||
direction: 'next',
|
||||
id: flattenData[flattenData.length - 1].id
|
||||
}),
|
||||
[flattenData]
|
||||
() => !disableRefresh && fetchPreviousPage(),
|
||||
[]
|
||||
)
|
||||
const flOnEndReach = useCallback(() => fetchNextPage(), [])
|
||||
const flFooter = useCallback(() => {
|
||||
return <TimelineEnd isFetchingMore={isFetchingMore} />
|
||||
}, [isFetchingMore])
|
||||
return <TimelineEnd hasNextPage={hasNextPage} />
|
||||
}, [hasNextPage])
|
||||
const onScrollToIndexFailed = useCallback(error => {
|
||||
const offset = error.averageItemLength * error.index
|
||||
flRef.current?.scrollToOffset({ offset })
|
||||
@ -159,11 +146,11 @@ const Timeline: React.FC<Props> = ({
|
||||
onEndReached={flOnEndReach}
|
||||
keyExtractor={flKeyExtrator}
|
||||
ListFooterComponent={flFooter}
|
||||
refreshing={isFetchingPreviousPage}
|
||||
ListEmptyComponent={flItemEmptyComponent}
|
||||
ItemSeparatorComponent={flItemSeparatorComponent}
|
||||
onEndReachedThreshold={!disableRefresh ? 0.75 : null}
|
||||
refreshing={!disableRefresh && isLoading && flattenData.length > 0}
|
||||
{...(toot && isSuccess && { onScrollToIndexFailed })}
|
||||
{...(toot && status === 'success' && { onScrollToIndexFailed })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
||||
|
||||
export interface Props {
|
||||
item: Mastodon.Conversation
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
highlighted?: boolean
|
||||
}
|
||||
// Unread and mark as unread
|
||||
|
@ -15,7 +15,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
export interface Props {
|
||||
item: Mastodon.Status
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
index: number
|
||||
pinnedLength?: number
|
||||
highlighted?: boolean
|
||||
|
@ -15,7 +15,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
export interface Props {
|
||||
notification: Mastodon.Notification
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
highlighted?: boolean
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { ActionSheetIOS, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import client from '@api/client'
|
||||
@ -44,7 +44,7 @@ const fireMutation = async ({
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
status: Mastodon.Status
|
||||
}
|
||||
|
||||
@ -55,20 +55,21 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
||||
const iconColorAction = (state: boolean) =>
|
||||
state ? theme.primary : theme.secondary
|
||||
|
||||
const queryCache = useQueryCache()
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
const queryClient = useQueryClient()
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: ({ id, type, stateKey, prevState }) => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
switch (type) {
|
||||
case 'favourite':
|
||||
case 'reblog':
|
||||
case 'bookmark':
|
||||
queryCache.setQueryData(queryKey, old =>
|
||||
(old as {}[]).map((paging: any) => ({
|
||||
queryClient.setQueryData(queryKey, (old: any) => {
|
||||
old.pages.map((paging: any) => ({
|
||||
toots: paging.toots.map((toot: any) => {
|
||||
if (toot.id === id) {
|
||||
console.log(toot[stateKey])
|
||||
toot[stateKey] =
|
||||
typeof prevState === 'boolean' ? !prevState : true
|
||||
}
|
||||
@ -76,7 +77,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
||||
}),
|
||||
pointer: paging.pointer
|
||||
}))
|
||||
)
|
||||
return old
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
@ -84,7 +86,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
||||
},
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试' })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
queryClient.setQueryData(queryKey, oldData)
|
||||
}
|
||||
})
|
||||
|
||||
@ -99,7 +101,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
||||
}, [])
|
||||
const onPressReblog = useCallback(
|
||||
() =>
|
||||
mutateAction({
|
||||
mutate({
|
||||
id: status.id,
|
||||
type: 'reblog',
|
||||
stateKey: 'reblogged',
|
||||
@ -109,7 +111,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
||||
)
|
||||
const onPressFavourite = useCallback(
|
||||
() =>
|
||||
mutateAction({
|
||||
mutate({
|
||||
id: status.id,
|
||||
type: 'favourite',
|
||||
stateKey: 'favourited',
|
||||
@ -119,7 +121,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
||||
)
|
||||
const onPressBookmark = useCallback(
|
||||
() =>
|
||||
mutateAction({
|
||||
mutate({
|
||||
id: status.id,
|
||||
type: 'bookmark',
|
||||
stateKey: 'bookmarked',
|
||||
|
@ -4,7 +4,7 @@ import { Video } from 'expo-av'
|
||||
import { ButtonRound } from '@components/Button'
|
||||
|
||||
export interface Props {
|
||||
media_attachments: Mastodon.AttachmentVideo[]
|
||||
media_attachments: Mastodon.Attachment[]
|
||||
width: number
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: App.QueryKey
|
||||
queryKey?: QueryKey.Timeline
|
||||
account: Mastodon.Account
|
||||
}
|
||||
|
||||
|
@ -5,15 +5,15 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
||||
export interface Props {
|
||||
isFetchingMore: false | 'previous' | 'next' | undefined
|
||||
hasNextPage?: boolean
|
||||
}
|
||||
|
||||
const TimelineEnd: React.FC<Props> = ({ isFetchingMore }) => {
|
||||
const TimelineEnd: React.FC<Props> = ({ hasNextPage }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{isFetchingMore ? (
|
||||
{hasNextPage ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<Text style={[styles.text, { color: theme.secondary }]}>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import { toast } from '@components/toast'
|
||||
|
||||
@ -11,7 +11,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
|
||||
export interface Props {
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
id: string
|
||||
account: Mastodon.Account
|
||||
created_at?: Mastodon.Status['created_at']
|
||||
@ -43,14 +43,14 @@ const HeaderConversation: React.FC<Props> = ({
|
||||
account,
|
||||
created_at
|
||||
}) => {
|
||||
const queryCache = useQueryCache()
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
const queryClient = useQueryClient()
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: () => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
queryCache.setQueryData(queryKey, old =>
|
||||
(old as {}[]).map((paging: any) => ({
|
||||
queryClient.setQueryData(queryKey, (old: any) =>
|
||||
old.pages.map((paging: any) => ({
|
||||
toots: paging.toots.filter((toot: any) => toot.id !== id),
|
||||
pointer: paging.pointer
|
||||
}))
|
||||
@ -60,16 +60,13 @@ const HeaderConversation: React.FC<Props> = ({
|
||||
},
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试', autoHide: false })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
},
|
||||
onSettled: () => {
|
||||
queryCache.invalidateQueries(queryKey)
|
||||
queryClient.setQueryData(queryKey, oldData)
|
||||
}
|
||||
})
|
||||
|
||||
const { theme } = useTheme()
|
||||
|
||||
const actionOnPress = useCallback(() => mutateAction({ id }), [])
|
||||
const actionOnPress = useCallback(() => mutate({ id }), [])
|
||||
|
||||
const actionChildren = useMemo(
|
||||
() => (
|
||||
|
@ -15,7 +15,7 @@ import HeaderDefaultActionsStatus from '@components/Timelines/Timeline/Shared/He
|
||||
import HeaderDefaultActionsDomain from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: App.QueryKey
|
||||
queryKey?: QueryKey.Timeline
|
||||
status: Mastodon.Status
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
|
||||
import { toast } from '@components/toast'
|
||||
@ -56,7 +56,7 @@ const fireMutation = async ({
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
accountId: string
|
||||
account: string
|
||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||
@ -68,19 +68,16 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
account,
|
||||
setBottomSheetVisible
|
||||
}) => {
|
||||
const queryCache = useQueryCache()
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
const queryClient = useQueryClient()
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: () => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
return oldData
|
||||
},
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试', autoHide: false })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
},
|
||||
onSettled: () => {
|
||||
queryCache.invalidateQueries(queryKey)
|
||||
queryClient.setQueryData(queryKey, oldData)
|
||||
}
|
||||
})
|
||||
|
||||
@ -90,7 +87,7 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
mutate({
|
||||
type: 'mute',
|
||||
id: accountId,
|
||||
stateKey: 'muting'
|
||||
@ -102,7 +99,7 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
mutate({
|
||||
type: 'block',
|
||||
id: accountId,
|
||||
stateKey: 'blocking'
|
||||
@ -114,7 +111,7 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
mutate({
|
||||
type: 'reports',
|
||||
id: accountId
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import MenuContainer from '@components/Menu/Container'
|
||||
import MenuHeader from '@components/Menu/Header'
|
||||
@ -30,7 +30,7 @@ const fireMutation = async ({ domain }: { domain: string }) => {
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
domain: string
|
||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
@ -40,19 +40,19 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({
|
||||
domain,
|
||||
setBottomSheetVisible
|
||||
}) => {
|
||||
const queryCache = useQueryCache()
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
const queryClient = useQueryClient()
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: () => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
return oldData
|
||||
},
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试', autoHide: false })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
queryClient.setQueryData(queryKey, oldData)
|
||||
},
|
||||
onSettled: () => {
|
||||
queryCache.invalidateQueries(queryKey)
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
}
|
||||
})
|
||||
|
||||
@ -62,7 +62,7 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({ domain })
|
||||
mutate({ domain })
|
||||
}}
|
||||
iconFront='cloud-off'
|
||||
title={`屏蔽域名 ${domain}`}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import React from 'react'
|
||||
import { Alert } from 'react-native'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
|
||||
import { toast } from '@components/toast'
|
||||
@ -55,7 +55,7 @@ const fireMutation = async ({
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
queryKey: App.QueryKey
|
||||
queryKey: QueryKey.Timeline
|
||||
status: Mastodon.Status
|
||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
@ -66,17 +66,17 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
setBottomSheetVisible
|
||||
}) => {
|
||||
const navigation = useNavigation()
|
||||
const queryCache = useQueryCache()
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
const queryClient = useQueryClient()
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: ({ id, type, stateKey, prevState }) => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
switch (type) {
|
||||
case 'mute':
|
||||
case 'pin':
|
||||
queryCache.setQueryData(queryKey, old =>
|
||||
(old as {}[]).map((paging: any) => ({
|
||||
queryClient.setQueryData(queryKey, (old: any) =>
|
||||
old.pages.map((paging: any) => ({
|
||||
toots: paging.toots.map((toot: any) => {
|
||||
if (toot.id === id) {
|
||||
toot[stateKey] =
|
||||
@ -89,8 +89,8 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
)
|
||||
break
|
||||
case 'delete':
|
||||
queryCache.setQueryData(queryKey, old =>
|
||||
(old as {}[]).map((paging: any) => ({
|
||||
queryClient.setQueryData(queryKey, (old: any) =>
|
||||
old.pages.map((paging: any) => ({
|
||||
toots: paging.toots.filter((toot: any) => toot.id !== id),
|
||||
pointer: paging.pointer
|
||||
}))
|
||||
@ -102,7 +102,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
},
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试' })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
queryClient.setQueryData(queryKey, oldData)
|
||||
}
|
||||
})
|
||||
|
||||
@ -112,7 +112,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
mutate({
|
||||
type: 'delete',
|
||||
id: status.id,
|
||||
stateKey: 'id'
|
||||
@ -138,7 +138,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
url: `statuses/${status.id}`
|
||||
})
|
||||
.then(res => {
|
||||
queryCache.invalidateQueries(queryKey)
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
setBottomSheetVisible(false)
|
||||
navigation.navigate(getCurrentTab(navigation), {
|
||||
screen: 'Screen-Shared-Compose',
|
||||
@ -159,7 +159,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
mutate({
|
||||
type: 'mute',
|
||||
id: status.id,
|
||||
stateKey: 'muted',
|
||||
@ -174,7 +174,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
mutate({
|
||||
type: 'pin',
|
||||
id: status.id,
|
||||
stateKey: 'pinned',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import { ButtonRow } from '@components/Button'
|
||||
import { toast } from '@components/toast'
|
||||
@ -47,21 +47,21 @@ const fireMutation = async ({
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
queryKey: App.QueryKey
|
||||
status: Mastodon.Status
|
||||
queryKey: QueryKey.Timeline
|
||||
status: Required<Mastodon.Status, 'poll'>
|
||||
}
|
||||
|
||||
const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
const queryCache = useQueryCache()
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
const queryClient = useQueryClient()
|
||||
const { mutate } = useMutation(fireMutation, {
|
||||
onMutate: ({ id, options }) => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
queryCache.setQueryData(queryKey, old =>
|
||||
(old as {}[]).map((paging: any) => ({
|
||||
queryClient.setQueryData(queryKey, (old: any) =>
|
||||
old.pages.map((paging: any) => ({
|
||||
toots: paging.toots.map((toot: any) => {
|
||||
if (toot.poll?.id === id) {
|
||||
const poll = toot.poll
|
||||
@ -98,13 +98,13 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
},
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试' })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
queryClient.setQueryData(queryKey, oldData)
|
||||
}
|
||||
})
|
||||
|
||||
const pollExpiration = useMemo(() => {
|
||||
// how many voted
|
||||
if (poll!.expired) {
|
||||
if (poll.expired) {
|
||||
return (
|
||||
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
||||
投票已结束
|
||||
@ -113,20 +113,20 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
} else {
|
||||
return (
|
||||
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
||||
{relativeTime(poll!.expires_at)}截止
|
||||
{relativeTime(poll.expires_at)}截止
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [singleOptions, setSingleOptions] = useState({
|
||||
...[false, false, false, false].slice(0, poll!.options.length)
|
||||
...[false, false, false, false].slice(0, poll.options.length)
|
||||
})
|
||||
const [multipleOptions, setMultipleOptions] = useState({
|
||||
...[false, false, false, false].slice(0, poll!.options.length)
|
||||
...[false, false, false, false].slice(0, poll.options.length)
|
||||
})
|
||||
const isSelected = (index: number) => {
|
||||
if (poll!.multiple) {
|
||||
if (poll.multiple) {
|
||||
return multipleOptions[index] ? 'check-square' : 'square'
|
||||
} else {
|
||||
return singleOptions[index] ? 'check-circle' : 'circle'
|
||||
@ -135,28 +135,28 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{poll!.options.map((option, index) =>
|
||||
poll!.voted ? (
|
||||
{poll.options.map((option, index) =>
|
||||
poll.voted ? (
|
||||
<View key={index} style={styles.poll}>
|
||||
<View style={styles.optionSelected}>
|
||||
<View style={styles.contentSelected}>
|
||||
<Emojis
|
||||
content={option.title}
|
||||
emojis={poll!.emojis}
|
||||
emojis={poll.emojis}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
numberOfLines={1}
|
||||
/>
|
||||
{poll!.own_votes!.includes(index) && (
|
||||
{poll.own_votes!.includes(index) && (
|
||||
<Feather
|
||||
style={styles.voted}
|
||||
name={poll!.multiple ? 'check-square' : 'check-circle'}
|
||||
name={poll.multiple ? 'check-square' : 'check-circle'}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={theme.primary}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.percentage, { color: theme.primary }]}>
|
||||
{Math.round((option.votes_count / poll!.votes_count) * 100)}%
|
||||
{Math.round((option.votes_count / poll.votes_count) * 100)}%
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -165,7 +165,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
styles.background,
|
||||
{
|
||||
width: `${Math.round(
|
||||
(option.votes_count / poll!.votes_count) * 100
|
||||
(option.votes_count / poll.votes_count) * 100
|
||||
)}%`,
|
||||
backgroundColor: theme.border
|
||||
}
|
||||
@ -177,7 +177,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
<Pressable
|
||||
style={[styles.optionUnselected]}
|
||||
onPress={() => {
|
||||
if (poll!.multiple) {
|
||||
if (poll.multiple) {
|
||||
setMultipleOptions({
|
||||
...multipleOptions,
|
||||
[index]: !multipleOptions[index]
|
||||
@ -189,7 +189,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
index === 1,
|
||||
index === 2,
|
||||
index === 3
|
||||
].slice(0, poll!.options.length)
|
||||
].slice(0, poll.options.length)
|
||||
})
|
||||
}
|
||||
}}
|
||||
@ -203,7 +203,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
<View style={styles.contentUnselected}>
|
||||
<Emojis
|
||||
content={option.title}
|
||||
emojis={poll!.emojis}
|
||||
emojis={poll.emojis}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
/>
|
||||
</View>
|
||||
@ -217,9 +217,9 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
||||
<ButtonRow
|
||||
onPress={() => {
|
||||
if (poll.multiple) {
|
||||
mutateAction({ id: poll.id, options: multipleOptions })
|
||||
mutate({ id: poll.id, options: multipleOptions })
|
||||
} else {
|
||||
mutateAction({ id: poll.id, options: singleOptions })
|
||||
mutate({ id: poll.id, options: singleOptions })
|
||||
}
|
||||
}}
|
||||
text='投票'
|
||||
|
@ -6,13 +6,13 @@ import MenuButton from '@components/Menu/Button'
|
||||
import { MenuContainer } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useQueryCache } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
const Logout: React.FC = () => {
|
||||
const { t } = useTranslation('meRoot')
|
||||
const dispatch = useDispatch()
|
||||
const navigation = useNavigation()
|
||||
const queryCache = useQueryCache()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const alertOption = {
|
||||
title: t('content.logout.alert.title'),
|
||||
@ -22,7 +22,7 @@ const Logout: React.FC = () => {
|
||||
text: t('content.logout.alert.buttons.logout'),
|
||||
style: 'destructive' as const,
|
||||
onPress: () => {
|
||||
queryCache.clear()
|
||||
queryClient.clear()
|
||||
dispatch(updateLocal({}))
|
||||
navigation.navigate('Screen-Public', {
|
||||
screen: 'Screen-Public-Root',
|
||||
|
@ -22,7 +22,7 @@ const ScreenMeSettings: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuContainer marginTop={true}>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('content.language.heading')}
|
||||
content={t(`content.language.options.${settingsLanguage}`)}
|
||||
|
@ -25,7 +25,7 @@ import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler'
|
||||
import { PostAction } from '@screens/Shared/Compose'
|
||||
import { ComposeAction } from '@screens/Shared/Compose'
|
||||
import client from '@api/client'
|
||||
import AttachmentVideo from '@components/Timelines/Timeline/Shared/Attachment/AttachmentVideo'
|
||||
|
||||
@ -35,7 +35,7 @@ export interface Props {
|
||||
route: {
|
||||
params: {
|
||||
attachment: Mastodon.Attachment & { local_url: string }
|
||||
composeDispatch: Dispatch<PostAction>
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
}
|
||||
}
|
||||
navigation: any
|
||||
|
@ -26,7 +26,7 @@ import { searchFetch } from '@utils/fetches/searchFetch'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import {
|
||||
PostAction,
|
||||
ComposeAction,
|
||||
ComposeState,
|
||||
ComposeContext
|
||||
} from '@screens/Shared/Compose'
|
||||
@ -45,7 +45,7 @@ const ListItem = React.memo(
|
||||
}: {
|
||||
item: Mastodon.Account & Mastodon.Tag
|
||||
composeState: ComposeState
|
||||
composeDispatch: Dispatch<PostAction>
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
textInputRef: RefObject<TextInput>
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
|
@ -12,7 +12,7 @@ export interface Props {
|
||||
}
|
||||
|
||||
const ComposeRootFooter: React.FC<Props> = ({ textInputRef }) => {
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -2,7 +2,7 @@ import { Dispatch } from 'react'
|
||||
import { ActionSheetIOS, Alert } from 'react-native'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
|
||||
import { PostAction, ComposeState } from '@screens/Shared/Compose'
|
||||
import { ComposeAction, ComposeState } from '@screens/Shared/Compose'
|
||||
import client from '@api/client'
|
||||
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
||||
|
||||
@ -13,7 +13,7 @@ const uploadAttachment = async ({
|
||||
}: {
|
||||
result: NonNullable<ImageInfo>
|
||||
composeState: ComposeState
|
||||
composeDispatch: Dispatch<PostAction>
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
}) => {
|
||||
const formData = new FormData()
|
||||
// @ts-ignore
|
||||
@ -84,7 +84,7 @@ const addAttachments = async ({
|
||||
...params
|
||||
}: {
|
||||
composeState: ComposeState
|
||||
composeDispatch: Dispatch<PostAction>
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
}): Promise<any> => {
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { debounce, differenceWith, isEqual } from 'lodash'
|
||||
import React, { createElement, Dispatch } from 'react'
|
||||
import { Text } from 'react-native'
|
||||
import { RefetchOptions } from 'react-query/types/core/query'
|
||||
import { FetchOptions } from 'react-query/types/core/query'
|
||||
import Autolinker from '@root/modules/autolinker'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { PostAction, ComposeState } from '@screens/Shared/Compose'
|
||||
import { ComposeAction, ComposeState } from '@screens/Shared/Compose'
|
||||
|
||||
export interface Params {
|
||||
textInput: ComposeState['textInputFocus']['current']
|
||||
composeDispatch: Dispatch<PostAction>
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
content: string
|
||||
refetch?: (options?: RefetchOptions | undefined) => Promise<any>
|
||||
refetch?: (options?: FetchOptions | undefined) => Promise<any>
|
||||
disableDebounce?: boolean
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Dispatch } from 'react'
|
||||
import { PostAction, ComposeState } from '@screens/Shared/Compose'
|
||||
import { ComposeAction, ComposeState } from '@screens/Shared/Compose'
|
||||
import formatText from './formatText'
|
||||
|
||||
const updateText = ({
|
||||
@ -9,7 +9,7 @@ const updateText = ({
|
||||
type
|
||||
}: {
|
||||
composeState: ComposeState
|
||||
composeDispatch: Dispatch<PostAction>
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
newText: string
|
||||
type: 'emoji' | 'suggestion'
|
||||
}) => {
|
||||
|
@ -1,9 +1,12 @@
|
||||
import client from '@api/client'
|
||||
|
||||
export const accountFetch = async (
|
||||
key: string,
|
||||
{ id }: { id: string }
|
||||
): Promise<Mastodon.Account> => {
|
||||
export const accountFetch = async ({
|
||||
queryKey
|
||||
}: {
|
||||
queryKey: QueryKey.Account
|
||||
}): Promise<Mastodon.Account> => {
|
||||
const [_, { id }] = queryKey
|
||||
|
||||
const res = await client({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
|
@ -1,9 +1,12 @@
|
||||
import client from '@api/client'
|
||||
|
||||
export const applicationFetch = async (
|
||||
key: string,
|
||||
{ instanceDomain }: { instanceDomain: string }
|
||||
): Promise<Mastodon.AppOauth> => {
|
||||
export const applicationFetch = async ({
|
||||
queryKey
|
||||
}: {
|
||||
queryKey: QueryKey.Application
|
||||
}): Promise<Mastodon.AppOauth> => {
|
||||
const [_, { instanceDomain }] = queryKey
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('client_name', 'test_dudu')
|
||||
formData.append('redirect_uris', 'exp://127.0.0.1:19000')
|
||||
|
@ -1,9 +1,12 @@
|
||||
import client from '@api/client'
|
||||
|
||||
export const instanceFetch = async (
|
||||
key: string,
|
||||
{ instanceDomain }: { instanceDomain: string }
|
||||
): Promise<Mastodon.Instance> => {
|
||||
export const instanceFetch = async ({
|
||||
queryKey
|
||||
}: {
|
||||
queryKey: QueryKey.Instance
|
||||
}): Promise<Mastodon.Instance> => {
|
||||
const [_, { instanceDomain }] = queryKey
|
||||
|
||||
const res = await client({
|
||||
method: 'get',
|
||||
instance: 'remote',
|
||||
|
@ -1,9 +1,12 @@
|
||||
import client from '@api/client'
|
||||
|
||||
export const relationshipFetch = async (
|
||||
key: string,
|
||||
{ id }: { id: string }
|
||||
): Promise<Mastodon.Relationship> => {
|
||||
export const relationshipFetch = async ({
|
||||
queryKey
|
||||
}: {
|
||||
queryKey: QueryKey.Relationship
|
||||
}): Promise<Mastodon.Relationship> => {
|
||||
const [_, { id }] = queryKey
|
||||
|
||||
const res = await client({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
|
@ -1,17 +1,10 @@
|
||||
import client from '@api/client'
|
||||
|
||||
export const searchFetch = async (
|
||||
{} = {},
|
||||
{
|
||||
type,
|
||||
term,
|
||||
limit = 20
|
||||
}: {
|
||||
type?: 'accounts' | 'hashtags' | 'statuses'
|
||||
term: string
|
||||
limit?: number
|
||||
}
|
||||
): Promise<
|
||||
export const searchFetch = async ({
|
||||
queryKey
|
||||
}: {
|
||||
queryKey: QueryKey.Search
|
||||
}): Promise<
|
||||
| Mastodon.Account[]
|
||||
| Mastodon.Tag[]
|
||||
| Mastodon.Status[]
|
||||
@ -21,6 +14,7 @@ export const searchFetch = async (
|
||||
statuses: Mastodon.Status[]
|
||||
}
|
||||
> => {
|
||||
const [_, { type, term, limit = 20 }] = queryKey
|
||||
const res = await client({
|
||||
version: 'v2',
|
||||
method: 'get',
|
||||
|
@ -2,43 +2,28 @@ import { uniqBy } from 'lodash'
|
||||
|
||||
import client from '@api/client'
|
||||
|
||||
export const timelineFetch = async (
|
||||
key: string,
|
||||
{
|
||||
page,
|
||||
params = {},
|
||||
account,
|
||||
hashtag,
|
||||
list,
|
||||
toot
|
||||
}: {
|
||||
page: App.Pages
|
||||
params?: {
|
||||
[key: string]: string | number | boolean
|
||||
}
|
||||
hashtag?: Mastodon.Tag['name']
|
||||
list?: Mastodon.List['id']
|
||||
toot?: Mastodon.Status
|
||||
account?: Mastodon.Account['id']
|
||||
},
|
||||
pagination: {
|
||||
direction: 'prev' | 'next'
|
||||
id: string
|
||||
}
|
||||
): Promise<{
|
||||
export const timelineFetch = async ({
|
||||
queryKey,
|
||||
pageParam
|
||||
}: {
|
||||
queryKey: QueryKey.Timeline
|
||||
pageParam?: { direction: 'prev' | 'next'; id: Mastodon.Status['id'] }
|
||||
}): Promise<{
|
||||
toots: Mastodon.Status[]
|
||||
pointer?: number
|
||||
pinnedLength?: number
|
||||
}> => {
|
||||
const [page, { account, hashtag, list, toot }] = queryKey
|
||||
let res
|
||||
let params: { [key: string]: string } = {}
|
||||
|
||||
if (pagination && pagination.id) {
|
||||
switch (pagination.direction) {
|
||||
if (pageParam) {
|
||||
switch (pageParam.direction) {
|
||||
case 'prev':
|
||||
params.min_id = pagination.id
|
||||
params.min_id = pageParam.id
|
||||
break
|
||||
case 'next':
|
||||
params.max_id = pagination.id
|
||||
params.max_id = pageParam.id
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -91,8 +76,8 @@ export const timelineFetch = async (
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Account_Default':
|
||||
if (pagination && pagination.id) {
|
||||
if (pagination.direction === 'prev') {
|
||||
if (pageParam) {
|
||||
if (pageParam.direction === 'prev') {
|
||||
res = await client({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
@ -175,10 +160,10 @@ export const timelineFetch = async (
|
||||
url: `conversations`,
|
||||
params
|
||||
})
|
||||
if (pagination) {
|
||||
if (pageParam) {
|
||||
// Bug in pull to refresh in conversations
|
||||
res.body = res.body.filter(
|
||||
(b: Mastodon.Conversation) => b.id !== pagination.id
|
||||
(b: Mastodon.Conversation) => b.id !== pageParam.id
|
||||
)
|
||||
}
|
||||
return Promise.resolve({ toots: res.body })
|
||||
@ -220,5 +205,7 @@ export const timelineFetch = async (
|
||||
toots: [...res.body.ancestors, toot, ...res.body.descendants],
|
||||
pointer: res.body.ancestors.length
|
||||
})
|
||||
default:
|
||||
return Promise.reject()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user