enhance: cleanup, ux/ui changes, safer approach to android's ptr

This commit is contained in:
Soxasora 2024-11-18 15:46:18 +01:00
parent 5a359feeed
commit a992426058
2 changed files with 13 additions and 36 deletions

View File

@ -2,12 +2,10 @@ import { useRouter } from 'next/router'
import { useState, useRef, useEffect, useCallback } from 'react' import { useState, useRef, useEffect, useCallback } from 'react'
import styles from './pull-to-refresh.module.css' import styles from './pull-to-refresh.module.css'
const PULL_THRESHOLD = 300 const REFRESH_THRESHOLD = 300
const REFRESH_TIMEOUT = 500
export default function PullToRefresh ({ children, android }) { export default function PullToRefresh ({ children, android }) {
const router = useRouter() const router = useRouter()
const [isRefreshing, setIsRefreshing] = useState(false)
const [pullDistance, setPullDistance] = useState(0) const [pullDistance, setPullDistance] = useState(0)
const [isPWA, setIsPWA] = useState(false) const [isPWA, setIsPWA] = useState(false)
const [isAndroid, setIsAndroid] = useState(false) const [isAndroid, setIsAndroid] = useState(false)
@ -21,9 +19,7 @@ export default function PullToRefresh ({ children, android }) {
setIsPWA(androidPWA || iosPWA) setIsPWA(androidPWA || iosPWA)
} }
useEffect(() => { useEffect(checkPWA, [])
checkPWA()
}, [])
const handleTouchStart = useCallback((e) => { const handleTouchStart = useCallback((e) => {
// don't handle if the user is not scrolling from the top of the page, is not on a PWA or if we want Android's native PTR // don't handle if the user is not scrolling from the top of the page, is not on a PWA or if we want Android's native PTR
@ -32,19 +28,15 @@ export default function PullToRefresh ({ children, android }) {
}, [isPWA, isAndroid, android]) }, [isPWA, isAndroid, android])
const handleTouchMove = useCallback((e) => { const handleTouchMove = useCallback((e) => {
if (touchStartY.current === 0) return if (touchStartY.current === 0) return // prevent unintended refresh by checking if the user has actually started touching
touchEndY.current = e.touches[0].clientY touchEndY.current = e.touches[0].clientY
setPullDistance(touchEndY.current - touchStartY.current) setPullDistance(touchEndY.current - touchStartY.current)
}, []) }, [])
const handleTouchEnd = useCallback(() => { const handleTouchEnd = useCallback(() => {
if (touchStartY.current === 0 || touchEndY.current === 0) return if (touchStartY.current === 0 || touchEndY.current === 0) return // if the user has started touch or is actually 'pulling'
if (touchEndY.current - touchStartY.current > PULL_THRESHOLD) { if (touchEndY.current - touchStartY.current > REFRESH_THRESHOLD) {
setIsRefreshing(true)
router.push(router.asPath) // reload the same path router.push(router.asPath) // reload the same path
setTimeout(() => {
setIsRefreshing(false)
}, REFRESH_TIMEOUT) // simulate loading time
} }
setPullDistance(0) // using this to reset the message behavior setPullDistance(0) // using this to reset the message behavior
touchStartY.current = 0 // avoid random refreshes by resetting touch touchStartY.current = 0 // avoid random refreshes by resetting touch
@ -54,10 +46,12 @@ export default function PullToRefresh ({ children, android }) {
useEffect(() => { useEffect(() => {
// don't handle if the user is not on a PWA or if we want Android's native PTR // don't handle if the user is not on a PWA or if we want Android's native PTR
if (!isPWA || (isAndroid && !android)) return if (!isPWA || (isAndroid && !android)) return
document.body.style.overscrollBehaviorY = 'contain' // disable Android's native PTR
document.addEventListener('touchstart', handleTouchStart) document.addEventListener('touchstart', handleTouchStart)
document.addEventListener('touchmove', handleTouchMove) document.addEventListener('touchmove', handleTouchMove)
document.addEventListener('touchend', handleTouchEnd) document.addEventListener('touchend', handleTouchEnd)
return () => { return () => {
document.body.style.overscrollBehaviorY = '' // if unmounted reset the overscroll behavior
document.removeEventListener('touchstart', handleTouchStart) document.removeEventListener('touchstart', handleTouchStart)
document.removeEventListener('touchmove', handleTouchMove) document.removeEventListener('touchmove', handleTouchMove)
document.removeEventListener('touchend', handleTouchEnd) document.removeEventListener('touchend', handleTouchEnd)
@ -65,29 +59,21 @@ export default function PullToRefresh ({ children, android }) {
}, [isPWA, isAndroid, android, handleTouchStart, handleTouchMove, handleTouchEnd]) }, [isPWA, isAndroid, android, handleTouchStart, handleTouchMove, handleTouchEnd])
const getPullMessage = () => { const getPullMessage = () => {
if (isRefreshing) return 'refreshing...' if (pullDistance > REFRESH_THRESHOLD) return 'release to refresh'
if (pullDistance > PULL_THRESHOLD) return 'release to refresh'
if (pullDistance > 0) return 'pull down to refresh' if (pullDistance > 0) return 'pull down to refresh'
return '' return ''
} }
return ( return (
<div className={android ? styles.pullToRefreshContainer : ''}> {/* android prop if true disables its native PTR */} <div>
<div <div
onTouchStart={handleTouchStart} onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove} onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd} onTouchEnd={handleTouchEnd}
> >
{pullDistance > 0 || isRefreshing <p className={`${styles.pullMessage} ${pullDistance > 50 && styles.fadeIn}`}>
? (
<>
<p className={`${styles.pullMessage} ${pullDistance > 50 || isRefreshing ? styles.fadeIn : ''}`}>
{getPullMessage()} {getPullMessage()}
</p> </p>
{isRefreshing && <div className={styles.spacer} />}
</>
)
: null}
{children} {children}
</div> </div>
</div> </div>

View File

@ -1,13 +1,9 @@
.pullToRefreshContainer {
overscroll-behavior: contain;
}
.pullMessage { .pullMessage {
position: fixed; position: fixed;
left: 50%; left: 50%;
top: -50px; top: -50px;
transform: translateX(-50%); transform: translateX(-50%);
font-size: 18px; font-size: small;
color: #a5a5a5; color: #a5a5a5;
opacity: 0; opacity: 0;
transition: opacity 0.3s ease-in-out; transition: opacity 0.3s ease-in-out;
@ -18,8 +14,3 @@
opacity: 1; opacity: 1;
top: 0; top: 0;
} }
.spacer {
position: relative;
margin-bottom: 32px;
}