diff --git a/Model/Applications/PipedAPI.swift b/Model/Applications/PipedAPI.swift index 99eced7a..d834a260 100644 --- a/Model/Applications/PipedAPI.swift +++ b/Model/Applications/PipedAPI.swift @@ -72,10 +72,12 @@ final class PipedAPI: Service, ObservableObject, VideosAPI { } configureTransformer(pathPattern("comments/*")) { (content: Entity) -> CommentsPage in - let comments = content.json.dictionaryValue["comments"]?.arrayValue.map { PipedAPI.extractComment(from: $0)! } ?? [] - let nextPage = content.json.dictionaryValue["nextpage"]?.stringValue + let details = content.json.dictionaryValue + let comments = details["comments"]?.arrayValue.map { PipedAPI.extractComment(from: $0)! } ?? [] + let nextPage = details["nextpage"]?.stringValue + let disabled = details["disabled"]?.boolValue ?? false - return CommentsPage(comments: comments, nextPage: nextPage) + return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled) } if account.token.isNil { diff --git a/Model/CommentsModel.swift b/Model/CommentsModel.swift index 9f9a5191..5f0c73e4 100644 --- a/Model/CommentsModel.swift +++ b/Model/CommentsModel.swift @@ -4,16 +4,27 @@ import SwiftyJSON final class CommentsModel: ObservableObject { @Published var all = [Comment]() - @Published var replies = [Comment]() @Published var nextPage: String? @Published var firstPage = true - @Published var loaded = false + @Published var loaded = true + @Published var disabled = false + + @Published var replies = [Comment]() + @Published var repliesLoaded = false var accounts: AccountsModel! var player: PlayerModel! + var instance: Instance? { + InstancesModel.find(Defaults[.commentsInstanceID]) + } + + var api: VideosAPI? { + instance.isNil ? nil : PipedAPI(account: instance!.anonymousAccount) + } + static var enabled: Bool { !Defaults[.commentsInstanceID].isNil && !Defaults[.commentsInstanceID]!.isEmpty } @@ -27,23 +38,23 @@ final class CommentsModel: ObservableObject { return } - loaded = false - clear() + reset() - guard let instance = InstancesModel.find(Defaults[.commentsInstanceID]), - !player.currentVideo.isNil + guard !instance.isNil, + !(player?.currentVideo.isNil ?? true) else { return } firstPage = page.isNil || page!.isEmpty - PipedAPI(account: instance.anonymousAccount).comments(player.currentVideo!.videoID, page: page)? + api?.comments(player.currentVideo!.videoID, page: page)? .load() .onSuccess { [weak self] response in if let page: CommentsPage = response.typedContent() { self?.all = page.comments self?.nextPage = page.nextPage + self?.disabled = page.disabled } } .onCompletion { [weak self] _ in @@ -61,19 +72,27 @@ final class CommentsModel: ObservableObject { } replies = [] + repliesLoaded = false - accounts.api.comments(player.currentVideo!.videoID, page: page)?.load().onSuccess { response in - if let page: CommentsPage = response.typedContent() { - self.replies = page.comments + api?.comments(player.currentVideo!.videoID, page: page)? + .load() + .onSuccess { [weak self] response in + if let page: CommentsPage = response.typedContent() { + self?.replies = page.comments + } + } + .onCompletion { [weak self] _ in + self?.repliesLoaded = true } - } } - func clear() { + func reset() { all = [] - replies = [] + disabled = false firstPage = true nextPage = nil loaded = false + replies = [] + repliesLoaded = false } } diff --git a/Model/CommentsPage.swift b/Model/CommentsPage.swift index 78128378..9c7c314d 100644 --- a/Model/CommentsPage.swift +++ b/Model/CommentsPage.swift @@ -3,4 +3,5 @@ import Foundation struct CommentsPage { var comments = [Comment]() var nextPage: String? + var disabled = false } diff --git a/Model/Player/PlayerQueue.swift b/Model/Player/PlayerQueue.swift index 271a2805..8a6a4e68 100644 --- a/Model/Player/PlayerQueue.swift +++ b/Model/Player/PlayerQueue.swift @@ -37,7 +37,7 @@ extension PlayerModel { } func playItem(_ item: PlayerQueueItem, video: Video? = nil, at time: TimeInterval? = nil) { - comments.clear() + comments.reset() currentItem = item if !time.isNil { diff --git a/Shared/Player/CommentView.swift b/Shared/Player/CommentView.swift index 6dc16d95..13e159f8 100644 --- a/Shared/Player/CommentView.swift +++ b/Shared/Player/CommentView.swift @@ -40,7 +40,7 @@ struct CommentView: View { Spacer() - VStack(spacing: 5) { + VStack(alignment: .trailing, spacing: 8) { likes statusIcons } @@ -65,7 +65,14 @@ struct CommentView: View { commentText if comment.hasReplies { - repliesButton + HStack(spacing: repliesButtonStackSpacing) { + repliesButton + + ProgressView() + .scaleEffect(progressViewScale, anchor: .center) + .opacity(repliesID == comment.id && !comments.repliesLoaded ? 1 : 0) + .frame(maxHeight: 0) + } if comment.id == repliesID { repliesList @@ -148,16 +155,15 @@ struct CommentView: View { comments.loadReplies(page: comment.repliesPage!) } label: { HStack(spacing: 5) { - Image(systemName: self.repliesID == comment.id ? "arrow.turn.left.up" : "arrow.turn.right.down") + Image(systemName: repliesID == comment.id ? "arrow.turn.left.up" : "arrow.turn.right.down") Text("Replies") } #if os(tvOS) .padding(10) #endif } - .buttonStyle(.plain) - .padding(.top, 2) + .padding(.vertical, 2) #if os(tvOS) .padding(.leading, 5) #else @@ -165,6 +171,24 @@ struct CommentView: View { #endif } + private var repliesButtonStackSpacing: Double { + #if os(tvOS) + 24 + #elseif os(iOS) + 4 + #else + 2 + #endif + } + + private var progressViewScale: Double { + #if os(macOS) + 0.4 + #else + 0.8 + #endif + } + private var repliesList: some View { Group { let last = comments.replies.last @@ -179,8 +203,8 @@ struct CommentView: View { .padding(.vertical, 5) } } - .padding(.leading, 22) } + .padding(.leading, 22) } private var commentText: some View { @@ -219,3 +243,13 @@ struct CommentView: View { } } } + +struct CommentView_Previews: PreviewProvider { + static var fixture: Comment { + Comment.fixture + } + + static var previews: some View { + CommentView(comment: fixture, repliesID: .constant(fixture.id)) + } +} diff --git a/Shared/Player/CommentsView.swift b/Shared/Player/CommentsView.swift index 9e63e2d6..ced2994f 100644 --- a/Shared/Player/CommentsView.swift +++ b/Shared/Player/CommentsView.swift @@ -7,41 +7,73 @@ struct CommentsView: View { @EnvironmentObject private var player var body: some View { - ScrollView(.vertical, showsIndicators: false) { - VStack(alignment: .leading) { - let last = comments.all.last - ForEach(comments.all) { comment in - CommentView(comment: comment, repliesID: $repliesID) + Group { + if comments.disabled { + Text("Comments are disabled for this video") + .foregroundColor(.secondary) + } else if comments.loaded && comments.all.isEmpty { + Text("No comments") + .foregroundColor(.secondary) + } else if !comments.loaded { + progressView + } else { + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading) { + let last = comments.all.last + ForEach(comments.all) { comment in + CommentView(comment: comment, repliesID: $repliesID) - if comment != last { - Divider() - .padding(.vertical, 5) + if comment != last { + Divider() + .padding(.vertical, 5) + } + } + + HStack { + if comments.nextPageAvailable { + Button { + repliesID = nil + comments.loadNextPage() + } label: { + Label("Show more", systemImage: "arrow.turn.down.right") + } + } + + if !comments.firstPage { + Button { + repliesID = nil + comments.load(page: nil) + } label: { + Label("Show first", systemImage: "arrow.turn.down.left") + } + } + } + .buttonStyle(.plain) + .padding(.vertical, 8) + .foregroundColor(.secondary) } } - - HStack { - if comments.nextPageAvailable { - Button { - comments.loadNextPage() - } label: { - Label("Show more", systemImage: "arrow.turn.down.right") - } - } - - if !comments.firstPage { - Button { - comments.load(page: nil) - } label: { - Label("Show first", systemImage: "arrow.turn.down.left") - } - } - } - .buttonStyle(.plain) - .padding(.vertical, 5) - .foregroundColor(.secondary) } } .padding(.horizontal) + .onAppear { + if !comments.loaded { + comments.load() + } + } + } + + private var progressView: some View { + VStack { + Spacer() + + HStack { + Spacer() + ProgressView() + Spacer() + } + Spacer() + } } } diff --git a/Shared/Player/VideoDetails.swift b/Shared/Player/VideoDetails.swift index 793afb10..3f6b7c80 100644 --- a/Shared/Player/VideoDetails.swift +++ b/Shared/Player/VideoDetails.swift @@ -141,6 +141,8 @@ struct VideoDetails: View { player.closeCurrentItem() if !sidebarQueue { currentPage = .queue + } else { + currentPage = .info } } label: { Label("Close Video", systemImage: "xmark.circle")