fix: three Reader bugs — image overflow, load more, refresh read state
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s

#22 Image overflow: added .frame(maxWidth: .infinity) before
.frame(height: 180) on AsyncImage to constrain width within card.
Card's .clipShape already clips corners.

#23 Load more not triggering: added loadMoreIfNeeded(for:) that
fires onAppear for entries 5 from the bottom. No longer relies
solely on the bottom sentinel Color.clear which could be missed.
Also increased sentinel height from 1pt to 40pt.

#24 Refresh not updating read state: flushDeferredReads() now
called before vm.refresh() in .refreshable. Deferred marks are
synced to API before re-fetching, so the server returns correct
read states. Also clears markedByScroll set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-04 13:08:27 -05:00
parent d8f0e5d845
commit 92a44faac3

View File

@@ -66,6 +66,9 @@ struct EntryListView: View {
} }
} }
.refreshable { .refreshable {
// Flush any deferred read marks before refreshing
flushDeferredReads()
markedByScroll.removeAll()
await vm.refresh() await vm.refresh()
} }
.navigationDestination(for: ReaderEntry.self) { entry in .navigationDestination(for: ReaderEntry.self) { entry in
@@ -176,6 +179,7 @@ struct EntryListView: View {
entryContextMenu(entry: entry, vm: vm) entryContextMenu(entry: entry, vm: vm)
} }
) )
.onAppear { loadMoreIfNeeded(for: entry) }
} }
loadMoreTrigger loadMoreTrigger
@@ -201,6 +205,7 @@ struct EntryListView: View {
entryContextMenu(entry: entry, vm: vm) entryContextMenu(entry: entry, vm: vm)
} }
) )
.onAppear { loadMoreIfNeeded(for: entry) }
Divider() Divider()
.padding(.leading, 36) .padding(.leading, 36)
@@ -212,14 +217,25 @@ struct EntryListView: View {
} }
} }
// Trigger loadMore when an entry near the bottom appears
private func loadMoreIfNeeded(for entry: ReaderEntry) {
let entries = vm.entries
guard entries.count >= 5 else { return }
let threshold = entries[entries.count - 5].id
if entry.id == threshold {
Task { await vm.loadMore() }
}
}
private var loadMoreTrigger: some View { private var loadMoreTrigger: some View {
Group { Group {
if vm.isLoadingMore { if vm.isLoadingMore {
ProgressView() ProgressView()
.padding() .padding()
} else { } else if vm.entries.count > 0 {
// Fallback trigger at the very bottom
Color.clear Color.clear
.frame(height: 1) .frame(height: 40)
.onAppear { .onAppear {
Task { await vm.loadMore() } Task { await vm.loadMore() }
} }
@@ -242,6 +258,7 @@ struct EntryCardView: View {
image image
.resizable() .resizable()
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity)
.frame(height: 180) .frame(height: 180)
.clipped() .clipped()
default: default: