refactor: create centralized reading progress controller

- Add readingProgressController following the same pattern as highlightsController and writingsController
- Controller manages reading progress (kind:39802) centrally with subscriptions
- Remove duplicated reading progress loading logic from Explore, Profile, and Me components
- Components now subscribe to controller updates instead of loading data individually
- Supports incremental sync and force reload
- Improves efficiency and maintainability
This commit is contained in:
Gigi
2025-10-19 11:06:57 +02:00
parent 80b26abff2
commit 5fd8976097
4 changed files with 286 additions and 97 deletions

View File

@@ -30,10 +30,7 @@ import { useStoreTimeline } from '../hooks/useStoreTimeline'
import { dedupeHighlightsById, dedupeWritingsByReplaceable } from '../utils/dedupe'
import { writingsController } from '../services/writingsController'
import { nostrverseWritingsController } from '../services/nostrverseWritingsController'
import { queryEvents } from '../services/dataFetch'
import { processReadingProgress } from '../services/readingDataProcessor'
import { ReadItem } from '../services/readsService'
import { RELAYS } from '../config/relays'
import { readingProgressController } from '../services/readingProgressController'
const { getArticleTitle, getArticleImage, getArticlePublished, getArticleSummary } = Helpers
@@ -177,40 +174,32 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
return () => unsub()
}, [])
// Load reading progress data
// Subscribe to reading progress controller
useEffect(() => {
// Get initial state immediately
setReadingProgressMap(readingProgressController.getProgressMap())
// Subscribe to updates
const unsubProgress = readingProgressController.onProgress(setReadingProgressMap)
return () => {
unsubProgress()
}
}, [])
// Load reading progress data when logged in
useEffect(() => {
if (!activeAccount?.pubkey) {
setReadingProgressMap(new Map())
return
}
const loadReadingProgress = async () => {
try {
const progressEvents = await queryEvents(
relayPool,
{ kinds: [KINDS.ReadingProgress], authors: [activeAccount.pubkey] },
{ relayUrls: RELAYS }
)
const readsMap = new Map<string, ReadItem>()
processReadingProgress(progressEvents, readsMap)
// Convert to naddr -> progress map
const progressMap = new Map<string, number>()
for (const [id, item] of readsMap.entries()) {
if (item.readingProgress !== undefined && item.type === 'article') {
progressMap.set(id, item.readingProgress)
}
}
setReadingProgressMap(progressMap)
} catch (err) {
console.error('Failed to load reading progress:', err)
}
}
loadReadingProgress()
}, [activeAccount?.pubkey, relayPool, refreshTrigger])
readingProgressController.start({
relayPool,
eventStore,
pubkey: activeAccount.pubkey,
force: refreshTrigger > 0
})
}, [activeAccount?.pubkey, relayPool, eventStore, refreshTrigger])
// Update visibility when settings/login state changes
useEffect(() => {

View File

@@ -31,10 +31,7 @@ import { filterByReadingProgress } from '../utils/readingProgressUtils'
import { deriveReadsFromBookmarks } from '../utils/readsFromBookmarks'
import { deriveLinksFromBookmarks } from '../utils/linksFromBookmarks'
import { mergeReadItem } from '../utils/readItemMerge'
import { queryEvents } from '../services/dataFetch'
import { processReadingProgress } from '../services/readingDataProcessor'
import { RELAYS } from '../config/relays'
import { KINDS } from '../config/kinds'
import { readingProgressController } from '../services/readingProgressController'
interface MeProps {
relayPool: RelayPool
@@ -156,40 +153,32 @@ const Me: React.FC<MeProps> = ({
}
}
// Subscribe to reading progress controller
useEffect(() => {
// Get initial state immediately
setReadingProgressMap(readingProgressController.getProgressMap())
// Subscribe to updates
const unsubProgress = readingProgressController.onProgress(setReadingProgressMap)
return () => {
unsubProgress()
}
}, [])
// Load reading progress data for writings tab
useEffect(() => {
if (!viewingPubkey) {
setReadingProgressMap(new Map())
return
}
const loadReadingProgress = async () => {
try {
const progressEvents = await queryEvents(
relayPool,
{ kinds: [KINDS.ReadingProgress], authors: [viewingPubkey] },
{ relayUrls: RELAYS }
)
const readsMap = new Map<string, ReadItem>()
processReadingProgress(progressEvents, readsMap)
// Convert to naddr -> progress map
const progressMap = new Map<string, number>()
for (const [id, item] of readsMap.entries()) {
if (item.readingProgress !== undefined && item.type === 'article') {
progressMap.set(id, item.readingProgress)
}
}
setReadingProgressMap(progressMap)
} catch (err) {
console.error('Failed to load reading progress:', err)
}
}
loadReadingProgress()
}, [viewingPubkey, relayPool, refreshTrigger])
readingProgressController.start({
relayPool,
eventStore,
pubkey: viewingPubkey,
force: refreshTrigger > 0
})
}, [viewingPubkey, relayPool, eventStore, refreshTrigger])
// Tab-specific loading functions
const loadHighlightsTab = async () => {

View File

@@ -19,9 +19,7 @@ import { toBlogPostPreview } from '../utils/toBlogPostPreview'
import { usePullToRefresh } from 'use-pull-to-refresh'
import RefreshIndicator from './RefreshIndicator'
import { Hooks } from 'applesauce-react'
import { queryEvents } from '../services/dataFetch'
import { processReadingProgress } from '../services/readingDataProcessor'
import { ReadItem } from '../services/readsService'
import { readingProgressController } from '../services/readingProgressController'
interface ProfileProps {
relayPool: RelayPool
@@ -66,40 +64,32 @@ const Profile: React.FC<ProfileProps> = ({
}
}, [propActiveTab])
// Load reading progress data for logged-in user
// Subscribe to reading progress controller
useEffect(() => {
// Get initial state immediately
setReadingProgressMap(readingProgressController.getProgressMap())
// Subscribe to updates
const unsubProgress = readingProgressController.onProgress(setReadingProgressMap)
return () => {
unsubProgress()
}
}, [])
// Load reading progress data when logged in
useEffect(() => {
if (!activeAccount?.pubkey) {
setReadingProgressMap(new Map())
return
}
const loadReadingProgress = async () => {
try {
const progressEvents = await queryEvents(
relayPool,
{ kinds: [KINDS.ReadingProgress], authors: [activeAccount.pubkey] },
{ relayUrls: RELAYS }
)
const readsMap = new Map<string, ReadItem>()
processReadingProgress(progressEvents, readsMap)
// Convert to naddr -> progress map
const progressMap = new Map<string, number>()
for (const [id, item] of readsMap.entries()) {
if (item.readingProgress !== undefined && item.type === 'article') {
progressMap.set(id, item.readingProgress)
}
}
setReadingProgressMap(progressMap)
} catch (err) {
console.error('Failed to load reading progress:', err)
}
}
loadReadingProgress()
}, [activeAccount?.pubkey, relayPool, refreshTrigger])
readingProgressController.start({
relayPool,
eventStore,
pubkey: activeAccount.pubkey,
force: refreshTrigger > 0
})
}, [activeAccount?.pubkey, relayPool, eventStore, refreshTrigger])
// Background fetch to populate event store (non-blocking)
useEffect(() => {