fix: scroll mark-as-read — delta filter and threshold tuned from logs
EVIDENCE (from xcode.txt): - down=false ALWAYS: per-entry deltas are ~1-2pt per callback, but filter required >2pt. Every delta was rejected. - cumDown stuck at 129: threshold was max(100, 956*0.2) = 191. With most deltas rejected, cumulative barely grew. FIXES: 1. Delta filter: >2pt → >0.5pt for direction detection. Cumulative accumulation accepts any delta >0 (no filter). Per-entry callbacks deliver small deltas — filtering at 2pt discarded virtually all genuine scroll events. 2. Threshold: removed 20% viewport scaling, fixed at 100pt. The scaling made sense for a global offset tracker (large deltas), not per-entry tracking (small deltas). Removed debug logging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,8 +15,9 @@ struct EntryListView: View {
|
|||||||
// Safe because this view only appears on iPhone in portrait/landscape.
|
// Safe because this view only appears on iPhone in portrait/landscape.
|
||||||
private var viewportHeight: CGFloat { UIScreen.main.bounds.height }
|
private var viewportHeight: CGFloat { UIScreen.main.bounds.height }
|
||||||
|
|
||||||
// Dynamic threshold: max(100pt, 20% of viewport)
|
// Fixed threshold — per-entry deltas are small (~1-2pt each),
|
||||||
private var activationThreshold: CGFloat { max(100, viewportHeight * 0.2) }
|
// so 100pt accumulates after scrolling roughly one screenful.
|
||||||
|
private let activationThreshold: CGFloat = 100
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if vm.isLoading && vm.entries.isEmpty {
|
if vm.isLoading && vm.entries.isEmpty {
|
||||||
@@ -73,8 +74,10 @@ struct EntryListView: View {
|
|||||||
let isScrollingDown: Bool
|
let isScrollingDown: Bool
|
||||||
if let prev = prevMinY {
|
if let prev = prevMinY {
|
||||||
let delta = prev - newMinY // positive = scrolling down
|
let delta = prev - newMinY // positive = scrolling down
|
||||||
isScrollingDown = delta > 2
|
// Per-entry deltas are small (~1-2pt per callback).
|
||||||
if isScrollingDown {
|
// Accept anything > 0.5 as genuine downward scroll.
|
||||||
|
isScrollingDown = delta > 0.5
|
||||||
|
if delta > 0 {
|
||||||
cumulativeDown += delta
|
cumulativeDown += delta
|
||||||
if cumulativeDown > activationThreshold {
|
if cumulativeDown > activationThreshold {
|
||||||
trackingActive = true
|
trackingActive = true
|
||||||
@@ -96,17 +99,12 @@ struct EntryListView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Mark-as-read ---
|
// --- Mark-as-read ---
|
||||||
let debugThis = vm.entries.prefix(3).contains(where: { $0.id == entryId })
|
|
||||||
if debugThis {
|
|
||||||
print("[SCROLL] id=\(entryId) minY=\(Int(newMinY)) maxY=\(Int(frame.maxY)) down=\(isScrollingDown) active=\(trackingActive) cumDown=\(Int(cumulativeDown)) wasVis=\(wasVisible.contains(entryId)) visR=\(String(format:"%.2f",visibleRatio)) vpH=\(Int(viewportHeight)) read=\(entry.isRead) marked=\(markedByScroll.contains(entryId))")
|
|
||||||
}
|
|
||||||
guard trackingActive,
|
guard trackingActive,
|
||||||
isScrollingDown,
|
isScrollingDown,
|
||||||
!entry.isRead,
|
!entry.isRead,
|
||||||
!markedByScroll.contains(entryId),
|
!markedByScroll.contains(entryId),
|
||||||
wasVisible.contains(entryId),
|
wasVisible.contains(entryId),
|
||||||
frame.maxY < 0 else { return }
|
frame.maxY < 0 else { return }
|
||||||
if debugThis { print("[SCROLL-READ] ✅ id=\(entryId)") }
|
|
||||||
|
|
||||||
markedByScroll.insert(entryId)
|
markedByScroll.insert(entryId)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user