Merge pull request #285 from tooot-app/main

Final test for v4
This commit is contained in:
xmflsct 2022-05-12 00:11:47 +02:00 committed by GitHub
commit b94af5d8bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 70 additions and 472 deletions

View File

@ -31,7 +31,7 @@ PODS:
- EXJSONUtils
- EXNotifications (0.15.2):
- ExpoModulesCore
- Expo (45.0.2):
- Expo (45.0.3):
- ExpoModulesCore
- ExpoCrypto (10.2.0):
- ExpoModulesCore
@ -849,7 +849,7 @@ SPEC CHECKSUMS:
EXJSONUtils: 2a74b8f40f1523cc3f92af99c91aa78201737a77
EXManifests: 0c6134b7b6f3236a93a778c3f44ba1cfb3f9fa3d
EXNotifications: ea9fc56d27d1fee229489c5d8f452c7f367c237e
Expo: 27c7466cea6c8be416d8e50a0c0faa48cf8b4d1a
Expo: 353cd6d2154838c0c8bb1072d3ac89eb535a2ef4
ExpoCrypto: d0d0f3e20875dc450b4ec88f0fb608da5c2c6c17
ExpoHaptics: ad58ec96a25e57579c14a47c7d71f0de0de8656a
ExpoImageManipulator: b55580bbc7b10099c7707949903e7176a8542ee8

View File

@ -46,7 +46,7 @@
"@sentry/react-native": "3.4.2",
"@sharcoux/slider": "6.0.3",
"axios": "0.27.2",
"expo": "45.0.2",
"expo": "45.0.3",
"expo-auth-session": "3.6.0",
"expo-av": "11.2.3",
"expo-constants": "^13.1.1",
@ -68,7 +68,7 @@
"expo-updates": "0.13.1",
"expo-video-thumbnails": "6.3.0",
"expo-web-browser": "10.2.0",
"i18next": "21.8.0",
"i18next": "21.8.1",
"li": "1.3.0",
"lodash": "4.17.21",
"react": "17.0.2",
@ -118,7 +118,7 @@
"babel-plugin-module-resolver": "4.1.0",
"babel-plugin-transform-remove-console": "6.9.4",
"chalk": "4.1.2",
"dotenv": "16.0.0",
"dotenv": "16.0.1",
"patch-package": "6.4.7",
"postinstall-postinstall": "2.1.0",
"react-native-clean-project": "4.0.1",

View File

