mirror of
https://github.com/tooot-app/app
synced 2025-02-13 18:30:42 +01:00
Fixed #49
This commit is contained in:
parent
7e1916989d
commit
e6adbf6986
@ -1,3 +1,5 @@
|
|||||||
# [tooot](https://tooot.app/) app for Mastodon
|
# [tooot](https://tooot.app/) app for Mastodon
|
||||||
|
|
||||||
[![GPL-3.0](https://img.shields.io/github/license/tooot-app/push?style=flat-square)](LICENSE) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/tooot-app/app/build?style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/tooot-app/app?style=flat-square) ![GitHub package.json version](https://img.shields.io/github/package-json/v/tooot-app/app?style=flat-square) ![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/tooot-app/app?style=flat-square)
|
[![GPL-3.0](https://img.shields.io/github/license/tooot-app/push?style=flat-square)](LICENSE) ![GitHub issues](https://img.shields.io/github/issues/tooot-app/app?style=flat-square) ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/tooot-app/app?include_prereleases&style=flat-square) ![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/tooot-app/app?style=flat-square)
|
||||||
|
|
||||||
|
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/tooot-app/app/build?style=flat-square) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/tooot-app/app/build/candidate?label=build%20candidate&style=flat-square) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/tooot-app/app/build/release?label=build%20release&style=flat-square)
|
||||||
|
17
src/@types/react-navigation.d.ts
vendored
17
src/@types/react-navigation.d.ts
vendored
@ -84,15 +84,24 @@ declare namespace Nav {
|
|||||||
'Tab-Shared-Hashtag': {
|
'Tab-Shared-Hashtag': {
|
||||||
hashtag: Mastodon.Tag['name']
|
hashtag: Mastodon.Tag['name']
|
||||||
}
|
}
|
||||||
'Tab-Shared-Relationships': {
|
|
||||||
account: Mastodon.Account
|
|
||||||
initialType: 'following' | 'followers'
|
|
||||||
}
|
|
||||||
'Tab-Shared-Search': { text: string | undefined }
|
'Tab-Shared-Search': { text: string | undefined }
|
||||||
'Tab-Shared-Toot': {
|
'Tab-Shared-Toot': {
|
||||||
toot: Mastodon.Status
|
toot: Mastodon.Status
|
||||||
rootQueryKey: any
|
rootQueryKey: any
|
||||||
}
|
}
|
||||||
|
'Tab-Shared-Users':
|
||||||
|
| {
|
||||||
|
reference: 'accounts'
|
||||||
|
id: Mastodon.Account['id']
|
||||||
|
type: 'following' | 'followers'
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
reference: 'statuses'
|
||||||
|
id: Mastodon.Status['id']
|
||||||
|
type: 'reblogged_by' | 'favourited_by'
|
||||||
|
count: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TabLocalStackParamList = {
|
type TabLocalStackParamList = {
|
||||||
|
@ -17,6 +17,7 @@ import { uniqBy } from 'lodash'
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import TimelineActionsUsers from './Shared/ActionsUsers'
|
||||||
import TimelineFullConversation from './Shared/FullConversation'
|
import TimelineFullConversation from './Shared/FullConversation'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -128,6 +129,8 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
<TimelineFullConversation queryKey={queryKey} status={actualStatus} />
|
<TimelineFullConversation queryKey={queryKey} status={actualStatus} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<TimelineActionsUsers status={actualStatus} highlighted={highlighted} />
|
||||||
|
|
||||||
{queryKey && !disableDetails && (
|
{queryKey && !disableDetails && (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
@ -300,7 +300,8 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: StyleConstants.Font.Size.L + StyleConstants.Spacing.S * 4
|
minHeight: StyleConstants.Font.Size.L + StyleConstants.Spacing.S * 4,
|
||||||
|
marginHorizontal: StyleConstants.Spacing.S
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
90
src/components/Timeline/Shared/ActionsUsers.tsx
Normal file
90
src/components/Timeline/Shared/ActionsUsers.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import analytics from '@components/analytics'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
status: Mastodon.Status
|
||||||
|
highlighted: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimelineActionsUsers = React.memo(
|
||||||
|
({ status, highlighted }: Props) => {
|
||||||
|
if (!highlighted) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.TabLocalStackParamList>
|
||||||
|
>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.base}>
|
||||||
|
{status.reblogs_count > 0 ? (
|
||||||
|
<Text
|
||||||
|
style={[styles.text, { color: theme.secondary }]}
|
||||||
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_actionsusers_press_boosted', {
|
||||||
|
count: status.reblogs_count
|
||||||
|
})
|
||||||
|
navigation.push('Tab-Shared-Users', {
|
||||||
|
reference: 'statuses',
|
||||||
|
id: status.id,
|
||||||
|
type: 'reblogged_by',
|
||||||
|
count: status.reblogs_count
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('shared.actionsUsers.reblogged_by', {
|
||||||
|
count: status.reblogs_count
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
{status.favourites_count > 0 ? (
|
||||||
|
<Text
|
||||||
|
style={[styles.text, { color: theme.secondary }]}
|
||||||
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_actionsusers_press_boosted', {
|
||||||
|
count: status.favourites_count
|
||||||
|
})
|
||||||
|
navigation.push('Tab-Shared-Users', {
|
||||||
|
reference: 'statuses',
|
||||||
|
id: status.id,
|
||||||
|
type: 'favourited_by',
|
||||||
|
count: status.favourites_count
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('shared.actionsUsers.favourited_by', {
|
||||||
|
count: status.favourites_count
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
(prev, next) =>
|
||||||
|
prev.status.reblogs_count === next.status.reblogs_count &&
|
||||||
|
prev.status.favourites_count === next.status.favourites_count
|
||||||
|
)
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
pressable: { margin: StyleConstants.Spacing.M },
|
||||||
|
text: {
|
||||||
|
...StyleConstants.FontStyle.S,
|
||||||
|
padding: StyleConstants.Spacing.S * 1.5,
|
||||||
|
paddingLeft: 0,
|
||||||
|
marginRight: StyleConstants.Spacing.S
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TimelineActionsUsers
|
@ -22,9 +22,9 @@ export default {
|
|||||||
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
||||||
sharedAttachments: require('./screens/sharedAttachments').default,
|
sharedAttachments: require('./screens/sharedAttachments').default,
|
||||||
sharedCompose: require('./screens/sharedCompose').default,
|
sharedCompose: require('./screens/sharedCompose').default,
|
||||||
sharedRelationships: require('./screens/sharedRelationships').default,
|
|
||||||
sharedSearch: require('./screens/sharedSearch').default,
|
sharedSearch: require('./screens/sharedSearch').default,
|
||||||
sharedToot: require('./screens/sharedToot').default,
|
sharedToot: require('./screens/sharedToot').default,
|
||||||
|
sharedUsers: require('./screens/sharedUsers').default,
|
||||||
|
|
||||||
componentInstance: require('./components/instance').default,
|
componentInstance: require('./components/instance').default,
|
||||||
componentParse: require('./components/parse').default,
|
componentParse: require('./components/parse').default,
|
||||||
|
@ -39,6 +39,10 @@ export default {
|
|||||||
function: 'Bookmark toot'
|
function: 'Bookmark toot'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actionsUsers: {
|
||||||
|
reblogged_by: '$t(sharedUsers:heading.statuses.reblogged_by)',
|
||||||
|
favourited_by: '$t(sharedUsers:heading.statuses.favourited_by)'
|
||||||
|
},
|
||||||
attachment: {
|
attachment: {
|
||||||
sensitive: {
|
sensitive: {
|
||||||
button: 'Show sensitive media'
|
button: 'Show sensitive media'
|
||||||
|
@ -4,8 +4,8 @@ export default {
|
|||||||
created_at: 'Registered: {{date}}',
|
created_at: 'Registered: {{date}}',
|
||||||
summary: {
|
summary: {
|
||||||
statuses_count: '{{count}} toots',
|
statuses_count: '{{count}} toots',
|
||||||
following_count: 'Following {{count}}',
|
following_count: '$t(sharedUsers:heading.accounts.following)',
|
||||||
followers_count: '{{count}} followers'
|
followers_count: '$t(sharedUsers:heading.accounts.followers)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
export default {
|
|
||||||
heading: {
|
|
||||||
segments: {
|
|
||||||
left: 'Following',
|
|
||||||
right: 'Followers'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
12
src/i18n/en/screens/sharedUsers.ts
Normal file
12
src/i18n/en/screens/sharedUsers.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export default {
|
||||||
|
heading: {
|
||||||
|
accounts: {
|
||||||
|
following: 'Following {{count}}',
|
||||||
|
followers: '{{count}} followers'
|
||||||
|
},
|
||||||
|
statuses: {
|
||||||
|
reblogged_by: '{{count}} boosted',
|
||||||
|
favourited_by: '{{count}} favourited'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,9 +22,9 @@ export default {
|
|||||||
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
||||||
sharedAttachments: require('./screens/sharedAttachments').default,
|
sharedAttachments: require('./screens/sharedAttachments').default,
|
||||||
sharedCompose: require('./screens/sharedCompose').default,
|
sharedCompose: require('./screens/sharedCompose').default,
|
||||||
sharedRelationships: require('./screens/sharedRelationships').default,
|
|
||||||
sharedSearch: require('./screens/sharedSearch').default,
|
sharedSearch: require('./screens/sharedSearch').default,
|
||||||
sharedToot: require('./screens/sharedToot').default,
|
sharedToot: require('./screens/sharedToot').default,
|
||||||
|
sharedUsers: require('./screens/sharedUsers').default,
|
||||||
|
|
||||||
componentInstance: require('./components/instance').default,
|
componentInstance: require('./components/instance').default,
|
||||||
componentParse: require('./components/parse').default,
|
componentParse: require('./components/parse').default,
|
||||||
|
@ -39,6 +39,10 @@ export default {
|
|||||||
function: '收藏嘟文'
|
function: '收藏嘟文'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actionsUsers: {
|
||||||
|
reblogged_by: '$t(sharedUsers:heading.statuses.reblogged_by)',
|
||||||
|
favourited_by: '$t(sharedUsers:heading.statuses.favourited_by)'
|
||||||
|
},
|
||||||
attachment: {
|
attachment: {
|
||||||
sensitive: {
|
sensitive: {
|
||||||
button: '显示敏感内容'
|
button: '显示敏感内容'
|
||||||
|
@ -4,8 +4,8 @@ export default {
|
|||||||
created_at: '注册时间:{{date}}',
|
created_at: '注册时间:{{date}}',
|
||||||
summary: {
|
summary: {
|
||||||
statuses_count: '{{count}} 条嘟文',
|
statuses_count: '{{count}} 条嘟文',
|
||||||
following_count: '关注 {{count}} 人',
|
following_count: '$t(sharedUsers:heading.accounts.following)',
|
||||||
followers_count: '被 {{count}} 人关注'
|
followers_count: '$t(sharedUsers:heading.accounts.followers)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
export default {
|
|
||||||
heading: {
|
|
||||||
segments: {
|
|
||||||
left: '关注中',
|
|
||||||
right: '关注者'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
12
src/i18n/zh-Hans/screens/sharedUsers.ts
Normal file
12
src/i18n/zh-Hans/screens/sharedUsers.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export default {
|
||||||
|
heading: {
|
||||||
|
accounts: {
|
||||||
|
following: '关注 {{count}} 人',
|
||||||
|
followers: '被 {{count}} 人关注'
|
||||||
|
},
|
||||||
|
statuses: {
|
||||||
|
reblogged_by: '{{count}} 人转嘟',
|
||||||
|
favourited_by: '{{count}} 人收藏'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,15 +48,17 @@ const AccountInformationStats: React.FC<Props> = ({ account, myInfo }) => {
|
|||||||
<Text
|
<Text
|
||||||
style={[styles.stat, { color: theme.primary, textAlign: 'right' }]}
|
style={[styles.stat, { color: theme.primary, textAlign: 'right' }]}
|
||||||
children={t('content.summary.following_count', {
|
children={t('content.summary.following_count', {
|
||||||
count: account.following_count || 0
|
count: account.following_count
|
||||||
})}
|
})}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('account_stats_following_press', {
|
analytics('account_stats_following_press', {
|
||||||
count: account.following_count
|
count: account.following_count
|
||||||
})
|
})
|
||||||
navigation.push('Tab-Shared-Relationships', {
|
navigation.push('Tab-Shared-Users', {
|
||||||
account,
|
reference: 'accounts',
|
||||||
initialType: 'following'
|
id: account.id,
|
||||||
|
type: 'following',
|
||||||
|
count: account.following_count
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -73,15 +75,17 @@ const AccountInformationStats: React.FC<Props> = ({ account, myInfo }) => {
|
|||||||
<Text
|
<Text
|
||||||
style={[styles.stat, { color: theme.primary, textAlign: 'center' }]}
|
style={[styles.stat, { color: theme.primary, textAlign: 'center' }]}
|
||||||
children={t('content.summary.followers_count', {
|
children={t('content.summary.followers_count', {
|
||||||
count: account.followers_count || 0
|
count: account.followers_count
|
||||||
})}
|
})}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('account_stats_followers_press', {
|
analytics('account_stats_followers_press', {
|
||||||
count: account.followers_count
|
count: account.followers_count
|
||||||
})
|
})
|
||||||
navigation.push('Tab-Shared-Relationships', {
|
navigation.push('Tab-Shared-Users', {
|
||||||
account,
|
reference: 'accounts',
|
||||||
initialType: 'followers'
|
id: account.id,
|
||||||
|
type: 'followers',
|
||||||
|
count: account.followers_count
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
import SegmentedControl from '@react-native-community/segmented-control'
|
|
||||||
import { useNavigation } from '@react-navigation/native'
|
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
|
||||||
import { TabView } from 'react-native-tab-view'
|
|
||||||
import RelationshipsList from './Relationships/List'
|
|
||||||
import { SharedRelationshipsProp } from './sharedScreens'
|
|
||||||
|
|
||||||
const TabSharedRelationships = React.memo(
|
|
||||||
({
|
|
||||||
route: {
|
|
||||||
params: { account, initialType }
|
|
||||||
}
|
|
||||||
}: SharedRelationshipsProp) => {
|
|
||||||
const { t } = useTranslation('sharedRelationships')
|
|
||||||
const { mode } = useTheme()
|
|
||||||
const navigation = useNavigation()
|
|
||||||
|
|
||||||
const [segment, setSegment] = useState(initialType === 'following' ? 0 : 1)
|
|
||||||
useEffect(() => {
|
|
||||||
const updateHeaderRight = () =>
|
|
||||||
navigation.setOptions({
|
|
||||||
headerCenter: () => (
|
|
||||||
<View style={styles.segmentsContainer}>
|
|
||||||
<SegmentedControl
|
|
||||||
appearance={mode}
|
|
||||||
values={[
|
|
||||||
t('heading.segments.left'),
|
|
||||||
t('heading.segments.right')
|
|
||||||
]}
|
|
||||||
selectedIndex={segment}
|
|
||||||
onChange={({ nativeEvent }) =>
|
|
||||||
setSegment(nativeEvent.selectedSegmentIndex)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return updateHeaderRight()
|
|
||||||
}, [segment, mode])
|
|
||||||
|
|
||||||
const routes: {
|
|
||||||
key: SharedRelationshipsProp['route']['params']['initialType']
|
|
||||||
}[] = [{ key: 'following' }, { key: 'followers' }]
|
|
||||||
|
|
||||||
const renderScene = ({
|
|
||||||
route
|
|
||||||
}: {
|
|
||||||
route: {
|
|
||||||
key: SharedRelationshipsProp['route']['params']['initialType']
|
|
||||||
}
|
|
||||||
}) => {
|
|
||||||
return <RelationshipsList id={account.id} type={route.key} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TabView
|
|
||||||
lazy
|
|
||||||
swipeEnabled
|
|
||||||
renderScene={renderScene}
|
|
||||||
renderTabBar={() => null}
|
|
||||||
onIndexChange={index => setSegment(index)}
|
|
||||||
navigationState={{ index: segment, routes }}
|
|
||||||
initialLayout={{ width: Dimensions.get('screen').width }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
segmentsContainer: {
|
|
||||||
flexBasis: '60%'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TabSharedRelationships
|
|
@ -1,84 +0,0 @@
|
|||||||
import ComponentAccount from '@components/Account'
|
|
||||||
import ComponentSeparator from '@components/Separator'
|
|
||||||
import { useScrollToTop } from '@react-navigation/native'
|
|
||||||
import {
|
|
||||||
QueryKeyRelationships,
|
|
||||||
useRelationshipsQuery
|
|
||||||
} from '@utils/queryHooks/relationships'
|
|
||||||
import React, { useCallback, useMemo, useRef } from 'react'
|
|
||||||
import { RefreshControl, StyleSheet } from 'react-native'
|
|
||||||
import { FlatList } from 'react-native-gesture-handler'
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
id: Mastodon.Account['id']
|
|
||||||
type: 'following' | 'followers'
|
|
||||||
}
|
|
||||||
|
|
||||||
const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
|
||||||
const queryKey: QueryKeyRelationships = ['Relationships', { type, id }]
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
isFetching,
|
|
||||||
refetch,
|
|
||||||
fetchNextPage,
|
|
||||||
isFetchingNextPage
|
|
||||||
} = useRelationshipsQuery({
|
|
||||||
...queryKey[1],
|
|
||||||
options: {
|
|
||||||
getPreviousPageParam: firstPage =>
|
|
||||||
firstPage.links?.prev && { since_id: firstPage.links.next },
|
|
||||||
getNextPageParam: lastPage =>
|
|
||||||
lastPage.links?.next && { max_id: lastPage.links.next }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const flattenData = data?.pages ? data.pages.flatMap(d => [...d.body]) : []
|
|
||||||
|
|
||||||
const flRef = useRef<FlatList<Mastodon.Account>>(null)
|
|
||||||
|
|
||||||
const keyExtractor = useCallback(({ id }) => id, [])
|
|
||||||
const renderItem = useCallback(
|
|
||||||
({ item }) => <ComponentAccount account={item} origin='relationship' />,
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const onEndReached = useCallback(
|
|
||||||
() => !isFetchingNextPage && fetchNextPage(),
|
|
||||||
[isFetchingNextPage]
|
|
||||||
)
|
|
||||||
const refreshControl = useMemo(
|
|
||||||
() => (
|
|
||||||
<RefreshControl refreshing={isFetching} onRefresh={() => refetch()} />
|
|
||||||
),
|
|
||||||
[isFetching]
|
|
||||||
)
|
|
||||||
|
|
||||||
useScrollToTop(flRef)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FlatList
|
|
||||||
ref={flRef}
|
|
||||||
windowSize={11}
|
|
||||||
data={flattenData}
|
|
||||||
initialNumToRender={5}
|
|
||||||
maxToRenderPerBatch={5}
|
|
||||||
style={styles.flatList}
|
|
||||||
renderItem={renderItem}
|
|
||||||
onEndReached={onEndReached}
|
|
||||||
keyExtractor={keyExtractor}
|
|
||||||
onEndReachedThreshold={0.75}
|
|
||||||
refreshControl={refreshControl}
|
|
||||||
ItemSeparatorComponent={ComponentSeparator}
|
|
||||||
maintainVisibleContentPosition={{
|
|
||||||
minIndexForVisible: 0,
|
|
||||||
autoscrollToTopThreshold: 2
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
flatList: {
|
|
||||||
minHeight: '100%'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default RelationshipsList
|
|
66
src/screens/Tabs/Shared/Users.tsx
Normal file
66
src/screens/Tabs/Shared/Users.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import ComponentAccount from '@components/Account'
|
||||||
|
import ComponentSeparator from '@components/Separator'
|
||||||
|
import { QueryKeyUsers, useUsersQuery } from '@utils/queryHooks/users'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
import { FlatList } from 'react-native-gesture-handler'
|
||||||
|
import { SharedUsersProp } from './sharedScreens'
|
||||||
|
|
||||||
|
const TabSharedUsers = React.memo(
|
||||||
|
({ route: { params } }: SharedUsersProp) => {
|
||||||
|
const queryKey: QueryKeyUsers = ['Users', params]
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
hasNextPage,
|
||||||
|
fetchNextPage,
|
||||||
|
isFetchingNextPage
|
||||||
|
} = useUsersQuery({
|
||||||
|
...queryKey[1],
|
||||||
|
options: {
|
||||||
|
getPreviousPageParam: firstPage =>
|
||||||
|
firstPage.links?.prev && { since_id: firstPage.links.next },
|
||||||
|
getNextPageParam: lastPage =>
|
||||||
|
lastPage.links?.next && { max_id: lastPage.links.next }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const flattenData = data?.pages
|
||||||
|
? data.pages.flatMap(page => [...page.body])
|
||||||
|
: []
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item }) => <ComponentAccount account={item} origin='relationship' />,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const onEndReached = useCallback(
|
||||||
|
() => hasNextPage && !isFetchingNextPage && fetchNextPage(),
|
||||||
|
[hasNextPage, isFetchingNextPage]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
windowSize={11}
|
||||||
|
data={flattenData}
|
||||||
|
initialNumToRender={5}
|
||||||
|
maxToRenderPerBatch={5}
|
||||||
|
style={styles.flatList}
|
||||||
|
renderItem={renderItem}
|
||||||
|
onEndReached={onEndReached}
|
||||||
|
onEndReachedThreshold={0.75}
|
||||||
|
ItemSeparatorComponent={ComponentSeparator}
|
||||||
|
maintainVisibleContentPosition={{
|
||||||
|
minIndexForVisible: 0,
|
||||||
|
autoscrollToTopThreshold: 2
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
)
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
flatList: {
|
||||||
|
minHeight: '100%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TabSharedUsers
|
@ -5,9 +5,9 @@ import { StackScreenProps } from '@react-navigation/stack'
|
|||||||
import TabSharedAccount from '@screens/Tabs/Shared/Account'
|
import TabSharedAccount from '@screens/Tabs/Shared/Account'
|
||||||
import TabSharedAttachments from '@screens/Tabs/Shared/Attachments'
|
import TabSharedAttachments from '@screens/Tabs/Shared/Attachments'
|
||||||
import TabSharedHashtag from '@screens/Tabs/Shared/Hashtag'
|
import TabSharedHashtag from '@screens/Tabs/Shared/Hashtag'
|
||||||
import TabSharedRelationships from '@screens/Tabs/Shared/Relationships'
|
|
||||||
import TabSharedSearch from '@screens/Tabs/Shared/Search'
|
import TabSharedSearch from '@screens/Tabs/Shared/Search'
|
||||||
import TabSharedToot from '@screens/Tabs/Shared/Toot'
|
import TabSharedToot from '@screens/Tabs/Shared/Toot'
|
||||||
|
import TabSharedUsers from '@screens/Tabs/Shared/Users'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
@ -41,11 +41,6 @@ export type SharedHashtagProp = StackScreenProps<
|
|||||||
'Tab-Shared-Hashtag'
|
'Tab-Shared-Hashtag'
|
||||||
>
|
>
|
||||||
|
|
||||||
export type SharedRelationshipsProp = StackScreenProps<
|
|
||||||
BaseScreens,
|
|
||||||
'Tab-Shared-Relationships'
|
|
||||||
>
|
|
||||||
|
|
||||||
export type SharedSearchProp = StackScreenProps<
|
export type SharedSearchProp = StackScreenProps<
|
||||||
BaseScreens,
|
BaseScreens,
|
||||||
'Tab-Shared-Search'
|
'Tab-Shared-Search'
|
||||||
@ -53,6 +48,8 @@ export type SharedSearchProp = StackScreenProps<
|
|||||||
|
|
||||||
export type SharedTootProp = StackScreenProps<BaseScreens, 'Tab-Shared-Toot'>
|
export type SharedTootProp = StackScreenProps<BaseScreens, 'Tab-Shared-Toot'>
|
||||||
|
|
||||||
|
export type SharedUsersProp = StackScreenProps<BaseScreens, 'Tab-Shared-Users'>
|
||||||
|
|
||||||
const sharedScreens = (
|
const sharedScreens = (
|
||||||
Stack: TypedNavigator<
|
Stack: TypedNavigator<
|
||||||
BaseScreens,
|
BaseScreens,
|
||||||
@ -133,14 +130,6 @@ const sharedScreens = (
|
|||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
||||||
})}
|
})}
|
||||||
/>,
|
/>,
|
||||||
<Stack.Screen
|
|
||||||
key='Tab-Shared-Relationships'
|
|
||||||
name='Tab-Shared-Relationships'
|
|
||||||
component={TabSharedRelationships}
|
|
||||||
options={({ navigation }: SharedRelationshipsProp) => ({
|
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
|
||||||
})}
|
|
||||||
/>,
|
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
key='Tab-Shared-Search'
|
key='Tab-Shared-Search'
|
||||||
name='Tab-Shared-Search'
|
name='Tab-Shared-Search'
|
||||||
@ -210,6 +199,20 @@ const sharedScreens = (
|
|||||||
}),
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
||||||
})}
|
})}
|
||||||
|
/>,
|
||||||
|
<Stack.Screen
|
||||||
|
key='Tab-Shared-Users'
|
||||||
|
name='Tab-Shared-Users'
|
||||||
|
component={TabSharedUsers}
|
||||||
|
options={({
|
||||||
|
navigation,
|
||||||
|
route: {
|
||||||
|
params: { reference, type, count }
|
||||||
|
}
|
||||||
|
}: SharedUsersProp) => ({
|
||||||
|
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />,
|
||||||
|
headerTitle: t(`sharedUsers:heading.${reference}.${type}`, { count })
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2,46 +2,46 @@ import apiInstance from '@api/instance'
|
|||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
import { useInfiniteQuery, UseInfiniteQueryOptions } from 'react-query'
|
import { useInfiniteQuery, UseInfiniteQueryOptions } from 'react-query'
|
||||||
|
|
||||||
export type QueryKeyRelationships = [
|
export type QueryKeyUsers = [
|
||||||
'Relationships',
|
'Users',
|
||||||
{ type: 'following' | 'followers'; id: Mastodon.Account['id'] }
|
Nav.TabSharedStackParamList['Tab-Shared-Users']
|
||||||
]
|
]
|
||||||
|
|
||||||
const queryFunction = ({
|
const queryFunction = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
pageParam
|
pageParam
|
||||||
}: {
|
}: {
|
||||||
queryKey: QueryKeyRelationships
|
queryKey: QueryKeyUsers
|
||||||
pageParam?: { [key: string]: string }
|
pageParam?: { [key: string]: string }
|
||||||
}) => {
|
}) => {
|
||||||
const { type, id } = queryKey[1]
|
const { reference, id, type } = queryKey[1]
|
||||||
let params: { [key: string]: string } = { ...pageParam }
|
let params: { [key: string]: string } = { ...pageParam }
|
||||||
|
|
||||||
return apiInstance<Mastodon.Account[]>({
|
return apiInstance<Mastodon.Account[]>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `accounts/${id}/${type}`,
|
url: `${reference}/${id}/${type}`,
|
||||||
params
|
params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const useRelationshipsQuery = <TData = Mastodon.Account[]>({
|
const useUsersQuery = ({
|
||||||
options,
|
options,
|
||||||
...queryKeyParams
|
...queryKeyParams
|
||||||
}: QueryKeyRelationships[1] & {
|
}: QueryKeyUsers[1] & {
|
||||||
options?: UseInfiniteQueryOptions<
|
options?: UseInfiniteQueryOptions<
|
||||||
{
|
{
|
||||||
body: Mastodon.Account[]
|
body: Mastodon.Account[]
|
||||||
links?: { prev?: string; next?: string }
|
links?: { prev?: string; next?: string }
|
||||||
},
|
},
|
||||||
AxiosError,
|
AxiosError,
|
||||||
TData
|
{
|
||||||
|
body: Mastodon.Account[]
|
||||||
|
links?: { prev?: string; next?: string }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
}) => {
|
}) => {
|
||||||
const queryKey: QueryKeyRelationships = [
|
const queryKey: QueryKeyUsers = ['Users', { ...queryKeyParams }]
|
||||||
'Relationships',
|
|
||||||
{ ...queryKeyParams }
|
|
||||||
]
|
|
||||||
return useInfiniteQuery(queryKey, queryFunction, options)
|
return useInfiniteQuery(queryKey, queryFunction, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useRelationshipsQuery }
|
export { useUsersQuery }
|
Loading…
x
Reference in New Issue
Block a user