Refactor the scroll-to-top UX
parent
9673225f78
commit
4e1876fe85
|
@ -1,28 +1,50 @@
|
||||||
import {useState} from 'react'
|
import {useState, useCallback, useRef} from 'react'
|
||||||
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
|
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
|
||||||
import {RootStoreModel} from 'state/index'
|
import {RootStoreModel} from 'state/index'
|
||||||
|
import {s} from 'lib/styles'
|
||||||
|
|
||||||
export type onMomentumScrollEndCb = (
|
|
||||||
event: NativeSyntheticEvent<NativeScrollEvent>,
|
|
||||||
) => void
|
|
||||||
export type OnScrollCb = (
|
export type OnScrollCb = (
|
||||||
event: NativeSyntheticEvent<NativeScrollEvent>,
|
event: NativeSyntheticEvent<NativeScrollEvent>,
|
||||||
) => void
|
) => void
|
||||||
|
export type ResetCb = () => void
|
||||||
|
|
||||||
export function useOnMainScroll(store: RootStoreModel) {
|
export function useOnMainScroll(
|
||||||
let [lastY, setLastY] = useState(0)
|
store: RootStoreModel,
|
||||||
let isMinimal = store.shell.minimalShellMode
|
): [OnScrollCb, boolean, ResetCb] {
|
||||||
return function onMainScroll(event: NativeSyntheticEvent<NativeScrollEvent>) {
|
let lastY = useRef(0)
|
||||||
|
let [isScrolledDown, setIsScrolledDown] = useState(false)
|
||||||
|
return [
|
||||||
|
useCallback(
|
||||||
|
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
const y = event.nativeEvent.contentOffset.y
|
const y = event.nativeEvent.contentOffset.y
|
||||||
const dy = y - (lastY || 0)
|
const dy = y - (lastY.current || 0)
|
||||||
setLastY(y)
|
lastY.current = y
|
||||||
|
|
||||||
if (!isMinimal && y > 10 && dy > 10) {
|
if (!store.shell.minimalShellMode && y > 10 && dy > 10) {
|
||||||
store.shell.setMinimalShellMode(true)
|
store.shell.setMinimalShellMode(true)
|
||||||
isMinimal = true
|
} else if (store.shell.minimalShellMode && (y <= 10 || dy < -10)) {
|
||||||
} else if (isMinimal && (y <= 10 || dy < -10)) {
|
|
||||||
store.shell.setMinimalShellMode(false)
|
store.shell.setMinimalShellMode(false)
|
||||||
isMinimal = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isScrolledDown &&
|
||||||
|
event.nativeEvent.contentOffset.y > s.window.height
|
||||||
|
) {
|
||||||
|
setIsScrolledDown(true)
|
||||||
|
} else if (
|
||||||
|
isScrolledDown &&
|
||||||
|
event.nativeEvent.contentOffset.y < s.window.height
|
||||||
|
) {
|
||||||
|
setIsScrolledDown(false)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[store, isScrolledDown],
|
||||||
|
),
|
||||||
|
isScrolledDown,
|
||||||
|
useCallback(() => {
|
||||||
|
setIsScrolledDown(false)
|
||||||
|
store.shell.setMinimalShellMode(false)
|
||||||
|
lastY.current = 1e8 // NOTE we set this very high so that the onScroll logic works right -prf
|
||||||
|
}, [store, setIsScrolledDown]),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,6 +154,7 @@ export const Feed = observer(function Feed({
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={0.6}
|
onEndReachedThreshold={0.6}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
|
scrollEventThrottle={100}
|
||||||
contentContainerStyle={s.contentContainer}
|
contentContainerStyle={s.contentContainer}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {PostsFeedModel} from 'state/models/feeds/posts'
|
import {PostsFeedModel} from 'state/models/feeds/posts'
|
||||||
import {FeedSlice} from './FeedSlice'
|
import {FeedSlice} from './FeedSlice'
|
||||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||||
import {OnScrollCb, onMomentumScrollEndCb} from 'lib/hooks/useOnMainScroll'
|
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {useAnalytics} from 'lib/analytics'
|
import {useAnalytics} from 'lib/analytics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -47,7 +47,6 @@ export const Feed = observer(function Feed({
|
||||||
onPressTryAgain?: () => void
|
onPressTryAgain?: () => void
|
||||||
onScroll?: OnScrollCb
|
onScroll?: OnScrollCb
|
||||||
scrollEventThrottle?: number
|
scrollEventThrottle?: number
|
||||||
onMomentumScrollEnd?: onMomentumScrollEndCb
|
|
||||||
renderEmptyState?: () => JSX.Element
|
renderEmptyState?: () => JSX.Element
|
||||||
testID?: string
|
testID?: string
|
||||||
headerOffset?: number
|
headerOffset?: number
|
||||||
|
|
|
@ -47,7 +47,7 @@ const styles = StyleSheet.create({
|
||||||
outer: {
|
outer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
right: 28,
|
right: 24,
|
||||||
bottom: 94,
|
bottom: 94,
|
||||||
width: 60,
|
width: 60,
|
||||||
height: 60,
|
height: 60,
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity} from 'react-native'
|
import {StyleSheet, TouchableOpacity} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import LinearGradient from 'react-native-linear-gradient'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {Text} from '../text/Text'
|
|
||||||
import {colors, gradients} from 'lib/styles'
|
|
||||||
import {clamp} from 'lodash'
|
import {clamp} from 'lodash'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
|
||||||
const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20}
|
const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20}
|
||||||
|
|
||||||
export const LoadLatestBtn = observer(
|
export const LoadLatestBtn = observer(
|
||||||
({onPress, label}: {onPress: () => void; label: string}) => {
|
({onPress, label}: {onPress: () => void; label: string}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
|
const pal = usePalette('default')
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.loadLatest,
|
styles.loadLatest,
|
||||||
|
pal.borderDark,
|
||||||
|
pal.view,
|
||||||
!store.shell.minimalShellMode && {
|
!store.shell.minimalShellMode && {
|
||||||
bottom: 60 + clamp(safeAreaInsets.bottom, 15, 30),
|
bottom: 60 + clamp(safeAreaInsets.bottom, 15, 30),
|
||||||
},
|
},
|
||||||
|
@ -26,16 +28,8 @@ export const LoadLatestBtn = observer(
|
||||||
hitSlop={HITSLOP}
|
hitSlop={HITSLOP}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={label}
|
accessibilityLabel={label}
|
||||||
accessibilityHint={label}>
|
accessibilityHint="">
|
||||||
<LinearGradient
|
<FontAwesomeIcon icon="angle-up" color={pal.colors.text} size={19} />
|
||||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={styles.loadLatestInner}>
|
|
||||||
<Text type="md-bold" style={styles.loadLatestText}>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -44,19 +38,14 @@ export const LoadLatestBtn = observer(
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
loadLatest: {
|
loadLatest: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 20,
|
left: 18,
|
||||||
bottom: 35,
|
bottom: 35,
|
||||||
shadowColor: '#000',
|
borderWidth: 1,
|
||||||
shadowOpacity: 0.3,
|
width: 52,
|
||||||
shadowOffset: {width: 0, height: 1},
|
height: 52,
|
||||||
},
|
borderRadius: 26,
|
||||||
loadLatestInner: {
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
paddingHorizontal: 14,
|
alignItems: 'center',
|
||||||
paddingVertical: 10,
|
justifyContent: 'center',
|
||||||
borderRadius: 30,
|
|
||||||
},
|
|
||||||
loadLatestText: {
|
|
||||||
color: colors.white,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,13 +20,13 @@ import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
import {Button} from 'view/com/util/forms/Button'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import * as Toast from 'view/com/util/Toast'
|
import * as Toast from 'view/com/util/Toast'
|
||||||
import {isDesktopWeb, isWeb} from 'platform/detection'
|
import {isDesktopWeb} from 'platform/detection'
|
||||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||||
import {shareUrl} from 'lib/sharing'
|
import {shareUrl} from 'lib/sharing'
|
||||||
import {toShareUrl} from 'lib/strings/url-helpers'
|
import {toShareUrl} from 'lib/strings/url-helpers'
|
||||||
import {Haptics} from 'lib/haptics'
|
import {Haptics} from 'lib/haptics'
|
||||||
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {OnScrollCb, onMomentumScrollEndCb} from 'lib/hooks/useOnMainScroll'
|
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'>
|
||||||
export const CustomFeedScreen = withAuthRequired(
|
export const CustomFeedScreen = withAuthRequired(
|
||||||
|
@ -48,7 +48,8 @@ export const CustomFeedScreen = withAuthRequired(
|
||||||
return feed
|
return feed
|
||||||
}, [store, uri])
|
}, [store, uri])
|
||||||
const isPinned = store.me.savedFeeds.isPinned(uri)
|
const isPinned = store.me.savedFeeds.isPinned(uri)
|
||||||
const [allowScrollToTop, setAllowScrollToTop] = useState(false)
|
const [onMainScroll, isScrolledDown, resetMainScroll] =
|
||||||
|
useOnMainScroll(store)
|
||||||
useSetTitle(currentFeed?.displayName)
|
useSetTitle(currentFeed?.displayName)
|
||||||
|
|
||||||
const onToggleSaved = React.useCallback(async () => {
|
const onToggleSaved = React.useCallback(async () => {
|
||||||
|
@ -66,6 +67,7 @@ export const CustomFeedScreen = withAuthRequired(
|
||||||
store.log.error('Failed up update feeds', {err})
|
store.log.error('Failed up update feeds', {err})
|
||||||
}
|
}
|
||||||
}, [store, currentFeed])
|
}, [store, currentFeed])
|
||||||
|
|
||||||
const onToggleLiked = React.useCallback(async () => {
|
const onToggleLiked = React.useCallback(async () => {
|
||||||
Haptics.default()
|
Haptics.default()
|
||||||
try {
|
try {
|
||||||
|
@ -81,6 +83,7 @@ export const CustomFeedScreen = withAuthRequired(
|
||||||
store.log.error('Failed up toggle like', {err})
|
store.log.error('Failed up toggle like', {err})
|
||||||
}
|
}
|
||||||
}, [store, currentFeed])
|
}, [store, currentFeed])
|
||||||
|
|
||||||
const onTogglePinned = React.useCallback(async () => {
|
const onTogglePinned = React.useCallback(async () => {
|
||||||
Haptics.default()
|
Haptics.default()
|
||||||
store.me.savedFeeds.togglePinnedFeed(currentFeed!).catch(e => {
|
store.me.savedFeeds.togglePinnedFeed(currentFeed!).catch(e => {
|
||||||
|
@ -88,11 +91,17 @@ export const CustomFeedScreen = withAuthRequired(
|
||||||
store.log.error('Failed to toggle pinned feed', {e})
|
store.log.error('Failed to toggle pinned feed', {e})
|
||||||
})
|
})
|
||||||
}, [store, currentFeed])
|
}, [store, currentFeed])
|
||||||
|
|
||||||
const onPressShare = React.useCallback(() => {
|
const onPressShare = React.useCallback(() => {
|
||||||
const url = toShareUrl(`/profile/${name}/feed/${rkey}`)
|
const url = toShareUrl(`/profile/${name}/feed/${rkey}`)
|
||||||
shareUrl(url)
|
shareUrl(url)
|
||||||
}, [name, rkey])
|
}, [name, rkey])
|
||||||
|
|
||||||
|
const onScrollToTop = React.useCallback(() => {
|
||||||
|
scrollElRef.current?.scrollToOffset({offset: 0, animated: true})
|
||||||
|
resetMainScroll()
|
||||||
|
}, [scrollElRef, resetMainScroll])
|
||||||
|
|
||||||
const renderHeaderBtns = React.useCallback(() => {
|
const renderHeaderBtns = React.useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.headerBtns}>
|
<View style={styles.headerBtns}>
|
||||||
|
@ -220,15 +229,17 @@ export const CustomFeedScreen = withAuthRequired(
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
<View style={styles.headerDetailsFooter}>
|
<View style={styles.headerDetailsFooter}>
|
||||||
|
{currentFeed ? (
|
||||||
<TextLink
|
<TextLink
|
||||||
type="md-medium"
|
type="md-medium"
|
||||||
style={pal.textLight}
|
style={pal.textLight}
|
||||||
href={`/profile/${name}/feed/${rkey}/liked-by`}
|
href={`/profile/${name}/feed/${rkey}/liked-by`}
|
||||||
text={`Liked by ${currentFeed?.data.likeCount} ${pluralize(
|
text={`Liked by ${currentFeed.data.likeCount} ${pluralize(
|
||||||
currentFeed?.data.likeCount || 0,
|
currentFeed?.data.likeCount || 0,
|
||||||
'user',
|
'user',
|
||||||
)}`}
|
)}`}
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
type={'default'}
|
type={'default'}
|
||||||
accessibilityLabel={
|
accessibilityLabel={
|
||||||
|
@ -267,46 +278,19 @@ export const CustomFeedScreen = withAuthRequired(
|
||||||
onTogglePinned,
|
onTogglePinned,
|
||||||
])
|
])
|
||||||
|
|
||||||
const onMomentumScrollEnd: onMomentumScrollEndCb = React.useCallback(
|
|
||||||
event => {
|
|
||||||
console.log('onMomentumScrollEnd')
|
|
||||||
if (event.nativeEvent.contentOffset.y > s.window.height * 3) {
|
|
||||||
setAllowScrollToTop(true)
|
|
||||||
} else {
|
|
||||||
setAllowScrollToTop(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
const onScroll: OnScrollCb = React.useCallback(event => {
|
|
||||||
// since onMomentumScrollEnd is not supported in react-native-web, we have to use onScroll which fires more often so is not desirable on mobile
|
|
||||||
if (isWeb) {
|
|
||||||
if (event.nativeEvent.contentOffset.y > s.window.height * 2) {
|
|
||||||
setAllowScrollToTop(true)
|
|
||||||
} else {
|
|
||||||
setAllowScrollToTop(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
<ViewHeader title="" renderButton={renderHeaderBtns} />
|
<ViewHeader title="" renderButton={renderHeaderBtns} />
|
||||||
<Feed
|
<Feed
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
feed={algoFeed}
|
feed={algoFeed}
|
||||||
onMomentumScrollEnd={onMomentumScrollEnd}
|
onScroll={onMainScroll}
|
||||||
onScroll={onScroll} // same logic as onMomentumScrollEnd but for web
|
scrollEventThrottle={100}
|
||||||
ListHeaderComponent={renderListHeaderComponent}
|
ListHeaderComponent={renderListHeaderComponent}
|
||||||
extraData={[uri, isPinned]}
|
extraData={[uri, isPinned]}
|
||||||
/>
|
/>
|
||||||
{allowScrollToTop ? (
|
{isScrolledDown ? (
|
||||||
<LoadLatestBtn
|
<LoadLatestBtn onPress={onScrollToTop} label="Scroll to top" />
|
||||||
onPress={() => {
|
|
||||||
scrollElRef.current?.scrollToOffset({offset: 0, animated: true})
|
|
||||||
}}
|
|
||||||
label="Scroll to top"
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -150,7 +150,8 @@ const FeedPage = observer(
|
||||||
renderEmptyState?: () => JSX.Element
|
renderEmptyState?: () => JSX.Element
|
||||||
}) => {
|
}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const onMainScroll = useOnMainScroll(store)
|
const [onMainScroll, isScrolledDown, resetMainScroll] =
|
||||||
|
useOnMainScroll(store)
|
||||||
const {screen, track} = useAnalytics()
|
const {screen, track} = useAnalytics()
|
||||||
const scrollElRef = React.useRef<FlatList>(null)
|
const scrollElRef = React.useRef<FlatList>(null)
|
||||||
const {appState} = useAppState({
|
const {appState} = useAppState({
|
||||||
|
@ -178,12 +179,13 @@ const FeedPage = observer(
|
||||||
|
|
||||||
const scrollToTop = React.useCallback(() => {
|
const scrollToTop = React.useCallback(() => {
|
||||||
scrollElRef.current?.scrollToOffset({offset: -HEADER_OFFSET})
|
scrollElRef.current?.scrollToOffset({offset: -HEADER_OFFSET})
|
||||||
}, [scrollElRef])
|
resetMainScroll()
|
||||||
|
}, [scrollElRef, resetMainScroll])
|
||||||
|
|
||||||
const onSoftReset = React.useCallback(() => {
|
const onSoftReset = React.useCallback(() => {
|
||||||
if (isPageFocused) {
|
if (isPageFocused) {
|
||||||
feed.refresh()
|
|
||||||
scrollToTop()
|
scrollToTop()
|
||||||
|
feed.refresh()
|
||||||
}
|
}
|
||||||
}, [isPageFocused, scrollToTop, feed])
|
}, [isPageFocused, scrollToTop, feed])
|
||||||
|
|
||||||
|
@ -254,10 +256,11 @@ const FeedPage = observer(
|
||||||
showPostFollowBtn
|
showPostFollowBtn
|
||||||
onPressTryAgain={onPressTryAgain}
|
onPressTryAgain={onPressTryAgain}
|
||||||
onScroll={onMainScroll}
|
onScroll={onMainScroll}
|
||||||
|
scrollEventThrottle={100}
|
||||||
renderEmptyState={renderEmptyState}
|
renderEmptyState={renderEmptyState}
|
||||||
headerOffset={HEADER_OFFSET}
|
headerOffset={HEADER_OFFSET}
|
||||||
/>
|
/>
|
||||||
{feed.hasNewLatest && !feed.isRefreshing && (
|
{isScrolledDown && (
|
||||||
<LoadLatestBtn onPress={onPressLoadLatest} label="Load new posts" />
|
<LoadLatestBtn onPress={onPressLoadLatest} label="Load new posts" />
|
||||||
)}
|
)}
|
||||||
<FAB
|
<FAB
|
||||||
|
|
|
@ -25,7 +25,8 @@ type Props = NativeStackScreenProps<
|
||||||
export const NotificationsScreen = withAuthRequired(
|
export const NotificationsScreen = withAuthRequired(
|
||||||
observer(({}: Props) => {
|
observer(({}: Props) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const onMainScroll = useOnMainScroll(store)
|
const [onMainScroll, isScrolledDown, resetMainScroll] =
|
||||||
|
useOnMainScroll(store)
|
||||||
const scrollElRef = React.useRef<FlatList>(null)
|
const scrollElRef = React.useRef<FlatList>(null)
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
|
|
||||||
|
@ -37,7 +38,8 @@ export const NotificationsScreen = withAuthRequired(
|
||||||
|
|
||||||
const scrollToTop = React.useCallback(() => {
|
const scrollToTop = React.useCallback(() => {
|
||||||
scrollElRef.current?.scrollToOffset({offset: 0})
|
scrollElRef.current?.scrollToOffset({offset: 0})
|
||||||
}, [scrollElRef])
|
resetMainScroll()
|
||||||
|
}, [scrollElRef, resetMainScroll])
|
||||||
|
|
||||||
const onPressLoadLatest = React.useCallback(() => {
|
const onPressLoadLatest = React.useCallback(() => {
|
||||||
scrollToTop()
|
scrollToTop()
|
||||||
|
@ -96,9 +98,11 @@ export const NotificationsScreen = withAuthRequired(
|
||||||
onScroll={onMainScroll}
|
onScroll={onMainScroll}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
/>
|
/>
|
||||||
{store.me.notifications.hasNewLatest &&
|
{isScrolledDown && (
|
||||||
!store.me.notifications.isRefreshing && (
|
<LoadLatestBtn
|
||||||
<LoadLatestBtn onPress={onPressLoadLatest} label="Load new notifications" />
|
onPress={onPressLoadLatest}
|
||||||
|
label="Load new notifications"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const SearchScreen = withAuthRequired(
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const scrollViewRef = React.useRef<ScrollView>(null)
|
const scrollViewRef = React.useRef<ScrollView>(null)
|
||||||
const flatListRef = React.useRef<FlatList>(null)
|
const flatListRef = React.useRef<FlatList>(null)
|
||||||
const onMainScroll = useOnMainScroll(store)
|
const [onMainScroll] = useOnMainScroll(store)
|
||||||
const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
|
const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
|
||||||
const [query, setQuery] = React.useState<string>('')
|
const [query, setQuery] = React.useState<string>('')
|
||||||
const autocompleteView = React.useMemo<UserAutocompleteModel>(
|
const autocompleteView = React.useMemo<UserAutocompleteModel>(
|
||||||
|
|
Loading…
Reference in New Issue