Skip to content
This repository has been archived by the owner on Oct 4, 2023. It is now read-only.

Commit

Permalink
[C-1029] Fix pull-to-refresh (#1912)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanjeffers committed Sep 13, 2022
1 parent 7582351 commit 98ed3f6
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 74 deletions.
33 changes: 7 additions & 26 deletions packages/mobile/src/components/core/PullToRefresh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ import { attachToScroll } from 'app/utils/animation'
import { colorize } from 'app/utils/colorizeLottie'
import { useThemeColors } from 'app/utils/theme'

const RESET_ICON_TIMEOUT_MS = 500
const PULL_DISTANCE = 75
const DEBOUNCE_TIME_MS = 1000

const useStyles = (topOffset: number) =>
makeStyles(() => ({
Expand Down Expand Up @@ -58,7 +56,6 @@ type UseOverflowHandlersConfig = {
/**
* A helper hook to get desired pull to refresh behavior.
* 1. Momentum scrolling does not trigger pull to refresh
* 2. Pull to refresh has a minimum debounce time
*/
export const useOverflowHandlers = ({
isRefreshing,
Expand All @@ -69,18 +66,9 @@ export const useOverflowHandlers = ({
const scrollAnim = useRef(new Animated.Value(0)).current

const [isMomentumScroll, setIsMomentumScroll] = useState(false)
const [isDebouncing, setIsDebouncing] = useState(false)

const wasRefreshing = usePrevious(isRefreshing)

const handleRefresh = useCallback(() => {
onRefresh?.()
setIsDebouncing(true)
setTimeout(() => {
setIsDebouncing(false)
}, DEBOUNCE_TIME_MS)
}, [onRefresh])

const scrollTo = useCallback(
(y: number) => {
if (scrollResponder && 'scrollTo' in scrollResponder) {
Expand All @@ -94,10 +82,10 @@ export const useOverflowHandlers = ({
)

useEffect(() => {
if (!isRefreshing && !isDebouncing && wasRefreshing) {
if (!isRefreshing && wasRefreshing) {
scrollTo(0)
}
}, [isRefreshing, isDebouncing, wasRefreshing, scrollTo])
}, [isRefreshing, wasRefreshing, scrollTo])

const onScrollBeginDrag = useCallback(() => {
setIsMomentumScroll(false)
Expand All @@ -113,9 +101,9 @@ export const useOverflowHandlers = ({
const handleScroll = attachToScroll(scrollAnim, { listener: onScroll })

return {
isRefreshing: onRefresh ? isRefreshing || isDebouncing : undefined,
isRefreshing: onRefresh ? Boolean(isRefreshing) : undefined,
isRefreshDisabled: isMomentumScroll,
handleRefresh: onRefresh ? handleRefresh : undefined,
handleRefresh: onRefresh,
scrollAnim,
handleScroll,
onScrollBeginDrag,
Expand Down Expand Up @@ -209,20 +197,13 @@ export const PullToRefresh = ({
if (!isRefreshing) {
hitTop.current = false
setDidHitTop(false)
// Reset animation after a timeout so there's enough time
// to reset the scroll with the spinner animation showing.
const timeoutId = setTimeout(() => {
setShouldShowSpinner(false)
animationRef.current?.reset()
}, RESET_ICON_TIMEOUT_MS)

return () => {
clearTimeout(timeoutId)
}
setShouldShowSpinner(false)
animationRef.current?.reset()
}
}, [isRefreshing, hitTop])

const listenerRef = useRef<string>()

useEffect(() => {
listenerRef.current = scrollAnim?.addListener(
({ value }: { value: number }) => {
Expand Down
35 changes: 30 additions & 5 deletions packages/mobile/src/components/lineup/Lineup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Status,
tippingSelectors
} from '@audius/common'
import { useFocusEffect } from '@react-navigation/native'
import { range } from 'lodash'
import type { SectionList as RNSectionList } from 'react-native'
import { Dimensions, StyleSheet, View } from 'react-native'
Expand Down Expand Up @@ -102,6 +103,8 @@ const useItemCounts = (variant: LineupVariant) =>
[variant]
)

const fallbackLineupSelector = (() => {}) as any

const styles = StyleSheet.create({
root: {
flex: 1
Expand Down Expand Up @@ -133,11 +136,13 @@ export const Lineup = ({
isFeed,
leadingElementId,
leadingElementDelineator,
lineup,
lineup: lineupProp,
lineupSelector = fallbackLineupSelector,
loadMore,
pullToRefresh,
rankIconCount = 0,
refresh,
refreshing,
refresh: refreshProp,
refreshing: refreshingProp,
showLeadingElementArtistPick = true,
start = 0,
variant = LineupVariant.MAIN,
Expand All @@ -151,6 +156,17 @@ export const Lineup = ({
const dispatch = useDispatch()
const ref = useRef<RNSectionList>(null)
const [isPastLoadThreshold, setIsPastLoadThreshold] = useState(false)
const selectedLineup = useSelector(lineupSelector)
const lineup = selectedLineup ?? lineupProp
const { status } = lineup
const refreshing = refreshingProp ?? status === Status.LOADING

const handleRefresh = useCallback(() => {
dispatch(actions.refreshInView(true))
}, [dispatch, actions])

const refresh = refreshProp ?? handleRefresh

useScrollToTop(() => {
ref.current?.scrollToLocation({
sectionIndex: 0,
Expand All @@ -159,6 +175,13 @@ export const Lineup = ({
})
}, disableTopTabScroll)

const handleInView = useCallback(() => {
dispatch(actions.setInView(true))
return () => dispatch(actions.setInView(false))
}, [dispatch, actions])

useFocusEffect(handleInView)

const itemCounts = useItemCounts(variant)

// Item count based on the current page
Expand Down Expand Up @@ -433,18 +456,20 @@ export const Lineup = ({
[isPastLoadThreshold]
)

const pullToRefreshProps =
pullToRefresh || refreshProp ? { onRefresh: refresh, refreshing } : {}

return (
<View style={styles.root}>
<SectionList
{...listProps}
{...pullToRefreshProps}
ref={ref}
onScroll={handleScroll}
ListHeaderComponent={header}
ListFooterComponent={<View style={{ height: 16 }} />}
onEndReached={handleLoadMore}
onEndReachedThreshold={LOAD_MORE_THRESHOLD}
onRefresh={refresh}
refreshing={refreshing}
sections={sections}
stickySectionHeadersEnabled={false}
keyExtractor={(item, index) => `${item?.id} ${index}`}
Expand Down
14 changes: 12 additions & 2 deletions packages/mobile/src/components/lineup/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import type {
Kind,
Lineup as LineupData,
Maybe,
LineupBaseActions
LineupBaseActions,
CommonState
} from '@audius/common'
import type { SectionListProps } from 'react-native'

Expand Down Expand Up @@ -84,7 +85,12 @@ export type LineupProps = {
/**
* The Lineup object containing entries
*/
lineup: LineupData<LineupItem>
lineup?: LineupData<LineupItem>

/**
* The Lineup selector, allowing the lineup to select lineup itself
*/
lineupSelector?: (state: CommonState) => LineupData<LineupItem>

/**
* Function called to load more entries
Expand Down Expand Up @@ -139,6 +145,10 @@ export type LineupProps = {
* This helps prevent collisions with any in-flight loading from web-app
*/
includeLineupStatus?: boolean
/**
* When `true`, add pull-to-refresh capability
*/
pullToRefresh?: boolean
} & Pick<
SectionListProps<unknown>,
'showsVerticalScrollIndicator' | 'ListEmptyComponent'
Expand Down
29 changes: 7 additions & 22 deletions packages/mobile/src/screens/feed-screen/FeedScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback } from 'react'

import {
Name,
lineupSelectors,
feedPageLineupActions as feedActions,
feedPageSelectors
} from '@audius/common'
import { useDispatch, useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'

import { Screen } from 'app/components/core'
import { Header } from 'app/components/header'
Expand All @@ -29,9 +29,6 @@ export const FeedScreen = () => {

const dispatch = useDispatch()

const feedLineup = useSelector(getFeedLineup)
const [isRefreshing, setIsRefreshing] = useState(false)

const loadMore = useCallback(
(offset: number, limit: number, overwrite: boolean) => {
dispatch(feedActions.fetchLineupMetadatas(offset, limit, overwrite))
Expand All @@ -40,32 +37,20 @@ export const FeedScreen = () => {
[dispatch]
)

useEffect(() => {
if (!feedLineup.isMetadataLoading) {
setIsRefreshing(false)
}
}, [feedLineup.isMetadataLoading])

const handleRefresh = useCallback(() => {
setIsRefreshing(true)
dispatch(feedActions.refreshInView(true))
}, [dispatch])

return (
<Screen>
<Header text={messages.header}>
<FeedFilterButton />
</Header>
<Lineup
actions={feedActions}
isFeed
pullToRefresh
delineate
lineup={feedLineup}
loadMore={loadMore}
refresh={handleRefresh}
refreshing={isRefreshing}
selfLoad
actions={feedActions}
lineupSelector={getFeedLineup}
loadMore={loadMore}
showsVerticalScrollIndicator={false}
isFeed
/>
</Screen>
)
Expand Down
24 changes: 5 additions & 19 deletions packages/mobile/src/screens/trending-screen/TrendingLineup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect } from 'react'

import {
Name,
Expand All @@ -9,7 +9,7 @@ import {
trendingPageSelectors
} from '@audius/common'
import { useNavigation } from '@react-navigation/native'
import { useDispatch, useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'

import { Lineup } from 'app/components/lineup'
import type { LineupProps } from 'app/components/lineup/types'
Expand Down Expand Up @@ -56,8 +56,6 @@ type TrendingLineupProps = BaseLineupProps & {

export const TrendingLineup = (props: TrendingLineupProps) => {
const { timeRange, ...other } = props
const trendingLineup = useSelector(selectorsMap[timeRange])
const [isRefreshing, setIsRefreshing] = useState(false)
const navigation = useNavigation()
const dispatch = useDispatch()
const trendingActions = actionsMap[timeRange]
Expand All @@ -71,17 +69,6 @@ export const TrendingLineup = (props: TrendingLineupProps) => {
return tabPressListener
}, [navigation, dispatch, timeRange])

useEffect(() => {
if (!trendingLineup.isMetadataLoading) {
setIsRefreshing(false)
}
}, [trendingLineup])

const handleRefresh = useCallback(() => {
setIsRefreshing(true)
dispatch(trendingActions.refreshInView(true))
}, [dispatch, trendingActions])

const handleLoadMore = useCallback(
(offset: number, limit: number, overwrite: boolean) => {
dispatch(trendingActions.fetchLineupMetadatas(offset, limit, overwrite))
Expand All @@ -93,12 +80,11 @@ export const TrendingLineup = (props: TrendingLineupProps) => {
return (
<Lineup
isTrending
lineup={trendingLineup}
selfLoad
pullToRefresh
lineupSelector={selectorsMap[timeRange]}
actions={trendingActions}
refresh={handleRefresh}
refreshing={isRefreshing}
loadMore={handleLoadMore}
selfLoad
{...other}
/>
)
Expand Down

0 comments on commit 98ed3f6

Please sign in to comment.