Comments improvements

* Show text when there is no comments or comments are disabled
* Show progress indicator for loading comments/replies
* Improve layout of icons and text spacing
This commit is contained in:
Arkadiusz Fal 2021-12-05 18:14:49 +01:00
parent 37b99c59e1
commit 1f495562fc
7 changed files with 142 additions and 52 deletions

View File

@ -72,10 +72,12 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
}
configureTransformer(pathPattern("comments/*")) { (content: Entity<JSON>) -> 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 {

View File

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

View File

@ -3,4 +3,5 @@ import Foundation
struct CommentsPage {
var comments = [Comment]()
var nextPage: String?
var disabled = false
}

View File

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

View File

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

View File

@ -7,41 +7,73 @@ struct CommentsView: View {
@EnvironmentObject<PlayerModel> 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()
}
}
}

View File

@ -141,6 +141,8 @@ struct VideoDetails: View {
player.closeCurrentItem()
if !sidebarQueue {
currentPage = .queue
} else {
currentPage = .info
}
} label: {
Label("Close Video", systemImage: "xmark.circle")