mirror of
https://github.com/tooot-app/app
synced 2025-02-02 11:36:56 +01:00
parent
1ece7b3fe3
commit
44379504eb
@ -61,7 +61,6 @@
|
||||
"expo-video-thumbnails": "^7.0.0",
|
||||
"expo-web-browser": "~12.0.0",
|
||||
"i18next": "^22.0.6",
|
||||
"li": "^1.3.0",
|
||||
"linkify-it": "^4.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
|
1
src/@types/app.d.ts
vendored
1
src/@types/app.d.ts
vendored
@ -3,6 +3,7 @@ declare namespace App {
|
||||
| 'Following'
|
||||
| 'Local'
|
||||
| 'LocalPublic'
|
||||
| 'Trending'
|
||||
| 'Notifications'
|
||||
| 'Hashtag'
|
||||
| 'List'
|
||||
|
1
src/@types/untyped.d.ts
vendored
1
src/@types/untyped.d.ts
vendored
@ -1,6 +1,5 @@
|
||||
declare module 'gl-react-blurhash'
|
||||
declare module 'htmlparser2-without-node-native'
|
||||
declare module 'li'
|
||||
declare module 'react-native-feather'
|
||||
declare module 'react-native-htmlview'
|
||||
declare module 'react-native-toast-message'
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { RootState } from '@root/store'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import li from 'li'
|
||||
import { ctx, handleError, userAgent } from './helpers'
|
||||
|
||||
export type Params = {
|
||||
@ -15,9 +14,10 @@ export type Params = {
|
||||
extras?: Omit<AxiosRequestConfig, 'method' | 'url' | 'params' | 'headers' | 'data'>
|
||||
}
|
||||
|
||||
type LinkFormat = { id: string; isOffset: boolean }
|
||||
export type InstanceResponse<T = unknown> = {
|
||||
body: T
|
||||
links: { prev?: string; next?: string }
|
||||
links: { prev?: LinkFormat; next?: LinkFormat }
|
||||
}
|
||||
|
||||
const apiInstance = async <T = unknown>({
|
||||
@ -74,17 +74,27 @@ const apiInstance = async <T = unknown>({
|
||||
...extras
|
||||
})
|
||||
.then(response => {
|
||||
let prev
|
||||
let next
|
||||
let links: {
|
||||
prev?: { id: string; isOffset: boolean }
|
||||
next?: { id: string; isOffset: boolean }
|
||||
} = {}
|
||||
|
||||
if (response.headers?.link) {
|
||||
const headersLinks = li.parse(response.headers?.link)
|
||||
prev = headersLinks.prev?.match(/_id=([0-9]*)/)?.[1]
|
||||
next = headersLinks.next?.match(/_id=([0-9]*)/)?.[1]
|
||||
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: { prev, next }
|
||||
})
|
||||
return Promise.resolve({ body: response.data, links })
|
||||
})
|
||||
.catch(handleError())
|
||||
}
|
||||
|
@ -6,17 +6,11 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback, useRef } from 'react'
|
||||
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
|
||||
import Animated, {
|
||||
useAnimatedScrollHandler,
|
||||
useSharedValue
|
||||
} from 'react-native-reanimated'
|
||||
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineEmpty from './Timeline/Empty'
|
||||
import TimelineFooter from './Timeline/Footer'
|
||||
import TimelineRefresh, {
|
||||
SEPARATION_Y_1,
|
||||
SEPARATION_Y_2
|
||||
} from './Timeline/Refresh'
|
||||
import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Timeline/Refresh'
|
||||
|
||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
||||
|
||||
@ -26,8 +20,7 @@ export interface Props {
|
||||
disableRefresh?: boolean
|
||||
disableInfinity?: boolean
|
||||
lookback?: Extract<App.Pages, 'Following' | 'Local' | 'LocalPublic'>
|
||||
customProps: Partial<FlatListProps<any>> &
|
||||
Pick<FlatListProps<any>, 'renderItem'>
|
||||
customProps: Partial<FlatListProps<any>> & Pick<FlatListProps<any>, 'renderItem'>
|
||||
}
|
||||
|
||||
const Timeline: React.FC<Props> = ({
|
||||
@ -39,30 +32,24 @@ const Timeline: React.FC<Props> = ({
|
||||
}) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
const {
|
||||
data,
|
||||
refetch,
|
||||
isFetching,
|
||||
isLoading,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage
|
||||
} = useTimelineQuery({
|
||||
...queryKey[1],
|
||||
options: {
|
||||
notifyOnChangeProps: Platform.select({
|
||||
ios: ['dataUpdatedAt', 'isFetching'],
|
||||
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
||||
}),
|
||||
getNextPageParam: lastPage =>
|
||||
lastPage?.links?.next && {
|
||||
max_id: lastPage.links.next
|
||||
}
|
||||
}
|
||||
})
|
||||
const { data, refetch, isFetching, isLoading, fetchNextPage, isFetchingNextPage } =
|
||||
useTimelineQuery({
|
||||
...queryKey[1],
|
||||
options: {
|
||||
notifyOnChangeProps: Platform.select({
|
||||
ios: ['dataUpdatedAt', 'isFetching'],
|
||||
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
||||
}),
|
||||
getNextPageParam: lastPage =>
|
||||
lastPage?.links?.next && {
|
||||
...(lastPage.links.next.isOffset
|
||||
? { offset: lastPage.links.next.id }
|
||||
: { max_id: lastPage.links.next.id })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const flattenData = data?.pages
|
||||
? data.pages?.flatMap(page => [...page.body])
|
||||
: []
|
||||
const flattenData = data?.pages ? data.pages?.flatMap(page => [...page.body]) : []
|
||||
|
||||
const onEndReached = useCallback(
|
||||
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
||||
@ -134,10 +121,7 @@ const Timeline: React.FC<Props> = ({
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={0.75}
|
||||
ListFooterComponent={
|
||||
<TimelineFooter
|
||||
queryKey={queryKey}
|
||||
disableInfinity={disableInfinity}
|
||||
/>
|
||||
<TimelineFooter queryKey={queryKey} disableInfinity={disableInfinity} />
|
||||
}
|
||||
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
||||
ItemSeparatorComponent={({ leadingItem }) =>
|
||||
@ -145,9 +129,7 @@ const Timeline: React.FC<Props> = ({
|
||||
<ComponentSeparator extraMarginLeft={0} />
|
||||
) : (
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={
|
||||
StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||
}
|
||||
extraMarginLeft={StyleConstants.Avatar.M + StyleConstants.Spacing.S}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -21,7 +21,11 @@ const TimelineFooter = React.memo(
|
||||
enabled: !disableInfinity,
|
||||
notifyOnChangeProps: ['hasNextPage'],
|
||||
getNextPageParam: lastPage =>
|
||||
lastPage?.links?.next && { max_id: lastPage.links.next }
|
||||
lastPage?.links?.next && {
|
||||
...(lastPage.links.next.isOffset
|
||||
? { offset: lastPage.links.next.id }
|
||||
: { max_id: lastPage.links.next.id })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -43,11 +47,7 @@ const TimelineFooter = React.memo(
|
||||
<Trans
|
||||
i18nKey='componentTimeline:end.message'
|
||||
components={[
|
||||
<Icon
|
||||
name='Coffee'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={colors.secondary}
|
||||
/>
|
||||
<Icon name='Coffee' size={StyleConstants.Font.Size.S} color={colors.secondary} />
|
||||
]}
|
||||
/>
|
||||
</CustomText>
|
||||
|
@ -1,10 +1,6 @@
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import {
|
||||
QueryKeyTimeline,
|
||||
TimelineData,
|
||||
useTimelineQuery
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { QueryKeyTimeline, TimelineData, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback, useRef, useState } from 'react'
|
||||
@ -31,14 +27,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5
|
||||
export const SEPARATION_Y_1 = -(
|
||||
CONTAINER_HEIGHT / 2 +
|
||||
StyleConstants.Font.Size.S / 2
|
||||
)
|
||||
export const SEPARATION_Y_2 = -(
|
||||
CONTAINER_HEIGHT * 1.5 +
|
||||
StyleConstants.Font.Size.S / 2
|
||||
)
|
||||
export const SEPARATION_Y_1 = -(CONTAINER_HEIGHT / 2 + StyleConstants.Font.Size.S / 2)
|
||||
export const SEPARATION_Y_2 = -(CONTAINER_HEIGHT * 1.5 + StyleConstants.Font.Size.S / 2)
|
||||
|
||||
const TimelineRefresh: React.FC<Props> = ({
|
||||
flRef,
|
||||
@ -57,87 +47,77 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
const fetchingLatestIndex = useRef(0)
|
||||
const refetchActive = useRef(false)
|
||||
|
||||
const {
|
||||
refetch,
|
||||
isFetching,
|
||||
isLoading,
|
||||
fetchPreviousPage,
|
||||
hasPreviousPage,
|
||||
isFetchingNextPage
|
||||
} = useTimelineQuery({
|
||||
...queryKey[1],
|
||||
options: {
|
||||
getPreviousPageParam: firstPage =>
|
||||
firstPage?.links?.prev && {
|
||||
min_id: firstPage.links.prev,
|
||||
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
||||
limit: '3'
|
||||
const { refetch, isFetching, isLoading, fetchPreviousPage, hasPreviousPage, isFetchingNextPage } =
|
||||
useTimelineQuery({
|
||||
...queryKey[1],
|
||||
options: {
|
||||
getPreviousPageParam: firstPage =>
|
||||
firstPage?.links?.prev && {
|
||||
...(firstPage.links.prev.isOffset
|
||||
? { offset: firstPage.links.prev.id }
|
||||
: { max_id: firstPage.links.prev.id }),
|
||||
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
||||
limit: '3'
|
||||
},
|
||||
select: data => {
|
||||
if (refetchActive.current) {
|
||||
data.pageParams = [data.pageParams[0]]
|
||||
data.pages = [data.pages[0]]
|
||||
refetchActive.current = false
|
||||
}
|
||||
return data
|
||||
},
|
||||
select: data => {
|
||||
if (refetchActive.current) {
|
||||
data.pageParams = [data.pageParams[0]]
|
||||
data.pages = [data.pages[0]]
|
||||
refetchActive.current = false
|
||||
}
|
||||
return data
|
||||
},
|
||||
onSuccess: () => {
|
||||
if (fetchingLatestIndex.current > 0) {
|
||||
if (fetchingLatestIndex.current > 5) {
|
||||
clearFirstPage()
|
||||
fetchingLatestIndex.current = 0
|
||||
} else {
|
||||
if (hasPreviousPage) {
|
||||
fetchPreviousPage()
|
||||
fetchingLatestIndex.current++
|
||||
} else {
|
||||
onSuccess: () => {
|
||||
if (fetchingLatestIndex.current > 0) {
|
||||
if (fetchingLatestIndex.current > 5) {
|
||||
clearFirstPage()
|
||||
fetchingLatestIndex.current = 0
|
||||
} else {
|
||||
if (hasPreviousPage) {
|
||||
fetchPreviousPage()
|
||||
fetchingLatestIndex.current++
|
||||
} else {
|
||||
clearFirstPage()
|
||||
fetchingLatestIndex.current = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { colors } = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const clearFirstPage = () => {
|
||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
||||
queryKey,
|
||||
data => {
|
||||
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
||||
return {
|
||||
pages: data.pages.slice(1),
|
||||
pageParams: data.pageParams.slice(1)
|
||||
}
|
||||
} else {
|
||||
return data
|
||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(queryKey, data => {
|
||||
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
||||
return {
|
||||
pages: data.pages.slice(1),
|
||||
pageParams: data.pageParams.slice(1)
|
||||
}
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
const prepareRefetch = () => {
|
||||
refetchActive.current = true
|
||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
||||
queryKey,
|
||||
data => {
|
||||
if (data) {
|
||||
data.pageParams = [undefined]
|
||||
const newFirstPage: TimelineData = { body: [] }
|
||||
for (let page of data.pages) {
|
||||
// @ts-ignore
|
||||
newFirstPage.body.push(...page.body)
|
||||
if (newFirstPage.body.length > 10) break
|
||||
}
|
||||
data.pages = [newFirstPage]
|
||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(queryKey, data => {
|
||||
if (data) {
|
||||
data.pageParams = [undefined]
|
||||
const newFirstPage: TimelineData = { body: [] }
|
||||
for (let page of data.pages) {
|
||||
// @ts-ignore
|
||||
newFirstPage.body.push(...page.body)
|
||||
if (newFirstPage.body.length > 10) break
|
||||
}
|
||||
|
||||
return data
|
||||
data.pages = [newFirstPage]
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
})
|
||||
}
|
||||
const callRefetch = async () => {
|
||||
await refetch()
|
||||
@ -161,10 +141,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
]
|
||||
}))
|
||||
const arrowTop = useAnimatedStyle(() => ({
|
||||
marginTop:
|
||||
scrollY.value < SEPARATION_Y_2
|
||||
? withTiming(CONTAINER_HEIGHT)
|
||||
: withTiming(0)
|
||||
marginTop: scrollY.value < SEPARATION_Y_2 ? withTiming(CONTAINER_HEIGHT) : withTiming(0)
|
||||
}))
|
||||
|
||||
const arrowStage = useSharedValue(0)
|
||||
@ -241,8 +218,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
const headerPadding = useAnimatedStyle(
|
||||
() => ({
|
||||
paddingTop:
|
||||
fetchingLatestIndex.current !== 0 ||
|
||||
(isFetching && !isLoading && !isFetchingNextPage)
|
||||
fetchingLatestIndex.current !== 0 || (isFetching && !isLoading && !isFetchingNextPage)
|
||||
? withTiming(StyleConstants.Spacing.M * 2.5)
|
||||
: withTiming(0)
|
||||
}),
|
||||
@ -254,10 +230,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
<View style={styles.base}>
|
||||
{isFetching ? (
|
||||
<View style={styles.container2}>
|
||||
<Circle
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={colors.secondary}
|
||||
/>
|
||||
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
|
@ -6,8 +6,9 @@
|
||||
"public": {
|
||||
"name": "",
|
||||
"segments": {
|
||||
"left": "Federated",
|
||||
"right": "Local"
|
||||
"federated": "Federated",
|
||||
"local": "Local",
|
||||
"trending": "Trending"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
|
@ -3,16 +3,10 @@ import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import {
|
||||
RootStackScreenProps,
|
||||
ScreenTabsStackParamList
|
||||
} from '@utils/navigation/navigators'
|
||||
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstanceActive
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
||||
import { getVersionUpdate, retrieveVersionLatest } from '@utils/slices/appSlice'
|
||||
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
||||
import { getInstanceAccount, getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
@ -125,11 +119,7 @@ const ScreenTabs = React.memo(
|
||||
>
|
||||
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||
<Tab.Screen
|
||||
name='Tab-Compose'
|
||||
component={composeComponent}
|
||||
listeners={composeListeners}
|
||||
/>
|
||||
<Tab.Screen name='Tab-Compose' component={composeComponent} listeners={composeListeners} />
|
||||
<Tab.Screen name='Tab-Notifications' component={TabNotifications} />
|
||||
<Tab.Screen
|
||||
name='Tab-Me'
|
||||
|
@ -1,106 +0,0 @@
|
||||
import { HeaderCenter, HeaderRight } from '@components/Header'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { ScreenTabsScreenProps, TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import TabShared from './Shared'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabLocalStackParamList>()
|
||||
|
||||
const TabLocal = React.memo(
|
||||
({ navigation }: ScreenTabsScreenProps<'Tab-Local'>) => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const { data: lists } = useListsQuery({})
|
||||
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>(['Timeline', { page: 'Following' }])
|
||||
|
||||
usePopToTop()
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen
|
||||
name='Tab-Local-Root'
|
||||
options={{
|
||||
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')
|
||||
}
|
||||
/>
|
||||
</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.Content>
|
||||
</DropdownMenu.Root>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||
accessibilityHint={t('common.search.accessibilityHint')}
|
||||
content='Search'
|
||||
onPress={() => navigation.navigate('Tab-Local', { screen: 'Tab-Shared-Search' })}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
children={() => (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
lookback='Following'
|
||||
customProps={{
|
||||
renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{TabShared({ Stack })}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
export default TabLocal
|
96
src/screens/Tabs/Local/Root.tsx
Normal file
96
src/screens/Tabs/Local/Root.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import { HeaderCenter, HeaderRight } from '@components/Header'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
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 { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
|
||||
const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-Root'>> = ({
|
||||
navigation
|
||||
}) => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const { data: lists } = useListsQuery()
|
||||
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>(['Timeline', { page: 'Following' }])
|
||||
|
||||
useEffect(() => {
|
||||
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')
|
||||
}
|
||||
/>
|
||||
</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.Content>
|
||||
</DropdownMenu.Root>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||
accessibilityHint={t('common.search.accessibilityHint')}
|
||||
content='Search'
|
||||
onPress={() => navigation.navigate('Tab-Shared-Search')}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [])
|
||||
|
||||
usePopToTop()
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
lookback='Following'
|
||||
customProps={{
|
||||
renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Root
|
18
src/screens/Tabs/Local/index.tsx
Normal file
18
src/screens/Tabs/Local/index.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import React from 'react'
|
||||
import TabShared from '../Shared'
|
||||
import Root from './Root'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabLocalStackParamList>()
|
||||
|
||||
const TabLocal: React.FC = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen name='Tab-Local-Root' component={Root} />
|
||||
{TabShared({ Stack })}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabLocal
|
@ -1,164 +0,0 @@
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabMeStackParamList } from '@utils/navigation/navigators'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import TabMeBookmarks from './Me/Bookmarks'
|
||||
import TabMeConversations from './Me/Cconversations'
|
||||
import TabMeFavourites from './Me/Favourites'
|
||||
import TabMeFollowedTags from './Me/FollowedTags'
|
||||
import TabMeList from './Me/List'
|
||||
import TabMeListAccounts from './Me/List/Accounts'
|
||||
import TabMeListEdit from './Me/List/Edit'
|
||||
import TabMeListList from './Me/List/List'
|
||||
import TabMeProfile from './Me/Profile'
|
||||
import TabMePush from './Me/Push'
|
||||
import TabMeRoot from './Me/Root'
|
||||
import TabMeSettings from './Me/Settings'
|
||||
import TabMeSettingsFontsize from './Me/SettingsFontsize'
|
||||
import TabMeSettingsLanguage from './Me/SettingsLanguage'
|
||||
import TabMeSwitch from './Me/Switch'
|
||||
import TabShared from './Shared'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabMeStackParamList>()
|
||||
|
||||
const TabMe = React.memo(
|
||||
() => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Root'
|
||||
component={TabMeRoot}
|
||||
options={{
|
||||
headerShadowVisible: false,
|
||||
headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' },
|
||||
headerShown: false
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Bookmarks'
|
||||
component={TabMeBookmarks}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.bookmarks.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Conversations'
|
||||
component={TabMeConversations}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.conversations.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Favourites'
|
||||
component={TabMeFavourites}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.favourites.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-FollowedTags'
|
||||
component={TabMeFollowedTags}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.followedTags.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List'
|
||||
component={TabMeList}
|
||||
options={({ route, navigation }: any) => ({
|
||||
title: t('me.stacks.list.name', { list: route.params.title }),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List-Accounts'
|
||||
component={TabMeListAccounts}
|
||||
options={({ navigation, route: { params } }) => ({
|
||||
title: t('me.stacks.listAccounts.name', { list: params.title }),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List-Edit'
|
||||
component={TabMeListEdit}
|
||||
options={{
|
||||
gestureEnabled: false,
|
||||
presentation: 'modal'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List-List'
|
||||
component={TabMeListList}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.lists.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Profile'
|
||||
component={TabMeProfile}
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: 'modal'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Push'
|
||||
component={TabMePush}
|
||||
options={({ navigation }) => ({
|
||||
title: t('me.stacks.push.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Settings'
|
||||
component={TabMeSettings}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.settings.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Settings-Fontsize'
|
||||
component={TabMeSettingsFontsize}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.fontSize.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Settings-Language'
|
||||
component={TabMeSettingsLanguage}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.language.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Switch'
|
||||
component={TabMeSwitch}
|
||||
options={({ navigation }) => ({
|
||||
presentation: 'modal',
|
||||
headerShown: true,
|
||||
title: t('me.stacks.switch.name'),
|
||||
headerLeft: () => (
|
||||
<HeaderLeft content='ChevronDown' onPress={() => navigation.goBack()} />
|
||||
)
|
||||
})}
|
||||
/>
|
||||
|
||||
{TabShared({ Stack })}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
export default TabMe
|
@ -27,7 +27,9 @@ const TabMeListAccounts: React.FC<TabMeStackScreenProps<'Tab-Me-List-Accounts'>>
|
||||
options: {
|
||||
getNextPageParam: lastPage =>
|
||||
lastPage?.links?.next && {
|
||||
max_id: lastPage.links.next
|
||||
...(lastPage.links.next.isOffset
|
||||
? { offset: lastPage.links.next.id }
|
||||
: { max_id: lastPage.links.next.id })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
159
src/screens/Tabs/Me/index.tsx
Normal file
159
src/screens/Tabs/Me/index.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabMeStackParamList } from '@utils/navigation/navigators'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import TabMeBookmarks from './Bookmarks'
|
||||
import TabMeConversations from './Cconversations'
|
||||
import TabMeFavourites from './Favourites'
|
||||
import TabMeFollowedTags from './FollowedTags'
|
||||
import TabMeList from './List'
|
||||
import TabMeListAccounts from './List/Accounts'
|
||||
import TabMeListEdit from './List/Edit'
|
||||
import TabMeListList from './List/List'
|
||||
import TabMeProfile from './Profile'
|
||||
import TabMePush from './Push'
|
||||
import TabMeRoot from './Root'
|
||||
import TabMeSettings from './Settings'
|
||||
import TabMeSettingsFontsize from './SettingsFontsize'
|
||||
import TabMeSettingsLanguage from './SettingsLanguage'
|
||||
import TabMeSwitch from './Switch'
|
||||
import TabShared from '../Shared'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabMeStackParamList>()
|
||||
|
||||
const TabMe: React.FC = () => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Root'
|
||||
component={TabMeRoot}
|
||||
options={{
|
||||
headerShadowVisible: false,
|
||||
headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' },
|
||||
headerShown: false
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Bookmarks'
|
||||
component={TabMeBookmarks}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.bookmarks.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Conversations'
|
||||
component={TabMeConversations}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.conversations.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Favourites'
|
||||
component={TabMeFavourites}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.favourites.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-FollowedTags'
|
||||
component={TabMeFollowedTags}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.followedTags.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List'
|
||||
component={TabMeList}
|
||||
options={({ route, navigation }: any) => ({
|
||||
title: t('me.stacks.list.name', { list: route.params.title }),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List-Accounts'
|
||||
component={TabMeListAccounts}
|
||||
options={({ navigation, route: { params } }) => ({
|
||||
title: t('me.stacks.listAccounts.name', { list: params.title }),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List-Edit'
|
||||
component={TabMeListEdit}
|
||||
options={{
|
||||
gestureEnabled: false,
|
||||
presentation: 'modal'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-List-List'
|
||||
component={TabMeListList}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.lists.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Profile'
|
||||
component={TabMeProfile}
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: 'modal'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Push'
|
||||
component={TabMePush}
|
||||
options={({ navigation }) => ({
|
||||
title: t('me.stacks.push.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Settings'
|
||||
component={TabMeSettings}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.settings.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Settings-Fontsize'
|
||||
component={TabMeSettingsFontsize}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.fontSize.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Settings-Language'
|
||||
component={TabMeSettingsLanguage}
|
||||
options={({ navigation }: any) => ({
|
||||
title: t('me.stacks.language.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Switch'
|
||||
component={TabMeSwitch}
|
||||
options={({ navigation }) => ({
|
||||
presentation: 'modal',
|
||||
headerShown: true,
|
||||
title: t('me.stacks.switch.name'),
|
||||
headerLeft: () => <HeaderLeft content='ChevronDown' onPress={() => navigation.goBack()} />
|
||||
})}
|
||||
/>
|
||||
|
||||
{TabShared({ Stack })}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabMe
|
@ -1,110 +0,0 @@
|
||||
import { HeaderRight } from '@components/Header'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import SegmentedControl from '@react-native-community/segmented-control'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { ScreenTabsScreenProps, TabPublicStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions } from 'react-native'
|
||||
import { TabView } from 'react-native-tab-view'
|
||||
import TabShared from './Shared'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabPublicStackParamList>()
|
||||
|
||||
const TabPublic = React.memo(
|
||||
({ navigation }: ScreenTabsScreenProps<'Tab-Public'>) => {
|
||||
const { t, i18n } = useTranslation('screenTabs')
|
||||
const { mode, theme } = useTheme()
|
||||
|
||||
const [segment, setSegment] = useState(0)
|
||||
const pages: {
|
||||
title: string
|
||||
key: Extract<App.Pages, 'Local' | 'LocalPublic'>
|
||||
}[] = [
|
||||
{
|
||||
title: t('tabs.public.segments.left'),
|
||||
key: 'LocalPublic'
|
||||
},
|
||||
{
|
||||
title: t('tabs.public.segments.right'),
|
||||
key: 'Local'
|
||||
}
|
||||
]
|
||||
const screenOptionsRoot = useMemo(
|
||||
() => ({
|
||||
headerTitle: () => (
|
||||
<SegmentedControl
|
||||
appearance={mode}
|
||||
values={pages.map(p => p.title)}
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) => setSegment(nativeEvent.selectedSegmentIndex)}
|
||||
style={{ flexBasis: '65%' }}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||
accessibilityHint={t('common.search.accessibilityHint')}
|
||||
content='Search'
|
||||
onPress={() => navigation.navigate('Tab-Public', { screen: 'Tab-Shared-Search' })}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
[theme, segment, i18n.language]
|
||||
)
|
||||
|
||||
const routes = pages.map(p => ({ key: p.key }))
|
||||
|
||||
const renderScene = useCallback(
|
||||
({
|
||||
route: { key: page }
|
||||
}: {
|
||||
route: {
|
||||
key: Extract<App.Pages, 'Local' | 'LocalPublic'>
|
||||
}
|
||||
}) => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page }]
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
lookback={page}
|
||||
customProps={{
|
||||
renderItem: ({ item }: any) => <TimelineDefault item={item} queryKey={queryKey} />
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[]
|
||||
)
|
||||
const children = useCallback(
|
||||
() => (
|
||||
<TabView
|
||||
lazy
|
||||
swipeEnabled
|
||||
renderScene={renderScene}
|
||||
renderTabBar={() => null}
|
||||
onIndexChange={index => setSegment(index)}
|
||||
navigationState={{ index: segment, routes }}
|
||||
initialLayout={{ width: Dimensions.get('screen').width }}
|
||||
/>
|
||||
),
|
||||
[segment]
|
||||
)
|
||||
|
||||
usePopToTop()
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen name='Tab-Public-Root' options={screenOptionsRoot} children={children} />
|
||||
{TabShared({ Stack })}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
export default TabPublic
|
84
src/screens/Tabs/Public/Root.tsx
Normal file
84
src/screens/Tabs/Public/Root.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import { HeaderRight } from '@components/Header'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import SegmentedControl from '@react-native-community/segmented-control'
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
import { TabPublicStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions } from 'react-native'
|
||||
import { SceneMap, TabView } from 'react-native-tab-view'
|
||||
|
||||
const Route = ({ route: { key: page } }: { route: any }) => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page }]
|
||||
return (
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
disableRefresh={page === 'Trending'}
|
||||
customProps={{
|
||||
renderItem: ({ item }: any) => <TimelineDefault item={item} queryKey={queryKey} />
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const renderScene = SceneMap({
|
||||
Local: Route,
|
||||
LocalPublic: Route,
|
||||
Trending: Route
|
||||
})
|
||||
|
||||
const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public-Root'>> = ({
|
||||
navigation
|
||||
}) => {
|
||||
const { mode } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const [segment, setSegment] = useState(0)
|
||||
const [routes] = useState([
|
||||
{ key: 'Local', title: t('tabs.public.segments.local') },
|
||||
{ key: 'LocalPublic', title: t('tabs.public.segments.federated') },
|
||||
{ key: 'Trending', title: t('tabs.public.segments.trending') }
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerTitle: () => (
|
||||
<SegmentedControl
|
||||
appearance={mode}
|
||||
values={routes.map(({ title }) => title)}
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) => setSegment(nativeEvent.selectedSegmentIndex)}
|
||||
style={{ flexBasis: '65%' }}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||
accessibilityHint={t('common.search.accessibilityHint')}
|
||||
content='Search'
|
||||
onPress={() => navigation.navigate('Tab-Shared-Search')}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [mode, segment])
|
||||
|
||||
usePopToTop()
|
||||
|
||||
return (
|
||||
<TabView
|
||||
lazy
|
||||
swipeEnabled
|
||||
renderScene={renderScene}
|
||||
renderTabBar={() => null}
|
||||
onIndexChange={index => setSegment(index)}
|
||||
navigationState={{ index: segment, routes }}
|
||||
initialLayout={{ width: Dimensions.get('screen').width }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Root
|
18
src/screens/Tabs/Public/index.tsx
Normal file
18
src/screens/Tabs/Public/index.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabPublicStackParamList } from '@utils/navigation/navigators'
|
||||
import React from 'react'
|
||||
import TabShared from '../Shared'
|
||||
import Root from './Root'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabPublicStackParamList>()
|
||||
|
||||
const TabPublic: React.FC = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen name='Tab-Public-Root' component={Root} />
|
||||
{TabShared({ Stack })}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabPublic
|
@ -23,8 +23,7 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
||||
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({
|
||||
...queryKey[1],
|
||||
options: {
|
||||
getPreviousPageParam: firstPage =>
|
||||
firstPage.links?.prev && { since_id: firstPage.links.next },
|
||||
getPreviousPageParam: firstPage => firstPage.links?.prev && { min_id: firstPage.links.next },
|
||||
getNextPageParam: lastPage => lastPage.links?.next && { max_id: lastPage.links.next }
|
||||
}
|
||||
})
|
||||
|
@ -20,9 +20,11 @@ const queryFunction = async () => {
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useListsQuery = ({ options }: { options?: UseQueryOptions<Mastodon.List[], AxiosError> }) => {
|
||||
const useListsQuery = (
|
||||
params: { options?: UseQueryOptions<Mastodon.List[], AxiosError> } | void
|
||||
) => {
|
||||
const queryKey: QueryKeyLists = ['Lists']
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
return useQuery(queryKey, queryFunction, params?.options)
|
||||
}
|
||||
|
||||
type MutationVarsLists =
|
||||
|
@ -40,6 +40,7 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
|
||||
})
|
||||
|
||||
case 'Local':
|
||||
console.log('local', params)
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: 'timelines/public',
|
||||
@ -56,6 +57,14 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
|
||||
params
|
||||
})
|
||||
|
||||
case 'Trending':
|
||||
console.log('trending', params)
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: 'trends/statuses',
|
||||
params
|
||||
})
|
||||
|
||||
case 'Notifications':
|
||||
const rootStore = store.getState()
|
||||
const notificationsFilter = getInstanceNotificationsFilter(rootStore)
|
||||
@ -206,53 +215,6 @@ const useTimelineQuery = ({
|
||||
})
|
||||
}
|
||||
|
||||
const prefetchTimelineQuery = async ({
|
||||
ids,
|
||||
queryKey
|
||||
}: {
|
||||
ids: Mastodon.Status['id'][]
|
||||
queryKey: QueryKeyTimeline
|
||||
}): Promise<Mastodon.Status['id'] | undefined> => {
|
||||
let page: string = ''
|
||||
let local: boolean = false
|
||||
switch (queryKey[1].page) {
|
||||
case 'Following':
|
||||
page = 'home'
|
||||
break
|
||||
case 'Local':
|
||||
page = 'public'
|
||||
local = true
|
||||
break
|
||||
case 'LocalPublic':
|
||||
page = 'public'
|
||||
break
|
||||
}
|
||||
|
||||
for (const id of ids) {
|
||||
const statuses = await apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: `timelines/${page}`,
|
||||
params: {
|
||||
min_id: id,
|
||||
limit: 1,
|
||||
...(local && { local: 'true' })
|
||||
}
|
||||
})
|
||||
if (statuses.body.length) {
|
||||
await queryClient.prefetchInfiniteQuery(queryKey, props =>
|
||||
queryFunction({
|
||||
...props,
|
||||
queryKey,
|
||||
pageParam: {
|
||||
max_id: statuses.body[0].id
|
||||
}
|
||||
})
|
||||
)
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Separator ---
|
||||
|
||||
enum MapPropertyToUrl {
|
||||
@ -460,4 +422,4 @@ const useTimelineMutation = ({
|
||||
})
|
||||
}
|
||||
|
||||
export { prefetchTimelineQuery, useTimelineQuery, useTimelineMutation }
|
||||
export { useTimelineQuery, useTimelineMutation }
|
||||
|
@ -2,7 +2,7 @@ import { InstanceResponse } from '@api/instance'
|
||||
|
||||
export const infinitePageParams = {
|
||||
getPreviousPageParam: (firstPage: InstanceResponse<any>) =>
|
||||
firstPage.links?.prev && { since_id: firstPage.links.next },
|
||||
firstPage.links?.prev && { min_id: firstPage.links.next },
|
||||
getNextPageParam: (lastPage: InstanceResponse<any>) =>
|
||||
lastPage.links?.next && { max_id: lastPage.links.next }
|
||||
}
|
||||
|
@ -8144,11 +8144,6 @@ leven@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||
integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
|
||||
|
||||
li@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/li/-/li-1.3.0.tgz#22c59bcaefaa9a8ef359cf759784e4bf106aea1b"
|
||||
integrity sha512-z34TU6GlMram52Tss5mt1m//ifRIpKH5Dqm7yUVOdHI+BQCs9qGPHFaCUTIzsWX7edN30aa2WrPwR7IO10FHaw==
|
||||
|
||||
lie@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
||||
|
Loading…
x
Reference in New Issue
Block a user