Channel pages

This commit is contained in:
Arkadiusz Fal
2022-11-27 11:42:16 +01:00
parent 909f035399
commit 33abe4d487
13 changed files with 354 additions and 107 deletions

View File

@@ -154,6 +154,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
content.json.arrayValue.map(self.extractVideo)
}
configureTransformer(pathPattern("channels/*/playlists"), requestMethods: [.get]) { (content: Entity<JSON>) -> [ContentItem] in
let playlists = (content.json.dictionaryValue["playlists"]?.arrayValue ?? []).compactMap { self.extractChannelPlaylist(from: $0) }
return ContentItem.array(of: playlists)
}
configureTransformer(pathPattern("playlists/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPlaylist in
self.extractChannelPlaylist(from: content.json)
}
@@ -287,8 +292,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
.onCompletion { _ in onCompletion() }
}
func channel(_ id: String) -> Resource {
resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
func channel(_ id: String, contentType: Channel.ContentType, data _: String?) -> Resource {
if contentType == .playlists {
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)/playlists"))
}
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
}
func channelByName(_: String) -> Resource? {
@@ -518,9 +526,12 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
return Channel(
id: json["authorId"].stringValue,
name: json["author"].stringValue,
bannerURL: json["authorBanners"].arrayValue.first?.dictionaryValue["url"]?.url,
thumbnailURL: URL(string: thumbnailURL),
description: json["description"].stringValue,
subscriptionsCount: json["subCount"].int,
subscriptionsText: json["subCountText"].string,
totalViews: json["totalViews"].int,
videos: json.dictionaryValue["latestVideos"]?.arrayValue.map(extractVideo) ?? []
)
}
@@ -532,7 +543,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
title: details["title"]?.stringValue ?? "",
thumbnailURL: details["playlistThumbnail"]?.url,
channel: extractChannel(from: json),
videos: details["videos"]?.arrayValue.compactMap(extractVideo) ?? []
videos: details["videos"]?.arrayValue.compactMap(extractVideo) ?? [],
videosCount: details["videoCount"]?.int
)
}

View File

@@ -40,6 +40,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
self.extractChannel(from: content.json)
}
configureTransformer(pathPattern("channels/tabs*")) { (content: Entity<JSON>) -> [ContentItem] in
(content.json.dictionaryValue["content"]?.arrayValue ?? []).compactMap { self.extractContentItem(from: $0) }
}
configureTransformer(pathPattern("c/*")) { (content: Entity<JSON>) -> Channel? in
self.extractChannel(from: content.json)
}
@@ -147,8 +151,13 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
resource(baseURL: account.url, path: "login")
}
func channel(_ id: String) -> Resource {
resource(baseURL: account.url, path: "channel/\(id)")
func channel(_ id: String, contentType: Channel.ContentType, data: String?) -> Resource {
if contentType == .videos {
return resource(baseURL: account.url, path: "channel/\(id)")
}
return resource(baseURL: account.url, path: "channels/tabs")
.withParam("data", data)
}
func channelByName(_ name: String) -> Resource? {
@@ -160,7 +169,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
}
func channelVideos(_ id: String) -> Resource {
channel(id)
channel(id, contentType: .videos)
}
func channelPlaylist(_ id: String) -> Resource? {
@@ -385,12 +394,25 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
attributes["avatar"]?.url ??
attributes["thumbnail"]?.url
let tabs = attributes["tabs"]?.arrayValue.compactMap { tab in
let name = tab["name"].string
let data = tab["data"].string
if let name, let data, let type = Channel.ContentType(rawValue: name) {
return Channel.Tab(contentType: type, data: data)
}
return nil
} ?? [Channel.Tab]()
return Channel(
id: id,
name: name,
bannerURL: attributes["bannerUrl"]?.url,
thumbnailURL: thumbnailURL,
subscriptionsCount: subscriptionsCount,
videos: videos
verified: attributes["verified"]?.bool,
videos: videos,
tabs: tabs
)
}

View File

@@ -6,7 +6,7 @@ protocol VideosAPI {
var account: Account! { get }
var signedIn: Bool { get }
func channel(_ id: String) -> Resource
func channel(_ id: String, contentType: Channel.ContentType, data: String?) -> Resource
func channelByName(_ name: String) -> Resource?
func channelByUsername(_ username: String) -> Resource?
func channelVideos(_ id: String) -> Resource
@@ -70,6 +70,10 @@ protocol VideosAPI {
}
extension VideosAPI {
func channel(_ id: String, contentType: Channel.ContentType, data: String? = nil) -> Resource {
channel(id, contentType: contentType, data: data)
}
func loadDetails(
_ item: PlayerQueueItem,
failureHandler: ((RequestError) -> Void)? = nil,

View File

@@ -4,29 +4,56 @@ import Foundation
import SwiftyJSON
struct Channel: Identifiable, Hashable {
enum ContentType: String, Identifiable {
case videos
case playlists
case livestreams
case shorts
case channels
var id: String {
rawValue
}
var contentItemType: ContentItem.ContentType {
switch self {
case .videos:
return .video
case .playlists:
return .playlist
case .livestreams:
return .video
case .shorts:
return .video
case .channels:
return .channel
}
}
}
struct Tab: Identifiable, Hashable {
var contentType: ContentType
var data: String
var id: String {
contentType.id
}
}
var id: String
var name: String
var bannerURL: URL?
var thumbnailURL: URL?
var description = ""
var subscriptionsCount: Int?
var subscriptionsText: String?
var totalViews: Int?
var verified: Bool? // swiftlint:disable discouraged_optional_boolean
var videos = [Video]()
private var subscriptionsCount: Int?
private var subscriptionsText: String?
init(
id: String,
name: String,
thumbnailURL: URL? = nil,
subscriptionsCount: Int? = nil,
subscriptionsText: String? = nil,
videos: [Video] = []
) {
self.id = id
self.name = name
self.thumbnailURL = thumbnailURL
self.subscriptionsCount = subscriptionsCount
self.subscriptionsText = subscriptionsText
self.videos = videos
}
var tabs = [Tab]()
var detailsLoaded: Bool {
!subscriptionsString.isNil
@@ -40,7 +67,17 @@ struct Channel: Identifiable, Hashable {
return subscriptionsText
}
var totalViewsString: String? {
guard let totalViews, totalViews > 0 else { return nil }
return totalViews.formattedAsAbbreviation()
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
var contentItem: ContentItem {
ContentItem(channel: self)
}
}

View File

@@ -30,6 +30,14 @@ struct ContentItem: Identifiable {
videos.map { ContentItem(video: $0) }
}
static func array(of playlists: [ChannelPlaylist]) -> [ContentItem] {
playlists.map { ContentItem(playlist: $0) }
}
static func array(of channels: [Channel]) -> [ContentItem] {
channels.map { ContentItem(channel: $0) }
}
static func < (lhs: ContentItem, rhs: ContentItem) -> Bool {
lhs.contentType < rhs.contentType
}

View File

@@ -138,6 +138,7 @@ final class NavigationModel: ObservableObject {
#endif
hideKeyboard()
presentingChannel = false
let presentingPlayer = player.presentingPlayer
player.hide()