feat(ui): replace custom pull-to-refresh with use-pull-to-refresh library for simplicity

- Remove custom usePullToRefresh hook and PullToRefreshIndicator
- Add use-pull-to-refresh library dependency
- Create simple RefreshIndicator component
- Apply pull-to-refresh to Explore and Me screens
- Simplify implementation while maintaining functionality
This commit is contained in:
Gigi
2025-10-15 09:32:25 +02:00
parent 3091ad7fd4
commit 5b2ee94062
8 changed files with 100 additions and 237 deletions

View File

@@ -18,8 +18,8 @@ import { UserSettings } from '../services/settingsService'
import BlogPostCard from './BlogPostCard'
import { HighlightItem } from './HighlightItem'
import { getCachedPosts, upsertCachedPost, setCachedPosts, getCachedHighlights, upsertCachedHighlight, setCachedHighlights } from '../services/exploreCache'
import { usePullToRefresh } from '../hooks/usePullToRefresh'
import PullToRefreshIndicator from './PullToRefreshIndicator'
import { usePullToRefresh } from 'use-pull-to-refresh'
import RefreshIndicator from './RefreshIndicator'
import { classifyHighlights } from '../utils/highlightClassification'
import { HighlightVisibility } from './HighlightsPanel'
@@ -41,7 +41,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
const [followedPubkeys, setFollowedPubkeys] = useState<Set<string>>(new Set())
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const exploreContainerRef = useRef<HTMLDivElement>(null)
const [refreshTrigger, setRefreshTrigger] = useState(0)
// Visibility filters (defaults from settings)
@@ -229,11 +228,13 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
}, [relayPool, activeAccount, refreshTrigger, eventStore, settings])
// Pull-to-refresh
const pullToRefreshState = usePullToRefresh(exploreContainerRef, {
const { isRefreshing, pullPosition } = usePullToRefresh({
onRefresh: () => {
setRefreshTrigger(prev => prev + 1)
},
isRefreshing: loading
maximumPullLength: 240,
refreshThreshold: 80,
isDisabled: !activeAccount
})
const getPostUrl = (post: BlogPostPreview) => {
@@ -393,15 +394,10 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
}
return (
<div
ref={exploreContainerRef}
className={`explore-container pull-to-refresh-container ${pullToRefreshState.isPulling ? 'is-pulling' : ''}`}
>
<PullToRefreshIndicator
isPulling={pullToRefreshState.isPulling}
pullDistance={pullToRefreshState.pullDistance}
canRefresh={pullToRefreshState.canRefresh}
isRefreshing={loading && pullToRefreshState.canRefresh}
<div className="explore-container">
<RefreshIndicator
isRefreshing={isRefreshing}
pullPosition={pullPosition}
/>
<div className="explore-header">
<h1>

View File

@@ -22,8 +22,8 @@ import { ViewMode } from './Bookmarks'
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
import { getCachedMeData, setCachedMeData, updateCachedHighlights } from '../services/meCache'
import { faBooks } from '../icons/customIcons'
import { usePullToRefresh } from '../hooks/usePullToRefresh'
import PullToRefreshIndicator from './PullToRefreshIndicator'
import { usePullToRefresh } from 'use-pull-to-refresh'
import RefreshIndicator from './RefreshIndicator'
import { getProfileUrl } from '../config/nostrGateways'
interface MeProps {
@@ -49,7 +49,6 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [viewMode, setViewMode] = useState<ViewMode>('cards')
const meContainerRef = useRef<HTMLDivElement>(null)
const [refreshTrigger, setRefreshTrigger] = useState(0)
// Update local state when prop changes
@@ -125,11 +124,13 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
}, [relayPool, viewingPubkey, isOwnProfile, activeAccount, refreshTrigger])
// Pull-to-refresh
const pullToRefreshState = usePullToRefresh(meContainerRef, {
const { isRefreshing, pullPosition } = usePullToRefresh({
onRefresh: () => {
setRefreshTrigger(prev => prev + 1)
},
isRefreshing: loading
maximumPullLength: 240,
refreshThreshold: 80,
isDisabled: !viewingPubkey
})
const handleHighlightDelete = (highlightId: string) => {
@@ -367,15 +368,10 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
}
return (
<div
ref={meContainerRef}
className={`explore-container pull-to-refresh-container ${pullToRefreshState.isPulling ? 'is-pulling' : ''}`}
>
<PullToRefreshIndicator
isPulling={pullToRefreshState.isPulling}
pullDistance={pullToRefreshState.pullDistance}
canRefresh={pullToRefreshState.canRefresh}
isRefreshing={loading && pullToRefreshState.canRefresh}
<div className="explore-container">
<RefreshIndicator
isRefreshing={isRefreshing}
pullPosition={pullPosition}
/>
<div className="explore-header">
{viewingPubkey && <AuthorCard authorPubkey={viewingPubkey} clickable={false} />}

View File

@@ -1,52 +0,0 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowDown } from '@fortawesome/free-solid-svg-icons'
interface PullToRefreshIndicatorProps {
isPulling: boolean
pullDistance: number
canRefresh: boolean
isRefreshing: boolean
threshold?: number
}
const PullToRefreshIndicator: React.FC<PullToRefreshIndicatorProps> = ({
isPulling,
pullDistance,
canRefresh,
threshold = 80
}) => {
// Only show when actively pulling, not when refreshing
if (!isPulling) return null
const opacity = Math.min(pullDistance / threshold, 1)
const rotation = (pullDistance / threshold) * 180
return (
<div
className="pull-to-refresh-indicator"
style={{
opacity,
transform: `translateY(${-20 + pullDistance / 2}px)`
}}
>
<div
className="pull-to-refresh-icon"
style={{
transform: `rotate(${rotation}deg)`
}}
>
<FontAwesomeIcon
icon={faArrowDown}
style={{ color: canRefresh ? 'var(--accent-color, #3b82f6)' : 'var(--text-secondary)' }}
/>
</div>
<div className="pull-to-refresh-text">
{canRefresh ? 'Release to refresh' : 'Pull to refresh'}
</div>
</div>
)
}
export default PullToRefreshIndicator

View File

@@ -0,0 +1,63 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowRotateRight } from '@fortawesome/free-solid-svg-icons'
interface RefreshIndicatorProps {
isRefreshing: boolean
pullPosition: number
}
const THRESHOLD = 80
/**
* Simple pull-to-refresh visual indicator
*/
const RefreshIndicator: React.FC<RefreshIndicatorProps> = ({
isRefreshing,
pullPosition
}) => {
const isVisible = isRefreshing || pullPosition > 0
if (!isVisible) return null
const opacity = Math.min(pullPosition / THRESHOLD, 1)
const translateY = isRefreshing ? THRESHOLD / 3 : pullPosition / 3
return (
<div
style={{
position: 'fixed',
top: `${translateY}px`,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 30,
opacity,
transition: isRefreshing ? 'opacity 0.2s' : 'none'
}}
>
<div
style={{
width: '32px',
height: '32px',
borderRadius: '50%',
backgroundColor: 'var(--surface-secondary, #ffffff)',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<FontAwesomeIcon
icon={faArrowRotateRight}
style={{
transform: isRefreshing ? 'none' : `rotate(${pullPosition}deg)`,
color: 'var(--accent-color, #3b82f6)'
}}
className={isRefreshing ? 'fa-spin' : ''}
/>
</div>
</div>
)
}
export default RefreshIndicator