@ -1,11 +1,11 @@
import ComponentSeparator from '@components/Separator'
import { useScrollToTop } from '@react-navigation/native'
import { useAppDispatch } from '@root/store'
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
import {
getInstanceActive,
updateInstanceTimelineLookback
} from '@utils/slices/instancesSlice'
QueryKeyTimeline,
TimelineData,
useTimelineQuery
} from '@utils/queryHooks/timeline'
import { getInstanceActive } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useCallback, useRef } from 'react'
@ -14,22 +14,12 @@ import {
FlatListProps,
Platform,
RefreshControl,
StyleSheet,
ViewabilityConfigCallbackPairs
StyleSheet
} from 'react-native'
import Animated, {
useAnimatedScrollHandler,
useSharedValue
} from 'react-native-reanimated'
import { InfiniteData, useQueryClient } from 'react-query'
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'
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
export interface Props {
flRef?: RefObject<FlatList<any>>
@ -46,17 +36,19 @@ const Timeline: React.FC<Props> = ({
queryKey,
disableRefresh = false,
disableInfinity = false,
lookback,
customProps
}) => {
const { colors } = useTheme()
const queryClient = useQueryClient()
const {
data,
refetch,
isFetching,
isLoading,
fetchPreviousPage,
fetchNextPage,
isFetchingPreviousPage,
isFetchingNextPage
} = useTimelineQuery({
...queryKey[1],
@ -65,6 +57,12 @@ const Timeline: React.FC<Props> = ({
ios: ['dataUpdatedAt', 'isFetching'],
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
}),
getPreviousPageParam: firstPage =>
firstPage?.links?.prev && {
min_id: firstPage.links.prev,
// https://github.com/facebook/react-native/issues/25239
limit: '10'
},
getNextPageParam: lastPage =>
lastPage?.links?.next && {
max_id: lastPage.links.next
@ -93,26 +91,6 @@ const Timeline: React.FC<Props> = ({
)
const flRef = useRef<FlatList>(null)
const scrollY = useSharedValue(0)
const fetchingType = useSharedValue<0 | 1 | 2>(0)
const onScroll = useAnimatedScrollHandler(
{
onScroll: ({ contentOffset: { y } }) => {
scrollY.value = y
},
onEndDrag: ({ contentOffset: { y } }) => {
if (!disableRefresh && !isFetching) {
if (y <= SEPARATION_Y_2) {
fetchingType.value = 2
} else if (y <= SEPARATION_Y_1) {
fetchingType.value = 1
}
}
}
},
[isFetching]
)
const androidRefreshControl = Platform.select({
android: {
@ -128,27 +106,6 @@ const Timeline: React.FC<Props> = ({
}
})
const dispatch = useAppDispatch()
const viewabilityPairs = useRef<ViewabilityConfigCallbackPairs>([
{
viewabilityConfig: {
minimumViewTime: 10,
viewAreaCoveragePercentThreshold: 10
},
onViewableItemsChanged: ({ viewableItems }) => {
lookback &&
dispatch(
updateInstanceTimelineLookback({
[lookback]: {
queryKey,
ids: viewableItems.map(item => item.key).slice(0, 3)
}
})
)
}
}
])
useScrollToTop(flRef)
useSelector(getInstanceActive, (prev, next) => {
if (prev !== next) {
@ -158,43 +115,44 @@ const Timeline: React.FC<Props> = ({
})
return (
<>
<TimelineRefresh
flRef={flRef}
queryKey={queryKey}
scrollY={scrollY}
fetchingType={fetchingType}
disableRefresh={disableRefresh}
/>
<AnimatedFlatList
ref={customFLRef || flRef}
scrollEventThrottle={16}
onScroll={onScroll}
windowSize={7}
data={flattenData}
initialNumToRender={6}
maxToRenderPerBatch={3}
style={styles.flatList}
onEndReached={onEndReached}
onEndReachedThreshold={0.75}
ListFooterComponent={
<TimelineFooter
queryKey={queryKey}
disableInfinity={disableInfinity}
/>
<FlatList
ref={customFLRef || flRef}
scrollEventThrottle={16}
windowSize={7}
data={flattenData}
initialNumToRender={6}
maxToRenderPerBatch={3}
style={styles.flatList}
onEndReached={onEndReached}
onEndReachedThreshold={0.75}
ListFooterComponent={
<TimelineFooter queryKey={queryKey} disableInfinity={disableInfinity} />
}
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
ItemSeparatorComponent={ItemSeparatorComponent}
maintainVisibleContentPosition={{ minIndexForVisible: 0 }}
refreshing={isFetchingPreviousPage}
onRefresh={() => {
if (!disableRefresh && !isFetchingPreviousPage) {
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
}
}
)
fetchPreviousPage()
}
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
ItemSeparatorComponent={ItemSeparatorComponent}
maintainVisibleContentPosition={{
minIndexForVisible: 0
}}
{...(lookback && {
viewabilityConfigCallbackPairs: viewabilityPairs.current
})}
{...androidRefreshControl}
{...customProps}
/>
</>
}}
{...androidRefreshControl}
{...customProps}
/>
)
}

View File

@ -1,32 +0,0 @@
import CustomText from '@components/Text'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { View } from 'react-native'
const TimelineLookback = React.memo(
() => {
const { t } = useTranslation('componentTimeline')
const { colors } = useTheme()
return (
<View
style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
padding: StyleConstants.Spacing.S,
backgroundColor: colors.backgroundDefault
}}
>
<CustomText fontStyle='S' style={{ color: colors.primaryDefault }}>
{t('lookback.message')}
</CustomText>
</View>
)
},
() => true
)
export default TimelineLookback

View File

@ -1,328 +0,0 @@
import haptics from '@components/haptics'
import Icon from '@components/Icon'
import CustomText from '@components/Text'
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'
import { useTranslation } from 'react-i18next'
import { FlatList, Platform, View } from 'react-native'
import { Circle } from 'react-native-animated-spinkit'
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
import { InfiniteData, useQueryClient } from 'react-query'
export interface Props {
flRef: RefObject<FlatList<any>>
queryKey: QueryKeyTimeline
scrollY: Animated.SharedValue<number>
fetchingType: Animated.SharedValue<0 | 1 | 2>
disableRefresh?: boolean
}
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
)
const TimelineRefresh: React.FC<Props> = ({
flRef,
queryKey,
scrollY,
fetchingType,
disableRefresh = false
}) => {
if (Platform.OS !== 'ios') {
return null
}
if (disableRefresh) {
return null
}
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: '5'
},
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 {
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
}
}
)
}
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]
}
return data
}
)
}
const callRefetch = async () => {
await refetch()
setTimeout(() => flRef.current?.scrollToOffset({ offset: 1 }), 50)
}
const [textRight, setTextRight] = useState(0)
const arrowY = useAnimatedStyle(() => ({
transform: [
{
translateY: interpolate(
scrollY.value,
[0, SEPARATION_Y_1],
[
-CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.M / 2,
CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.S / 2
],
Extrapolate.CLAMP
)
}
]
}))
const arrowTop = useAnimatedStyle(() => ({
marginTop:
scrollY.value < SEPARATION_Y_2
? withTiming(CONTAINER_HEIGHT)
: withTiming(0)
}))
const arrowStage = useSharedValue(0)
const onLayout = useCallback(
({ nativeEvent }) => {
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
}
},
[textRight]
)
useAnimatedReaction(
() => {
if (isFetching) {
return false
}
switch (arrowStage.value) {
case 0:
if (scrollY.value < SEPARATION_Y_1) {
arrowStage.value = 1
return true
}
return false
case 1:
if (scrollY.value < SEPARATION_Y_2) {
arrowStage.value = 2
return true
}
if (scrollY.value > SEPARATION_Y_1) {
arrowStage.value = 0
return false
}
return false
case 2:
if (scrollY.value > SEPARATION_Y_2) {
arrowStage.value = 1
return false
}
return false
}
},
data => {
if (data) {
runOnJS(haptics)('Light')
}
},
[isFetching]
)
const wrapperStartLatest = () => {
fetchingLatestIndex.current = 1
}
useAnimatedReaction(
() => {
return fetchingType.value
},
data => {
fetchingType.value = 0
switch (data) {
case 1:
runOnJS(wrapperStartLatest)()
runOnJS(clearFirstPage)()
runOnJS(fetchPreviousPage)()
break
case 2:
runOnJS(prepareRefetch)()
runOnJS(callRefetch)()
break
}
},
[]
)
const headerPadding = useAnimatedStyle(
() => ({
paddingTop:
fetchingLatestIndex.current !== 0 ||
(isFetching && !isLoading && !isFetchingNextPage)
? withTiming(StyleConstants.Spacing.M * 2.5)
: withTiming(0)
}),
[fetchingLatestIndex.current, isFetching, isFetchingNextPage, isLoading]
)
return (
<Animated.View style={headerPadding}>
<View
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: CONTAINER_HEIGHT * 2,
alignItems: 'center'
}}
>
{isFetching ? (
<View style={{ height: CONTAINER_HEIGHT, justifyContent: 'center' }}>
<Circle
size={StyleConstants.Font.Size.L}
color={colors.secondary}
/>
</View>
) : (
<>
<View
style={{
flex: 1,
flexDirection: 'row',
height: CONTAINER_HEIGHT
}}
>
<CustomText
fontStyle='S'
style={{
lineHeight: CONTAINER_HEIGHT,
color: colors.primaryDefault
}}
onLayout={onLayout}
children={t('refresh.fetchPreviousPage')}
/>
<Animated.View
style={[
{
position: 'absolute',
left: textRight + StyleConstants.Spacing.S
},
arrowY,
arrowTop
]}
children={
<Icon
name='ArrowLeft'
size={StyleConstants.Font.Size.M}
color={colors.primaryDefault}
/>
}
/>
</View>
<View
style={{ height: CONTAINER_HEIGHT, justifyContent: 'center' }}
>
<CustomText
fontStyle='S'
style={{
lineHeight: CONTAINER_HEIGHT,
color: colors.primaryDefault
}}
onLayout={onLayout}
children={t('refresh.refetch')}
/>
</View>
</>
)}
</View>
</Animated.View>
)
}
export default TimelineRefresh

