Migrate Swift Language Mode From V5 To V6

This commit is contained in:
Brendan Szymanski 2026-06-16 00:19:09 -04:00
parent 7c0a0d481f
commit 45cbfb418b
18 changed files with 103 additions and 103 deletions

View file

@ -85,5 +85,5 @@ let package = Package(
plugins: [.plugin(name: "GenerateLocalized", package: "localized")]
),
],
swiftLanguageModes: [.v5]
swiftLanguageModes: [.v6]
)

View file

@ -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<Page> = .init()
@State nonisolated(unsafe) var stack: NavigationStack<Page> = .init()
@Injected(\.pageAnimationTracker) var pageAnimationTracker
var view: Body {

View file

@ -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<NavigationStack<Page>>) {
public init(navigation: Binding<NavigationStack<Page>>) {
_navigation = navigation
}
@Injected(\.client) var client
@Injected(\.userId) var userId
@Binding var navigation: NavigationStack<Page>
@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<Page>
@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],

View file

@ -11,7 +11,7 @@ import LuminateUI
public struct LibraryPage: View {
nonisolated public init(
public init(
title: String, items: [Components.Schemas.BaseItemDto],
navigation: Binding<NavigationStack<Page>>
) {
@ -22,7 +22,7 @@ public struct LibraryPage: View {
private var items: [Components.Schemas.BaseItemDto]
private var title: String
@Binding var navigation: NavigationStack<Page>
@Binding nonisolated(unsafe) var navigation: NavigationStack<Page>
public var view: Body {
ScrollView {

View file

@ -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)

View file

@ -20,6 +20,7 @@ import Foundation
/// ```swift
/// ObservationRegistrar.onChange = { StateManager.updateViews() }
/// ```
@MainActor
public class ObservationRegistrar {
/// Global callback invoked when any tracked property changes.

View file

@ -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:

View file

@ -28,7 +28,7 @@ public struct Injected<T> {
}
}
private final class ObserverRef {
private final class ObserverRef: @unchecked Sendable {
let id: UUID
init(id: UUID) { self.id = id }
deinit { DIContainer.shared.removeObserver(id) }

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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
}
}
}

View file

@ -12,7 +12,7 @@ import LuminateCore
public struct LibraryGrid: View {
public var libraries: [Components.Schemas.BaseItemDto]
@Binding public var navigation: NavigationStack<Page>
@Binding public nonisolated(unsafe) var navigation: NavigationStack<Page>
public var title: String?
public init(

View file

@ -13,14 +13,14 @@ public struct MediaRow: View {
public var title: String
public var items: [Components.Schemas.BaseItemDto]
@Binding public var navigation: NavigationStack<Page>
public var onSeeAll: (() -> Void)?
@Binding public nonisolated(unsafe) var navigation: NavigationStack<Page>
public var onSeeAll: (@Sendable () -> Void)?
public init(
title: String,
items: [Components.Schemas.BaseItemDto],
navigation: Binding<NavigationStack<Page>>,
onSeeAll: (() -> Void)? = nil
onSeeAll: (@Sendable () -> Void)? = nil
) {
self.title = title
self.items = items

View file

@ -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()
}
}
}

View file

@ -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,

View file

@ -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

View file

@ -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
}
}
}