mirror of
https://github.com/yattee/yattee.git
synced 2025-01-05 04:17:02 +00:00
Add infinite scroll for comments
This commit is contained in:
parent
ac755d0ee6
commit
1db4a3197d
@ -8,7 +8,6 @@ final class CommentsModel: ObservableObject {
|
|||||||
@Published var nextPage: String?
|
@Published var nextPage: String?
|
||||||
@Published var firstPage = true
|
@Published var firstPage = true
|
||||||
|
|
||||||
@Published var loading = false
|
|
||||||
@Published var loaded = false
|
@Published var loaded = false
|
||||||
@Published var disabled = false
|
@Published var disabled = false
|
||||||
|
|
||||||
@ -41,37 +40,43 @@ final class CommentsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func load(page: String? = nil) {
|
func load(page: String? = nil) {
|
||||||
guard Self.enabled, !loading else {
|
guard Self.enabled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reset()
|
|
||||||
|
|
||||||
loading = true
|
|
||||||
|
|
||||||
guard !Self.instance.isNil,
|
guard !Self.instance.isNil,
|
||||||
!(player?.currentVideo.isNil ?? true)
|
!(player?.currentVideo.isNil ?? true)
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !firstPage && !nextPageAvailable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
firstPage = page.isNil || page!.isEmpty
|
firstPage = page.isNil || page!.isEmpty
|
||||||
|
|
||||||
api?.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
|
self?.disabled = page.disabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onCompletion { [weak self] _ in
|
.onCompletion { [weak self] _ in
|
||||||
self?.loading = false
|
|
||||||
self?.loaded = true
|
self?.loaded = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadNextPageIfNeeded(current comment: Comment) {
|
||||||
|
let thresholdIndex = all.index(all.endIndex, offsetBy: -5)
|
||||||
|
if all.firstIndex(where: { $0 == comment }) == thresholdIndex {
|
||||||
|
loadNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func loadNextPage() {
|
func loadNextPage() {
|
||||||
load(page: nextPage)
|
load(page: nextPage)
|
||||||
}
|
}
|
||||||
@ -108,7 +113,6 @@ final class CommentsModel: ObservableObject {
|
|||||||
firstPage = true
|
firstPage = true
|
||||||
nextPage = nil
|
nextPage = nil
|
||||||
loaded = false
|
loaded = false
|
||||||
loading = false
|
|
||||||
replies = []
|
replies = []
|
||||||
repliesLoaded = false
|
repliesLoaded = false
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CommentsView: View {
|
struct CommentsView: View {
|
||||||
|
var embedInScrollView = false
|
||||||
@State private var repliesID: Comment.ID?
|
@State private var repliesID: Comment.ID?
|
||||||
|
|
||||||
@EnvironmentObject<CommentsModel> private var comments
|
@EnvironmentObject<CommentsModel> private var comments
|
||||||
@ -8,54 +9,38 @@ struct CommentsView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if comments.disabled {
|
if comments.disabled {
|
||||||
Text("Comments are disabled for this video")
|
NoCommentsView(text: "Comments are disabled", systemImage: "xmark.circle.fill")
|
||||||
.foregroundColor(.secondary)
|
|
||||||
} else if comments.loaded && comments.all.isEmpty {
|
} else if comments.loaded && comments.all.isEmpty {
|
||||||
Text("No comments")
|
NoCommentsView(text: "No comments", systemImage: "0.circle.fill")
|
||||||
.foregroundColor(.secondary)
|
|
||||||
} else if !comments.loaded {
|
} else if !comments.loaded {
|
||||||
PlaceholderProgressView()
|
PlaceholderProgressView()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
comments.load()
|
comments.load()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
let last = comments.all.last
|
||||||
VStack(alignment: .leading) {
|
let commentsStack = LazyVStack {
|
||||||
let last = comments.all.last
|
ForEach(comments.all) { comment in
|
||||||
ForEach(comments.all) { comment in
|
CommentView(comment: comment, repliesID: $repliesID)
|
||||||
CommentView(comment: comment, repliesID: $repliesID)
|
.onAppear {
|
||||||
|
comments.loadNextPageIfNeeded(current: comment)
|
||||||
if comment != last {
|
|
||||||
Divider()
|
|
||||||
.padding(.vertical, 5)
|
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, comment == last ? 5 : 0)
|
||||||
|
|
||||||
|
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(.system(size: 13))
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if embedInScrollView {
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
commentsStack
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commentsStack
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
28
Shared/Player/NoCommentsView.swift
Normal file
28
Shared/Player/NoCommentsView.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NoCommentsView: View {
|
||||||
|
var text: String
|
||||||
|
var systemImage: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .center, spacing: 10) {
|
||||||
|
Image(systemName: systemImage)
|
||||||
|
.font(.system(size: 36))
|
||||||
|
|
||||||
|
Text(text)
|
||||||
|
#if !os(tvOS)
|
||||||
|
.font(.system(size: 12))
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
|
#if !os(tvOS)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoCommentsView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
NoCommentsView(text: "No comments", systemImage: "xmark.circle.fill")
|
||||||
|
}
|
||||||
|
}
|
@ -512,6 +512,9 @@
|
|||||||
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||||
37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||||
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||||
|
37DD9DA32785BBC900539416 /* NoCommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DA22785BBC900539416 /* NoCommentsView.swift */; };
|
||||||
|
37DD9DA42785BBC900539416 /* NoCommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DA22785BBC900539416 /* NoCommentsView.swift */; };
|
||||||
|
37DD9DA52785BBC900539416 /* NoCommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DA22785BBC900539416 /* NoCommentsView.swift */; };
|
||||||
37E04C0F275940FB00172673 /* VerticalScrollingFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */; };
|
37E04C0F275940FB00172673 /* VerticalScrollingFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */; };
|
||||||
37E084AC2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; };
|
37E084AC2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; };
|
||||||
37E084AD2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; };
|
37E084AD2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; };
|
||||||
@ -774,6 +777,7 @@
|
|||||||
37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SwipeGesture.swift"; sourceTree = "<group>"; };
|
37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SwipeGesture.swift"; sourceTree = "<group>"; };
|
||||||
37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||||
37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; };
|
37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; };
|
||||||
|
37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; };
|
||||||
37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalScrollingFix.swift; sourceTree = "<group>"; };
|
37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalScrollingFix.swift; sourceTree = "<group>"; };
|
||||||
37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsNavigationLink.swift; sourceTree = "<group>"; };
|
37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsNavigationLink.swift; sourceTree = "<group>"; };
|
||||||
37E2EEAA270656EC00170416 /* PlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsView.swift; sourceTree = "<group>"; };
|
37E2EEAA270656EC00170416 /* PlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsView.swift; sourceTree = "<group>"; };
|
||||||
@ -909,6 +913,7 @@
|
|||||||
children = (
|
children = (
|
||||||
371B7E602759706A00D21217 /* CommentsView.swift */,
|
371B7E602759706A00D21217 /* CommentsView.swift */,
|
||||||
37EF9A75275BEB8E0043B585 /* CommentView.swift */,
|
37EF9A75275BEB8E0043B585 /* CommentView.swift */,
|
||||||
|
37DD9DA22785BBC900539416 /* NoCommentsView.swift */,
|
||||||
37B81B0126D2CAE700675966 /* PlaybackBar.swift */,
|
37B81B0126D2CAE700675966 /* PlaybackBar.swift */,
|
||||||
37BE0BD226A1D4780092E2DB /* Player.swift */,
|
37BE0BD226A1D4780092E2DB /* Player.swift */,
|
||||||
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */,
|
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */,
|
||||||
@ -1914,6 +1919,7 @@
|
|||||||
37FB28412721B22200A57617 /* ContentItem.swift in Sources */,
|
37FB28412721B22200A57617 /* ContentItem.swift in Sources */,
|
||||||
37C3A24D272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
37C3A24D272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
||||||
37484C2D26FC844700287258 /* InstanceSettings.swift in Sources */,
|
37484C2D26FC844700287258 /* InstanceSettings.swift in Sources */,
|
||||||
|
37DD9DA32785BBC900539416 /* NoCommentsView.swift in Sources */,
|
||||||
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
||||||
37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */,
|
37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */,
|
||||||
37319F0527103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
37319F0527103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
||||||
@ -1992,6 +1998,7 @@
|
|||||||
374C053627242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
374C053627242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
||||||
37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||||
37E70928271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
37E70928271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
||||||
|
37DD9DA42785BBC900539416 /* NoCommentsView.swift in Sources */,
|
||||||
37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||||
37C3A24E272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
37C3A24E272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
||||||
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
|
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
|
||||||
@ -2316,6 +2323,7 @@
|
|||||||
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
||||||
37F49BA826CB0FCE00304AC0 /* PlaylistFormView.swift in Sources */,
|
37F49BA826CB0FCE00304AC0 /* PlaylistFormView.swift in Sources */,
|
||||||
373197DA2732060100EF734F /* RelatedView.swift in Sources */,
|
373197DA2732060100EF734F /* RelatedView.swift in Sources */,
|
||||||
|
37DD9DA52785BBC900539416 /* NoCommentsView.swift in Sources */,
|
||||||
37D4B19926717E1500C925CA /* Video.swift in Sources */,
|
37D4B19926717E1500C925CA /* Video.swift in Sources */,
|
||||||
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */,
|
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */,
|
||||||
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
||||||
|
@ -130,7 +130,11 @@ struct NowPlayingView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sections.contains(.comments) {
|
if sections.contains(.comments) {
|
||||||
if !comments.loaded {
|
if comments.disabled {
|
||||||
|
NoCommentsView(text: "Comments are disabled", systemImage: "xmark.circle.fill")
|
||||||
|
} else if comments.loaded && comments.all.isEmpty {
|
||||||
|
NoCommentsView(text: "No comments", systemImage: "0.circle.fill")
|
||||||
|
} else if !comments.loaded {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
PlaceholderProgressView()
|
PlaceholderProgressView()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
@ -142,6 +146,14 @@ struct NowPlayingView: View {
|
|||||||
ForEach(comments.all) { comment in
|
ForEach(comments.all) { comment in
|
||||||
CommentView(comment: comment, repliesID: $repliesID)
|
CommentView(comment: comment, repliesID: $repliesID)
|
||||||
}
|
}
|
||||||
|
if comments.nextPageAvailable {
|
||||||
|
Text("Scroll to load more...")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.leading)
|
||||||
|
.onAppear {
|
||||||
|
comments.loadNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user