mirror of
https://github.com/tooot-app/app
synced 2025-02-02 11:36:56 +01:00
Fixed #447
This commit is contained in:
parent
7f8a8de898
commit
4a35e910c1
4
src/@types/app.d.ts
vendored
4
src/@types/app.d.ts
vendored
@ -8,9 +8,7 @@ declare namespace App {
|
||||
| 'Hashtag'
|
||||
| 'List'
|
||||
| 'Toot'
|
||||
| 'Account_Default'
|
||||
| 'Account_All'
|
||||
| 'Account_Attachments'
|
||||
| 'Account'
|
||||
| 'Conversations'
|
||||
| 'Bookmarks'
|
||||
| 'Favourites'
|
||||
|
@ -1,6 +1,4 @@
|
||||
import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
@ -9,16 +7,10 @@ export interface Props {
|
||||
content?: string
|
||||
inverted?: boolean
|
||||
onPress?: () => void
|
||||
dropdown?: boolean
|
||||
}
|
||||
|
||||
// Used for Android mostly
|
||||
const HeaderCenter: React.FC<Props> = ({
|
||||
content,
|
||||
inverted = false,
|
||||
onPress,
|
||||
dropdown = false
|
||||
}) => {
|
||||
const HeaderCenter: React.FC<Props> = ({ content, inverted = false, onPress }) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
return (
|
||||
@ -33,13 +25,6 @@ const HeaderCenter: React.FC<Props> = ({
|
||||
children={content}
|
||||
{...(onPress && { onPress })}
|
||||
/>
|
||||
<Icon
|
||||
name='ChevronDown'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={colors.primaryDefault}
|
||||
style={{ marginLeft: StyleConstants.Spacing.XS, opacity: dropdown ? undefined : 0 }}
|
||||
strokeWidth={3}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ export interface Props {
|
||||
fill?: string
|
||||
strokeWidth?: number
|
||||
style?: StyleProp<ViewStyle>
|
||||
crossOut?: boolean
|
||||
}
|
||||
|
||||
const Icon: React.FC<Props> = ({
|
||||
@ -20,7 +21,8 @@ const Icon: React.FC<Props> = ({
|
||||
color,
|
||||
fill,
|
||||
strokeWidth = 2,
|
||||
style
|
||||
style,
|
||||
crossOut = false
|
||||
}) => {
|
||||
return (
|
||||
<View
|
||||
@ -42,6 +44,17 @@ const Icon: React.FC<Props> = ({
|
||||
fill,
|
||||
strokeWidth
|
||||
})}
|
||||
{crossOut ? (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: [{ rotate: '45deg' }],
|
||||
width: size * 1.35,
|
||||
borderBottomColor: color,
|
||||
borderBottomWidth: strokeWidth
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ const ParseHTML = React.memo(
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
const route = useRoute()
|
||||
const { colors, theme } = useTheme()
|
||||
const { t, i18n } = useTranslation('componentParse')
|
||||
const { t } = useTranslation('componentParse')
|
||||
if (!expandHint) {
|
||||
expandHint = t('HTML.defaultHint')
|
||||
}
|
||||
@ -304,7 +304,7 @@ const ParseHTML = React.memo(
|
||||
</View>
|
||||
)
|
||||
},
|
||||
[theme, i18n.language]
|
||||
[theme]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -30,8 +30,8 @@ const RelationshipOutgoing = React.memo(
|
||||
haptics('Success')
|
||||
queryClient.setQueryData<Mastodon.Relationship[]>(queryKeyRelationship, [res])
|
||||
if (action === 'block') {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Following' }]
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
const queryKey = ['Timeline', { page: 'Following' }]
|
||||
queryClient.invalidateQueries({ queryKey, exact: false })
|
||||
}
|
||||
},
|
||||
onError: (err: any, { payload: { action } }) => {
|
||||
|
@ -45,6 +45,7 @@ export const shouldFilter = ({
|
||||
status: Mastodon.Status
|
||||
queryKey: QueryKeyTimeline
|
||||
}): string | null => {
|
||||
const page = queryKey[1]
|
||||
const instance = getInstance(store.getState())
|
||||
const ownAccount = getInstanceAccount(store.getState())?.id === status.account?.id
|
||||
|
||||
@ -93,11 +94,11 @@ export const shouldFilter = ({
|
||||
}
|
||||
}
|
||||
|
||||
switch (queryKey[1].page) {
|
||||
switch (page.page) {
|
||||
case 'Following':
|
||||
case 'Local':
|
||||
case 'List':
|
||||
case 'Account_Default':
|
||||
case 'Account':
|
||||
if (filter.context.includes('home')) {
|
||||
checkFilter(filter)
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import menuAccount from '@components/contextMenu/account'
|
||||
import menuInstance from '@components/contextMenu/instance'
|
||||
import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
@ -31,7 +30,6 @@ const TimelineHeaderAndroid: React.FC = () => {
|
||||
queryKey
|
||||
})
|
||||
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||
const mInstance = menuInstance({ status, queryKey, rootQueryKey })
|
||||
|
||||
return (
|
||||
<View style={{ position: 'absolute', top: 0, right: 0 }}>
|
||||
@ -77,16 +75,6 @@ const TimelineHeaderAndroid: React.FC = () => {
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mInstance.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
) : null}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import menuAccount from '@components/contextMenu/account'
|
||||
import menuInstance from '@components/contextMenu/instance'
|
||||
import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
@ -38,7 +37,6 @@ const TimelineHeaderDefault: React.FC = () => {
|
||||
queryKey
|
||||
})
|
||||
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||
const mInstance = menuInstance({ status, queryKey, rootQueryKey })
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
@ -116,17 +114,6 @@ const TimelineHeaderDefault: React.FC = () => {
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mInstance.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Pressable>
|
||||
|
@ -33,7 +33,7 @@ const TimelinePoll: React.FC = () => {
|
||||
const poll = status.poll
|
||||
|
||||
const { colors, theme } = useTheme()
|
||||
const { t, i18n } = useTranslation('componentTimeline')
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
|
||||
const [allOptions, setAllOptions] = useState(new Array(status.poll.options.length).fill(false))
|
||||
|
||||
@ -127,7 +127,7 @@ const TimelinePoll: React.FC = () => {
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [theme, i18n.language, poll.expired, poll.voted, allOptions, mutation.isLoading])
|
||||
}, [theme, poll.expired, poll.voted, allOptions, mutation.isLoading])
|
||||
|
||||
const isSelected = useCallback(
|
||||
(index: number): string =>
|
||||
|
@ -50,7 +50,7 @@ const menuAccount = ({
|
||||
setEnabled(true)
|
||||
}
|
||||
}, [openChange, enabled])
|
||||
const { data, isFetching } = useRelationshipQuery({ id: account.id, options: { enabled } })
|
||||
const { data, isFetched } = useRelationshipQuery({ id: account.id, options: { enabled } })
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const timelineMutation = useTimelineMutation({
|
||||
@ -99,8 +99,8 @@ const menuAccount = ({
|
||||
haptics('Success')
|
||||
queryClient.setQueryData<Mastodon.Relationship[]>(queryKeyRelationship, [res])
|
||||
if (action === 'block') {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Following' }]
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
const queryKey = ['Timeline', { page: 'Following' }]
|
||||
queryClient.invalidateQueries({ queryKey, exact: false })
|
||||
}
|
||||
},
|
||||
onError: (err: any, { payload: { action } }) => {
|
||||
@ -131,7 +131,7 @@ const menuAccount = ({
|
||||
type: 'outgoing',
|
||||
payload: { action: 'follow', state: !data?.requested ? data.following : true }
|
||||
}),
|
||||
disabled: !data || isFetching,
|
||||
disabled: !data || !isFetched,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
@ -152,9 +152,9 @@ const menuAccount = ({
|
||||
key: 'account-list',
|
||||
item: {
|
||||
onSelect: () => navigation.navigate('Tab-Shared-Account-In-Lists', { account }),
|
||||
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
|
||||
disabled: Platform.OS !== 'android' ? !data || !isFetched : false,
|
||||
destructive: false,
|
||||
hidden: isFetching ? false : !data?.following
|
||||
hidden: !isFetched || !data?.following
|
||||
},
|
||||
title: t('account.inLists'),
|
||||
icon: 'checklist'
|
||||
@ -169,7 +169,7 @@ const menuAccount = ({
|
||||
id: account.id,
|
||||
payload: { property: 'mute', currentValue: data?.muting }
|
||||
}),
|
||||
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
|
||||
disabled: Platform.OS !== 'android' ? !data || !isFetched : false,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
@ -192,7 +192,7 @@ const menuAccount = ({
|
||||
id: account.id,
|
||||
payload: { property: 'block', currentValue: data?.blocking }
|
||||
}),
|
||||
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
|
||||
disabled: Platform.OS !== 'android' ? !data || !isFetched : false,
|
||||
destructive: !data?.blocking,
|
||||
hidden: false
|
||||
},
|
||||
|
@ -1,7 +1,11 @@
|
||||
{
|
||||
"tabs": {
|
||||
"local": {
|
||||
"name": "Following"
|
||||
"name": "Following",
|
||||
"options": {
|
||||
"showBoosts": "Show boosts",
|
||||
"showReplies": "Show replies"
|
||||
}
|
||||
},
|
||||
"public": {
|
||||
"segments": {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { HeaderCenter, HeaderRight } from '@components/Header'
|
||||
import { 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 { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
@ -6,66 +8,160 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getInstanceFollowingPage, updateInstanceFollowingPage } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
|
||||
const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-Root'>> = ({
|
||||
navigation
|
||||
}) => {
|
||||
const { colors, mode } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const { data: lists } = useListsQuery()
|
||||
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>(['Timeline', { page: 'Following' }])
|
||||
const dispatch = useDispatch()
|
||||
const instanceFollowingPage = useSelector(getInstanceFollowingPage)
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>([
|
||||
'Timeline',
|
||||
{ page: 'Following', ...instanceFollowingPage }
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const page = queryKey[1]
|
||||
|
||||
navigation.setOptions({
|
||||
headerTitle: () => (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<HeaderCenter
|
||||
dropdown={(lists?.length ?? 0) > 0}
|
||||
content={
|
||||
queryKey[1].page === 'List' && queryKey[1].list?.length
|
||||
? lists?.find(list => list.id === queryKey[1].list)?.title
|
||||
: t('tabs.local.name')
|
||||
}
|
||||
/>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
{page.page === 'List' ? (
|
||||
<Icon
|
||||
name='List'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={colors.primaryDefault}
|
||||
style={{ marginRight: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
) : null}
|
||||
<CustomText
|
||||
style={{ color: colors.primaryDefault }}
|
||||
fontSize='L'
|
||||
fontWeight='Bold'
|
||||
numberOfLines={1}
|
||||
children={
|
||||
page.page === 'List' && page.list?.length
|
||||
? lists?.find(list => list.id === page.list)?.title
|
||||
: t('tabs.local.name')
|
||||
}
|
||||
/>
|
||||
{page.page === 'Following' && !instanceFollowingPage.showBoosts ? (
|
||||
<Icon
|
||||
name='MessageCircle'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={colors.red}
|
||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
||||
crossOut
|
||||
/>
|
||||
) : null}
|
||||
{page.page === 'Following' && !instanceFollowingPage.showReplies ? (
|
||||
<Icon
|
||||
name='Repeat'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={colors.red}
|
||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
||||
crossOut
|
||||
/>
|
||||
) : null}
|
||||
<Icon
|
||||
name='ChevronDown'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={colors.primaryDefault}
|
||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
</View>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{lists?.length
|
||||
? [
|
||||
{
|
||||
key: 'default',
|
||||
item: {
|
||||
onSelect: () => setQueryKey(['Timeline', { page: 'Following' }]),
|
||||
disabled: queryKey[1].page === 'Following',
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
title: t('tabs.local.name'),
|
||||
icon: ''
|
||||
},
|
||||
...lists?.map(list => ({
|
||||
key: list.id,
|
||||
item: {
|
||||
onSelect: () => setQueryKey(['Timeline', { page: 'List', list: list.id }]),
|
||||
disabled: queryKey[1].page === 'List' && queryKey[1].list === list.id,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
title: list.title,
|
||||
icon: ''
|
||||
}))
|
||||
].map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))
|
||||
: undefined}
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
key='default'
|
||||
onSelect={() =>
|
||||
setQueryKey(['Timeline', { page: 'Following', ...instanceFollowingPage }])
|
||||
}
|
||||
disabled={page.page === 'Following'}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={t('tabs.local.name')} />
|
||||
<DropdownMenu.ItemIcon iosIconName='house' />
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.CheckboxItem
|
||||
key='showBoosts'
|
||||
value={instanceFollowingPage.showBoosts ? 'on' : 'mixed'}
|
||||
onValueChange={() => {
|
||||
setQueryKey([
|
||||
'Timeline',
|
||||
{
|
||||
page: 'Following',
|
||||
showBoosts: !instanceFollowingPage.showBoosts,
|
||||
showReplies: instanceFollowingPage.showReplies
|
||||
}
|
||||
])
|
||||
dispatch(
|
||||
updateInstanceFollowingPage({ showBoosts: !instanceFollowingPage.showBoosts })
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
<DropdownMenu.ItemTitle children={t('tabs.local.options.showBoosts')} />
|
||||
</DropdownMenu.CheckboxItem>
|
||||
<DropdownMenu.CheckboxItem
|
||||
key='showReplies'
|
||||
value={instanceFollowingPage.showReplies ? 'on' : 'mixed'}
|
||||
onValueChange={() => {
|
||||
setQueryKey([
|
||||
'Timeline',
|
||||
{
|
||||
page: 'Following',
|
||||
showBoosts: instanceFollowingPage.showBoosts,
|
||||
showReplies: !instanceFollowingPage.showReplies
|
||||
}
|
||||
])
|
||||
dispatch(
|
||||
updateInstanceFollowingPage({ showReplies: !instanceFollowingPage.showReplies })
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={t('tabs.local.options.showReplies')} />
|
||||
<DropdownMenu.ItemIndicator />
|
||||
</DropdownMenu.CheckboxItem>
|
||||
</DropdownMenu.Group>
|
||||
|
||||
<DropdownMenu.Group>
|
||||
{lists?.length
|
||||
? [
|
||||
...lists?.map(list => ({
|
||||
key: list.id,
|
||||
item: {
|
||||
onSelect: () => setQueryKey(['Timeline', { page: 'List', list: list.id }]),
|
||||
disabled: page.page === 'List' && page.list === list.id,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
title: list.title,
|
||||
icon: 'list.bullet'
|
||||
}))
|
||||
].map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))
|
||||
: undefined}
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
),
|
||||
@ -78,7 +174,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [])
|
||||
}, [mode, queryKey[1], instanceFollowingPage])
|
||||
|
||||
usePopToTop()
|
||||
|
||||
|
@ -79,7 +79,7 @@ const TabMeProfileFields: React.FC<
|
||||
navigation
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const { t, i18n } = useTranslation('screenTabs')
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { mutateAsync, status } = useProfileMutation()
|
||||
|
||||
const allProps: EmojisState['inputProps'] = []
|
||||
@ -144,7 +144,7 @@ const TabMeProfileFields: React.FC<
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [theme, i18n.language, dirty, status, allProps.map(p => p.value)])
|
||||
}, [theme, dirty, status, allProps.map(p => p.value)])
|
||||
|
||||
return (
|
||||
<ComponentEmojis inputProps={allProps}>
|
||||
|
@ -23,7 +23,7 @@ const TabMeProfileName: React.FC<
|
||||
navigation
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const { t, i18n } = useTranslation('screenTabs')
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { mutateAsync, status } = useProfileMutation()
|
||||
|
||||
const [value, setValue] = useState(display_name)
|
||||
@ -90,7 +90,7 @@ const TabMeProfileName: React.FC<
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [theme, i18n.language, dirty, status, value])
|
||||
}, [theme, dirty, status, value])
|
||||
|
||||
return (
|
||||
<ComponentEmojis inputProps={[displayNameProps]}>
|
||||
|
@ -23,7 +23,7 @@ const TabMeProfileNote: React.FC<
|
||||
navigation
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const { t, i18n } = useTranslation('screenTabs')
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { mutateAsync, status } = useProfileMutation()
|
||||
|
||||
const [notes, setNotes] = useState(note)
|
||||
@ -90,7 +90,7 @@ const TabMeProfileNote: React.FC<
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [theme, i18n.language, dirty, status, notes])
|
||||
}, [theme, dirty, status, notes])
|
||||
|
||||
return (
|
||||
<ComponentEmojis inputProps={[notesProps]}>
|
||||
|
@ -26,7 +26,7 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||
params: { account }
|
||||
}
|
||||
}) => {
|
||||
const { t, i18n } = useTranslation('screenTabs')
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const mShare = menuShare({ type: 'account', url: account.url })
|
||||
@ -89,8 +89,9 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>([
|
||||
'Timeline',
|
||||
{ page: 'Account_Default', account: account.id }
|
||||
{ page: 'Account', account: account.id, exclude_reblogs: true, only_media: false }
|
||||
])
|
||||
const page = queryKey[1]
|
||||
const isFetchingTimeline = useIsFetching(queryKey)
|
||||
const fetchedTimeline = useRef(false)
|
||||
useEffect(() => {
|
||||
@ -113,14 +114,32 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||
<SegmentedControl
|
||||
appearance={mode}
|
||||
values={[t('shared.account.toots.default'), t('shared.account.toots.all')]}
|
||||
selectedIndex={queryKey[1].page === 'Account_Default' ? 0 : 1}
|
||||
selectedIndex={page.page === 'Account' ? 0 : 1}
|
||||
onChange={({ nativeEvent }) => {
|
||||
switch (nativeEvent.selectedSegmentIndex) {
|
||||
case 0:
|
||||
setQueryKey([queryKey[0], { ...queryKey[1], page: 'Account_Default' }])
|
||||
setQueryKey([
|
||||
queryKey[0],
|
||||
{
|
||||
...page,
|
||||
page: 'Account',
|
||||
account: account.id,
|
||||
exclude_reblogs: true,
|
||||
only_media: false
|
||||
}
|
||||
])
|
||||
break
|
||||
case 1:
|
||||
setQueryKey([queryKey[0], { ...queryKey[1], page: 'Account_All' }])
|
||||
setQueryKey([
|
||||
queryKey[0],
|
||||
{
|
||||
...page,
|
||||
page: 'Account',
|
||||
account: account.id,
|
||||
exclude_reblogs: false,
|
||||
only_media: false
|
||||
}
|
||||
])
|
||||
break
|
||||
}
|
||||
}}
|
||||
@ -152,7 +171,7 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}, [data, fetchedTimeline.current, queryKey[1].page, i18n.language, mode])
|
||||
}, [data, fetchedTimeline.current, queryKey[1].page, mode])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -3,11 +3,11 @@ import Icon from '@components/Icon'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import { Dimensions, ListRenderItem, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Dimensions, ListRenderItem, Pressable, View } from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||
|
||||
@ -15,117 +15,111 @@ export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
}
|
||||
|
||||
const AccountAttachments = React.memo(
|
||||
({ account }: Props) => {
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
const { colors } = useTheme()
|
||||
const AccountAttachments: React.FC<Props> = ({ account }) => {
|
||||
if (!account) return null
|
||||
|
||||
const DISPLAY_AMOUNT = 6
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
const { colors } = useTheme()
|
||||
|
||||
const width =
|
||||
(Dimensions.get('screen').width - StyleConstants.Spacing.Global.PagePadding * 2) / 4
|
||||
const DISPLAY_AMOUNT = 6
|
||||
|
||||
const queryKeyParams = {
|
||||
page: 'Account_Attachments' as 'Account_Attachments',
|
||||
account: account?.id
|
||||
}
|
||||
const { data, refetch } = useTimelineQuery({
|
||||
...queryKeyParams,
|
||||
options: { enabled: false }
|
||||
})
|
||||
useEffect(() => {
|
||||
if (account?.id) {
|
||||
refetch()
|
||||
}
|
||||
}, [account])
|
||||
const width = (Dimensions.get('screen').width - StyleConstants.Spacing.Global.PagePadding * 2) / 4
|
||||
|
||||
const flattenData = data?.pages
|
||||
? data.pages
|
||||
.flatMap(d => [...d.body])
|
||||
.filter(status => !(status as Mastodon.Status).sensitive)
|
||||
.splice(0, DISPLAY_AMOUNT)
|
||||
: []
|
||||
|
||||
const renderItem = useCallback<ListRenderItem<Mastodon.Status>>(
|
||||
({ item, index }) => {
|
||||
if (index === DISPLAY_AMOUNT - 1) {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
account && navigation.push('Tab-Shared-Attachments', { account })
|
||||
}}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
backgroundColor: colors.backgroundOverlayInvert,
|
||||
width: width,
|
||||
height: width,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
children={
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.primaryOverlay}
|
||||
size={StyleConstants.Font.Size.L * 1.5}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
original: item.media_attachments[0]?.preview_url || item.media_attachments[0]?.url,
|
||||
remote: item.media_attachments[0]?.remote_url
|
||||
}}
|
||||
blurhash={
|
||||
item.media_attachments[0] && (item.media_attachments[0].blurhash || undefined)
|
||||
}
|
||||
dimension={{ width: width, height: width }}
|
||||
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
||||
onPress={() => navigation.push('Tab-Shared-Toot', { toot: item })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
[account]
|
||||
)
|
||||
|
||||
const styleContainer = useAnimatedStyle(() => {
|
||||
if (flattenData.length) {
|
||||
return {
|
||||
height: withTiming(width + StyleConstants.Spacing.Global.PagePadding * 2),
|
||||
paddingVertical: StyleConstants.Spacing.Global.PagePadding,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.border
|
||||
}
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}, [flattenData.length])
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.base, styleContainer]}>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={flattenData}
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
</Animated.View>
|
||||
)
|
||||
},
|
||||
(_, next) => next.account === undefined
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
flex: 1
|
||||
const queryKeyParams: QueryKeyTimeline[1] = {
|
||||
page: 'Account',
|
||||
account: account.id,
|
||||
exclude_reblogs: true,
|
||||
only_media: true
|
||||
}
|
||||
})
|
||||
const { data, refetch } = useTimelineQuery({
|
||||
...queryKeyParams,
|
||||
options: { enabled: false }
|
||||
})
|
||||
useEffect(() => {
|
||||
if (account?.id) {
|
||||
refetch()
|
||||
}
|
||||
}, [account])
|
||||
|
||||
const flattenData = data?.pages
|
||||
? data.pages
|
||||
.flatMap(d => [...d.body])
|
||||
.filter(status => !(status as Mastodon.Status).sensitive)
|
||||
.splice(0, DISPLAY_AMOUNT)
|
||||
: []
|
||||
|
||||
const renderItem = useCallback<ListRenderItem<Mastodon.Status>>(
|
||||
({ item, index }) => {
|
||||
if (index === DISPLAY_AMOUNT - 1) {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
account && navigation.push('Tab-Shared-Attachments', { account })
|
||||
}}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
backgroundColor: colors.backgroundOverlayInvert,
|
||||
width: width,
|
||||
height: width,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
children={
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.primaryOverlay}
|
||||
size={StyleConstants.Font.Size.L * 1.5}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
original: item.media_attachments[0]?.preview_url || item.media_attachments[0]?.url,
|
||||
remote: item.media_attachments[0]?.remote_url
|
||||
}}
|
||||
blurhash={
|
||||
item.media_attachments[0] && (item.media_attachments[0].blurhash || undefined)
|
||||
}
|
||||
dimension={{ width: width, height: width }}
|
||||
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
||||
onPress={() => navigation.push('Tab-Shared-Toot', { toot: item })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
[account]
|
||||
)
|
||||
|
||||
const styleContainer = useAnimatedStyle(() => {
|
||||
if (flattenData.length) {
|
||||
return {
|
||||
height: withTiming(width + StyleConstants.Spacing.Global.PagePadding * 2),
|
||||
paddingVertical: StyleConstants.Spacing.Global.PagePadding,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.border
|
||||
}
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}, [flattenData.length])
|
||||
|
||||
return (
|
||||
<Animated.View style={[{ flex: 1 }, styleContainer]}>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={flattenData}
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountAttachments
|
||||
|
@ -44,7 +44,7 @@ const TabSharedAttachments: React.FC<TabSharedStackScreenProps<'Tab-Shared-Attac
|
||||
|
||||
const queryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Account_Attachments', account: account.id }
|
||||
{ page: 'Account', account: account.id, exclude_reblogs: true, only_media: true }
|
||||
]
|
||||
|
||||
return (
|
||||
|
@ -133,8 +133,11 @@ const instancesMigration = {
|
||||
11: (state: { instances: InstanceV10[] }): { instances: InstanceV11[] } => {
|
||||
return {
|
||||
instances: state.instances.map(instance => {
|
||||
delete instance.timelinesLookback
|
||||
|
||||
return {
|
||||
...instance,
|
||||
followingPage: { showBoosts: true, showReplies: true },
|
||||
mePage: { ...instance.mePage, followedTags: { shown: false } },
|
||||
notifications_filter: {
|
||||
...instance.notifications_filter,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ComposeStateDraft } from '@screens/Compose/utils/types'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
|
||||
export type InstanceV11 = {
|
||||
active: boolean
|
||||
@ -42,11 +41,9 @@ export type InstanceV11 = {
|
||||
private?: string // legacy
|
||||
}
|
||||
}
|
||||
timelinesLookback?: {
|
||||
[key: string]: {
|
||||
queryKey: QueryKeyTimeline
|
||||
ids: Mastodon.Status['id'][]
|
||||
}
|
||||
followingPage: {
|
||||
showBoosts: boolean
|
||||
showReplies: boolean
|
||||
}
|
||||
mePage: {
|
||||
followedTags: { shown: boolean }
|
||||
|
@ -18,25 +18,66 @@ import updateStatusProperty from './timeline/updateStatusProperty'
|
||||
|
||||
export type QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{
|
||||
page: App.Pages
|
||||
hashtag?: Mastodon.Tag['name']
|
||||
list?: Mastodon.List['id']
|
||||
toot?: Mastodon.Status['id']
|
||||
account?: Mastodon.Account['id']
|
||||
}
|
||||
(
|
||||
| {
|
||||
page: Exclude<App.Pages, 'Following' | 'Hashtag' | 'List' | 'Toot' | 'Account'>
|
||||
}
|
||||
| {
|
||||
page: 'Following'
|
||||
showBoosts: boolean
|
||||
showReplies: boolean
|
||||
}
|
||||
| {
|
||||
page: 'Hashtag'
|
||||
hashtag: Mastodon.Tag['name']
|
||||
}
|
||||
| {
|
||||
page: 'List'
|
||||
list: Mastodon.List['id']
|
||||
}
|
||||
| {
|
||||
page: 'Toot'
|
||||
toot: Mastodon.Status['id']
|
||||
}
|
||||
| {
|
||||
page: 'Account'
|
||||
account: Mastodon.Account['id']
|
||||
exclude_reblogs: boolean
|
||||
only_media: boolean
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyTimeline>) => {
|
||||
const { page, account, hashtag, list, toot } = queryKey[1]
|
||||
let params: { [key: string]: string } = { ...pageParam }
|
||||
const page = queryKey[1]
|
||||
let params: { [key: string]: string } = { ...pageParam, limit: 40 }
|
||||
|
||||
switch (page) {
|
||||
switch (page.page) {
|
||||
case 'Following':
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: 'timelines/home',
|
||||
params
|
||||
}).then(res => {
|
||||
if (!page.showBoosts || !page.showReplies) {
|
||||
return {
|
||||
...res,
|
||||
body: res.body
|
||||
.filter(status => {
|
||||
if (!page.showBoosts && status.reblog) {
|
||||
return null
|
||||
}
|
||||
if (!page.showReplies && status.in_reply_to_id?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return status
|
||||
})
|
||||
.filter(s => s)
|
||||
}
|
||||
} else {
|
||||
return res
|
||||
}
|
||||
})
|
||||
|
||||
case 'Local':
|
||||
@ -91,62 +132,57 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
|
||||
}
|
||||
})
|
||||
|
||||
case 'Account_Default':
|
||||
if (pageParam && pageParam.hasOwnProperty('max_id')) {
|
||||
case 'Account':
|
||||
if (page.exclude_reblogs) {
|
||||
if (pageParam && pageParam.hasOwnProperty('max_id')) {
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${page.account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true',
|
||||
...params
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const res1 = await apiInstance<(Mastodon.Status & { _pinned: boolean })[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${page.account}/statuses`,
|
||||
params: {
|
||||
pinned: 'true'
|
||||
}
|
||||
})
|
||||
res1.body = res1.body.map(status => {
|
||||
status._pinned = true
|
||||
return status
|
||||
})
|
||||
const res2 = await apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${page.account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true'
|
||||
}
|
||||
})
|
||||
return {
|
||||
body: uniqBy([...res1.body, ...res2.body], 'id'),
|
||||
...(res2.links.next && { links: { next: res2.links.next } })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${account}/statuses`,
|
||||
url: `accounts/${page.account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true',
|
||||
...params
|
||||
...params,
|
||||
exclude_replies: page.exclude_reblogs.toString(),
|
||||
only_media: page.only_media.toString()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const res1 = await apiInstance<(Mastodon.Status & { _pinned: boolean })[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
pinned: 'true'
|
||||
}
|
||||
})
|
||||
res1.body = res1.body.map(status => {
|
||||
status._pinned = true
|
||||
return status
|
||||
})
|
||||
const res2 = await apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true'
|
||||
}
|
||||
})
|
||||
return {
|
||||
body: uniqBy([...res1.body, ...res2.body], 'id'),
|
||||
...(res2.links.next && { links: { next: res2.links.next } })
|
||||
}
|
||||
}
|
||||
|
||||
case 'Account_All':
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'Account_Attachments':
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
only_media: 'true',
|
||||
...params
|
||||
}
|
||||
})
|
||||
|
||||
case 'Hashtag':
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `timelines/tag/${hashtag}`,
|
||||
url: `timelines/tag/${page.hashtag}`,
|
||||
params
|
||||
})
|
||||
|
||||
@ -174,21 +210,21 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
|
||||
case 'List':
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `timelines/list/${list}`,
|
||||
url: `timelines/list/${page.list}`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'Toot':
|
||||
const res1_1 = await apiInstance<Mastodon.Status>({
|
||||
method: 'get',
|
||||
url: `statuses/${toot}`
|
||||
url: `statuses/${page.toot}`
|
||||
})
|
||||
const res2_1 = await apiInstance<{
|
||||
ancestors: Mastodon.Status[]
|
||||
descendants: Mastodon.Status[]
|
||||
}>({
|
||||
method: 'get',
|
||||
url: `statuses/${toot}/context`
|
||||
url: `statuses/${page.toot}/context`
|
||||
})
|
||||
return {
|
||||
body: [...res2_1.body.ancestors, res1_1.body, ...res2_1.body.descendants]
|
||||
|
@ -107,7 +107,10 @@ const addInstance = createAsyncThunk(
|
||||
},
|
||||
keys: { auth: undefined, public: undefined, private: undefined }
|
||||
},
|
||||
timelinesLookback: {},
|
||||
followingPage: {
|
||||
showBoosts: true,
|
||||
showReplies: true
|
||||
},
|
||||
mePage: {
|
||||
followedTags: { shown: false },
|
||||
lists: { shown: false },
|
||||
|
@ -81,16 +81,15 @@ const instancesSlice = createSlice({
|
||||
return newInstance
|
||||
})
|
||||
},
|
||||
updateInstanceTimelineLookback: (
|
||||
updateInstanceFollowingPage: (
|
||||
{ instances },
|
||||
action: PayloadAction<InstanceLatest['timelinesLookback']>
|
||||
action: PayloadAction<Partial<InstanceLatest['followingPage']>>
|
||||
) => {
|
||||
const activeIndex = findInstanceActive(instances)
|
||||
instances[activeIndex] &&
|
||||
(instances[activeIndex].timelinesLookback = {
|
||||
...instances[activeIndex].timelinesLookback,
|
||||
...action.payload
|
||||
})
|
||||
instances[activeIndex].followingPage = {
|
||||
...instances[activeIndex].followingPage,
|
||||
...action.payload
|
||||
}
|
||||
},
|
||||
updateInstanceMePage: (
|
||||
{ instances },
|
||||
@ -360,8 +359,8 @@ export const getInstanceNotificationsFilter = ({ instances: { instances } }: Roo
|
||||
export const getInstancePush = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.push
|
||||
|
||||
export const getInstanceTimelinesLookback = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.timelinesLookback
|
||||
export const getInstanceFollowingPage = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.followingPage
|
||||
|
||||
export const getInstanceMePage = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.mePage
|
||||
@ -379,7 +378,7 @@ export const {
|
||||
updateInstanceDraft,
|
||||
removeInstanceDraft,
|
||||
disableAllPushes,
|
||||
updateInstanceTimelineLookback,
|
||||
updateInstanceFollowingPage,
|
||||
updateInstanceMePage,
|
||||
countInstanceEmoji
|
||||
} = instancesSlice.actions
|
||||
|
Loading…
x
Reference in New Issue
Block a user