diff --git a/src/components/Instance/index.tsx b/src/components/Instance/index.tsx index d9997133..ef24f068 100644 --- a/src/components/Instance/index.tsx +++ b/src/components/Instance/index.tsx @@ -173,6 +173,10 @@ const ComponentInstance: React.FC = ({ lists: { shown: false }, announcements: { shown: false, unread: 0 } }, + page_account_timeline: { + excludeBoosts: true, + excludeReplies: true + }, drafts: [], emojis_frequent: [] } diff --git a/src/screens/Tabs/Shared/Account/Attachments.tsx b/src/screens/Tabs/Shared/Account/Attachments.tsx index 151a2295..5460aaae 100644 --- a/src/screens/Tabs/Shared/Account/Attachments.tsx +++ b/src/screens/Tabs/Shared/Account/Attachments.tsx @@ -34,9 +34,8 @@ const AccountAttachments: React.FC = ({ remote_id, remote_domain }) => { const { data } = useTimelineQuery({ page: 'Account', + type: 'attachments', id: account?.id, - exclude_reblogs: false, - only_media: true, ...(remote_id && remote_domain && { remote_id, remote_domain }), options: { enabled: !!account?.id || (!!remote_id && !!remote_domain) } }) @@ -53,6 +52,7 @@ const AccountAttachments: React.FC = ({ remote_id, remote_domain }) => { flex: 1, height: width + StyleConstants.Spacing.Global.PagePadding * 2, paddingVertical: StyleConstants.Spacing.Global.PagePadding, + paddingRight: StyleConstants.Spacing.Global.PagePadding, borderTopWidth: 1, borderTopColor: colors.border }} @@ -70,7 +70,7 @@ const AccountAttachments: React.FC = ({ remote_id, remote_domain }) => { children={ = ({ remote_id, remote_domain }) => { blurhash: item.media_attachments[0]?.blurhash }} dimension={{ width, height: width }} - style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }} + style={{ + marginLeft: StyleConstants.Spacing.Global.PagePadding, + borderRadius: StyleConstants.BorderRadius / 2, + overflow: 'hidden' + }} onPress={() => navigation.push('Tab-Shared-Toot', { toot: item })} dim /> diff --git a/src/screens/Tabs/Shared/Account/Header.tsx b/src/screens/Tabs/Shared/Account/Header.tsx index ce209c74..a46127bf 100644 --- a/src/screens/Tabs/Shared/Account/Header.tsx +++ b/src/screens/Tabs/Shared/Account/Header.tsx @@ -9,11 +9,12 @@ const AccountHeader: React.FC = () => { const { account } = useContext(AccountContext) const topInset = useSafeAreaInsets().top + const height = Dimensions.get('window').width / 3 + topInset return ( { if (account) { Image.getSize(account.header, (width, height) => diff --git a/src/screens/Tabs/Shared/Account/Information/Fields.tsx b/src/screens/Tabs/Shared/Account/Information/Fields.tsx index df1b80e1..6f31e9f2 100644 --- a/src/screens/Tabs/Shared/Account/Information/Fields.tsx +++ b/src/screens/Tabs/Shared/Account/Information/Fields.tsx @@ -19,7 +19,8 @@ const AccountInformationFields: React.FC = () => { style={{ borderTopWidth: StyleSheet.hairlineWidth, marginBottom: StyleConstants.Spacing.M, - borderTopColor: colors.border + borderTopColor: colors.border, + marginHorizontal: -StyleConstants.Spacing.Global.PagePadding }} > {account.fields.map((field, index) => ( diff --git a/src/screens/Tabs/Shared/Account/Information/Stats.tsx b/src/screens/Tabs/Shared/Account/Information/Stats.tsx index 42aeb7d3..5857f6e0 100644 --- a/src/screens/Tabs/Shared/Account/Information/Stats.tsx +++ b/src/screens/Tabs/Shared/Account/Information/Stats.tsx @@ -23,25 +23,6 @@ const AccountInformationStats: React.FC = () => { return ( - {account ? ( - { - pageMe && account && navigation.push('Tab-Shared-Account', { account }) - }} - /> - ) : ( - - )} {account ? ( { } const styles = StyleSheet.create({ - stats: { - flex: 1, - justifyContent: 'space-between' - }, - stat: { - ...StyleConstants.FontStyle.S - } + stats: { flex: 1, gap: StyleConstants.Spacing.L }, + stat: { ...StyleConstants.FontStyle.S } }) export default AccountInformationStats diff --git a/src/screens/Tabs/Shared/Account/Nav.tsx b/src/screens/Tabs/Shared/Account/Nav.tsx index 3155483f..67b43dec 100644 --- a/src/screens/Tabs/Shared/Account/Nav.tsx +++ b/src/screens/Tabs/Shared/Account/Nav.tsx @@ -1,9 +1,10 @@ +import GracefullyImage from '@components/GracefullyImage' import { ParseEmojis } from '@components/Parse' import CustomText from '@components/Text' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { useContext } from 'react' -import { Dimensions, StyleSheet, View } from 'react-native' +import { StyleSheet, View } from 'react-native' import Animated, { Extrapolate, interpolate, useAnimatedStyle } from 'react-native-reanimated' import { useSafeAreaInsets } from 'react-native-safe-area-context' import AccountContext from './Context' @@ -18,23 +19,11 @@ const AccountNav: React.FC = ({ scrollY }) => { const { colors } = useTheme() const headerHeight = useSafeAreaInsets().top + 44 - const nameY = - Dimensions.get('window').width / 3 + - StyleConstants.Avatar.L - - StyleConstants.Spacing.Global.PagePadding * 2 + - StyleConstants.Spacing.M - - headerHeight - const styleOpacity = useAnimatedStyle(() => { return { opacity: interpolate(scrollY.value, [0, 200], [0, 1], Extrapolate.CLAMP) } }) - const styleMarginTop = useAnimatedStyle(() => { - return { - marginTop: interpolate(scrollY.value, [nameY, nameY + 20], [50, 0], Extrapolate.CLAMP) - } - }) return ( = ({ scrollY }) => { flex: 1, alignItems: 'center', overflow: 'hidden', - marginTop: useSafeAreaInsets().top + (44 - StyleConstants.Font.Size.L) / 2 + marginTop: useSafeAreaInsets().top + StyleConstants.Font.Size.L / 2 }} > - + {account ? ( - - + - + + + + ) : null} - + ) diff --git a/src/screens/Tabs/Shared/Account/index.tsx b/src/screens/Tabs/Shared/Account/index.tsx index 527b5c1d..394029ae 100644 --- a/src/screens/Tabs/Shared/Account/index.tsx +++ b/src/screens/Tabs/Shared/Account/index.tsx @@ -1,20 +1,20 @@ import menuAccount from '@components/contextMenu/account' import menuShare from '@components/contextMenu/share' import { HeaderLeft, HeaderRight } from '@components/Header' +import Icon from '@components/Icon' +import CustomText from '@components/Text' import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' -import SegmentedControl from '@react-native-segmented-control/segmented-control' -import { useQueryClient } from '@tanstack/react-query' import { TabSharedStackScreenProps } from '@utils/navigation/navigators' +import { queryClient } from '@utils/queryHooks' import { useAccountQuery } from '@utils/queryHooks/account' import { useRelationshipQuery } from '@utils/queryHooks/relationship' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { useAccountStorage } from '@utils/storage/actions' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import React, { Fragment, useEffect, useMemo, useState } from 'react' +import React, { Fragment, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { Platform, Text, View } from 'react-native' +import { Platform, Pressable, Text, View } from 'react-native' import { useSharedValue } from 'react-native-reanimated' import * as DropdownMenu from 'zeego/dropdown-menu' import AccountAttachments from './Attachments' @@ -29,7 +29,7 @@ const TabSharedAccount: React.FC params: { account } } }) => { - const { t } = useTranslation('screenTabs') + const { t } = useTranslation(['common', 'screenTabs']) const { colors, mode } = useTheme() const { data, dataUpdatedAt, isFetched } = useAccountQuery({ @@ -46,18 +46,15 @@ const TabSharedAccount: React.FC id: data?.id, options: { enabled: account._remote ? isFetched : true } }) - - const queryClient = useQueryClient() - const [queryKey, setQueryKey] = useState([ + const queryKeyDefault: QueryKeyTimeline = [ 'Timeline', { page: 'Account', + type: 'default', id: data?.id, - exclude_reblogs: true, - only_media: false, ...(account._remote && { remote_id: account.id, remote_domain: account._remote }) } - ]) + ] const mShare = menuShare({ type: 'account', url: data?.url }) const mAccount = menuAccount({ type: 'account', openChange: true, account: data }) @@ -72,10 +69,10 @@ const TabSharedAccount: React.FC {}} background @@ -150,15 +147,10 @@ const TabSharedAccount: React.FC } }) }, [mAccount]) - useEffect(() => { - navigation.setParams({ queryKey }) - }, [queryKey[1]]) const scrollY = useSharedValue(0) - const page = queryKey[1] - - const [segment, setSegment] = useState(0) + const [timelineSettings, setTimelineSettings] = useAccountStorage.object('page_account_timeline') const ListHeaderComponent = useMemo(() => { return ( <> @@ -170,45 +162,97 @@ const TabSharedAccount: React.FC /> {!data?.suspended ? ( - // @ts-ignore - { - setSegment(nativeEvent.selectedSegmentIndex) - switch (nativeEvent.selectedSegmentIndex) { - case 0: - setQueryKey([ - queryKey[0], - { - ...page, - page: 'Account', - id: data?.id, - exclude_reblogs: true, - only_media: false - } - ]) - break - case 1: - setQueryKey([ - queryKey[0], - { - ...page, - page: 'Account', - id: data?.id, - exclude_reblogs: false, - only_media: false - } - ]) - break - } - }} - style={{ - marginTop: StyleConstants.Spacing.M, - marginHorizontal: StyleConstants.Spacing.Global.PagePadding - }} - /> + + + + + + } + /> + + + + + + + + + { + setTimelineSettings({ + ...timelineSettings, + excludeBoosts: !timelineSettings?.excludeBoosts + }) + queryClient.refetchQueries(queryKeyDefault) + }} + > + + + + { + setTimelineSettings({ + ...timelineSettings, + excludeReplies: !timelineSettings?.excludeReplies + }) + queryClient.refetchQueries(queryKeyDefault) + }} + > + + + + + + ) : null} {data?.suspended ? ( textAlign: 'center' }} > - {t('shared.account.suspended')} + {t('screenTabs:shared.account.suspended')} ) : null} ) - }, [segment, dataUpdatedAt, mode]) + }, [timelineSettings, dataUpdatedAt, mode]) const [domain] = useAccountStorage.string('auth.account.domain') @@ -252,16 +296,14 @@ const TabSharedAccount: React.FC ListHeaderComponent ) : ( , onScroll: ({ nativeEvent }) => (scrollY.value = nativeEvent.contentOffset.y), ListHeaderComponent, - maintainVisibleContentPosition: undefined, - onRefresh: () => queryClient.refetchQueries(queryKey), - refreshing: false + refreshing: false, + onRefresh: () => queryClient.refetchQueries(queryKeyDefault) }} /> )} diff --git a/src/screens/Tabs/Shared/Attachments.tsx b/src/screens/Tabs/Shared/Attachments.tsx index b9f100e2..6e8ed39b 100644 --- a/src/screens/Tabs/Shared/Attachments.tsx +++ b/src/screens/Tabs/Shared/Attachments.tsx @@ -46,7 +46,12 @@ const TabSharedAttachments: React.FC diff --git a/src/utils/queryHooks/timeline.ts b/src/utils/queryHooks/timeline.ts index 8755c692..ec394da8 100644 --- a/src/utils/queryHooks/timeline.ts +++ b/src/utils/queryHooks/timeline.ts @@ -15,7 +15,6 @@ import { useNavState } from '@utils/navigation/navigators' import { queryClient } from '@utils/queryHooks' import { getAccountStorage, setAccountStorage } from '@utils/storage/actions' import { AxiosError } from 'axios' -import { uniqBy } from 'lodash' import { searchLocalStatus } from './search' import deleteItem from './timeline/deleteItem' import editItem from './timeline/editItem' @@ -43,10 +42,9 @@ export type QueryKeyTimeline = [ } | { page: 'Account' + type: 'default' | 'attachments' id?: Mastodon.Account['id'] - exclude_reblogs: boolean - only_media: boolean - // remote info + // remote remote_id?: Mastodon.Account['id'] remote_domain?: string } @@ -163,139 +161,82 @@ export const queryFunctionTimeline = async ({ }) case 'Account': - const reject = Promise.reject('Timeline query account id not provided') + if (!page.id) return Promise.reject('Timeline account missing id') - if (page.only_media) { - let res - if (page.remote_domain && page.remote_id) { - res = await apiGeneral({ - method: 'get', - domain: page.remote_domain, - url: `api/v1/accounts/${page.remote_id}/statuses`, - params: { - only_media: true, - ...params - } - }) - .then(res => ({ - ...res, - body: res.body.map(status => appendRemote.status(status, page.remote_domain!)) - })) - .catch(() => {}) - } - if (!res && page.id) { - res = await apiInstance({ - method: 'get', - url: `accounts/${page.id}/statuses`, - params: { - only_media: true, - ...params - } - }) - } - return res || reject - } else if (page.exclude_reblogs) { - if (pageParam && pageParam.hasOwnProperty('max_id')) { - let res - if (page.remote_domain && page.remote_id) { - res = await apiGeneral({ - method: 'get', - domain: page.remote_domain, - url: `api/v1/accounts/${page.remote_id}/statuses`, - params: { - exclude_replies: true, - ...params - } - }) - .then(res => ({ - ...res, - body: res.body.map(status => appendRemote.status(status, page.remote_domain!)) - })) - .catch(() => {}) + let typeParams + switch (page.type) { + case 'default': + const filters = getAccountStorage.object('page_account_timeline') + typeParams = { + exclude_reblogs: + typeof filters?.excludeBoosts === 'boolean' ? filters.excludeBoosts : true, + exclude_replies: + typeof filters?.excludeReplies === 'boolean' ? filters.excludeReplies : true } - if (!res && page.id) { - res = await apiInstance({ - method: 'get', - url: `accounts/${page.id}/statuses`, - params: { - exclude_replies: true, - ...params - } - }) - } - return res || reject - } else { - let res - if (page.remote_domain && page.remote_id) { - res = await apiGeneral({ - method: 'get', - domain: page.remote_domain, - url: `api/v1/accounts/${page.remote_id}/statuses`, - params: { exclude_replies: true } - }) - .then(res => ({ - ...res, - body: res.body.map(status => appendRemote.status(status, page.remote_domain!)) - })) - .catch(() => {}) - } - if (!res && page.id) { - const resPinned = await apiInstance<(Mastodon.Status & { _pinned: boolean })[]>({ - method: 'get', - url: `accounts/${page.id}/statuses`, - params: { pinned: true } - }).then(res => ({ - ...res, - body: res.body.map(status => { - status._pinned = true - return status - }) - })) - const resDefault = await apiInstance({ - method: 'get', - url: `accounts/${page.id}/statuses`, - params: { exclude_replies: true } - }) - return { - body: uniqBy([...resPinned.body, ...resDefault.body], 'id'), - links: resDefault.links - } - } - return res || reject - } - } else { - let res - if (page.remote_domain && page.remote_id) { - res = await apiGeneral({ - method: 'get', - domain: page.remote_domain, - url: `api/v1/accounts/${page.remote_id}/statuses`, - params: { - ...params, - exclude_replies: false, - only_media: false - } - }) - .then(res => ({ - ...res, - body: res.body.map(status => appendRemote.status(status, page.remote_domain!)) - })) - .catch(() => {}) - } - if (!res && page.id) { - res = await apiInstance({ - method: 'get', - url: `accounts/${page.id}/statuses`, - params: { - ...params, - exclude_replies: false, - only_media: false - } - }) - } - return res || reject + break + case 'attachments': + typeParams = { only_media: true, exclude_reblogs: true } + break } + let pinned + if (page.type === 'default' && !params.hasOwnProperty('max_id')) { + if (page.remote_domain && page.remote_id) { + pinned = await apiGeneral({ + method: 'get', + domain: page.remote_domain, + url: `api/v1/accounts/${page.remote_id}/statuses`, + params: { pinned: true } + }) + .then(res => ({ + ...res, + body: res.body.map(status => appendRemote.status(status, page.remote_domain!)) + })) + .catch(() => {}) + } + if (!pinned) { + pinned = await apiInstance({ + method: 'get', + url: `accounts/${page.id}/statuses`, + params: { pinned: true } + }) + } + } + + let res + if (page.remote_domain && page.remote_id) { + res = await apiGeneral({ + method: 'get', + domain: page.remote_domain, + url: `api/v1/accounts/${page.remote_id}/statuses`, + params: { + ...typeParams, + ...params + } + }) + .then(res => ({ + ...res, + body: res.body.map(status => appendRemote.status(status, page.remote_domain!)) + })) + .catch(() => {}) + } + if (!res) { + res = await apiInstance({ + method: 'get', + url: `accounts/${page.id}/statuses`, + params: { + ...typeParams, + ...params + } + }) + } + return pinned + ? { + body: [...pinned.body.map(status => ({ ...status, _pinned: true })), ...res.body], + links: res.links + } + : res + case 'Hashtag': return apiInstance({ method: 'get', diff --git a/src/utils/storage/account/v0.ts b/src/utils/storage/account/v0.ts index d1ec06de..81c71156 100644 --- a/src/utils/storage/account/v0.ts +++ b/src/utils/storage/account/v0.ts @@ -53,6 +53,10 @@ export type AccountV0 = { unread: number } } + page_account_timeline: { + excludeBoosts: boolean + excludeReplies: boolean + } drafts: ComposeStateDraft[] emojis_frequent: { emoji: Pick