diff --git a/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift b/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift index 008af4e..b314e3e 100644 --- a/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift +++ b/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift @@ -131,21 +131,24 @@ struct EntryListView: View { wasVisible.insert(entry.id) } } - .onChange(of: frame.maxY) { oldMaxY, newMaxY in - // Mark as read when entry scrolls above viewport. - // All 6 conditions must be true: + .onChange(of: frame.maxY) { _, newMaxY in + // Mark as read when entry is above viewport. + // 5 conditions must be true: // 1. Tracking active (scrolled past dynamic threshold) // 2. Scrolling downward // 3. Entry is unread // 4. Not already marked by scroll // 5. Was >=50% visible at some point (genuinely seen) - // 6. Bottom edge just crossed above viewport + // + // We check newMaxY < 0 (entry fully above viewport) + // instead of requiring an exact crossing event, because + // LazyVStack can recycle the view between onChange calls, + // causing the 0-boundary crossing to never be delivered. guard trackingActive, isScrollingDown, !entry.isRead, !markedByScroll.contains(entry.id), wasVisible.contains(entry.id), - oldMaxY >= 0, newMaxY < 0 else { return } markedByScroll.insert(entry.id)