diff --git a/README.md b/README.md index 9e59c2d..60832f6 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,15 @@ https://codeberg.org/readeck/readeck iPad View

-## TestFlight Beta Access +## Download -You can now join the public TestFlight beta for the Readeck iOS app: +### App Store (Stable Releases) +The official app is available on the App Store with stable, tested releases: + +[Download Readeck on the App Store](https://apps.apple.com/de/app/readeck/id6748764703) + +### TestFlight Beta Access (Early Releases) +For early access to new features and beta versions (use with caution): [Join the Readeck Beta on TestFlight](https://testflight.apple.com/join/cV55mKsR) diff --git a/readeck/Data/CoreData/CoreDataManager.swift b/readeck/Data/CoreData/CoreDataManager.swift index 0eeb145..b5b6593 100644 --- a/readeck/Data/CoreData/CoreDataManager.swift +++ b/readeck/Data/CoreData/CoreDataManager.swift @@ -50,6 +50,16 @@ class CoreDataManager { return persistentContainer.viewContext } + var mainContext: NSManagedObjectContext { + return persistentContainer.viewContext + } + + func newBackgroundContext() -> NSManagedObjectContext { + let context = persistentContainer.newBackgroundContext() + context.automaticallyMergesChangesFromParent = true + return context + } + func save() { if context.hasChanges { do { diff --git a/readeck/Data/Repository/LabelsRepository.swift b/readeck/Data/Repository/LabelsRepository.swift index fda950e..bb73e26 100644 --- a/readeck/Data/Repository/LabelsRepository.swift +++ b/readeck/Data/Repository/LabelsRepository.swift @@ -1,7 +1,7 @@ import Foundation import CoreData -class LabelsRepository: PLabelsRepository { +class LabelsRepository: PLabelsRepository, @unchecked Sendable { private let api: PAPI private let coreDataManager = CoreDataManager.shared @@ -17,27 +17,28 @@ class LabelsRepository: PLabelsRepository { } func saveLabels(_ dtos: [BookmarkLabelDto]) async throws { - for dto in dtos { - if !tagExists(name: dto.name) { - dto.toEntity(context: coreDataManager.context) + let backgroundContext = coreDataManager.newBackgroundContext() + + try await backgroundContext.perform { [weak self] in + guard let self = self else { return } + for dto in dtos { + if !self.tagExists(name: dto.name, in: backgroundContext) { + dto.toEntity(context: backgroundContext) + } } + try backgroundContext.save() } - try coreDataManager.context.save() } - private func tagExists(name: String) -> Bool { + private func tagExists(name: String, in context: NSManagedObjectContext) -> Bool { let fetchRequest: NSFetchRequest = TagEntity.fetchRequest() fetchRequest.predicate = NSPredicate(format: "name == %@", name) - var exists = false - coreDataManager.context.performAndWait { - do { - let results = try coreDataManager.context.fetch(fetchRequest) - exists = !results.isEmpty - } catch { - exists = false - } + do { + let count = try context.count(for: fetchRequest) + return count > 0 + } catch { + return false } - return exists } } diff --git a/readeck/UI/Components/WebView.swift b/readeck/UI/Components/WebView.swift index 2c48860..382f1a3 100644 --- a/readeck/UI/Components/WebView.swift +++ b/readeck/UI/Components/WebView.swift @@ -285,6 +285,8 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler var onHeightChange: ((CGFloat) -> Void)? var onScroll: ((Double) -> Void)? var hasHeightUpdate: Bool = false + var isScrolling: Bool = false + var scrollEndTimer: Timer? func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { @@ -300,7 +302,8 @@ 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 { - if self.hasHeightUpdate == false { + // Block height updates during active scrolling to prevent flicker + if !self.isScrolling && !self.hasHeightUpdate { self.onHeightChange?(height) self.hasHeightUpdate = true } @@ -308,6 +311,15 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler } 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) } }