diff --git a/README.md b/README.md
index 60832f6..99e4f80 100644
--- a/README.md
+++ b/README.md
@@ -9,13 +9,24 @@ https://codeberg.org/readeck/readeck
## Screenshots
+### iPhone
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+### iPad
+
+
+
+
+
+
+
## Download
diff --git a/readeck.xcodeproj/project.pbxproj b/readeck.xcodeproj/project.pbxproj
index 76967da..893e0b2 100644
--- a/readeck.xcodeproj/project.pbxproj
+++ b/readeck.xcodeproj/project.pbxproj
@@ -83,6 +83,7 @@
Data/CoreData/CoreDataManager.swift,
"Data/Extensions/NSManagedObjectContext+SafeFetch.swift",
Data/KeychainHelper.swift,
+ Data/Utils/LabelUtils.swift,
Domain/Model/Bookmark.swift,
Domain/Model/BookmarkLabel.swift,
Logger.swift,
diff --git a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift
index 105eabe..8029622 100644
--- a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift
+++ b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift
@@ -64,8 +64,7 @@ struct BookmarkDetailView: View {
})
.frame(height: webViewHeight)
.cornerRadius(14)
- .padding(.horizontal, 4)
- .animation(.easeInOut, value: webViewHeight)
+ .padding(.horizontal, 4)
} else if viewModel.isLoadingArticle {
ProgressView("Loading article...")
.frame(maxWidth: .infinity, alignment: .center)
diff --git a/readeck/UI/Components/WebView.swift b/readeck/UI/Components/WebView.swift
index 382f1a3..22cd835 100644
--- a/readeck/UI/Components/WebView.swift
+++ b/readeck/UI/Components/WebView.swift
@@ -228,9 +228,24 @@ struct WebView: UIViewRepresentable {
\(htmlContent)
@@ -284,9 +314,13 @@ struct WebView: UIViewRepresentable {
class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var onHeightChange: ((CGFloat) -> Void)?
var onScroll: ((Double) -> Void)?
- var hasHeightUpdate: Bool = false
var isScrolling: Bool = false
var scrollEndTimer: Timer?
+ var heightUpdateTimer: Timer?
+ var lastHeight: CGFloat = 0
+ var pendingHeight: CGFloat = 0
+ var scrollVelocity: Double = 0
+ var lastScrollTime: Date = Date()
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
@@ -302,26 +336,75 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "heightUpdate", let height = message.body as? CGFloat {
DispatchQueue.main.async {
- // Block height updates during active scrolling to prevent flicker
- if !self.isScrolling && !self.hasHeightUpdate {
- self.onHeightChange?(height)
- self.hasHeightUpdate = true
- }
+ self.handleHeightUpdate(height: height)
}
}
if message.name == "scrollProgress", let progress = message.body as? Double {
DispatchQueue.main.async {
- // Track scrolling state
- self.isScrolling = true
-
- // Reset scrolling state after scroll ends
- self.scrollEndTimer?.invalidate()
- self.scrollEndTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in
- self.isScrolling = false
- }
-
- self.onScroll?(progress)
+ self.handleScrollProgress(progress: progress)
}
}
}
+
+ private func handleHeightUpdate(height: CGFloat) {
+ // Store the pending height
+ pendingHeight = height
+
+ // If we're actively scrolling, defer the height update
+ if isScrolling {
+ return
+ }
+
+ // Apply height update immediately if not scrolling
+ applyHeightUpdate(height: height)
+ }
+
+ private func handleScrollProgress(progress: Double) {
+ let now = Date()
+ let timeDelta = now.timeIntervalSince(lastScrollTime)
+
+ // Calculate scroll velocity to detect fast scrolling
+ if timeDelta > 0 {
+ scrollVelocity = abs(progress) / timeDelta
+ }
+
+ lastScrollTime = now
+ isScrolling = true
+
+ // Longer delay for scroll end detection, especially during fast scrolling
+ let scrollEndDelay: TimeInterval = scrollVelocity > 2.0 ? 0.8 : 0.5
+
+ scrollEndTimer?.invalidate()
+ scrollEndTimer = Timer.scheduledTimer(withTimeInterval: scrollEndDelay, repeats: false) { [weak self] _ in
+ self?.handleScrollEnd()
+ }
+
+ onScroll?(progress)
+ }
+
+ private func handleScrollEnd() {
+ isScrolling = false
+ scrollVelocity = 0
+
+ // Apply any pending height update after scrolling ends
+ if pendingHeight != lastHeight && pendingHeight > 0 {
+ // Add small delay to ensure scroll has fully stopped
+ heightUpdateTimer?.invalidate()
+ heightUpdateTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { [weak self] _ in
+ guard let self = self else { return }
+ self.applyHeightUpdate(height: self.pendingHeight)
+ }
+ }
+ }
+
+ private func applyHeightUpdate(height: CGFloat) {
+ // Only update if height actually changed significantly
+ let heightDifference = abs(height - lastHeight)
+ if heightDifference < 5 { // Ignore tiny height changes that cause flicker
+ return
+ }
+
+ lastHeight = height
+ onHeightChange?(height)
+ }
}