Skip to content

Commit

Permalink
Merge branch 'feature/swift-6'
Browse files Browse the repository at this point in the history
  • Loading branch information
mbernson committed Jan 26, 2025
2 parents 6290a99 + 7be74f2 commit 51472fd
Showing 17 changed files with 105 additions and 114 deletions.
2 changes: 1 addition & 1 deletion CCCApi/Sources/CCCApi/ApiService.swift
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ public class ApiService {

public static let shared = ApiService()

public init() {
private init() {
session = URLSession(configuration: .default)

// Format should be: yyyy-MM-dd'T'HH:mm:ss.mmm+hh:mm
4 changes: 2 additions & 2 deletions CCCApi/Sources/CCCApi/Models/Conference.swift
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ struct ConferencesResponse: Decodable {
}

/// e.g. the congress or lecture series like datengarten or openchaos
public struct Conference: Decodable, Identifiable {
public struct Conference: Decodable, Identifiable, Sendable {
public let acronym: String
public let slug: String
public let title: String
@@ -84,7 +84,7 @@ public struct Conference: Decodable, Identifiable {
}
}

public struct AspectRatio: Decodable {
public struct AspectRatio: Decodable, Sendable {
public let width: Double
public let height: Double

2 changes: 1 addition & 1 deletion CCCApi/Sources/CCCApi/Models/Recording.swift
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import Foundation

/// A recording is a file that belongs to a talk (event).
/// These can be video or audio recordings of the talk in different formats and languages (live-translation), subtitle tracks as srt or slides as pdf.
public struct Recording: Decodable, Identifiable, Equatable {
public struct Recording: Decodable, Identifiable, Equatable, Sendable {
/// approximate file size in megabytes
public let size: Int?
/// duration in seconds
4 changes: 2 additions & 2 deletions CCCApi/Sources/CCCApi/Models/Talk.swift
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ typealias Event = Talk

/// Every talk (alias event, in other systems also called lecture or session) is assigned to exactly one conference (e.g. the congress or lecture series like datengarten or openchaos) and consists of multiple files alias recordings.
/// These files can be video or audio recordings of the talk in different formats and languages (live-translation), subtitle tracks as srt or slides as pdf.
public struct Talk: Decodable, Identifiable, Equatable {
public struct Talk: Decodable, Identifiable, Equatable, Sendable {
public var id: String { guid }

public let guid: String
@@ -144,7 +144,7 @@ struct TalkExtended: Decodable {
}
}

public struct RelatedTalk: Decodable, Equatable {
public struct RelatedTalk: Decodable, Equatable, Sendable {
let eventID: Int
let eventGUID: String
let weight: Int
4 changes: 2 additions & 2 deletions CCCTube.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -658,7 +658,7 @@
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,3,6,7";
TVOS_DEPLOYMENT_TARGET = 17.6;
XROS_DEPLOYMENT_TARGET = 2.0;
@@ -704,7 +704,7 @@
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,3,6,7";
TVOS_DEPLOYMENT_TARGET = 17.6;
XROS_DEPLOYMENT_TARGET = 2.0;
32 changes: 21 additions & 11 deletions CCCTube/ContentView.swift
Original file line number Diff line number Diff line change
@@ -9,8 +9,8 @@ import CCCApi
import SwiftUI

struct ContentView: View {
@State var api: ApiService = .shared
@State private var talk: TalkToPlay?
@State var error: Error?

var body: some View {
TabView {
@@ -50,6 +50,7 @@ struct ContentView: View {
#endif
}
}
.alert("Failed to load data from the media.ccc.de API", error: $error)
.fullScreenCover(item: $talk) { talk in
NavigationStack {
TalkView(talk: talk.talk, selectedRecording: talk.recordingToPlay)
@@ -59,17 +60,26 @@ struct ContentView: View {
let factory = URLParser()
guard let route = factory.parseURL(url) else { return }
Task {
switch route {
case let .openTalk(id):
let talk = try await api.talk(id: id)
self.talk = TalkToPlay(talk: talk, recordingToPlay: nil)
case let .playTalk(id):
let talk = try await api.talk(id: id)
let recordings = try await api.recordings(for: talk)
let recording = recordings.first(where: { $0.isHighQuality }) ?? recordings.first(where: { $0.isVideo })
self.talk = TalkToPlay(talk: talk, recordingToPlay: recording)
}
await openRoute(route: route)
}
}
}

func openRoute(route: URLRoute) async {
let api = ApiService.shared
do {
switch route {
case let .openTalk(id):
let talk = try await api.talk(id: id)
self.talk = TalkToPlay(talk: talk, recordingToPlay: nil)
case let .playTalk(id):
let talk = try await api.talk(id: id)
let recordings = try await api.recordings(for: talk)
let recording = recordings.first(where: { $0.isHighQuality }) ?? recordings.first(where: { $0.isVideo })
self.talk = TalkToPlay(talk: talk, recordingToPlay: recording)
}
} catch {
self.error = error
}
}
}
2 changes: 1 addition & 1 deletion CCCTube/Features/Browse/BrowseView.swift
Original file line number Diff line number Diff line change
@@ -28,7 +28,6 @@ struct BrowseView: View {
@State var talks: [Talk] = []
@State var isLoading = true
@State var error: Error?
@State var api: ApiService = .shared

var body: some View {
NavigationStack {
@@ -74,6 +73,7 @@ struct BrowseView: View {
isLoading = true
defer { isLoading = false }
do {
let api = ApiService.shared
switch query {
case .recent:
talks = try await api.recentTalks()
4 changes: 1 addition & 3 deletions CCCTube/Features/Conferences/ConferenceView.swift
Original file line number Diff line number Diff line change
@@ -15,8 +15,6 @@ struct ConferenceView: View {
@State var isLoading = true
@State var error: Error?

@State var api: ApiService = .shared

var body: some View {
ScrollView {
#if os(tvOS)
@@ -58,7 +56,7 @@ struct ConferenceView: View {
isLoading = true
defer { isLoading = false }
do {
talks = try await api.conference(acronym: conference.acronym).events ?? []
talks = try await ApiService.shared.conference(acronym: conference.acronym).events ?? []
} catch is CancellationError {
} catch {
self.error = error
4 changes: 1 addition & 3 deletions CCCTube/Features/Conferences/ConferencesView.swift
Original file line number Diff line number Diff line change
@@ -13,8 +13,6 @@ struct ConferencesView: View {
@State var filterQuery = ""
@State var error: Error?

@State var api: ApiService = .shared

var body: some View {
NavigationStack {
ScrollView {
@@ -39,7 +37,7 @@ struct ConferencesView: View {

func refresh() async {
do {
conferences = try await api.conferences()
conferences = try await ApiService.shared.conferences()
.filter { conference in
conference.eventLastReleasedAt != nil
}
22 changes: 22 additions & 0 deletions CCCTube/Features/Search/SearchContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// SearchContext.swift
// CCCTube
//
// Created by Mathijs Bernson on 31/07/2022.
//

import Combine
import Foundation

final class SearchContext: ObservableObject {
private var searchQuerySubject = PassthroughSubject<String, Never>()
var searchQueryPublisher: AnyPublisher<String, Never> {
searchQuerySubject
.debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}

func send(query: String) {
searchQuerySubject.send(query)
}
}
45 changes: 36 additions & 9 deletions CCCTube/Features/Search/SearchView.swift
Original file line number Diff line number Diff line change
@@ -9,29 +9,33 @@ import CCCApi
import SwiftUI

struct SearchView: View {
@State var viewModel = SearchViewModel()
@State var searchContext = SearchContext()
@State var results: [Talk] = []
@State var query = ""
@State var isLoading = false
@State var error: Error?
@State var suggestions: [SearchSuggestion] = SearchSuggestion.defaultSuggestions.shuffled()

var body: some View {
NavigationStack {
Group {
#if os(tvOS)
List {
ForEach(viewModel.results) { talk in
ForEach(results) { talk in
NavigationLink {
TalkView(talk: talk)
} label: {
TalkListItem(talk: talk)
}

}
}
#else
if viewModel.isLoading {
if isLoading {
ProgressView()
} else if viewModel.results.isEmpty && !query.isEmpty {
} else if results.isEmpty && !query.isEmpty {
Text("No talks found")
} else if viewModel.results.isEmpty {
} else if results.isEmpty {
List {
ForEach(suggestions) { suggestion in
Button(suggestion.title) {
@@ -44,7 +48,7 @@ struct SearchView: View {
.listStyle(.plain)
} else {
ScrollView {
TalksGrid(talks: viewModel.results)
TalksGrid(talks: results)
}
}
#endif
@@ -54,7 +58,10 @@ struct SearchView: View {
#endif
.searchable(text: $query, prompt: "Search talks...")
.onChange(of: query) { _, query in
viewModel.updateSearchQuery(query)
searchContext.send(query: query)
}
.onReceive(searchContext.searchQueryPublisher) { query in
runSearch(query)
}
#if os(tvOS)
.searchSuggestions {
@@ -65,12 +72,32 @@ struct SearchView: View {
#endif
.onAppear(perform: runSearch)
.onSubmit(of: .search, runSearch)
.alert("Failed to load data from the media.ccc.de API", error: $viewModel.error)
.alert("Failed to load data from the media.ccc.de API", error: $error)
}
}

func runSearch() {
viewModel.search(query: query)
runSearch(query)
}

func runSearch(_ query: String) {
if query.isEmpty {
results.removeAll()
} else {
Task {
await search(query)
}
}
}

func search(_ query: String) async {
isLoading = true
defer { isLoading = false }
do {
results = try await ApiService.shared.searchTalks(query: query)
} catch {
self.error = error
}
}
}

46 changes: 0 additions & 46 deletions CCCTube/Features/Search/SearchViewModel.swift

This file was deleted.

9 changes: 3 additions & 6 deletions CCCTube/Features/Talk/TalkMetadataFactory.swift
Original file line number Diff line number Diff line change
@@ -50,9 +50,7 @@ struct TalkMetadataFactory {
return item
}

func createArtworkMetadataItem(forURL url: URL) async throws -> AVMetadataItem? {
let imageData = try await fetchImagePngData(forURL: url)
guard let imageData else { return nil }
func createArtworkMetadataItem(imageData: Data) -> AVMetadataItem? {
let thumbnailMetadata = AVMutableMetadataItem()
thumbnailMetadata.identifier = .commonIdentifierArtwork
thumbnailMetadata.dataType = kCMMetadataBaseDataType_PNG as String
@@ -62,9 +60,8 @@ struct TalkMetadataFactory {
return thumbnailMetadata
}

private func fetchImagePngData(forURL url: URL) async throws -> Data? {
func fetchImageData(forURL url: URL) async throws -> Data? {
let (imageData, _) = try await URLSession.shared.data(from: url)
let image = UIImage(data: imageData)
return image?.pngData()
return imageData
}
}
Loading

0 comments on commit 51472fd

Please sign in to comment.