fix: Improve Core Data thread safety and resolve scrolling flicker
- Add background context support to CoreDataManager - Fix TagEntity threading crashes in LabelsRepository - Prevent WebView height updates during scrolling to reduce flicker - Add App Store download link to README
This commit is contained in:
parent
2791b7f227
commit
ac7f4e66eb
10
README.md
10
README.md
@ -18,9 +18,15 @@ https://codeberg.org/readeck/readeck
|
||||
<img src="screenshots/ipad.webp" height="400" alt="iPad View">
|
||||
</p>
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
let backgroundContext = coreDataManager.newBackgroundContext()
|
||||
|
||||
try await backgroundContext.perform { [weak self] in
|
||||
guard let self = self else { return }
|
||||
for dto in dtos {
|
||||
if !tagExists(name: dto.name) {
|
||||
dto.toEntity(context: coreDataManager.context)
|
||||
if !self.tagExists(name: dto.name, in: backgroundContext) {
|
||||
dto.toEntity(context: backgroundContext)
|
||||
}
|
||||
}
|
||||
try coreDataManager.context.save()
|
||||
try backgroundContext.save()
|
||||
}
|
||||
}
|
||||
|
||||
private func tagExists(name: String) -> Bool {
|
||||
private func tagExists(name: String, in context: NSManagedObjectContext) -> Bool {
|
||||
let fetchRequest: NSFetchRequest<TagEntity> = 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
|
||||
let count = try context.count(for: fetchRequest)
|
||||
return count > 0
|
||||
} catch {
|
||||
exists = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
return exists
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user