Defer image loading during page transition animations
This commit is contained in:
parent
0c011eff01
commit
cd2c435a04
8 changed files with 89 additions and 6 deletions
|
|
@ -50,11 +50,21 @@ let package = Package(
|
||||||
.product(name: "Adwaita", package: "adwaita-swift"),
|
.product(name: "Adwaita", package: "adwaita-swift"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "LuminateUI",
|
||||||
|
dependencies: [
|
||||||
|
"LuminateCore",
|
||||||
|
"LuminateDI",
|
||||||
|
"LuminateObservationMacros",
|
||||||
|
.product(name: "Adwaita", package: "adwaita-swift"),
|
||||||
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "LuminateHome",
|
name: "LuminateHome",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"LuminateCore",
|
"LuminateCore",
|
||||||
"LuminateDI",
|
"LuminateDI",
|
||||||
|
"LuminateUI",
|
||||||
"LuminateObservationMacros",
|
"LuminateObservationMacros",
|
||||||
.product(name: "Adwaita", package: "adwaita-swift"),
|
.product(name: "Adwaita", package: "adwaita-swift"),
|
||||||
]
|
]
|
||||||
|
|
@ -64,6 +74,7 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"LuminateCore",
|
"LuminateCore",
|
||||||
"LuminateDI",
|
"LuminateDI",
|
||||||
|
"LuminateUI",
|
||||||
"LuminateObservationMacros",
|
"LuminateObservationMacros",
|
||||||
.product(name: "Adwaita", package: "adwaita-swift"),
|
.product(name: "Adwaita", package: "adwaita-swift"),
|
||||||
]
|
]
|
||||||
|
|
@ -73,6 +84,7 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"LuminateCore",
|
"LuminateCore",
|
||||||
"LuminateDI",
|
"LuminateDI",
|
||||||
|
"LuminateUI",
|
||||||
"LuminateObservationMacros",
|
"LuminateObservationMacros",
|
||||||
.product(name: "Adwaita", package: "adwaita-swift"),
|
.product(name: "Adwaita", package: "adwaita-swift"),
|
||||||
]
|
]
|
||||||
|
|
@ -83,6 +95,7 @@ let package = Package(
|
||||||
"LuminateHome",
|
"LuminateHome",
|
||||||
"LuminateLibrary",
|
"LuminateLibrary",
|
||||||
"LuminatePlayer",
|
"LuminatePlayer",
|
||||||
|
"LuminateUI",
|
||||||
"LuminateDI",
|
"LuminateDI",
|
||||||
"LuminateObservationMacros",
|
"LuminateObservationMacros",
|
||||||
.product(name: "Adwaita", package: "adwaita-swift"),
|
.product(name: "Adwaita", package: "adwaita-swift"),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import LuminateDI
|
||||||
import LuminateHome
|
import LuminateHome
|
||||||
import LuminateLibrary
|
import LuminateLibrary
|
||||||
import LuminatePlayer
|
import LuminatePlayer
|
||||||
|
import LuminateUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct Luminate: App {
|
struct Luminate: App {
|
||||||
|
|
@ -19,6 +20,7 @@ struct Luminate: App {
|
||||||
if let store = try? SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) {
|
if let store = try? SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) {
|
||||||
DIContainer.shared.register(\.persistence, value: store)
|
DIContainer.shared.register(\.persistence, value: store)
|
||||||
}
|
}
|
||||||
|
DIContainer.shared.register(\.pageAnimationTracker, value: PageAnimationTracker())
|
||||||
}
|
}
|
||||||
|
|
||||||
var scene: Scene {
|
var scene: Scene {
|
||||||
|
|
@ -40,6 +42,8 @@ struct Luminate: App {
|
||||||
DIContainer.shared.register(\.client, value: client)
|
DIContainer.shared.register(\.client, value: client)
|
||||||
DIContainer.shared.register(\.userId, value: id)
|
DIContainer.shared.register(\.userId, value: id)
|
||||||
DIContainer.shared.register(\.imageService, value: ImageService())
|
DIContainer.shared.register(\.imageService, value: ImageService())
|
||||||
|
DIContainer.shared.register(
|
||||||
|
\.pageAnimationTracker, value: PageAnimationTracker())
|
||||||
|
|
||||||
self.client = client
|
self.client = client
|
||||||
self.userId = id
|
self.userId = id
|
||||||
|
|
@ -72,6 +76,7 @@ struct Luminate: App {
|
||||||
DIContainer.shared.register(\.client, value: client)
|
DIContainer.shared.register(\.client, value: client)
|
||||||
DIContainer.shared.register(\.userId, value: auth.userId)
|
DIContainer.shared.register(\.userId, value: auth.userId)
|
||||||
DIContainer.shared.register(\.imageService, value: ImageService())
|
DIContainer.shared.register(\.imageService, value: ImageService())
|
||||||
|
DIContainer.shared.register(\.pageAnimationTracker, value: PageAnimationTracker())
|
||||||
|
|
||||||
self.client = client
|
self.client = client
|
||||||
self.userId = auth.userId
|
self.userId = auth.userId
|
||||||
|
|
@ -87,6 +92,7 @@ struct ContentView: View {
|
||||||
var client: JellyfinClient
|
var client: JellyfinClient
|
||||||
var userId: String
|
var userId: String
|
||||||
@State var stack: NavigationStack<Page> = .init()
|
@State var stack: NavigationStack<Page> = .init()
|
||||||
|
@Injected(\.pageAnimationTracker) var pageAnimationTracker
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
NavigationView($stack, "Luminate") { page in
|
NavigationView($stack, "Luminate") { page in
|
||||||
|
|
@ -107,5 +113,11 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
.navigationTitle("Luminate")
|
.navigationTitle("Luminate")
|
||||||
}
|
}
|
||||||
|
.pushed {
|
||||||
|
pageAnimationTracker.markPush()
|
||||||
|
}
|
||||||
|
.popped {
|
||||||
|
pageAnimationTracker.markPush()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
Sources/LuminateCore/PageAnimationTracking.swift
Normal file
6
Sources/LuminateCore/PageAnimationTracking.swift
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol PageAnimationTracking: AnyObject {
|
||||||
|
var isAnimating: Bool { get }
|
||||||
|
func markPush()
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ public struct InjectionValues {
|
||||||
public var imageService: ImageService?
|
public var imageService: ImageService?
|
||||||
public var webSocketClient: WebSocketClient?
|
public var webSocketClient: WebSocketClient?
|
||||||
public var persistence: PersistenceService?
|
public var persistence: PersistenceService?
|
||||||
|
public var pageAnimationTracker: (any PageAnimationTracking)?
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ struct HomePosterCell: View {
|
||||||
|
|
||||||
var item: Components.Schemas.BaseItemDto
|
var item: Components.Schemas.BaseItemDto
|
||||||
@Injected(\.client) var client
|
@Injected(\.client) var client
|
||||||
|
@Injected(\.pageAnimationTracker) var pageAnimationTracker
|
||||||
@State private var imageData: Data?
|
@State private var imageData: Data?
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
|
|
@ -47,8 +48,10 @@ struct HomePosterCell: View {
|
||||||
.padding(12, .horizontal)
|
.padding(12, .horizontal)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
Idle {
|
||||||
loadImage()
|
loadImage()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.overflow(.hidden)
|
.overflow(.hidden)
|
||||||
.card()
|
.card()
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +70,13 @@ struct HomePosterCell: View {
|
||||||
)
|
)
|
||||||
else { return }
|
else { return }
|
||||||
let service = ImageService()
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import Adwaita
|
import Adwaita
|
||||||
import Foundation
|
import Foundation
|
||||||
import LuminateCore
|
import LuminateCore
|
||||||
|
import LuminateDI
|
||||||
|
|
||||||
struct PosterCell: View {
|
struct PosterCell: View {
|
||||||
|
|
||||||
var item: Components.Schemas.BaseItemDto
|
var item: Components.Schemas.BaseItemDto
|
||||||
var client: JellyfinClient
|
var client: JellyfinClient
|
||||||
|
@Injected(\.pageAnimationTracker) var pageAnimationTracker
|
||||||
@State private var imageData: Data?
|
@State private var imageData: Data?
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
|
|
@ -27,9 +29,11 @@ struct PosterCell: View {
|
||||||
.frame(maxWidth: 150)
|
.frame(maxWidth: 150)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
Idle {
|
||||||
loadImage()
|
loadImage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func loadImage() {
|
private func loadImage() {
|
||||||
guard let tag = item.primaryImageTag,
|
guard let tag = item.primaryImageTag,
|
||||||
|
|
@ -44,7 +48,13 @@ struct PosterCell: View {
|
||||||
)
|
)
|
||||||
guard let url else { return }
|
guard let url else { return }
|
||||||
let service = ImageService()
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import Adwaita
|
import Adwaita
|
||||||
import Foundation
|
import Foundation
|
||||||
import LuminateCore
|
import LuminateCore
|
||||||
|
import LuminateDI
|
||||||
|
|
||||||
struct SearchView: View {
|
struct SearchView: View {
|
||||||
|
|
||||||
|
|
@ -59,6 +60,7 @@ struct SearchResultRow: View {
|
||||||
|
|
||||||
var hint: Components.Schemas.SearchHint
|
var hint: Components.Schemas.SearchHint
|
||||||
var client: JellyfinClient
|
var client: JellyfinClient
|
||||||
|
@Injected(\.pageAnimationTracker) var pageAnimationTracker
|
||||||
@State private var imageData: Data?
|
@State private var imageData: Data?
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
|
|
@ -99,9 +101,11 @@ struct SearchResultRow: View {
|
||||||
}
|
}
|
||||||
.padding(5, .vertical)
|
.padding(5, .vertical)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
Idle {
|
||||||
loadImage()
|
loadImage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func loadImage() {
|
private func loadImage() {
|
||||||
guard let tag = hint.primaryImageTag,
|
guard let tag = hint.primaryImageTag,
|
||||||
|
|
@ -114,7 +118,13 @@ struct SearchResultRow: View {
|
||||||
)
|
)
|
||||||
else { return }
|
else { return }
|
||||||
let service = ImageService()
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
Sources/LuminateUI/PageAnimationTracker.swift
Normal file
22
Sources/LuminateUI/PageAnimationTracker.swift
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue