mirror of
https://github.com/yattee/yattee.git
synced 2024-11-10 08:18:19 +00:00
253 lines
7.2 KiB
Swift
253 lines
7.2 KiB
Swift
import SDWebImageSwiftUI
|
|
import SwiftUI
|
|
|
|
struct CommentView: View {
|
|
let comment: Comment
|
|
@Binding var repliesID: Comment.ID?
|
|
|
|
@State private var subscribed = false
|
|
|
|
#if os(iOS)
|
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
|
#endif
|
|
|
|
@Environment(\.colorScheme) private var colorScheme
|
|
@Environment(\.navigationStyle) private var navigationStyle
|
|
|
|
@ObservedObject private var comments = CommentsModel.shared
|
|
var subscriptions = SubscribedChannelsModel.shared
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading) {
|
|
HStack(spacing: 10) {
|
|
HStack(spacing: 10) {
|
|
ZStack(alignment: .bottomTrailing) {
|
|
authorAvatar
|
|
|
|
if subscribed {
|
|
Image(systemName: "star.circle.fill")
|
|
#if os(tvOS)
|
|
.background(Color.background(scheme: colorScheme))
|
|
#else
|
|
.background(Color.background)
|
|
#endif
|
|
.clipShape(Circle())
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.onAppear {
|
|
subscribed = subscriptions.isSubscribing(comment.channel.id)
|
|
}
|
|
|
|
authorAndTime
|
|
}
|
|
.contextMenu {
|
|
Button(action: openChannelAction) {
|
|
Label("\(comment.channel.name) Channel", systemImage: "rectangle.stack.fill.badge.person.crop")
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Group {
|
|
#if os(iOS)
|
|
if horizontalSizeClass == .regular {
|
|
Group {
|
|
statusIcons
|
|
likes
|
|
}
|
|
} else {
|
|
VStack(alignment: .trailing, spacing: 8) {
|
|
likes
|
|
statusIcons
|
|
}
|
|
}
|
|
#else
|
|
statusIcons
|
|
likes
|
|
#endif
|
|
}
|
|
}
|
|
#if os(tvOS)
|
|
.font(.system(size: 25).bold())
|
|
#else
|
|
.font(.system(size: 15))
|
|
#endif
|
|
|
|
Group {
|
|
commentText
|
|
|
|
if comment.hasReplies {
|
|
HStack(spacing: repliesButtonStackSpacing) {
|
|
repliesButton
|
|
|
|
ProgressView()
|
|
.scaleEffect(Constants.progressViewScale, anchor: .center)
|
|
.opacity(repliesID == comment.id && !comments.repliesLoaded ? 1 : 0)
|
|
.frame(maxHeight: 0)
|
|
}
|
|
|
|
if comment.id == repliesID {
|
|
repliesList
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if os(tvOS)
|
|
.padding(.horizontal, 20)
|
|
#endif
|
|
.padding(.bottom, 10)
|
|
}
|
|
|
|
private var authorAvatar: some View {
|
|
WebImage(url: URL(string: comment.authorAvatarURL)!, options: [.lowPriority])
|
|
.resizable()
|
|
.placeholder {
|
|
Rectangle().fill(Color("PlaceholderColor"))
|
|
}
|
|
.retryOnAppear(true)
|
|
.indicator(.activity)
|
|
.mask(RoundedRectangle(cornerRadius: 60))
|
|
#if os(tvOS)
|
|
.frame(width: 80, height: 80, alignment: .leading)
|
|
.focusable()
|
|
#else
|
|
.frame(width: 45, height: 45, alignment: .leading)
|
|
#endif
|
|
}
|
|
|
|
private var authorAndTime: some View {
|
|
VStack(alignment: .leading) {
|
|
Text(comment.author)
|
|
#if os(tvOS)
|
|
.font(.system(size: 30).bold())
|
|
#else
|
|
.font(.system(size: 14).bold())
|
|
#endif
|
|
|
|
Text(comment.time)
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.lineLimit(1)
|
|
}
|
|
|
|
private var statusIcons: some View {
|
|
HStack(spacing: 15) {
|
|
if comment.pinned {
|
|
Image(systemName: "pin.fill")
|
|
}
|
|
if comment.hearted {
|
|
Image(systemName: "heart.fill")
|
|
}
|
|
}
|
|
#if !os(tvOS)
|
|
.font(.system(size: 12))
|
|
#endif
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
private var likes: some View {
|
|
Group {
|
|
if comment.likeCount > 0 {
|
|
HStack(spacing: 5) {
|
|
Image(systemName: "hand.thumbsup")
|
|
Text("\(comment.likeCount.formattedAsAbbreviation())")
|
|
}
|
|
#if !os(tvOS)
|
|
.font(.system(size: 12))
|
|
#endif
|
|
}
|
|
}
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
private var repliesButton: some View {
|
|
Button {
|
|
repliesID = repliesID == comment.id ? nil : comment.id
|
|
|
|
guard !repliesID.isNil, !comment.repliesPage.isNil else {
|
|
return
|
|
}
|
|
|
|
comments.loadReplies(page: comment.repliesPage!)
|
|
} label: {
|
|
HStack(spacing: 5) {
|
|
Image(systemName: repliesID == comment.id ? "arrow.turn.left.up" : "arrow.turn.right.down")
|
|
Text("Replies")
|
|
}
|
|
#if os(tvOS)
|
|
.padding(10)
|
|
#endif
|
|
}
|
|
.buttonStyle(.plain)
|
|
.padding(.vertical, 2)
|
|
#if os(tvOS)
|
|
.padding(.leading, 5)
|
|
#else
|
|
.font(.system(size: 13))
|
|
.foregroundColor(.secondary)
|
|
#endif
|
|
}
|
|
|
|
private var repliesButtonStackSpacing: Double {
|
|
#if os(tvOS)
|
|
24
|
|
#elseif os(iOS)
|
|
4
|
|
#else
|
|
2
|
|
#endif
|
|
}
|
|
|
|
private var repliesList: some View {
|
|
Group {
|
|
let last = comments.replies.last
|
|
ForEach(comments.replies) { comment in
|
|
Self(comment: comment, repliesID: $repliesID)
|
|
#if os(tvOS)
|
|
.focusable()
|
|
#endif
|
|
|
|
if comment != last {
|
|
Divider()
|
|
.padding(.vertical, 5)
|
|
}
|
|
}
|
|
}
|
|
.padding(.leading, 22)
|
|
}
|
|
|
|
private var commentText: some View {
|
|
Text(comment.text)
|
|
#if !os(tvOS)
|
|
.textSelection(.enabled)
|
|
#endif
|
|
#if os(macOS)
|
|
.font(.system(size: 14))
|
|
#elseif os(iOS)
|
|
.font(.system(size: 15))
|
|
#endif
|
|
.lineSpacing(3)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
private func openChannelAction() {
|
|
NavigationModel.shared.openChannel(
|
|
comment.channel,
|
|
navigationStyle: navigationStyle
|
|
)
|
|
}
|
|
}
|
|
|
|
struct CommentView_Previews: PreviewProvider {
|
|
static var fixture: Comment {
|
|
Comment.fixture
|
|
}
|
|
|
|
static var previews: some View {
|
|
CommentView(comment: fixture, repliesID: .constant(fixture.id))
|
|
.padding(5)
|
|
}
|
|
}
|