diff --git a/Package.swift b/Package.swift index 4cc75e7..7a4a21b 100644 --- a/Package.swift +++ b/Package.swift @@ -50,11 +50,21 @@ let package = Package( .product(name: "Adwaita", package: "adwaita-swift"), ] ), + .target( + name: "LuminateUI", + dependencies: [ + "LuminateCore", + "LuminateDI", + "LuminateObservationMacros", + .product(name: "Adwaita", package: "adwaita-swift"), + ] + ), .target( name: "LuminateHome", dependencies: [ "LuminateCore", "LuminateDI", + "LuminateUI", "LuminateObservationMacros", .product(name: "Adwaita", package: "adwaita-swift"), ] @@ -64,6 +74,7 @@ let package = Package( dependencies: [ "LuminateCore", "LuminateDI", + "LuminateUI", "LuminateObservationMacros", .product(name: "Adwaita", package: "adwaita-swift"), ] @@ -73,6 +84,7 @@ let package = Package( dependencies: [ "LuminateCore", "LuminateDI", + "LuminateUI", "LuminateObservationMacros", .product(name: "Adwaita", package: "adwaita-swift"), ] @@ -83,6 +95,7 @@ let package = Package( "LuminateHome", "LuminateLibrary", "LuminatePlayer", + "LuminateUI", "LuminateDI", "LuminateObservationMacros", .product(name: "Adwaita", package: "adwaita-swift"), diff --git a/Sources/Luminate/Luminate.swift b/Sources/Luminate/Luminate.swift index ed35e09..33a5c61 100644 --- a/Sources/Luminate/Luminate.swift +++ b/Sources/Luminate/Luminate.swift @@ -5,6 +5,7 @@ import LuminateDI import LuminateHome import LuminateLibrary import LuminatePlayer +import LuminateUI @main struct Luminate: App { @@ -19,6 +20,7 @@ struct Luminate: App { if let store = try? SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) { DIContainer.shared.register(\.persistence, value: store) } + DIContainer.shared.register(\.pageAnimationTracker, value: PageAnimationTracker()) } var scene: Scene { @@ -40,6 +42,8 @@ struct Luminate: App { DIContainer.shared.register(\.client, value: client) DIContainer.shared.register(\.userId, value: id) DIContainer.shared.register(\.imageService, value: ImageService()) + DIContainer.shared.register( + \.pageAnimationTracker, value: PageAnimationTracker()) self.client = client self.userId = id @@ -72,6 +76,7 @@ struct Luminate: App { DIContainer.shared.register(\.client, value: client) DIContainer.shared.register(\.userId, value: auth.userId) DIContainer.shared.register(\.imageService, value: ImageService()) + DIContainer.shared.register(\.pageAnimationTracker, value: PageAnimationTracker()) self.client = client self.userId = auth.userId @@ -87,6 +92,7 @@ struct ContentView: View { var client: JellyfinClient var userId: String @State var stack: NavigationStack = .init() + @Injected(\.pageAnimationTracker) var pageAnimationTracker var view: Body { NavigationView($stack, "Luminate") { page in @@ -107,5 +113,11 @@ struct ContentView: View { } .navigationTitle("Luminate") } + .pushed { + pageAnimationTracker.markPush() + } + .popped { + pageAnimationTracker.markPush() + } } } diff --git a/Sources/LuminateCore/PageAnimationTracking.swift b/Sources/LuminateCore/PageAnimationTracking.swift new file mode 100644 index 0000000..ffef5f7 --- /dev/null +++ b/Sources/LuminateCore/PageAnimationTracking.swift @@ -0,0 +1,6 @@ +import Foundation + +public protocol PageAnimationTracking: AnyObject { + var isAnimating: Bool { get } + func markPush() +} diff --git a/Sources/LuminateDI/InjectionValues.swift b/Sources/LuminateDI/InjectionValues.swift index 263bfa6..1bda9a5 100644 --- a/Sources/LuminateDI/InjectionValues.swift +++ b/Sources/LuminateDI/InjectionValues.swift @@ -8,6 +8,7 @@ public struct InjectionValues { public var imageService: ImageService? public var webSocketClient: WebSocketClient? public var persistence: PersistenceService? + public var pageAnimationTracker: (any PageAnimationTracking)? public init() {} } diff --git a/Sources/LuminateHome/HomePosterCell.swift b/Sources/LuminateHome/HomePosterCell.swift index f82e257..3bc9d01 100644 --- a/Sources/LuminateHome/HomePosterCell.swift +++ b/Sources/LuminateHome/HomePosterCell.swift @@ -7,6 +7,7 @@ struct HomePosterCell: View { var item: Components.Schemas.BaseItemDto @Injected(\.client) var client + @Injected(\.pageAnimationTracker) var pageAnimationTracker @State private var imageData: Data? var view: Body { @@ -47,7 +48,9 @@ struct HomePosterCell: View { .padding(12, .horizontal) } .onAppear { - loadImage() + Idle { + loadImage() + } } .overflow(.hidden) .card() @@ -67,7 +70,13 @@ struct HomePosterCell: View { ) else { return } let service = ImageService() - imageData = try? await service.loadImage(url: url) + let data = try? await service.loadImage(url: url) + + if pageAnimationTracker.isAnimating { + _imageData.rawValue = data + } else { + imageData = data + } } } } diff --git a/Sources/LuminateLibrary/PosterCell.swift b/Sources/LuminateLibrary/PosterCell.swift index ef47ad2..bac802b 100644 --- a/Sources/LuminateLibrary/PosterCell.swift +++ b/Sources/LuminateLibrary/PosterCell.swift @@ -1,11 +1,13 @@ import Adwaita import Foundation import LuminateCore +import LuminateDI struct PosterCell: View { var item: Components.Schemas.BaseItemDto var client: JellyfinClient + @Injected(\.pageAnimationTracker) var pageAnimationTracker @State private var imageData: Data? var view: Body { @@ -27,7 +29,9 @@ struct PosterCell: View { .frame(maxWidth: 150) } .onAppear { - loadImage() + Idle { + loadImage() + } } } @@ -44,7 +48,13 @@ struct PosterCell: View { ) guard let url else { return } let service = ImageService() - imageData = try? await service.loadImage(url: url) + let data = try? await service.loadImage(url: url) + + if pageAnimationTracker.isAnimating { + _imageData.rawValue = data + } else { + imageData = data + } } } } diff --git a/Sources/LuminateLibrary/SearchView.swift b/Sources/LuminateLibrary/SearchView.swift index 3763d73..972c8d0 100644 --- a/Sources/LuminateLibrary/SearchView.swift +++ b/Sources/LuminateLibrary/SearchView.swift @@ -1,6 +1,7 @@ import Adwaita import Foundation import LuminateCore +import LuminateDI struct SearchView: View { @@ -59,6 +60,7 @@ struct SearchResultRow: View { var hint: Components.Schemas.SearchHint var client: JellyfinClient + @Injected(\.pageAnimationTracker) var pageAnimationTracker @State private var imageData: Data? var view: Body { @@ -99,7 +101,9 @@ struct SearchResultRow: View { } .padding(5, .vertical) .onAppear { - loadImage() + Idle { + loadImage() + } } } @@ -114,7 +118,13 @@ struct SearchResultRow: View { ) else { return } let service = ImageService() - imageData = try? await service.loadImage(url: url) + let data = try? await service.loadImage(url: url) + + if pageAnimationTracker.isAnimating { + _imageData.rawValue = data + } else { + imageData = data + } } } } diff --git a/Sources/LuminateUI/PageAnimationTracker.swift b/Sources/LuminateUI/PageAnimationTracker.swift new file mode 100644 index 0000000..ef27752 --- /dev/null +++ b/Sources/LuminateUI/PageAnimationTracker.swift @@ -0,0 +1,22 @@ +import Adwaita +import Foundation +import LuminateCore + +public class PageAnimationTracker: PageAnimationTracking { + public var isAnimating = false + private var pushGeneration = 0 + + public init() {} + + public func markPush() { + isAnimating = true + pushGeneration += 1 + let captured = pushGeneration + Idle(delay: 250) { [weak self] in + guard let self, self.pushGeneration == captured else { return false } + self.isAnimating = false + StateManager.updateViews() + return false + } + } +}