fix: three Reader bugs — image overflow, load more, refresh read state
#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:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user