View File

@ -3902,10 +3902,10 @@ domutils@^2.8.0:
domelementtype "^2.2.0"
domhandler "^4.2.0"
dotenv@16.0.0:
version "16.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411"
integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==
dotenv@16.0.1:
version "16.0.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==
ee-first@1.1.1:
version "1.1.1"
@ -4372,10 +4372,10 @@ expo-web-browser@10.2.0, expo-web-browser@~10.2.0:
dependencies:
compare-urls "^2.0.0"
expo@45.0.2:
version "45.0.2"
resolved "https://registry.yarnpkg.com/expo/-/expo-45.0.2.tgz#d773d1b6f427bcdbfcb3da26274b35b2749c632c"
integrity sha512-WRVNTEc2ZqstGwVIxCAfJUTsmhFVAWR4vNaF4kTDEQa2J5GHbFuSQbENYrTeLldLc5Q/60HX3xfxUu8pqqdjGA==
expo@45.0.3:
version "45.0.3"
resolved "https://registry.yarnpkg.com/expo/-/expo-45.0.3.tgz#7682b4dd29c6ec9de440bc151c3aa43ec392ec6e"
integrity sha512-xI22uW8ekA5152mcN1bgXLFOFO0a2eafHr0Kgq80BvV6MyYbkZS3M8trtjo9Cs6LVMMVgyc0MWI/2cd5sGjMgg==
dependencies:
"@babel/runtime" "^7.14.0"
"@expo/cli" "0.1.4"
@ -5028,10 +5028,10 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
i18next@21.8.0:
version "21.8.0"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.8.0.tgz#2ad46a882fe1b793e499c47a934bb115b7c618b3"
integrity sha512-opNd7cQj0PDlUX15hPjtzReRxy5/Rn405YvHTBEm1nf1YJhsqYFFFhHMwuU4NEHZNlrepHk5uK+CJbFtB+KO3w==
i18next@21.8.1:
version "21.8.1"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.8.1.tgz#44e083ade3dfe08c8099882c560d3c7294cdf679"
integrity sha512-uulZBD5kLME7Ucz8pFwpC7jXDjq4BHkio3b6GBw1ykXNHA8rIAk1S2t6zb2Ripf9OMetMkysiDRlPlCVqjvQeg==
dependencies:
"@babel/runtime" "^7.17.2"