mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
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:
parent
37b99c59e1
commit
1f495562fc
@ -72,10 +72,12 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configureTransformer(pathPattern("comments/*")) { (content: Entity<JSON>) -> CommentsPage in
|
configureTransformer(pathPattern("comments/*")) { (content: Entity<JSON>) -> CommentsPage in
|
||||||
let comments = content.json.dictionaryValue["comments"]?.arrayValue.map { PipedAPI.extractComment(from: $0)! } ?? []
|
let details = content.json.dictionaryValue
|
||||||
let nextPage = content.json.dictionaryValue["nextpage"]?.stringValue
|
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 {
|
if account.token.isNil {
|
||||||
|
@ -4,16 +4,27 @@ import SwiftyJSON
|
|||||||
|
|
||||||
final class CommentsModel: ObservableObject {
|
final class CommentsModel: ObservableObject {
|
||||||
@Published var all = [Comment]()
|
@Published var all = [Comment]()
|
||||||
@Published var replies = [Comment]()
|
|
||||||
|
|
||||||
@Published var nextPage: String?
|
@Published var nextPage: String?
|
||||||
@Published var firstPage = true
|
@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 accounts: AccountsModel!
|
||||||
var player: PlayerModel!
|
var player: PlayerModel!
|
||||||
|
|
||||||
|
var instance: Instance? {
|
||||||
|
InstancesModel.find(Defaults[.commentsInstanceID])
|
||||||
|
}
|
||||||
|
|
||||||
|
var api: VideosAPI? {
|
||||||
|
instance.isNil ? nil : PipedAPI(account: instance!.anonymousAccount)
|
||||||
|
}
|
||||||
|
|
||||||
static var enabled: Bool {
|
static var enabled: Bool {
|
||||||
!Defaults[.commentsInstanceID].isNil && !Defaults[.commentsInstanceID]!.isEmpty
|
!Defaults[.commentsInstanceID].isNil && !Defaults[.commentsInstanceID]!.isEmpty
|
||||||
}
|
}
|
||||||
@ -27,23 +38,23 @@ final class CommentsModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded = false
|
reset()
|
||||||
clear()
|
|
||||||
|
|
||||||
guard let instance = InstancesModel.find(Defaults[.commentsInstanceID]),
|
guard !instance.isNil,
|
||||||
!player.currentVideo.isNil
|
!(player?.currentVideo.isNil ?? true)
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
firstPage = page.isNil || page!.isEmpty
|
firstPage = page.isNil || page!.isEmpty
|
||||||
|
|
||||||
PipedAPI(account: instance.anonymousAccount).comments(player.currentVideo!.videoID, page: page)?
|
api?.comments(player.currentVideo!.videoID, page: page)?
|
||||||
.load()
|
.load()
|
||||||
.onSuccess { [weak self] response in
|
.onSuccess { [weak self] response in
|
||||||
if let page: CommentsPage = response.typedContent() {
|
if let page: CommentsPage = response.typedContent() {
|
||||||
self?.all = page.comments
|
self?.all = page.comments
|
||||||
self?.nextPage = page.nextPage
|
self?.nextPage = page.nextPage
|
||||||
|
self?.disabled = page.disabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onCompletion { [weak self] _ in
|
.onCompletion { [weak self] _ in
|
||||||
@ -61,19 +72,27 @@ final class CommentsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
replies = []
|
replies = []
|
||||||
|
repliesLoaded = false
|
||||||
|
|
||||||
accounts.api.comments(player.currentVideo!.videoID, page: page)?.load().onSuccess { response in
|
api?.comments(player.currentVideo!.videoID, page: page)?
|
||||||
if let page: CommentsPage = response.typedContent() {
|
.load()
|
||||||
self.replies = page.comments
|
.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 = []
|
all = []
|
||||||
replies = []
|
disabled = false
|
||||||
firstPage = true
|
firstPage = true
|
||||||
nextPage = nil
|
nextPage = nil
|
||||||
loaded = false
|
loaded = false
|
||||||
|
replies = []
|
||||||
|
repliesLoaded = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,5 @@ import Foundation
|
|||||||
struct CommentsPage {
|
struct CommentsPage {
|
||||||
var comments = [Comment]()
|
var comments = [Comment]()
|
||||||
var nextPage: String?
|
var nextPage: String?
|
||||||
|
var disabled = false
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func playItem(_ item: PlayerQueueItem, video: Video? = nil, at time: TimeInterval? = nil) {
|
func playItem(_ item: PlayerQueueItem, video: Video? = nil, at time: TimeInterval? = nil) {
|
||||||
comments.clear()
|
comments.reset()
|
||||||
currentItem = item
|
currentItem = item
|
||||||
|
|
||||||
if !time.isNil {
|
if !time.isNil {
|
||||||
|
@ -40,7 +40,7 @@ struct CommentView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(spacing: 5) {
|
VStack(alignment: .trailing, spacing: 8) {
|
||||||
likes
|
likes
|
||||||
statusIcons
|
statusIcons
|
||||||
}
|
}
|
||||||
@ -65,7 +65,14 @@ struct CommentView: View {
|
|||||||
commentText
|
commentText
|
||||||
|
|
||||||
if comment.hasReplies {
|
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 {
|
if comment.id == repliesID {
|
||||||
repliesList
|
repliesList
|
||||||
@ -148,16 +155,15 @@ struct CommentView: View {
|
|||||||
comments.loadReplies(page: comment.repliesPage!)
|
comments.loadReplies(page: comment.repliesPage!)
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 5) {
|
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")
|
Text("Replies")
|
||||||
}
|
}
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.padding(.top, 2)
|
.padding(.vertical, 2)
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.padding(.leading, 5)
|
.padding(.leading, 5)
|
||||||
#else
|
#else
|
||||||
@ -165,6 +171,24 @@ struct CommentView: View {
|
|||||||
#endif
|
#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 {
|
private var repliesList: some View {
|
||||||
Group {
|
Group {
|
||||||
let last = comments.replies.last
|
let last = comments.replies.last
|
||||||
@ -179,8 +203,8 @@ struct CommentView: View {
|
|||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.leading, 22)
|
|
||||||
}
|
}
|
||||||
|
.padding(.leading, 22)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var commentText: some View {
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,41 +7,73 @@ struct CommentsView: View {
|
|||||||
@EnvironmentObject<PlayerModel> private var player
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
Group {
|
||||||
VStack(alignment: .leading) {
|
if comments.disabled {
|
||||||
let last = comments.all.last
|
Text("Comments are disabled for this video")
|
||||||
ForEach(comments.all) { comment in
|
.foregroundColor(.secondary)
|
||||||
CommentView(comment: comment, repliesID: $repliesID)
|
} 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 {
|
if comment != last {
|
||||||
Divider()
|
Divider()
|
||||||
.padding(.vertical, 5)
|
.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)
|
.padding(.horizontal)
|
||||||
|
.onAppear {
|
||||||
|
if !comments.loaded {
|
||||||
|
comments.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var progressView: some View {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
ProgressView()
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +141,8 @@ struct VideoDetails: View {
|
|||||||
player.closeCurrentItem()
|
player.closeCurrentItem()
|
||||||
if !sidebarQueue {
|
if !sidebarQueue {
|
||||||
currentPage = .queue
|
currentPage = .queue
|
||||||
|
} else {
|
||||||
|
currentPage = .info
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Close Video", systemImage: "xmark.circle")
|
Label("Close Video", systemImage: "xmark.circle")
|
||||||
|
Loading…
Reference in New Issue
Block a user