From 45cbfb418b1b19810356155e9a5f0304d3c8a433 Mon Sep 17 00:00:00 2001 From: Brendan Szymanski Date: Tue, 16 Jun 2026 00:19:09 -0400 Subject: [PATCH] Migrate Swift Language Mode From V5 To V6 --- Package.swift | 2 +- Sources/Luminate/Luminate.swift | 27 +++++++++++-------- Sources/Luminate/Pages/HomeView.swift | 20 +++++++------- Sources/Luminate/Pages/LibraryPage.swift | 4 +-- Sources/Luminate/ServerSetupView.swift | 18 ++++++------- Sources/LuminateCore/Observation.swift | 1 + Sources/LuminateCore/SQLiteStore.swift | 4 +-- Sources/LuminateDI/Injected.swift | 2 +- Sources/LuminatePlayer/PlayerView.swift | 18 ++++++------- .../LuminateUI/Components/EpisodeList.swift | 12 ++++----- .../Components/HomePosterCell.swift | 6 ++--- Sources/LuminateUI/Components/ItemGrid.swift | 16 +++++------ .../LuminateUI/Components/LibraryGrid.swift | 2 +- Sources/LuminateUI/Components/MediaRow.swift | 6 ++--- .../Components/MovieDetailView.swift | 24 ++++++++--------- .../LuminateUI/Components/PosterCell.swift | 6 ++--- .../LuminateUI/Components/SearchView.swift | 20 +++++++------- .../LuminateUI/Components/TVShowView.swift | 18 ++++++------- 18 files changed, 103 insertions(+), 103 deletions(-) diff --git a/Package.swift b/Package.swift index cf7b7ed..550c7df 100644 --- a/Package.swift +++ b/Package.swift @@ -85,5 +85,5 @@ let package = Package( plugins: [.plugin(name: "GenerateLocalized", package: "localized")] ), ], - swiftLanguageModes: [.v5] + swiftLanguageModes: [.v6] ) diff --git a/Sources/Luminate/Luminate.swift b/Sources/Luminate/Luminate.swift index bff3425..c82e329 100644 --- a/Sources/Luminate/Luminate.swift +++ b/Sources/Luminate/Luminate.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/14/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore import LuminateDI @@ -15,15 +15,20 @@ import LuminateUI @main struct Luminate: App { - let app = AdwaitaApp(id: "dev.bscubed.Luminate") - @State private var client: JellyfinClient? - @State private var userId = "" - @State private var isLaunchLoading = true + nonisolated(unsafe) let app = AdwaitaApp(id: "dev.bscubed.Luminate") + @State private nonisolated(unsafe) var client: JellyfinClient? + @State private nonisolated(unsafe) var userId = "" + @State private nonisolated(unsafe) var isLaunchLoading = true init() { - ObservationRegistrar.onChange = { StateManager.updateViews() } - if let store = try? SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) { - DIContainer.shared.register(\.persistence, value: store) + Task { @MainActor in + ObservationRegistrar.onChange = { StateManager.updateViews() } + } + let dbURL = SQLiteStore.defaultDatabaseURL() + Task { [dbURL] in + if let store = try? await SQLiteStore(dbURL: dbURL) { + DIContainer.shared.register(\.persistence, value: store) + } } DIContainer.shared.register(\.imageService, value: ImageService()) DIContainer.shared.register(\.pageAnimationTracker, value: PageAnimationTracker()) @@ -63,11 +68,11 @@ struct Luminate: App { } private func loadSavedSession() { - Task { + Task { [self] in do { defer { isLaunchLoading = false } - let store = try SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) + let store = try await SQLiteStore(dbURL: SQLiteStore.defaultDatabaseURL()) let auth = try await store.loadAuth() guard let serverURL = URL(string: auth.serverURL) else { return } @@ -92,7 +97,7 @@ struct ContentView: View { var client: JellyfinClient var userId: String - @State var stack: NavigationStack = .init() + @State nonisolated(unsafe) var stack: NavigationStack = .init() @Injected(\.pageAnimationTracker) var pageAnimationTracker var view: Body { diff --git a/Sources/Luminate/Pages/HomeView.swift b/Sources/Luminate/Pages/HomeView.swift index b795cbe..0ac4f1e 100644 --- a/Sources/Luminate/Pages/HomeView.swift +++ b/Sources/Luminate/Pages/HomeView.swift @@ -5,26 +5,26 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import LuminateCore import LuminateDI import LuminateUI public struct HomeView: View { - nonisolated public init(navigation: Binding>) { + public init(navigation: Binding>) { _navigation = navigation } @Injected(\.client) var client @Injected(\.userId) var userId - @Binding var navigation: NavigationStack - @State private var resumeItems: [Components.Schemas.BaseItemDto] = [] - @State private var nextUpItems: [Components.Schemas.BaseItemDto] = [] - @State private var latestItems: [Components.Schemas.BaseItemDto] = [] - @State private var libraries: [Components.Schemas.BaseItemDto] = [] - @State private var isLoading = true - @State private var isLoadingData = false + @Binding nonisolated(unsafe) var navigation: NavigationStack + @State private nonisolated(unsafe) var resumeItems: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var nextUpItems: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var latestItems: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var libraries: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var isLoading = true + @State private nonisolated(unsafe) var isLoadingData = false public var view: Body { ScrollView { @@ -96,7 +96,7 @@ public struct HomeView: View { } private func loadHomeData() { - Task { + Task { [self] in async let resume = client.getItems( userId: userId, filters: [.isResumable], diff --git a/Sources/Luminate/Pages/LibraryPage.swift b/Sources/Luminate/Pages/LibraryPage.swift index 49e75eb..4faddbd 100644 --- a/Sources/Luminate/Pages/LibraryPage.swift +++ b/Sources/Luminate/Pages/LibraryPage.swift @@ -11,7 +11,7 @@ import LuminateUI public struct LibraryPage: View { - nonisolated public init( + public init( title: String, items: [Components.Schemas.BaseItemDto], navigation: Binding> ) { @@ -22,7 +22,7 @@ public struct LibraryPage: View { private var items: [Components.Schemas.BaseItemDto] private var title: String - @Binding var navigation: NavigationStack + @Binding nonisolated(unsafe) var navigation: NavigationStack public var view: Body { ScrollView { diff --git a/Sources/Luminate/ServerSetupView.swift b/Sources/Luminate/ServerSetupView.swift index fd0b8d5..c3bc3aa 100644 --- a/Sources/Luminate/ServerSetupView.swift +++ b/Sources/Luminate/ServerSetupView.swift @@ -5,21 +5,21 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore import LuminateDI public struct ServerSetupView: View { - @State private var serverURL = "" - @State private var username = "" - @State private var password = "" - @State private var isLoading = false - @State private var error: String? - public var onLogin: (JellyfinClient, String) -> Void + @State private nonisolated(unsafe) var serverURL = "" + @State private nonisolated(unsafe) var username = "" + @State private nonisolated(unsafe) var password = "" + @State private nonisolated(unsafe) var isLoading = false + @State private nonisolated(unsafe) var error: String? + public var onLogin: @Sendable (JellyfinClient, String) -> Void - public init(onLogin: @escaping (JellyfinClient, String) -> Void) { + public init(onLogin: @escaping @Sendable (JellyfinClient, String) -> Void) { self.onLogin = onLogin } @@ -65,7 +65,7 @@ public struct ServerSetupView: View { } isLoading = true error = nil - Task { + Task { [self] in do { let client = JellyfinClient(serverURL: url) let result = try await client.authenticate(username: username, password: password) diff --git a/Sources/LuminateCore/Observation.swift b/Sources/LuminateCore/Observation.swift index 136d65b..49cbf1f 100644 --- a/Sources/LuminateCore/Observation.swift +++ b/Sources/LuminateCore/Observation.swift @@ -20,6 +20,7 @@ import Foundation /// ```swift /// ObservationRegistrar.onChange = { StateManager.updateViews() } /// ``` +@MainActor public class ObservationRegistrar { /// Global callback invoked when any tracked property changes. diff --git a/Sources/LuminateCore/SQLiteStore.swift b/Sources/LuminateCore/SQLiteStore.swift index d379c6d..13e2e32 100644 --- a/Sources/LuminateCore/SQLiteStore.swift +++ b/Sources/LuminateCore/SQLiteStore.swift @@ -14,7 +14,7 @@ public actor SQLiteStore: PersistenceService { private let db: Connection - public init(dbURL: URL) throws { + public init(dbURL: URL) async throws { let directory = dbURL.deletingLastPathComponent() try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) db = try Connection(dbURL.path) @@ -23,7 +23,7 @@ public actor SQLiteStore: PersistenceService { try migrate() } - private func migrate() throws { + private nonisolated func migrate() throws { let version = db.userVersion switch version { case 0: diff --git a/Sources/LuminateDI/Injected.swift b/Sources/LuminateDI/Injected.swift index abf75e6..7b73d4d 100644 --- a/Sources/LuminateDI/Injected.swift +++ b/Sources/LuminateDI/Injected.swift @@ -28,7 +28,7 @@ public struct Injected { } } -private final class ObserverRef { +private final class ObserverRef: @unchecked Sendable { let id: UUID init(id: UUID) { self.id = id } deinit { DIContainer.shared.removeObserver(id) } diff --git a/Sources/LuminatePlayer/PlayerView.swift b/Sources/LuminatePlayer/PlayerView.swift index f869cff..ed85ce7 100644 --- a/Sources/LuminatePlayer/PlayerView.swift +++ b/Sources/LuminatePlayer/PlayerView.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore @@ -17,11 +17,11 @@ public struct PlayerView: View { public var mediaSourceId: String public var playSessionId: String public var streamURL: URL - @State private var isPlaying = true - @State private var position: Double = 0 - @State private var duration: Double = 0 - @State private var showControls = true - public var onClose: () -> Void + @State private nonisolated(unsafe) var isPlaying = true + @State private nonisolated(unsafe) var position: Double = 0 + @State private nonisolated(unsafe) var duration: Double = 0 + @State private nonisolated(unsafe) var showControls = true + public var onClose: @Sendable () -> Void public init( item: Components.Schemas.BaseItemDto, @@ -30,7 +30,7 @@ public struct PlayerView: View { mediaSourceId: String, playSessionId: String, streamURL: URL, - onClose: @escaping () -> Void + onClose: @escaping @Sendable () -> Void ) { self.item = item self.client = client @@ -70,7 +70,7 @@ public struct PlayerView: View { } private func startPlayback() { - Task { + Task { [self] in try? await client.reportPlaybackStart( info: .init( itemId: item.id, @@ -82,7 +82,7 @@ public struct PlayerView: View { } private func stopPlayback() { - Task { + Task { [self] in try? await client.reportPlaybackStopped( info: .init( itemId: item.id, diff --git a/Sources/LuminateUI/Components/EpisodeList.swift b/Sources/LuminateUI/Components/EpisodeList.swift index 75b4a09..7702e1d 100644 --- a/Sources/LuminateUI/Components/EpisodeList.swift +++ b/Sources/LuminateUI/Components/EpisodeList.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore @@ -15,7 +15,7 @@ struct EpisodeList: View { var seasonId: String var client: JellyfinClient var userId: String - @State private var episodes: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var episodes: [Components.Schemas.BaseItemDto] = [] var view: Body { VStack { @@ -32,13 +32,13 @@ struct EpisodeList: View { } private func loadEpisodes() { - Task { + Task { [self] in let result = try? await client.getEpisodes( seriesId: seriesId, userId: userId, seasonId: seasonId ) - await MainActor.run { episodes = result?.items ?? [] } + episodes = result?.items ?? [] } } } @@ -47,7 +47,7 @@ struct EpisodeRow: View { var episode: Components.Schemas.BaseItemDto var client: JellyfinClient - @State private var imageData: Data? + @State private nonisolated(unsafe) var imageData: Data? var view: Body { HStack { @@ -89,7 +89,7 @@ struct EpisodeRow: View { guard let tag = episode.primaryImageTag, let itemId = episode.id else { return } - Task { + Task { [self] in guard let url = await client.imageURL( itemId: itemId, diff --git a/Sources/LuminateUI/Components/HomePosterCell.swift b/Sources/LuminateUI/Components/HomePosterCell.swift index 1043548..e5b076d 100644 --- a/Sources/LuminateUI/Components/HomePosterCell.swift +++ b/Sources/LuminateUI/Components/HomePosterCell.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore import LuminateDI @@ -15,7 +15,7 @@ struct HomePosterCell: View { var item: Components.Schemas.BaseItemDto @Injected(\.client) var client @Injected(\.pageAnimationTracker) var pageAnimationTracker - @State private var imageData: Data? + @State private nonisolated(unsafe) var imageData: Data? var view: Body { VStack { @@ -67,7 +67,7 @@ struct HomePosterCell: View { guard let tag = item.primaryImageTag, let itemId = item.seriesId ?? item.id else { return } - Task { + Task { [self] in guard let url = await client.imageURL( itemId: itemId, diff --git a/Sources/LuminateUI/Components/ItemGrid.swift b/Sources/LuminateUI/Components/ItemGrid.swift index 7c9802b..36cbf94 100644 --- a/Sources/LuminateUI/Components/ItemGrid.swift +++ b/Sources/LuminateUI/Components/ItemGrid.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import LuminateCore public struct ItemGrid: View { @@ -15,8 +15,8 @@ public struct ItemGrid: View { var parentId: String? var includeItemTypes: [Components.Schemas.BaseItemKind]? var title: String? - @State private var items: [Components.Schemas.BaseItemDto] = [] - @State private var isLoading = false + @State private nonisolated(unsafe) var items: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var isLoading = false private let pageSize: Int32 = 50 public init( @@ -58,7 +58,7 @@ public struct ItemGrid: View { private func loadItems() { isLoading = true - Task { + Task { [self] in do { let result = try await client.getItems( userId: userId, @@ -71,12 +71,10 @@ public struct ItemGrid: View { limit: pageSize, recursive: true ) - await MainActor.run { - items = result.items ?? [] - isLoading = false - } + items = result.items ?? [] + isLoading = false } catch { - await MainActor.run { isLoading = false } + isLoading = false } } } diff --git a/Sources/LuminateUI/Components/LibraryGrid.swift b/Sources/LuminateUI/Components/LibraryGrid.swift index 701f2c1..e60682e 100644 --- a/Sources/LuminateUI/Components/LibraryGrid.swift +++ b/Sources/LuminateUI/Components/LibraryGrid.swift @@ -12,7 +12,7 @@ import LuminateCore public struct LibraryGrid: View { public var libraries: [Components.Schemas.BaseItemDto] - @Binding public var navigation: NavigationStack + @Binding public nonisolated(unsafe) var navigation: NavigationStack public var title: String? public init( diff --git a/Sources/LuminateUI/Components/MediaRow.swift b/Sources/LuminateUI/Components/MediaRow.swift index d8a2923..806725f 100644 --- a/Sources/LuminateUI/Components/MediaRow.swift +++ b/Sources/LuminateUI/Components/MediaRow.swift @@ -13,14 +13,14 @@ public struct MediaRow: View { public var title: String public var items: [Components.Schemas.BaseItemDto] - @Binding public var navigation: NavigationStack - public var onSeeAll: (() -> Void)? + @Binding public nonisolated(unsafe) var navigation: NavigationStack + public var onSeeAll: (@Sendable () -> Void)? public init( title: String, items: [Components.Schemas.BaseItemDto], navigation: Binding>, - onSeeAll: (() -> Void)? = nil + onSeeAll: (@Sendable () -> Void)? = nil ) { self.title = title self.items = items diff --git a/Sources/LuminateUI/Components/MovieDetailView.swift b/Sources/LuminateUI/Components/MovieDetailView.swift index 13d6708..50a3ff5 100644 --- a/Sources/LuminateUI/Components/MovieDetailView.swift +++ b/Sources/LuminateUI/Components/MovieDetailView.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore @@ -14,10 +14,10 @@ struct MovieDetailView: View { var item: Components.Schemas.BaseItemDto var client: JellyfinClient var userId: String - @State private var isFavorite: Bool - @State private var isPlayed: Bool - @State private var similarItems: [Components.Schemas.BaseItemDto] = [] - @State private var backdropData: Data? + @State private nonisolated(unsafe) var isFavorite: Bool + @State private nonisolated(unsafe) var isPlayed: Bool + @State private nonisolated(unsafe) var similarItems: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var backdropData: Data? init(item: Components.Schemas.BaseItemDto, client: JellyfinClient, userId: String) { self.item = item @@ -115,7 +115,7 @@ struct MovieDetailView: View { private func loadBackdrop() { guard let tag = item.backdropImageTag, let itemId = item.id else { return } - Task { + Task { [self] in guard let url = await client.imageURL( itemId: itemId, imageType: .backdrop, tag: tag, maxWidth: 1920 @@ -127,7 +127,7 @@ struct MovieDetailView: View { } private func loadSimilar() { - Task { + Task { [self] in let result = try? await client.getItems( userId: userId, includeItemTypes: [.movie], @@ -136,29 +136,29 @@ struct MovieDetailView: View { limit: 10, recursive: true ) - await MainActor.run { similarItems = result?.items ?? [] } + similarItems = result?.items ?? [] } } private func toggleFavorite() { - Task { + Task { [self] in if isFavorite { try? await client.unmarkFavoriteItem(itemId: item.id ?? "", userId: userId) } else { try? await client.markFavoriteItem(itemId: item.id ?? "", userId: userId) } - await MainActor.run { isFavorite.toggle() } + isFavorite.toggle() } } private func togglePlayed() { - Task { + Task { [self] in if isPlayed { try? await client.markUnplayedItem(itemId: item.id ?? "", userId: userId) } else { try? await client.markPlayedItem(itemId: item.id ?? "", userId: userId) } - await MainActor.run { isPlayed.toggle() } + isPlayed.toggle() } } } diff --git a/Sources/LuminateUI/Components/PosterCell.swift b/Sources/LuminateUI/Components/PosterCell.swift index eb00375..e419465 100644 --- a/Sources/LuminateUI/Components/PosterCell.swift +++ b/Sources/LuminateUI/Components/PosterCell.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore import LuminateDI @@ -15,7 +15,7 @@ struct PosterCell: View { var item: Components.Schemas.BaseItemDto var client: JellyfinClient @Injected(\.pageAnimationTracker) var pageAnimationTracker - @State private var imageData: Data? + @State private nonisolated(unsafe) var imageData: Data? var view: Body { VStack { @@ -46,7 +46,7 @@ struct PosterCell: View { guard let tag = item.primaryImageTag, let itemId = item.id else { return } - Task { + Task { [self] in let url = await client.imageURL( itemId: itemId, imageType: .primary, diff --git a/Sources/LuminateUI/Components/SearchView.swift b/Sources/LuminateUI/Components/SearchView.swift index 9e02ee3..4b32ac1 100644 --- a/Sources/LuminateUI/Components/SearchView.swift +++ b/Sources/LuminateUI/Components/SearchView.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore import LuminateDI @@ -14,9 +14,9 @@ struct SearchView: View { var client: JellyfinClient var userId: String - @State private var searchText = "" - @State private var results: [Components.Schemas.SearchHint] = [] - @State private var isSearching = false + @State private nonisolated(unsafe) var searchText = "" + @State private nonisolated(unsafe) var results: [Components.Schemas.SearchHint] = [] + @State private nonisolated(unsafe) var isSearching = false var view: Body { VStack { @@ -45,16 +45,14 @@ struct SearchView: View { return } isSearching = true - Task { + Task { [self] in let result = try? await client.getSearchHints( searchTerm: searchText, userId: userId, limit: 50 ) - await MainActor.run { - results = result?.searchHints ?? [] - isSearching = false - } + results = result?.searchHints ?? [] + isSearching = false } } } @@ -68,7 +66,7 @@ struct SearchResultRow: View { var hint: Components.Schemas.SearchHint var client: JellyfinClient @Injected(\.pageAnimationTracker) var pageAnimationTracker - @State private var imageData: Data? + @State private nonisolated(unsafe) var imageData: Data? var view: Body { HStack { @@ -118,7 +116,7 @@ struct SearchResultRow: View { guard let tag = hint.primaryImageTag, let itemId = hint.id ?? hint.itemId else { return } - Task { + Task { [self] in guard let url = await client.imageURL( itemId: itemId, imageType: .primary, tag: tag, maxWidth: 160 diff --git a/Sources/LuminateUI/Components/TVShowView.swift b/Sources/LuminateUI/Components/TVShowView.swift index e109f44..9a68c7d 100644 --- a/Sources/LuminateUI/Components/TVShowView.swift +++ b/Sources/LuminateUI/Components/TVShowView.swift @@ -5,7 +5,7 @@ // Created by Brendan Szymanski on 6/5/26. // -import Adwaita +@preconcurrency import Adwaita import Foundation import LuminateCore @@ -14,9 +14,9 @@ struct TVShowView: View { var item: Components.Schemas.BaseItemDto var client: JellyfinClient var userId: String - @State private var seasons: [Components.Schemas.BaseItemDto] = [] - @State private var selectedSeasonId: String? - @State private var backdropData: Data? + @State private nonisolated(unsafe) var seasons: [Components.Schemas.BaseItemDto] = [] + @State private nonisolated(unsafe) var selectedSeasonId: String? + @State private nonisolated(unsafe) var backdropData: Data? var view: Body { ScrollView { @@ -86,7 +86,7 @@ struct TVShowView: View { private func loadBackdrop() { guard let tag = item.backdropImageTag, let itemId = item.id else { return } - Task { + Task { [self] in guard let url = await client.imageURL( itemId: itemId, imageType: .backdrop, tag: tag, maxWidth: 1920 @@ -98,12 +98,10 @@ struct TVShowView: View { } private func loadSeasons() { - Task { + Task { [self] in let result = try? await client.getSeasons(seriesId: item.id ?? "", userId: userId) - await MainActor.run { - seasons = result?.items ?? [] - selectedSeasonId = seasons.first?.id - } + seasons = result?.items ?? [] + selectedSeasonId = seasons.first?.id } } }