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 +

- Main View - Detail View - Add Bookmark - More Options - Share Extension - iPad View + iPhone Screenshot 1 + iPhone Screenshot 2 + iPhone Screenshot 3 + iPhone Screenshot 4 + iPhone Screenshot 5 +

+ +### iPad + +

+ iPad Screenshot 1 + iPad Screenshot 2 + iPad Screenshot 3 + iPad Screenshot 4 + iPad Screenshot 5

## 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) + } }