From a4cd24f31344caa1ddad20f8aa6d06ebf5cbd2c7 Mon Sep 17 00:00:00 2001 From: xmflsct Date: Thu, 15 Dec 2022 01:41:34 +0100 Subject: [PATCH] Fixed #457 --- src/api/general.ts | 24 ++++- src/components/Timeline/Shared/Feedback.tsx | 4 +- src/i18n/en/screens/tabs.json | 3 +- .../Tabs/Shared/Account/Attachments.tsx | 2 +- .../Shared/Account/Information/Account.tsx | 6 +- .../Tabs/Shared/Account/Information/Stats.tsx | 4 +- src/screens/Tabs/Shared/Users.tsx | 36 +++++++- src/utils/navigation/navigators.ts | 4 +- src/utils/queryHooks/users.ts | 89 +++++++++++++++---- 9 files changed, 141 insertions(+), 31 deletions(-) diff --git a/src/api/general.ts b/src/api/general.ts index 81e993f5..e29637d3 100644 --- a/src/api/general.ts +++ b/src/api/general.ts @@ -47,9 +47,27 @@ const apiGeneral = async ({ ...(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()) } diff --git a/src/components/Timeline/Shared/Feedback.tsx b/src/components/Timeline/Shared/Feedback.tsx index 8fa8f30a..40d647e7 100644 --- a/src/components/Timeline/Shared/Feedback.tsx +++ b/src/components/Timeline/Shared/Feedback.tsx @@ -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 }) diff --git a/src/i18n/en/screens/tabs.json b/src/i18n/en/screens/tabs.json index 98930190..5d7d1f2d 100644 --- a/src/i18n/en/screens/tabs.json +++ b/src/i18n/en/screens/tabs.json @@ -395,7 +395,8 @@ "statuses": { "reblogged_by": "{{count}} boosted", "favourited_by": "{{count}} favourited" - } + }, + "resultIncomplete": "Results from a remote instance is incomplete" } } } \ No newline at end of file diff --git a/src/screens/Tabs/Shared/Account/Attachments.tsx b/src/screens/Tabs/Shared/Account/Attachments.tsx index 778a4f60..64a98782 100644 --- a/src/screens/Tabs/Shared/Account/Attachments.tsx +++ b/src/screens/Tabs/Shared/Account/Attachments.tsx @@ -28,7 +28,7 @@ const AccountAttachments: React.FC = ({ account }) => { const queryKeyParams: QueryKeyTimeline[1] = { page: 'Account', account: account.id, - exclude_reblogs: true, + exclude_reblogs: false, only_media: true } const { data, refetch } = useTimelineQuery({ diff --git a/src/screens/Tabs/Shared/Account/Information/Account.tsx b/src/screens/Tabs/Shared/Account/Information/Account.tsx index e209e319..54d52c33 100644 --- a/src/screens/Tabs/Shared/Account/Information/Account.tsx +++ b/src/screens/Tabs/Shared/Account/Information/Account.tsx @@ -25,8 +25,8 @@ const AccountInformationAccount: React.FC = ({ 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 = ({ account }) => { }} selectable > - @{localInstance ? instanceAccount?.acct : account?.acct} + @{account?.acct} {localInstance ? `@${instanceUri}` : null} {relationship?.followed_by ? t('shared.account.followed_by') : null} diff --git a/src/screens/Tabs/Shared/Account/Information/Stats.tsx b/src/screens/Tabs/Shared/Account/Information/Stats.tsx index fe2c4e37..a9ff0751 100644 --- a/src/screens/Tabs/Shared/Account/Information/Stats.tsx +++ b/src/screens/Tabs/Shared/Account/Information/Stats.tsx @@ -53,7 +53,7 @@ const AccountInformationStats: React.FC = ({ 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 = ({ account, myInfo }) => { onPress={() => navigation.push('Tab-Shared-Users', { reference: 'accounts', - id: account.id, + account, type: 'followers', count: account.followers_count }) diff --git a/src/screens/Tabs/Shared/Users.tsx b/src/screens/Tabs/Shared/Users.tsx index 53aa4b25..fff09d70 100644 --- a/src/screens/Tabs/Shared/Users.tsx +++ b/src/screens/Tabs/Shared/Users.tsx @@ -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> = ({ navigation, route: { params } }) => { + const { colors } = useTheme() const { t } = useTranslation('screenTabs') useEffect(() => { navigation.setOptions({ @@ -23,8 +29,9 @@ const TabSharedUsers: React.FC> = 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> = minIndexForVisible: 0, autoscrollToTopThreshold: 2 }} + ListHeaderComponent={ + data?.pages[0]?.warnIncomplete === true ? ( + + + + {t('shared.users.resultIncomplete')} + + + ) : null + } /> ) } diff --git a/src/utils/navigation/navigators.ts b/src/utils/navigation/navigators.ts index 9762598a..0e5d2822 100644 --- a/src/utils/navigation/navigators.ts +++ b/src/utils/navigation/navigators.ts @@ -112,13 +112,13 @@ export type TabSharedStackParamList = { 'Tab-Shared-Users': | { reference: 'accounts' - id: Mastodon.Account['id'] + account: Pick type: 'following' | 'followers' count: number } | { reference: 'statuses' - id: Mastodon.Status['id'] + status: Pick type: 'reblogged_by' | 'favourited_by' count: number } diff --git a/src/utils/queryHooks/users.ts b/src/utils/queryHooks/users.ts index 75507b99..810ffa03 100644 --- a/src/utils/queryHooks/users.ts +++ b/src/utils/queryHooks/users.ts @@ -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) => { - const { reference, id, type } = queryKey[1] +const queryFunction = ({ queryKey, pageParam }: QueryFunctionContext) => { + const page = queryKey[1] let params: { [key: string]: string } = { ...pageParam } - return apiInstance({ - method: 'get', - url: `${reference}/${id}/${type}`, - params - }) + switch (page.reference) { + case 'statuses': + return apiInstance({ + 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({ + 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({ + 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({ + method: 'get', + domain, + url: `api/v1/${page.reference}/${res.body.accounts[0].id}/${page.type}`, + params + }) + .catch(() => { + return apiInstance({ + method: 'get', + url: `${page.reference}/${page.account.id}/${page.type}`, + params + }).then(res => ({ ...res, warnIncomplete: true })) + }) + .then(res => ({ ...res, warnIncomplete: false })) + } else { + return apiInstance({ + method: 'get', + url: `${page.reference}/${page.account.id}/${page.type}`, + params + }).then(res => ({ ...res, warnIncomplete: true })) + } + }) + .catch(() => { + return apiInstance({ + 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, + InstanceResponse & { warnIncomplete: boolean }, AxiosError > }) => {