mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Fixed #457
This commit is contained in:
		| @@ -47,9 +47,27 @@ const apiGeneral = async <T = unknown>({ | ||||
|     ...(body && { data: body }) | ||||
|   }) | ||||
|     .then(response => { | ||||
|       return Promise.resolve({ | ||||
|         body: response.data | ||||
|       }) | ||||
|       let links: { | ||||
|         prev?: { id: string; isOffset: boolean } | ||||
|         next?: { id: string; isOffset: boolean } | ||||
|       } = {} | ||||
|  | ||||
|       if (response.headers?.link) { | ||||
|         const linksParsed = response.headers.link.matchAll( | ||||
|           new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi') | ||||
|         ) | ||||
|         for (const link of linksParsed) { | ||||
|           switch (link[3]) { | ||||
|             case 'prev': | ||||
|               links.prev = { id: link[2], isOffset: link[1].includes('offset') } | ||||
|               break | ||||
|             case 'next': | ||||
|               links.next = { id: link[2], isOffset: link[1].includes('offset') } | ||||
|               break | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       return Promise.resolve({ body: response.data, links }) | ||||
|     }) | ||||
|     .catch(handleError()) | ||||
| } | ||||
|   | ||||
| @@ -37,7 +37,7 @@ const TimelineFeedback = () => { | ||||
|             onPress={() => | ||||
|               navigation.push('Tab-Shared-Users', { | ||||
|                 reference: 'statuses', | ||||
|                 id: status.id, | ||||
|                 status, | ||||
|                 type: 'reblogged_by', | ||||
|                 count: status.reblogs_count | ||||
|               }) | ||||
| @@ -59,7 +59,7 @@ const TimelineFeedback = () => { | ||||
|             onPress={() => | ||||
|               navigation.push('Tab-Shared-Users', { | ||||
|                 reference: 'statuses', | ||||
|                 id: status.id, | ||||
|                 status, | ||||
|                 type: 'favourited_by', | ||||
|                 count: status.favourites_count | ||||
|               }) | ||||
|   | ||||
| @@ -395,7 +395,8 @@ | ||||
|       "statuses": { | ||||
|         "reblogged_by": "{{count}} boosted", | ||||
|         "favourited_by": "{{count}} favourited" | ||||
|       } | ||||
|       }, | ||||
|       "resultIncomplete": "Results from a remote instance is incomplete" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -28,7 +28,7 @@ const AccountAttachments: React.FC<Props> = ({ account }) => { | ||||
|   const queryKeyParams: QueryKeyTimeline[1] = { | ||||
|     page: 'Account', | ||||
|     account: account.id, | ||||
|     exclude_reblogs: true, | ||||
|     exclude_reblogs: false, | ||||
|     only_media: true | ||||
|   } | ||||
|   const { data, refetch } = useTimelineQuery({ | ||||
|   | ||||
| @@ -25,8 +25,8 @@ const AccountInformationAccount: React.FC<Props> = ({ account }) => { | ||||
|     options: { enabled: account !== undefined } | ||||
|   }) | ||||
|  | ||||
|   const localInstance = instanceAccount.acct.includes('@') | ||||
|     ? instanceAccount.acct.includes(`@${instanceUri}`) | ||||
|   const localInstance = account?.acct.includes('@') | ||||
|     ? account?.acct.includes(`@${instanceUri}`) | ||||
|     : true | ||||
|  | ||||
|   if (account || (localInstance && instanceAccount)) { | ||||
| @@ -52,7 +52,7 @@ const AccountInformationAccount: React.FC<Props> = ({ account }) => { | ||||
|             }} | ||||
|             selectable | ||||
|           > | ||||
|             @{localInstance ? instanceAccount?.acct : account?.acct} | ||||
|             @{account?.acct} | ||||
|             {localInstance ? `@${instanceUri}` : null} | ||||
|           </CustomText> | ||||
|           {relationship?.followed_by ? t('shared.account.followed_by') : null} | ||||
|   | ||||
| @@ -53,7 +53,7 @@ const AccountInformationStats: React.FC<Props> = ({ account, myInfo }) => { | ||||
|           onPress={() => | ||||
|             navigation.push('Tab-Shared-Users', { | ||||
|               reference: 'accounts', | ||||
|               id: account.id, | ||||
|               account, | ||||
|               type: 'following', | ||||
|               count: account.following_count | ||||
|             }) | ||||
| @@ -77,7 +77,7 @@ const AccountInformationStats: React.FC<Props> = ({ account, myInfo }) => { | ||||
|           onPress={() => | ||||
|             navigation.push('Tab-Shared-Users', { | ||||
|               reference: 'accounts', | ||||
|               id: account.id, | ||||
|               account, | ||||
|               type: 'followers', | ||||
|               count: account.followers_count | ||||
|             }) | ||||
|   | ||||
| @@ -1,16 +1,22 @@ | ||||
| import ComponentAccount from '@components/Account' | ||||
| import { HeaderLeft } from '@components/Header' | ||||
| import Icon from '@components/Icon' | ||||
| import ComponentSeparator from '@components/Separator' | ||||
| import CustomText from '@components/Text' | ||||
| import { TabSharedStackScreenProps } from '@utils/navigation/navigators' | ||||
| import { QueryKeyUsers, useUsersQuery } from '@utils/queryHooks/users' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import React, { useCallback, useEffect } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { View } from 'react-native' | ||||
| import { FlatList } from 'react-native-gesture-handler' | ||||
|  | ||||
| const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> = ({ | ||||
|   navigation, | ||||
|   route: { params } | ||||
| }) => { | ||||
|   const { colors } = useTheme() | ||||
|   const { t } = useTranslation('screenTabs') | ||||
|   useEffect(() => { | ||||
|     navigation.setOptions({ | ||||
| @@ -23,8 +29,9 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> = | ||||
|   const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({ | ||||
|     ...queryKey[1], | ||||
|     options: { | ||||
|       getPreviousPageParam: firstPage => firstPage.links?.prev && { min_id: firstPage.links.next }, | ||||
|       getNextPageParam: lastPage => lastPage.links?.next && { max_id: lastPage.links.next } | ||||
|       getPreviousPageParam: firstPage => | ||||
|         firstPage.links?.prev?.id && { min_id: firstPage.links.prev.id }, | ||||
|       getNextPageParam: lastPage => lastPage.links?.next?.id && { max_id: lastPage.links.next.id } | ||||
|     } | ||||
|   }) | ||||
|   const flattenData = data?.pages ? data.pages.flatMap(page => [...page.body]) : [] | ||||
| @@ -49,6 +56,31 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> = | ||||
|         minIndexForVisible: 0, | ||||
|         autoscrollToTopThreshold: 2 | ||||
|       }} | ||||
|       ListHeaderComponent={ | ||||
|         data?.pages[0]?.warnIncomplete === true ? ( | ||||
|           <View | ||||
|             style={{ | ||||
|               flexDirection: 'row', | ||||
|               alignItems: 'center', | ||||
|               marginHorizontal: StyleConstants.Spacing.Global.PagePadding, | ||||
|               padding: StyleConstants.Spacing.S, | ||||
|               borderColor: colors.border, | ||||
|               borderWidth: 1, | ||||
|               borderRadius: StyleConstants.Spacing.S | ||||
|             }} | ||||
|           > | ||||
|             <Icon | ||||
|               name='AlertCircle' | ||||
|               color={colors.secondary} | ||||
|               size={StyleConstants.Font.Size.M} | ||||
|               style={{ marginRight: StyleConstants.Spacing.S }} | ||||
|             /> | ||||
|             <CustomText fontStyle='S' style={{ flexShrink: 1, color: colors.secondary }}> | ||||
|               {t('shared.users.resultIncomplete')} | ||||
|             </CustomText> | ||||
|           </View> | ||||
|         ) : null | ||||
|       } | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -112,13 +112,13 @@ export type TabSharedStackParamList = { | ||||
|   'Tab-Shared-Users': | ||||
|     | { | ||||
|         reference: 'accounts' | ||||
|         id: Mastodon.Account['id'] | ||||
|         account: Pick<Mastodon.Account, 'id' | 'username' | 'acct' | 'url'> | ||||
|         type: 'following' | 'followers' | ||||
|         count: number | ||||
|       } | ||||
|     | { | ||||
|         reference: 'statuses' | ||||
|         id: Mastodon.Status['id'] | ||||
|         status: Pick<Mastodon.Status, 'id'> | ||||
|         type: 'reblogged_by' | 'favourited_by' | ||||
|         count: number | ||||
|       } | ||||
|   | ||||
| @@ -6,24 +6,83 @@ import { | ||||
|   useInfiniteQuery, | ||||
|   UseInfiniteQueryOptions | ||||
| } from '@tanstack/react-query' | ||||
| import apiGeneral from '@api/general' | ||||
|  | ||||
| export type QueryKeyUsers = [ | ||||
|   'Users', | ||||
|   TabSharedStackParamList['Tab-Shared-Users'] | ||||
| ] | ||||
| export type QueryKeyUsers = ['Users', TabSharedStackParamList['Tab-Shared-Users']] | ||||
|  | ||||
| const queryFunction = ({ | ||||
|   queryKey, | ||||
|   pageParam | ||||
| }: QueryFunctionContext<QueryKeyUsers>) => { | ||||
|   const { reference, id, type } = queryKey[1] | ||||
| const queryFunction = ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyUsers>) => { | ||||
|   const page = queryKey[1] | ||||
|   let params: { [key: string]: string } = { ...pageParam } | ||||
|  | ||||
|   return apiInstance<Mastodon.Account[]>({ | ||||
|     method: 'get', | ||||
|     url: `${reference}/${id}/${type}`, | ||||
|     params | ||||
|   }) | ||||
|   switch (page.reference) { | ||||
|     case 'statuses': | ||||
|       return apiInstance<Mastodon.Account[]>({ | ||||
|         method: 'get', | ||||
|         url: `${page.reference}/${page.status.id}/${page.type}`, | ||||
|         params | ||||
|       }).then(res => ({ ...res, warnIncomplete: false })) | ||||
|     case 'accounts': | ||||
|       const localInstance = page.account.username === page.account.acct | ||||
|       if (localInstance) { | ||||
|         return apiInstance<Mastodon.Account[]>({ | ||||
|           method: 'get', | ||||
|           url: `${page.reference}/${page.account.id}/${page.type}`, | ||||
|           params | ||||
|         }).then(res => ({ ...res, warnIncomplete: false })) | ||||
|       } else { | ||||
|         const domain = page.account.url.match( | ||||
|           /^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)/i | ||||
|         )?.[1] | ||||
|         if (!domain) { | ||||
|           return apiInstance<Mastodon.Account[]>({ | ||||
|             method: 'get', | ||||
|             url: `${page.reference}/${page.account.id}/${page.type}`, | ||||
|             params | ||||
|           }).then(res => ({ ...res, warnIncomplete: true })) | ||||
|         } | ||||
|         return apiGeneral<{ accounts: Mastodon.Account[] }>({ | ||||
|           method: 'get', | ||||
|           domain, | ||||
|           url: 'api/v2/search', | ||||
|           params: { | ||||
|             q: `@${page.account.acct}`, | ||||
|             type: 'accounts', | ||||
|             limit: '1' | ||||
|           } | ||||
|         }) | ||||
|           .then(res => { | ||||
|             if (res?.body?.accounts?.length === 1) { | ||||
|               return apiGeneral<Mastodon.Account[]>({ | ||||
|                 method: 'get', | ||||
|                 domain, | ||||
|                 url: `api/v1/${page.reference}/${res.body.accounts[0].id}/${page.type}`, | ||||
|                 params | ||||
|               }) | ||||
|                 .catch(() => { | ||||
|                   return apiInstance<Mastodon.Account[]>({ | ||||
|                     method: 'get', | ||||
|                     url: `${page.reference}/${page.account.id}/${page.type}`, | ||||
|                     params | ||||
|                   }).then(res => ({ ...res, warnIncomplete: true })) | ||||
|                 }) | ||||
|                 .then(res => ({ ...res, warnIncomplete: false })) | ||||
|             } else { | ||||
|               return apiInstance<Mastodon.Account[]>({ | ||||
|                 method: 'get', | ||||
|                 url: `${page.reference}/${page.account.id}/${page.type}`, | ||||
|                 params | ||||
|               }).then(res => ({ ...res, warnIncomplete: true })) | ||||
|             } | ||||
|           }) | ||||
|           .catch(() => { | ||||
|             return apiInstance<Mastodon.Account[]>({ | ||||
|               method: 'get', | ||||
|               url: `${page.reference}/${page.account.id}/${page.type}`, | ||||
|               params | ||||
|             }).then(res => ({ ...res, warnIncomplete: true })) | ||||
|           }) | ||||
|       } | ||||
|   } | ||||
| } | ||||
|  | ||||
| const useUsersQuery = ({ | ||||
| @@ -31,7 +90,7 @@ const useUsersQuery = ({ | ||||
|   ...queryKeyParams | ||||
| }: QueryKeyUsers[1] & { | ||||
|   options?: UseInfiniteQueryOptions< | ||||
|     InstanceResponse<Mastodon.Account[]>, | ||||
|     InstanceResponse<Mastodon.Account[]> & { warnIncomplete: boolean }, | ||||
|     AxiosError | ||||
|   > | ||||
| }) => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user