mirror of
https://github.com/tooot-app/app
synced 2025-02-09 00:18:38 +01:00
parent
1ece7b3fe3
commit
44379504eb
@ -61,7 +61,6 @@
|
|||||||
"expo-video-thumbnails": "^7.0.0",
|
"expo-video-thumbnails": "^7.0.0",
|
||||||
"expo-web-browser": "~12.0.0",
|
"expo-web-browser": "~12.0.0",
|
||||||
"i18next": "^22.0.6",
|
"i18next": "^22.0.6",
|
||||||
"li": "^1.3.0",
|
|
||||||
"linkify-it": "^4.0.1",
|
"linkify-it": "^4.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"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'
|
| 'Following'
|
||||||
| 'Local'
|
| 'Local'
|
||||||
| 'LocalPublic'
|
| 'LocalPublic'
|
||||||
|
| 'Trending'
|
||||||
| 'Notifications'
|
| 'Notifications'
|
||||||
| 'Hashtag'
|
| 'Hashtag'
|
||||||
| 'List'
|
| '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 'gl-react-blurhash'
|
||||||
declare module 'htmlparser2-without-node-native'
|
declare module 'htmlparser2-without-node-native'
|
||||||
declare module 'li'
|
|
||||||
declare module 'react-native-feather'
|
declare module 'react-native-feather'
|
||||||
declare module 'react-native-htmlview'
|
declare module 'react-native-htmlview'
|
||||||
declare module 'react-native-toast-message'
|
declare module 'react-native-toast-message'
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import axios, { AxiosRequestConfig } from 'axios'
|
import axios, { AxiosRequestConfig } from 'axios'
|
||||||
import li from 'li'
|
|
||||||
import { ctx, handleError, userAgent } from './helpers'
|
import { ctx, handleError, userAgent } from './helpers'
|
||||||
|
|
||||||
export type Params = {
|
export type Params = {
|
||||||
@ -15,9 +14,10 @@ export type Params = {
|
|||||||
extras?: Omit<AxiosRequestConfig, 'method' | 'url' | 'params' | 'headers' | 'data'>
|
extras?: Omit<AxiosRequestConfig, 'method' | 'url' | 'params' | 'headers' | 'data'>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LinkFormat = { id: string; isOffset: boolean }
|
||||||
export type InstanceResponse<T = unknown> = {
|
export type InstanceResponse<T = unknown> = {
|
||||||
body: T
|
body: T
|
||||||
links: { prev?: string; next?: string }
|
links: { prev?: LinkFormat; next?: LinkFormat }
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiInstance = async <T = unknown>({
|
const apiInstance = async <T = unknown>({
|
||||||
@ -74,17 +74,27 @@ const apiInstance = async <T = unknown>({
|
|||||||
...extras
|
...extras
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let prev
|
let links: {
|
||||||
let next
|
prev?: { id: string; isOffset: boolean }
|
||||||
|
next?: { id: string; isOffset: boolean }
|
||||||
|
} = {}
|
||||||
|
|
||||||
if (response.headers?.link) {
|
if (response.headers?.link) {
|
||||||
const headersLinks = li.parse(response.headers?.link)
|
const linksParsed = response.headers.link.matchAll(
|
||||||
prev = headersLinks.prev?.match(/_id=([0-9]*)/)?.[1]
|
new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi')
|
||||||
next = headersLinks.next?.match(/_id=([0-9]*)/)?.[1]
|
)
|
||||||
|
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({
|
return Promise.resolve({ body: response.data, links })
|
||||||
body: response.data,
|
|
||||||
links: { prev, next }
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(handleError())
|
.catch(handleError())
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,11 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { RefObject, useCallback, useRef } from 'react'
|
import React, { RefObject, useCallback, useRef } from 'react'
|
||||||
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
|
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
|
||||||
import Animated, {
|
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||||
useAnimatedScrollHandler,
|
|
||||||
useSharedValue
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import TimelineEmpty from './Timeline/Empty'
|
import TimelineEmpty from './Timeline/Empty'
|
||||||
import TimelineFooter from './Timeline/Footer'
|
import TimelineFooter from './Timeline/Footer'
|
||||||
import TimelineRefresh, {
|
import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Timeline/Refresh'
|
||||||
SEPARATION_Y_1,
|
|
||||||
SEPARATION_Y_2
|
|
||||||
} from './Timeline/Refresh'
|
|
||||||
|
|
||||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
||||||
|
|
||||||
@ -26,8 +20,7 @@ export interface Props {
|
|||||||
disableRefresh?: boolean
|
disableRefresh?: boolean
|
||||||
disableInfinity?: boolean
|
disableInfinity?: boolean
|
||||||
lookback?: Extract<App.Pages, 'Following' | 'Local' | 'LocalPublic'>
|
lookback?: Extract<App.Pages, 'Following' | 'Local' | 'LocalPublic'>
|
||||||
customProps: Partial<FlatListProps<any>> &
|
customProps: Partial<FlatListProps<any>> & Pick<FlatListProps<any>, 'renderItem'>
|
||||||
Pick<FlatListProps<any>, 'renderItem'>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Timeline: React.FC<Props> = ({
|
const Timeline: React.FC<Props> = ({
|
||||||
@ -39,30 +32,24 @@ const Timeline: React.FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const {
|
const { data, refetch, isFetching, isLoading, fetchNextPage, isFetchingNextPage } =
|
||||||
data,
|
useTimelineQuery({
|
||||||
refetch,
|
...queryKey[1],
|
||||||
isFetching,
|
options: {
|
||||||
isLoading,
|
notifyOnChangeProps: Platform.select({
|
||||||
fetchNextPage,
|
ios: ['dataUpdatedAt', 'isFetching'],
|
||||||
isFetchingNextPage
|
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
||||||
} = useTimelineQuery({
|
}),
|
||||||
...queryKey[1],
|
getNextPageParam: lastPage =>
|
||||||
options: {
|
lastPage?.links?.next && {
|
||||||
notifyOnChangeProps: Platform.select({
|
...(lastPage.links.next.isOffset
|
||||||
ios: ['dataUpdatedAt', 'isFetching'],
|
? { offset: lastPage.links.next.id }
|
||||||
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
: { max_id: lastPage.links.next.id })
|
||||||
}),
|
}
|
||||||
getNextPageParam: lastPage =>
|
}
|
||||||
lastPage?.links?.next && {
|
})
|
||||||
max_id: lastPage.links.next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const flattenData = data?.pages
|
const flattenData = data?.pages ? data.pages?.flatMap(page => [...page.body]) : []
|
||||||
? data.pages?.flatMap(page => [...page.body])
|
|
||||||
: []
|
|
||||||
|
|
||||||
const onEndReached = useCallback(
|
const onEndReached = useCallback(
|
||||||
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
||||||
@ -134,10 +121,7 @@ const Timeline: React.FC<Props> = ({
|
|||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={0.75}
|
onEndReachedThreshold={0.75}
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
<TimelineFooter
|
<TimelineFooter queryKey={queryKey} disableInfinity={disableInfinity} />
|
||||||
queryKey={queryKey}
|
|
||||||
disableInfinity={disableInfinity}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
||||||
ItemSeparatorComponent={({ leadingItem }) =>
|
ItemSeparatorComponent={({ leadingItem }) =>
|
||||||
@ -145,9 +129,7 @@ const Timeline: React.FC<Props> = ({
|
|||||||
<ComponentSeparator extraMarginLeft={0} />
|
<ComponentSeparator extraMarginLeft={0} />
|
||||||
) : (
|
) : (
|
||||||
<ComponentSeparator
|
<ComponentSeparator
|
||||||
extraMarginLeft={
|
extraMarginLeft={StyleConstants.Avatar.M + StyleConstants.Spacing.S}
|
||||||
StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,11 @@ const TimelineFooter = React.memo(
|
|||||||
enabled: !disableInfinity,
|
enabled: !disableInfinity,
|
||||||
notifyOnChangeProps: ['hasNextPage'],
|
notifyOnChangeProps: ['hasNextPage'],
|
||||||
getNextPageParam: lastPage =>
|
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
|
<Trans
|
||||||
i18nKey='componentTimeline:end.message'
|
i18nKey='componentTimeline:end.message'
|
||||||
components={[
|
components={[
|
||||||
<Icon
|
<Icon name='Coffee' size={StyleConstants.Font.Size.S} color={colors.secondary} />
|
||||||
name='Coffee'
|
|
||||||
size={StyleConstants.Font.Size.S}
|
|
||||||
color={colors.secondary}
|
|
||||||
/>
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</CustomText>
|
</CustomText>
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import {
|
import { QueryKeyTimeline, TimelineData, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
QueryKeyTimeline,
|
|
||||||
TimelineData,
|
|
||||||
useTimelineQuery
|
|
||||||
} from '@utils/queryHooks/timeline'
|
|
||||||
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 React, { RefObject, useCallback, useRef, useState } from 'react'
|
import React, { RefObject, useCallback, useRef, useState } from 'react'
|
||||||
@ -31,14 +27,8 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5
|
const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5
|
||||||
export const SEPARATION_Y_1 = -(
|
export const SEPARATION_Y_1 = -(CONTAINER_HEIGHT / 2 + StyleConstants.Font.Size.S / 2)
|
||||||
CONTAINER_HEIGHT / 2 +
|
export const SEPARATION_Y_2 = -(CONTAINER_HEIGHT * 1.5 + StyleConstants.Font.Size.S / 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> = ({
|
const TimelineRefresh: React.FC<Props> = ({
|
||||||
flRef,
|
flRef,
|
||||||
@ -57,87 +47,77 @@ const TimelineRefresh: React.FC<Props> = ({
|
|||||||
const fetchingLatestIndex = useRef(0)
|
const fetchingLatestIndex = useRef(0)
|
||||||
const refetchActive = useRef(false)
|
const refetchActive = useRef(false)
|
||||||
|
|
||||||
const {
|
const { refetch, isFetching, isLoading, fetchPreviousPage, hasPreviousPage, isFetchingNextPage } =
|
||||||
refetch,
|
useTimelineQuery({
|
||||||
isFetching,
|
...queryKey[1],
|
||||||
isLoading,
|
options: {
|
||||||
fetchPreviousPage,
|
getPreviousPageParam: firstPage =>
|
||||||
hasPreviousPage,
|
firstPage?.links?.prev && {
|
||||||
isFetchingNextPage
|
...(firstPage.links.prev.isOffset
|
||||||
} = useTimelineQuery({
|
? { offset: firstPage.links.prev.id }
|
||||||
...queryKey[1],
|
: { max_id: firstPage.links.prev.id }),
|
||||||
options: {
|
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
||||||
getPreviousPageParam: firstPage =>
|
limit: '3'
|
||||||
firstPage?.links?.prev && {
|
},
|
||||||
min_id: firstPage.links.prev,
|
select: data => {
|
||||||
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
if (refetchActive.current) {
|
||||||
limit: '3'
|
data.pageParams = [data.pageParams[0]]
|
||||||
|
data.pages = [data.pages[0]]
|
||||||
|
refetchActive.current = false
|
||||||
|
}
|
||||||
|
return data
|
||||||
},
|
},
|
||||||
select: data => {
|
onSuccess: () => {
|
||||||
if (refetchActive.current) {
|
if (fetchingLatestIndex.current > 0) {
|
||||||
data.pageParams = [data.pageParams[0]]
|
if (fetchingLatestIndex.current > 5) {
|
||||||
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 {
|
|
||||||
clearFirstPage()
|
clearFirstPage()
|
||||||
fetchingLatestIndex.current = 0
|
fetchingLatestIndex.current = 0
|
||||||
|
} else {
|
||||||
|
if (hasPreviousPage) {
|
||||||
|
fetchPreviousPage()
|
||||||
|
fetchingLatestIndex.current++
|
||||||
|
} else {
|
||||||
|
clearFirstPage()
|
||||||
|
fetchingLatestIndex.current = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const clearFirstPage = () => {
|
const clearFirstPage = () => {
|
||||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(queryKey, data => {
|
||||||
queryKey,
|
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
||||||
data => {
|
return {
|
||||||
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
pages: data.pages.slice(1),
|
||||||
return {
|
pageParams: data.pageParams.slice(1)
|
||||||
pages: data.pages.slice(1),
|
|
||||||
pageParams: data.pageParams.slice(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
const prepareRefetch = () => {
|
const prepareRefetch = () => {
|
||||||
refetchActive.current = true
|
refetchActive.current = true
|
||||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(queryKey, data => {
|
||||||
queryKey,
|
if (data) {
|
||||||
data => {
|
data.pageParams = [undefined]
|
||||||
if (data) {
|
const newFirstPage: TimelineData = { body: [] }
|
||||||
data.pageParams = [undefined]
|
for (let page of data.pages) {
|
||||||
const newFirstPage: TimelineData = { body: [] }
|
// @ts-ignore
|
||||||
for (let page of data.pages) {
|
newFirstPage.body.push(...page.body)
|
||||||
// @ts-ignore
|
if (newFirstPage.body.length > 10) break
|
||||||
newFirstPage.body.push(...page.body)
|
|
||||||
if (newFirstPage.body.length > 10) break
|
|
||||||
}
|
|
||||||
data.pages = [newFirstPage]
|
|
||||||
}
|
}
|
||||||
|
data.pages = [newFirstPage]
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
return data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const callRefetch = async () => {
|
const callRefetch = async () => {
|
||||||
await refetch()
|
await refetch()
|
||||||
@ -161,10 +141,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
|||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
const arrowTop = useAnimatedStyle(() => ({
|
const arrowTop = useAnimatedStyle(() => ({
|
||||||
marginTop:
|
marginTop: scrollY.value < SEPARATION_Y_2 ? withTiming(CONTAINER_HEIGHT) : withTiming(0)
|
||||||
scrollY.value < SEPARATION_Y_2
|
|
||||||
? withTiming(CONTAINER_HEIGHT)
|
|
||||||
: withTiming(0)
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const arrowStage = useSharedValue(0)
|
const arrowStage = useSharedValue(0)
|
||||||
@ -241,8 +218,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
|||||||
const headerPadding = useAnimatedStyle(
|
const headerPadding = useAnimatedStyle(
|
||||||
() => ({
|
() => ({
|
||||||
paddingTop:
|
paddingTop:
|
||||||
fetchingLatestIndex.current !== 0 ||
|
fetchingLatestIndex.current !== 0 || (isFetching && !isLoading && !isFetchingNextPage)
|
||||||
(isFetching && !isLoading && !isFetchingNextPage)
|
|
||||||
? withTiming(StyleConstants.Spacing.M * 2.5)
|
? withTiming(StyleConstants.Spacing.M * 2.5)
|
||||||
: withTiming(0)
|
: withTiming(0)
|
||||||
}),
|
}),
|
||||||
@ -254,10 +230,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
|||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
<View style={styles.container2}>
|
<View style={styles.container2}>
|
||||||
<Circle
|
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
||||||
size={StyleConstants.Font.Size.L}
|
|
||||||
color={colors.secondary}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
"public": {
|
"public": {
|
||||||
"name": "",
|
"name": "",
|
||||||
"segments": {
|
"segments": {
|
||||||
"left": "Federated",
|
"federated": "Federated",
|
||||||
"right": "Local"
|
"local": "Local",
|
||||||
|
"trending": "Trending"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
|
@ -3,16 +3,10 @@ import haptics from '@components/haptics'
|
|||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
import {
|
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
||||||
RootStackScreenProps,
|
|
||||||
ScreenTabsStackParamList
|
|
||||||
} from '@utils/navigation/navigators'
|
|
||||||
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
|
||||||
import {
|
|
||||||
getInstanceAccount,
|
|
||||||
getInstanceActive
|
|
||||||
} from '@utils/slices/instancesSlice'
|
|
||||||
import { getVersionUpdate, retrieveVersionLatest } from '@utils/slices/appSlice'
|
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 { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
@ -125,11 +119,7 @@ const ScreenTabs = React.memo(
|
|||||||
>
|
>
|
||||||
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
||||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||||
<Tab.Screen
|
<Tab.Screen name='Tab-Compose' component={composeComponent} listeners={composeListeners} />
|
||||||
name='Tab-Compose'
|
|
||||||
component={composeComponent}
|
|
||||||
listeners={composeListeners}
|
|
||||||
/>
|
|
||||||
<Tab.Screen name='Tab-Notifications' component={TabNotifications} />
|
<Tab.Screen name='Tab-Notifications' component={TabNotifications} />
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name='Tab-Me'
|
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: {
|
options: {
|
||||||
getNextPageParam: lastPage =>
|
getNextPageParam: lastPage =>
|
||||||
lastPage?.links?.next && {
|
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({
|
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({
|
||||||
...queryKey[1],
|
...queryKey[1],
|
||||||
options: {
|
options: {
|
||||||
getPreviousPageParam: firstPage =>
|
getPreviousPageParam: firstPage => firstPage.links?.prev && { min_id: firstPage.links.next },
|
||||||
firstPage.links?.prev && { since_id: firstPage.links.next },
|
|
||||||
getNextPageParam: lastPage => lastPage.links?.next && { max_id: lastPage.links.next }
|
getNextPageParam: lastPage => lastPage.links?.next && { max_id: lastPage.links.next }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -20,9 +20,11 @@ const queryFunction = async () => {
|
|||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
|
|
||||||
const useListsQuery = ({ options }: { options?: UseQueryOptions<Mastodon.List[], AxiosError> }) => {
|
const useListsQuery = (
|
||||||
|
params: { options?: UseQueryOptions<Mastodon.List[], AxiosError> } | void
|
||||||
|
) => {
|
||||||
const queryKey: QueryKeyLists = ['Lists']
|
const queryKey: QueryKeyLists = ['Lists']
|
||||||
return useQuery(queryKey, queryFunction, options)
|
return useQuery(queryKey, queryFunction, params?.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MutationVarsLists =
|
type MutationVarsLists =
|
||||||
|
@ -40,6 +40,7 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
|
|||||||
})
|
})
|
||||||
|
|
||||||
case 'Local':
|
case 'Local':
|
||||||
|
console.log('local', params)
|
||||||
return apiInstance<Mastodon.Status[]>({
|
return apiInstance<Mastodon.Status[]>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'timelines/public',
|
url: 'timelines/public',
|
||||||
@ -56,6 +57,14 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
|
|||||||
params
|
params
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case 'Trending':
|
||||||
|
console.log('trending', params)
|
||||||
|
return apiInstance<Mastodon.Status[]>({
|
||||||
|
method: 'get',
|
||||||
|
url: 'trends/statuses',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
|
||||||
case 'Notifications':
|
case 'Notifications':
|
||||||
const rootStore = store.getState()
|
const rootStore = store.getState()
|
||||||
const notificationsFilter = getInstanceNotificationsFilter(rootStore)
|
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 ---
|
// --- Separator ---
|
||||||
|
|
||||||
enum MapPropertyToUrl {
|
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 = {
|
export const infinitePageParams = {
|
||||||
getPreviousPageParam: (firstPage: InstanceResponse<any>) =>
|
getPreviousPageParam: (firstPage: InstanceResponse<any>) =>
|
||||||
firstPage.links?.prev && { since_id: firstPage.links.next },
|
firstPage.links?.prev && { min_id: firstPage.links.next },
|
||||||
getNextPageParam: (lastPage: InstanceResponse<any>) =>
|
getNextPageParam: (lastPage: InstanceResponse<any>) =>
|
||||||
lastPage.links?.next && { max_id: lastPage.links.next }
|
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"
|
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||||
integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
|
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:
|
lie@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user