iOS 0.5.42: F1 and Viaplay hubs under Events (no player changes).
This commit is contained in:
parent
89ceccb557
commit
2da59616e8
6 changed files with 1871 additions and 5 deletions
|
|
@ -33,6 +33,7 @@
|
||||||
668CADCCEA404B50BB19A46E /* DetailScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A435950EF040DE83F30779 /* DetailScreens.swift */; };
|
668CADCCEA404B50BB19A46E /* DetailScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A435950EF040DE83F30779 /* DetailScreens.swift */; };
|
||||||
A72DE8139DAE48EC9975E0CF /* LiveTvScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E3A2A592E64C998AB1B72A /* LiveTvScreen.swift */; };
|
A72DE8139DAE48EC9975E0CF /* LiveTvScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E3A2A592E64C998AB1B72A /* LiveTvScreen.swift */; };
|
||||||
E5F6A7B8C9D0E1F2A3B4C5D6 /* EventsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A2B3C4D5E6F7A8B9C0D1E2 /* EventsScreen.swift */; };
|
E5F6A7B8C9D0E1F2A3B4C5D6 /* EventsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A2B3C4D5E6F7A8B9C0D1E2 /* EventsScreen.swift */; };
|
||||||
|
B7C3E9F1B2D4068E5A1C3E71 /* EventHubsScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3E9F1B2D4068E5A1C3E71 /* EventHubsScreens.swift */; };
|
||||||
A1CFC814F3894773A5BEA652 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 470B4DF416DE4B3FB7A60DDB /* SettingsScreen.swift */; };
|
A1CFC814F3894773A5BEA652 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 470B4DF416DE4B3FB7A60DDB /* SettingsScreen.swift */; };
|
||||||
B8D4E1F2A3C54B6D9E0F1A2B /* DevicesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D2E3F4A5B647C8D9E0F1A3 /* DevicesScreen.swift */; };
|
B8D4E1F2A3C54B6D9E0F1A2B /* DevicesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D2E3F4A5B647C8D9E0F1A3 /* DevicesScreen.swift */; };
|
||||||
B7C2E91A4F8D4A1E9C3B6D20 /* QualityBadges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A1F82E6B9C4E7A8F1D3C50 /* QualityBadges.swift */; };
|
B7C2E91A4F8D4A1E9C3B6D20 /* QualityBadges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A1F82E6B9C4E7A8F1D3C50 /* QualityBadges.swift */; };
|
||||||
|
|
@ -69,6 +70,7 @@
|
||||||
F0A435950EF040DE83F30779 /* DetailScreens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailScreens.swift; sourceTree = "<group>"; };
|
F0A435950EF040DE83F30779 /* DetailScreens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailScreens.swift; sourceTree = "<group>"; };
|
||||||
98E3A2A592E64C998AB1B72A /* LiveTvScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTvScreen.swift; sourceTree = "<group>"; };
|
98E3A2A592E64C998AB1B72A /* LiveTvScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTvScreen.swift; sourceTree = "<group>"; };
|
||||||
F1A2B3C4D5E6F7A8B9C0D1E2 /* EventsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsScreen.swift; sourceTree = "<group>"; };
|
F1A2B3C4D5E6F7A8B9C0D1E2 /* EventsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsScreen.swift; sourceTree = "<group>"; };
|
||||||
|
A7C3E9F1B2D4068E5A1C3E71 /* EventHubsScreens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHubsScreens.swift; sourceTree = "<group>"; };
|
||||||
470B4DF416DE4B3FB7A60DDB /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
470B4DF416DE4B3FB7A60DDB /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
||||||
C1D2E3F4A5B647C8D9E0F1A3 /* DevicesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesScreen.swift; sourceTree = "<group>"; };
|
C1D2E3F4A5B647C8D9E0F1A3 /* DevicesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesScreen.swift; sourceTree = "<group>"; };
|
||||||
D4A1F82E6B9C4E7A8F1D3C50 /* QualityBadges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QualityBadges.swift; sourceTree = "<group>"; };
|
D4A1F82E6B9C4E7A8F1D3C50 /* QualityBadges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QualityBadges.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -186,6 +188,7 @@
|
||||||
D4A1F82E6B9C4E7A8F1D3C50 /* QualityBadges.swift */,
|
D4A1F82E6B9C4E7A8F1D3C50 /* QualityBadges.swift */,
|
||||||
98E3A2A592E64C998AB1B72A /* LiveTvScreen.swift */,
|
98E3A2A592E64C998AB1B72A /* LiveTvScreen.swift */,
|
||||||
F1A2B3C4D5E6F7A8B9C0D1E2 /* EventsScreen.swift */,
|
F1A2B3C4D5E6F7A8B9C0D1E2 /* EventsScreen.swift */,
|
||||||
|
A7C3E9F1B2D4068E5A1C3E71 /* EventHubsScreens.swift */,
|
||||||
470B4DF416DE4B3FB7A60DDB /* SettingsScreen.swift */,
|
470B4DF416DE4B3FB7A60DDB /* SettingsScreen.swift */,
|
||||||
C1D2E3F4A5B647C8D9E0F1A3 /* DevicesScreen.swift */,
|
C1D2E3F4A5B647C8D9E0F1A3 /* DevicesScreen.swift */,
|
||||||
);
|
);
|
||||||
|
|
@ -291,6 +294,7 @@
|
||||||
B7C2E91A4F8D4A1E9C3B6D20 /* QualityBadges.swift in Sources */,
|
B7C2E91A4F8D4A1E9C3B6D20 /* QualityBadges.swift in Sources */,
|
||||||
A72DE8139DAE48EC9975E0CF /* LiveTvScreen.swift in Sources */,
|
A72DE8139DAE48EC9975E0CF /* LiveTvScreen.swift in Sources */,
|
||||||
E5F6A7B8C9D0E1F2A3B4C5D6 /* EventsScreen.swift in Sources */,
|
E5F6A7B8C9D0E1F2A3B4C5D6 /* EventsScreen.swift in Sources */,
|
||||||
|
B7C3E9F1B2D4068E5A1C3E71 /* EventHubsScreens.swift in Sources */,
|
||||||
A1CFC814F3894773A5BEA652 /* SettingsScreen.swift in Sources */,
|
A1CFC814F3894773A5BEA652 /* SettingsScreen.swift in Sources */,
|
||||||
B8D4E1F2A3C54B6D9E0F1A2B /* DevicesScreen.swift in Sources */,
|
B8D4E1F2A3C54B6D9E0F1A2B /* DevicesScreen.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
@ -341,7 +345,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 54;
|
CURRENT_PROJECT_VERSION = 55;
|
||||||
DEVELOPMENT_TEAM = X25NYX3K49;
|
DEVELOPMENT_TEAM = X25NYX3K49;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|
@ -356,7 +360,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks"
|
"@executable_path/Frameworks"
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.41;
|
MARKETING_VERSION = 0.5.42;
|
||||||
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -374,7 +378,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 54;
|
CURRENT_PROJECT_VERSION = 55;
|
||||||
DEVELOPMENT_TEAM = X25NYX3K49;
|
DEVELOPMENT_TEAM = X25NYX3K49;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|
@ -389,7 +393,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks"
|
"@executable_path/Frameworks"
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.41;
|
MARKETING_VERSION = 0.5.42;
|
||||||
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,39 @@ actor ApiClient {
|
||||||
return try await get("/api/v1/client/events/\(enc)/play")
|
return try await get("/api/v1/client/events/\(enc)/play")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func eventHubs() async throws -> [EventHub] {
|
||||||
|
struct Wrap: Decodable { let hubs: [EventHub]? }
|
||||||
|
let wrap: Wrap = try await get("/api/v1/client/hubs")
|
||||||
|
return wrap.hubs ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
func f1Home() async throws -> F1HubPayload {
|
||||||
|
try await get("/api/v1/client/f1/home")
|
||||||
|
}
|
||||||
|
|
||||||
|
func f1Meeting(meetingKey: String) async throws -> F1MeetingDetail {
|
||||||
|
let enc = Self.encodePathSegment(meetingKey)
|
||||||
|
return try await get("/api/v1/client/f1/meetings/\(enc)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func f1ContentPlay(contentId: String) async throws -> IptvPlayInfo {
|
||||||
|
let enc = Self.encodePathSegment(contentId)
|
||||||
|
return try await get("/api/v1/client/f1/content/\(enc)/play")
|
||||||
|
}
|
||||||
|
|
||||||
|
func viaplayHome() async throws -> ViaplayHubPayload {
|
||||||
|
try await get("/api/v1/client/viaplay/home")
|
||||||
|
}
|
||||||
|
|
||||||
|
func viaplayContentPlay(guid: String, productUrl: String?) async throws -> IptvPlayInfo {
|
||||||
|
let enc = Self.encodePathSegment(guid)
|
||||||
|
var q = ""
|
||||||
|
if let productUrl = productUrl?.trimmingCharacters(in: .whitespacesAndNewlines), !productUrl.isEmpty {
|
||||||
|
q = "?productUrl=\(Self.encodePathSegment(productUrl))"
|
||||||
|
}
|
||||||
|
return try await get("/api/v1/client/viaplay/content/\(enc)/play\(q)")
|
||||||
|
}
|
||||||
|
|
||||||
/// Encode path segment (incl. `:` in `custom:uuid`) — strenger dan urlPathAllowed.
|
/// Encode path segment (incl. `:` in `custom:uuid`) — strenger dan urlPathAllowed.
|
||||||
private static func encodePathSegment(_ raw: String) -> String {
|
private static func encodePathSegment(_ raw: String) -> String {
|
||||||
var allowed = CharacterSet.alphanumerics
|
var allowed = CharacterSet.alphanumerics
|
||||||
|
|
|
||||||
|
|
@ -593,3 +593,172 @@ struct ApiErrorBody: Decodable {
|
||||||
let error: ErrorObj?
|
let error: ErrorObj?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Event hubs (F1 / Viaplay)
|
||||||
|
|
||||||
|
struct EventHub: Decodable, Identifiable, Hashable {
|
||||||
|
let id: String
|
||||||
|
let label: String
|
||||||
|
let subtitle: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct F1Meeting: Decodable, Identifiable, Hashable {
|
||||||
|
var id: String { meetingKey }
|
||||||
|
let meetingKey: String
|
||||||
|
let title: String
|
||||||
|
let meetingNumber: Int?
|
||||||
|
let location: String?
|
||||||
|
let startMs: Double?
|
||||||
|
let endMs: Double?
|
||||||
|
let imageUrl: String?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case meetingKey, title, meetingNumber, location, startMs, endMs, imageUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
meetingKey = try decodeFlexibleString(c, forKey: .meetingKey)
|
||||||
|
title = try c.decodeIfPresent(String.self, forKey: .title) ?? ""
|
||||||
|
meetingNumber = try? c.decodeIfPresent(Int.self, forKey: .meetingNumber)
|
||||||
|
location = try c.decodeIfPresent(String.self, forKey: .location)
|
||||||
|
startMs = try? c.decodeIfPresent(Double.self, forKey: .startMs)
|
||||||
|
endMs = try? c.decodeIfPresent(Double.self, forKey: .endMs)
|
||||||
|
imageUrl = try c.decodeIfPresent(String.self, forKey: .imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct F1ReplayGroup: Decodable, Identifiable, Hashable {
|
||||||
|
var id: String { meetingKey }
|
||||||
|
let meetingKey: String
|
||||||
|
let title: String
|
||||||
|
let location: String?
|
||||||
|
let meetingNumber: Int?
|
||||||
|
let imageUrl: String?
|
||||||
|
let latestStart: String?
|
||||||
|
let sessions: [ScheduleEvent]
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case meetingKey, title, location, meetingNumber, imageUrl, latestStart, sessions
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
meetingKey = try decodeFlexibleString(c, forKey: .meetingKey)
|
||||||
|
title = try c.decodeIfPresent(String.self, forKey: .title) ?? ""
|
||||||
|
location = try c.decodeIfPresent(String.self, forKey: .location)
|
||||||
|
meetingNumber = try? c.decodeIfPresent(Int.self, forKey: .meetingNumber)
|
||||||
|
imageUrl = try c.decodeIfPresent(String.self, forKey: .imageUrl)
|
||||||
|
latestStart = try c.decodeIfPresent(String.self, forKey: .latestStart)
|
||||||
|
sessions = try c.decodeIfPresent([ScheduleEvent].self, forKey: .sessions) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct F1HubPayload: Decodable {
|
||||||
|
let live: [ScheduleEvent]
|
||||||
|
let upcoming: [ScheduleEvent]
|
||||||
|
let recentReplays: [ScheduleEvent]
|
||||||
|
let replayGroups: [F1ReplayGroup]
|
||||||
|
let meetings: [F1Meeting]
|
||||||
|
let nextMeeting: F1Meeting?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case live, upcoming, recentReplays, replayGroups, meetings, nextMeeting
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
live = try c.decodeIfPresent([ScheduleEvent].self, forKey: .live) ?? []
|
||||||
|
upcoming = try c.decodeIfPresent([ScheduleEvent].self, forKey: .upcoming) ?? []
|
||||||
|
recentReplays = try c.decodeIfPresent([ScheduleEvent].self, forKey: .recentReplays) ?? []
|
||||||
|
replayGroups = try c.decodeIfPresent([F1ReplayGroup].self, forKey: .replayGroups) ?? []
|
||||||
|
meetings = try c.decodeIfPresent([F1Meeting].self, forKey: .meetings) ?? []
|
||||||
|
nextMeeting = try c.decodeIfPresent(F1Meeting.self, forKey: .nextMeeting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct F1MeetingDetail: Decodable {
|
||||||
|
let meeting: F1Meeting?
|
||||||
|
let sessions: [ScheduleEvent]
|
||||||
|
let replays: [ScheduleEvent]
|
||||||
|
let upcoming: [ScheduleEvent]
|
||||||
|
let live: [ScheduleEvent]
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case meeting, sessions, replays, upcoming, live
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
meeting = try c.decodeIfPresent(F1Meeting.self, forKey: .meeting)
|
||||||
|
sessions = try c.decodeIfPresent([ScheduleEvent].self, forKey: .sessions) ?? []
|
||||||
|
replays = try c.decodeIfPresent([ScheduleEvent].self, forKey: .replays) ?? []
|
||||||
|
upcoming = try c.decodeIfPresent([ScheduleEvent].self, forKey: .upcoming) ?? []
|
||||||
|
live = try c.decodeIfPresent([ScheduleEvent].self, forKey: .live) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViaplaySport: Decodable, Identifiable, Hashable {
|
||||||
|
let id: String
|
||||||
|
let label: String
|
||||||
|
let count: Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViaplayHubItem: Decodable, Identifiable, Hashable {
|
||||||
|
let id: String
|
||||||
|
let guid: String
|
||||||
|
let title: String
|
||||||
|
let subtitle: String?
|
||||||
|
let imageUrl: String?
|
||||||
|
let kind: String?
|
||||||
|
let openAs: String?
|
||||||
|
let playable: Bool?
|
||||||
|
let durationLabel: String?
|
||||||
|
let start: String?
|
||||||
|
let badge: String?
|
||||||
|
let pageUrl: String?
|
||||||
|
|
||||||
|
var opensAsEvent: Bool { (openAs ?? "").lowercased() == "event" }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViaplayHubRail: Decodable, Identifiable, Hashable {
|
||||||
|
let id: String
|
||||||
|
let title: String
|
||||||
|
let items: [ViaplayHubItem]
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey { case id, title, items }
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
id = try c.decode(String.self, forKey: .id)
|
||||||
|
title = try c.decodeIfPresent(String.self, forKey: .title) ?? ""
|
||||||
|
items = (try? c.decodeIfPresent([ViaplayHubItem].self, forKey: .items)) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViaplayHubPayload: Decodable {
|
||||||
|
let live: [ScheduleEvent]
|
||||||
|
let today: [ScheduleEvent]
|
||||||
|
let weekend: [ScheduleEvent]
|
||||||
|
let later: [ScheduleEvent]
|
||||||
|
let catchup: [ScheduleEvent]
|
||||||
|
let sports: [ViaplaySport]
|
||||||
|
let events: [ScheduleEvent]
|
||||||
|
let rails: [ViaplayHubRail]
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case live, today, weekend, later, catchup, sports, events, rails
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
live = try c.decodeIfPresent([ScheduleEvent].self, forKey: .live) ?? []
|
||||||
|
today = try c.decodeIfPresent([ScheduleEvent].self, forKey: .today) ?? []
|
||||||
|
weekend = try c.decodeIfPresent([ScheduleEvent].self, forKey: .weekend) ?? []
|
||||||
|
later = try c.decodeIfPresent([ScheduleEvent].self, forKey: .later) ?? []
|
||||||
|
catchup = try c.decodeIfPresent([ScheduleEvent].self, forKey: .catchup) ?? []
|
||||||
|
sports = try c.decodeIfPresent([ViaplaySport].self, forKey: .sports) ?? []
|
||||||
|
events = try c.decodeIfPresent([ScheduleEvent].self, forKey: .events) ?? []
|
||||||
|
rails = try c.decodeIfPresent([ViaplayHubRail].self, forKey: .rails) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
1635
apps/ios/VMC/UI/EventHubsScreens.swift
Normal file
1635
apps/ios/VMC/UI/EventHubsScreens.swift
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -19,6 +19,7 @@ struct EventsScreen: View {
|
||||||
@EnvironmentObject private var app: AppModel
|
@EnvironmentObject private var app: AppModel
|
||||||
|
|
||||||
@State private var events: [ScheduleEvent] = []
|
@State private var events: [ScheduleEvent] = []
|
||||||
|
@State private var hubs: [EventHub] = []
|
||||||
@State private var loading = true
|
@State private var loading = true
|
||||||
@State private var error: String?
|
@State private var error: String?
|
||||||
@State private var sportFilter: String?
|
@State private var sportFilter: String?
|
||||||
|
|
@ -93,6 +94,10 @@ struct EventsScreen: View {
|
||||||
EventDetailScreen(eventId: eventId)
|
EventDetailScreen(eventId: eventId)
|
||||||
.environmentObject(app)
|
.environmentObject(app)
|
||||||
}
|
}
|
||||||
|
.navigationDestination(for: EventHubRoute.self) { route in
|
||||||
|
EventHubDestination(route: route)
|
||||||
|
.environmentObject(app)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.task { await refresh() }
|
.task { await refresh() }
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
@ -149,6 +154,9 @@ struct EventsScreen: View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 22) {
|
VStack(alignment: .leading, spacing: 22) {
|
||||||
headerBlock
|
headerBlock
|
||||||
|
if !hubs.isEmpty {
|
||||||
|
hubRow
|
||||||
|
}
|
||||||
filterChips
|
filterChips
|
||||||
if filtered.isEmpty {
|
if filtered.isEmpty {
|
||||||
emptyBlock
|
emptyBlock
|
||||||
|
|
@ -215,6 +223,20 @@ struct EventsScreen: View {
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var hubRow: some View {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
ForEach(hubs) { hub in
|
||||||
|
if let route = EventHubRoute.forHub(hub.id) {
|
||||||
|
NavigationLink(value: route) {
|
||||||
|
EventHubEntryCard(hub: hub)
|
||||||
|
}
|
||||||
|
.buttonStyle(HubPressStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
}
|
||||||
|
|
||||||
private var subtitleLine: String {
|
private var subtitleLine: String {
|
||||||
if let sportFilter {
|
if let sportFilter {
|
||||||
return EventSport.label(for: sportFilter)
|
return EventSport.label(for: sportFilter)
|
||||||
|
|
@ -359,6 +381,9 @@ struct EventsScreen: View {
|
||||||
self.error = error.localizedDescription
|
self.error = error.localizedDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let list = try? await app.api.eventHubs() {
|
||||||
|
hubs = list.filter { EventHubRoute.forHub($0.id) != nil }
|
||||||
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ struct SettingsScreen: View {
|
||||||
|
|
||||||
Section("Info") {
|
Section("Info") {
|
||||||
LabeledContent("App", value: "VMC iOS")
|
LabeledContent("App", value: "VMC iOS")
|
||||||
LabeledContent("Versie", value: "0.5.41")
|
LabeledContent("Versie", value: "0.5.42")
|
||||||
LabeledContent("Live TV", value: app.iptvEnabled ? "Aan" : "Uit")
|
LabeledContent("Live TV", value: app.iptvEnabled ? "Aan" : "Uit")
|
||||||
LabeledContent("Events", value: app.eventsEnabled ? "Aan" : "Uit")
|
LabeledContent("Events", value: app.eventsEnabled ? "Aan" : "Uit")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue