diff --git a/ios/Sources/Parra/Containers/Views/Reactions/FeedItemReactor.swift b/ios/Sources/Parra/Containers/Views/Reactions/FeedItemReactor.swift index bc378959..9a8c70b3 100644 --- a/ios/Sources/Parra/Containers/Views/Reactions/FeedItemReactor.swift +++ b/ios/Sources/Parra/Containers/Views/Reactions/FeedItemReactor.swift @@ -39,7 +39,11 @@ class FeedItemReactor: ObservableObject { @Published var currentReactions: [ParraReactionSummary] let feedItemId: String - let reactionOptionGroups: [ParraReactionOptionGroup] + @Published var reactionOptionGroups: [ParraReactionOptionGroup] + + var showReactions: Bool { + return !reactionOptionGroups.isEmpty + } func addNewReaction( option: ParraReactionOption, diff --git a/ios/Sources/Parra/Containers/Views/Reactions/FeedReactionView.swift b/ios/Sources/Parra/Containers/Views/Reactions/FeedReactionView.swift index 98b8296a..340de761 100644 --- a/ios/Sources/Parra/Containers/Views/Reactions/FeedReactionView.swift +++ b/ios/Sources/Parra/Containers/Views/Reactions/FeedReactionView.swift @@ -12,21 +12,10 @@ struct FeedReactionView: View { init?( feedItemId: String, - reactionOptionGroups: [ParraReactionOptionGroup]?, - reactions: [ParraReactionSummary]? + reactor: ObservedObject ) { - guard let reactionOptionGroups, !reactionOptionGroups.isEmpty else { - return nil - } - self.feedItemId = feedItemId - self._reactor = StateObject( - wrappedValue: FeedItemReactor( - feedItemId: feedItemId, - reactionOptionGroups: reactionOptionGroups, - reactions: reactions ?? [] - ) - ) + self._reactor = reactor } // MARK: - Internal @@ -60,6 +49,7 @@ struct FeedReactionView: View { isReactionPickerPresented = true } } + .contentShape(.rect) .onChange(of: pickerSelectedReaction) { oldValue, newValue in if let newValue, oldValue == nil { reactor.addNewReaction( @@ -91,15 +81,20 @@ struct FeedReactionView: View { @State private var isReactionPickerPresented: Bool = false @State private var pickerSelectedReaction: ParraReactionOption? - @StateObject private var reactor: FeedItemReactor + @ObservedObject private var reactor: FeedItemReactor } #Preview { ParraAppPreview { FeedReactionView( feedItemId: .uuid, - reactionOptionGroups: ParraReactionOptionGroup.validStates(), - reactions: ParraReactionSummary.validStates() + reactor: ObservedObject( + wrappedValue: FeedItemReactor( + feedItemId: .uuid, + reactionOptionGroups: ParraReactionOptionGroup.validStates(), + reactions: ParraReactionSummary.validStates() + ) + ) ) } } diff --git a/ios/Sources/Parra/Containers/Widgets/Feed/Public/ParraFeedListView.swift b/ios/Sources/Parra/Containers/Widgets/Feed/Public/ParraFeedListView.swift index 9bcedd71..af68e25c 100644 --- a/ios/Sources/Parra/Containers/Widgets/Feed/Public/ParraFeedListView.swift +++ b/ios/Sources/Parra/Containers/Widgets/Feed/Public/ParraFeedListView.swift @@ -50,7 +50,9 @@ public struct ParraFeedListView: View { ForEach( Array(items.enumerated()), id: \.element - ) { index, item in + ) { + index, + item in if case .feedItemYoutubeVideo(let data) = item.data { FeedYouTubeVideoView( youtubeVideo: data, @@ -78,13 +80,15 @@ public struct ParraFeedListView: View { } } else if case .creatorUpdate(let data) = item.data { FeedCreatorUpdateView( - creatorUpdate: data, - feedItemId: item.id, - reactionOptions: item.reactionOptions?.elements, - reactions: item.reactions?.elements, - containerGeometry: containerGeometry, - spacing: spacing, - performActionForFeedItemData: performActionForFeedItemData + params: FeedCreatorUpdateParams( + creatorUpdate: data, + feedItemId: item.id, + reactionOptions: item.reactionOptions?.elements, + reactions: item.reactions?.elements, + containerGeometry: containerGeometry, + spacing: spacing, + performActionForFeedItemData: performActionForFeedItemData + ) ) } else { EmptyView() diff --git a/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdateAttachmentsView.swift b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdateAttachmentsView.swift index d35fa297..5d6c70d2 100644 --- a/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdateAttachmentsView.swift +++ b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdateAttachmentsView.swift @@ -108,13 +108,13 @@ struct CreatorUpdateAttachmentsView: View { content .background(parraTheme.palette.secondaryBackground.toParraColor()) .sheet(isPresented: $isShowingFullScreen) { - FullScreenGalleryView( - photos: layout.allImages, - selectedPhoto: $selectedPhoto - ) + NavigationStack { + FullScreenGalleryView( + photos: layout.allImages, + selectedPhoto: $selectedPhoto + ) + } .transition(.opacity) - .presentationDragIndicator(.visible) - .presentationDetents([.large]) } } @@ -126,8 +126,15 @@ struct CreatorUpdateAttachmentsView: View { func renderSingle( _ image: ParraImageAsset ) -> some View { - let width = containerGeometry.size.width - outerSpacing * 2 - let height = (image.size.height / image.size.width) * width + let width = max( + containerGeometry.size.width - outerSpacing * 2, + 0.0 + ) + + let height = max( + (image.size.height / image.size.width) * width, + 0.0 + ) renderImageButton( for: image, @@ -176,7 +183,10 @@ struct CreatorUpdateAttachmentsView: View { _ leftImage: ParraImageAsset, _ rightImage: ParraImageAsset ) -> some View { - let width = (containerGeometry.size.width - outerSpacing * 2 - innerSpacing) / 2 + let width = max( + (containerGeometry.size.width - outerSpacing * 2 - innerSpacing) / 2, + 0.0 + ) renderDouble(leftImage, rightImage, width, width) } @@ -186,8 +196,15 @@ struct CreatorUpdateAttachmentsView: View { _ leftImage: ParraImageAsset, _ rightImage: ParraImageAsset ) -> some View { - let width = (containerGeometry.size.width - outerSpacing * 2 - innerSpacing) / 2 - let height = (leftImage.size.height / leftImage.size.width) * width + let width = max( + (containerGeometry.size.width - outerSpacing * 2 - innerSpacing) / 2, + 0.0 + ) + + let height = max( + (leftImage.size.height / leftImage.size.width) * width, + 0.0 + ) renderDouble(leftImage, rightImage, width, height) } @@ -199,10 +216,17 @@ struct CreatorUpdateAttachmentsView: View { ) -> some View { let imagesBeforeFold = otherImages.prefix(maxToDisplay - 1) let requiresShowMore = otherImages.count > maxToDisplay - 1 - let width = (containerGeometry.size.width - outerSpacing * 2 - innerSpacing) / 2 - let height = CGFloat(imagesBeforeFold.count) * width + CGFloat( - imagesBeforeFold.count - 1 - ) * innerSpacing + let width = max( + (containerGeometry.size.width - outerSpacing * 2 - innerSpacing) / 2, + 0.0 + ) + + let height = max( + CGFloat(imagesBeforeFold.count) * width + CGFloat( + imagesBeforeFold.count - 1 + ) * innerSpacing, + 0.0 + ) HStack(spacing: innerSpacing) { renderImageButton( @@ -255,10 +279,19 @@ struct CreatorUpdateAttachmentsView: View { ) -> some View { let imagesBeforeFold = otherImages.prefix(maxToDisplay - 1) let requiresShowMore = otherImages.count > maxToDisplay - 1 - let width = containerGeometry.size.width - outerSpacing * 2 - let height = (topImage.size.height / topImage.size.width) * width - let smallSize = (width - CGFloat(imagesBeforeFold.count - 1) * innerSpacing) / - CGFloat(imagesBeforeFold.count) + let width = max( + containerGeometry.size.width - outerSpacing * 2, + 0.0 + ) + let height = max( + (topImage.size.height / topImage.size.width) * width, + 0.0 + ) + let smallSize = max( + (width - CGFloat(imagesBeforeFold.count - 1) * innerSpacing) / + CGFloat(imagesBeforeFold.count), + 0.0 + ) VStack(spacing: innerSpacing) { renderImageButton( diff --git a/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateDetailHeaderView.swift b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateDetailHeaderView.swift new file mode 100644 index 00000000..ae0c52d7 --- /dev/null +++ b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateDetailHeaderView.swift @@ -0,0 +1,70 @@ +// +// FeedCreatorUpdateDetailHeaderView.swift +// Parra +// +// Created by Mick MacCallum on 12/16/24. +// + +import SwiftUI +import UIKit + +struct FeedCreatorUpdateDetailHeaderView: View { + let creatorUpdate: ParraCreatorUpdateAppStub + let feedItemId: String + let containerGeometry: GeometryProxy + @ObservedObject var reactor: FeedItemReactor + + var body: some View { + VStack(spacing: 0) { + FeedCreatorUpdateHeaderView( + creatorUpdate: creatorUpdate + ) + .padding([.horizontal, .top], 16) + + FeedCreatorUpdateContentView( + creatorUpdate: creatorUpdate + ) + .padding(.horizontal, 16) + + if let attachments = creatorUpdate.attachments?.elements, + !attachments.isEmpty, + containerGeometry.size.width > 0 && containerGeometry.size.height > 0 + { + ParraPaywalledContentView( + entitlement: creatorUpdate.attachmentPaywall?.entitlement, + context: creatorUpdate.attachmentPaywall?.context + ) { requiredEntitlement, unlock in + CreatorUpdateAttachmentsView( + attachments: attachments, + containerGeometry: containerGeometry, + requiredEntitlement: requiredEntitlement + ) { + do { + try await unlock() + } catch { + Logger.error("Error unlocking attachment", error) + } + } + } unlockedContentBuilder: { + CreatorUpdateAttachmentsView( + attachments: attachments, + containerGeometry: containerGeometry + ) + } + .padding(.top, 8) + } + + if reactor.showReactions { + FeedReactionView( + feedItemId: feedItemId, + reactor: _reactor + ) + .padding() + .frame( + maxWidth: .infinity, + alignment: .leading + ) + } + } + } +} diff --git a/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateDetailView.swift b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateDetailView.swift index ce05c620..27786114 100644 --- a/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateDetailView.swift +++ b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateDetailView.swift @@ -12,145 +12,22 @@ struct FeedCreatorUpdateDetailView: View { let creatorUpdate: ParraCreatorUpdateAppStub let feedItemId: String - let reactor: FeedItemReactor - - var showReactions: Bool { - return !reactor.reactionOptionGroups.isEmpty - } + @ObservedObject var reactor: FeedItemReactor var body: some View { - ScrollView { -// VStack { -// thumbnail -// -// VStack(alignment: .leading, spacing: 8) { -// componentFactory.buildLabel( -// text: youtubeVideo.title, -// localAttributes: .default(with: .title3) -// ) -// .lineLimit(3) -// .truncationMode(.tail) -// .frame( -// maxWidth: .infinity, -// alignment: .leading -// ) -// -// HStack(spacing: 8) { -// componentFactory.buildImage( -// config: ParraImageConfig(), -// content: .resource("YouTubeIcon", .module, .original), -// localAttributes: ParraAttributes.Image( -// size: CGSize(width: 22, height: 22), -// cornerRadius: .full, -// padding: .md, -// background: parraTheme.palette.primaryBackground -// ) -// ) -// -// VStack(alignment: .leading, spacing: 0) { -// componentFactory.buildLabel( -// content: ParraLabelContent( -// text: youtubeVideo.channelTitle -// ), -// localAttributes: ParraAttributes.Label( -// text: ParraAttributes.Text( -// style: .subheadline, -// weight: .medium -// ) -// ) -// ) -// -// HStack(spacing: 4) { -// componentFactory.buildLabel( -// content: ParraLabelContent( -// text: "YouTube" -// ), -// localAttributes: ParraAttributes.Label( -// text: ParraAttributes.Text( -// style: .caption, -// weight: .regular, -// color: parraTheme.palette.secondaryText -// .toParraColor() -// ) -// ) -// ) -// -// componentFactory.buildLabel( -// text: "•", -// localAttributes: ParraAttributes.Label( -// text: ParraAttributes.Text( -// style: .caption, -// weight: .light, -// color: parraTheme.palette.secondaryText -// .toParraColor() -// ) -// ) -// ) -// -// componentFactory.buildLabel( -// text: youtubeVideo.publishedAt.timeAgo( -// dateTimeStyle: .named -// ), -// localAttributes: ParraAttributes.Label( -// text: ParraAttributes.Text( -// style: .caption2, -// weight: .regular, -// color: parraTheme.palette.secondaryText -// .toParraColor() -// ) -// ) -// ) -// } -// } -// } -// -// if showReactions { -// VStack { -// FeedReactionView( -// feedItemId: feedItemId, -// reactionOptionGroups: reactionOptions, -// reactions: reactions -// ) -// } -// .padding( -// .padding(top: 8, leading: 0, bottom: 8, trailing: 0) -// ) -// .frame( -// maxWidth: .infinity, -// alignment: .leading -// ) -// } -// -// withContent(content: youtubeVideo.description) { content in -// Text( -// content.attributedStringWithHighlightedLinks( -// tint: parraTheme.palette.primary.toParraColor(), -// font: .system(.callout), -// foregroundColor: parraTheme.palette.secondaryText -// .toParraColor() -// ) -// ) -// .padding(.top, 12) -// .tint(parraTheme.palette.primary.toParraColor()) -// } -// } -// .padding(.top, 6) -// .safeAreaPadding(.horizontal) -// .padding(.bottom, 16) -// -// Spacer() -// } + GeometryReader { geometry in + ScrollView { + FeedCreatorUpdateDetailHeaderView( + creatorUpdate: creatorUpdate, + feedItemId: feedItemId, + containerGeometry: geometry, + reactor: reactor + ) + } + .background(parraTheme.palette.secondaryBackground) } - .background(parraTheme.palette.secondaryBackground) .toolbarBackground(.visible, for: .navigationBar) .toolbar { - ToolbarItem(placement: .topBarLeading) { -// ShareLink(item: youtubeVideo.url) { -// Image(systemName: "square.and.arrow.up") -// .foregroundColor(.secondary) -// } - } - ToolbarItem(placement: .topBarTrailing) { ParraDismissButton() } @@ -163,20 +40,4 @@ struct FeedCreatorUpdateDetailView: View { @Environment(\.parraTheme) private var parraTheme @Environment(FeedWidget.ContentObserver.self) private var contentObserver @Environment(\.presentationMode) private var presentationMode - -// @ViewBuilder @MainActor private var thumbnail: some View { -// let thumb = youtubeVideo.thumbnails.maxAvailable -// -// Button { -// contentObserver.performActionForFeedItemData( -// .feedItemYoutubeVideo(youtubeVideo) -// ) -// } label: { -// YouTubeThumbnailView( -// thumb: thumb -// ) -// .aspectRatio(thumb.width / thumb.height, contentMode: .fit) -// } -// .buttonStyle(.plain) -// } } diff --git a/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateView.swift b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateView.swift index c67e2c5e..aea587da 100644 --- a/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateView.swift +++ b/ios/Sources/Parra/Containers/Widgets/Feed/Views/CreatorUpdates/FeedCreatorUpdateView.swift @@ -7,9 +7,7 @@ import SwiftUI -struct FeedCreatorUpdateView: View { - // MARK: - Internal - +struct FeedCreatorUpdateParams { let creatorUpdate: ParraCreatorUpdateAppStub let feedItemId: String let reactionOptions: [ParraReactionOptionGroup]? @@ -17,67 +15,121 @@ struct FeedCreatorUpdateView: View { let containerGeometry: GeometryProxy let spacing: CGFloat let performActionForFeedItemData: (_ feedItemData: ParraFeedItemData) -> Void +} - var body: some View { - VStack(spacing: 0) { - FeedCreatorUpdateHeaderView( - creatorUpdate: creatorUpdate - ) - .padding([.horizontal, .top], 16) +struct FeedCreatorUpdateView: View { + // MARK: - Lifecycle - FeedCreatorUpdateContentView( - creatorUpdate: creatorUpdate + init( + params: FeedCreatorUpdateParams + ) { + self.params = params + self._reactor = ObservedObject( + wrappedValue: FeedItemReactor( + feedItemId: params.feedItemId, + reactionOptionGroups: params.reactionOptions ?? [], + reactions: params.reactions ?? [] ) - .padding(.horizontal, 16) - - if let attachments = creatorUpdate.attachments?.elements, - !attachments.isEmpty - { - ParraPaywalledContentView( - entitlement: creatorUpdate.attachmentPaywall?.entitlement, - context: creatorUpdate.attachmentPaywall?.context - ) { requiredEntitlement, unlock in - CreatorUpdateAttachmentsView( - attachments: attachments, - containerGeometry: containerGeometry, - requiredEntitlement: requiredEntitlement - ) { - do { - try await unlock() - } catch { - Logger.error("Error unlocking attachment", error) + ) + } + + // MARK: - Internal + + let params: FeedCreatorUpdateParams + + var body: some View { + let hasPaywallEntitlement = entitlements.hasEntitlement( + creatorUpdate.attachmentPaywall?.entitlement + ) + + Button(action: { + isPresentingModal = true + }) { + VStack(spacing: 0) { + FeedCreatorUpdateHeaderView( + creatorUpdate: creatorUpdate + ) + .padding([.horizontal, .top], 16) + + FeedCreatorUpdateContentView( + creatorUpdate: creatorUpdate + ) + .padding(.horizontal, 16) + + if let attachments = creatorUpdate.attachments?.elements, + !attachments.isEmpty + { + ParraPaywalledContentView( + entitlement: creatorUpdate.attachmentPaywall?.entitlement, + context: creatorUpdate.attachmentPaywall?.context + ) { requiredEntitlement, unlock in + CreatorUpdateAttachmentsView( + attachments: attachments, + containerGeometry: params.containerGeometry, + requiredEntitlement: requiredEntitlement + ) { + do { + try await unlock() + } catch { + Logger.error("Error unlocking attachment", error) + } } + } unlockedContentBuilder: { + CreatorUpdateAttachmentsView( + attachments: attachments, + containerGeometry: params.containerGeometry + ) } - } unlockedContentBuilder: { - CreatorUpdateAttachmentsView( - attachments: attachments, - containerGeometry: containerGeometry + .padding(.top, 8) + } + + if reactor.showReactions { + FeedReactionView( + feedItemId: params.feedItemId, + reactor: _reactor + ) + .padding() + .frame( + maxWidth: .infinity, + alignment: .leading ) } - .padding(.top, 8) } - - FeedReactionView( - feedItemId: feedItemId, - reactionOptionGroups: reactionOptions, - reactions: reactions - ) - .padding() - .frame( - maxWidth: .infinity, - alignment: .leading - ) } + // Required to prevent highlighting the button then dragging the scroll + // view from causing the button to be pressed. + .simultaneousGesture(TapGesture()) + .disabled(!redactionReasons.isEmpty && hasPaywallEntitlement) .background(parraTheme.palette.secondaryBackground) .applyCornerRadii(size: .xl, from: parraTheme) - .padding(.vertical, spacing) + .buttonStyle(.plain) + .padding(.vertical, params.spacing) .safeAreaPadding(.horizontal) + .sheet(isPresented: $isPresentingModal) {} content: { + NavigationStack { + FeedCreatorUpdateDetailView( + creatorUpdate: params.creatorUpdate, + feedItemId: params.feedItemId, + reactor: reactor + ) + } + .toolbarBackground(.visible, for: .navigationBar) + } } // MARK: - Private + @State private var isPresentingModal: Bool = false + @ObservedObject private var reactor: FeedItemReactor + @Environment(\.parraComponentFactory) private var componentFactory @Environment(\.parraTheme) private var parraTheme + @Environment(\.redactionReasons) private var redactionReasons + @Environment(\.parraUserEntitlements) private var entitlements + + private var creatorUpdate: ParraCreatorUpdateAppStub { + return params.creatorUpdate + } } public struct TestModel: Codable, Equatable, Hashable, Identifiable { @@ -120,13 +172,15 @@ public struct TestModel: Codable, Equatable, Hashable, Identifiable { GeometryReader { proxy in VStack { FeedCreatorUpdateView( - creatorUpdate: ParraCreatorUpdateAppStub.validStates()[0], - feedItemId: .uuid, - reactionOptions: ParraReactionOptionGroup.validStates(), - reactions: ParraReactionSummary.validStates(), - containerGeometry: proxy, - spacing: 18, - performActionForFeedItemData: { _ in } + params: FeedCreatorUpdateParams( + creatorUpdate: ParraCreatorUpdateAppStub.validStates()[0], + feedItemId: .uuid, + reactionOptions: ParraReactionOptionGroup.validStates(), + reactions: ParraReactionSummary.validStates(), + containerGeometry: proxy, + spacing: 18, + performActionForFeedItemData: { _ in } + ) ) } } diff --git a/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoDetailView.swift b/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoDetailView.swift index eaf48c23..d7b1e10e 100644 --- a/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoDetailView.swift +++ b/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoDetailView.swift @@ -12,16 +12,7 @@ struct FeedYouTubeVideoDetailView: View { let youtubeVideo: ParraFeedItemYoutubeVideoData let feedItemId: String - let reactionOptions: [ParraReactionOptionGroup]? - let reactions: [ParraReactionSummary]? - - var showReactions: Bool { - if let reactionOptions { - return !reactionOptions.isEmpty - } - - return false - } + @ObservedObject var reactor: FeedItemReactor var body: some View { ScrollView { @@ -109,12 +100,11 @@ struct FeedYouTubeVideoDetailView: View { } } - if showReactions { + if reactor.showReactions { VStack { FeedReactionView( feedItemId: feedItemId, - reactionOptionGroups: reactionOptions, - reactions: reactions + reactor: _reactor ) } .padding( @@ -183,5 +173,8 @@ struct FeedYouTubeVideoDetailView: View { .aspectRatio(thumb.width / thumb.height, contentMode: .fit) } .buttonStyle(.plain) + // Required to prevent highlighting the button then dragging the scroll + // view from causing the button to be pressed. + .simultaneousGesture(TapGesture()) } } diff --git a/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoView.swift b/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoView.swift index 3dd25b0f..95411d3e 100644 --- a/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoView.swift +++ b/ios/Sources/Parra/Containers/Widgets/Feed/Views/FeedYouTubeVideoView.swift @@ -8,6 +8,33 @@ import SwiftUI struct FeedYouTubeVideoView: View { + // MARK: - Lifecycle + + init( + youtubeVideo: ParraFeedItemYoutubeVideoData, + feedItemId: String, + reactionOptions: [ParraReactionOptionGroup]?, + reactions: [ParraReactionSummary]?, + containerGeometry: GeometryProxy, + spacing: CGFloat, + performActionForFeedItemData: @escaping (_: ParraFeedItemData) -> Void + ) { + self.youtubeVideo = youtubeVideo + self.feedItemId = feedItemId + self.reactionOptions = reactionOptions + self.reactions = reactions + self.containerGeometry = containerGeometry + self.spacing = spacing + self.performActionForFeedItemData = performActionForFeedItemData + self._reactor = ObservedObject( + wrappedValue: FeedItemReactor( + feedItemId: feedItemId, + reactionOptionGroups: reactionOptions ?? [], + reactions: reactions ?? [] + ) + ) + } + // MARK: - Internal let youtubeVideo: ParraFeedItemYoutubeVideoData @@ -18,15 +45,13 @@ struct FeedYouTubeVideoView: View { let spacing: CGFloat let performActionForFeedItemData: (_ feedItemData: ParraFeedItemData) -> Void - var showReactions: Bool { - if let reactionOptions { - return !reactionOptions.isEmpty - } - - return false - } - var body: some View { + let hasPaywallEntitlement = true + // TODO: When doing paywalled youtube videos +// entitlements.hasEntitlement( +// youtubeVideo.attachmentPaywall?.entitlement +// ) + Button(action: { isPresentingModal = true }) { @@ -51,14 +76,13 @@ struct FeedYouTubeVideoView: View { ) .padding(.top, 6) .padding(.horizontal, 12) - .padding(.bottom, showReactions ? 0 : 16) + .padding(.bottom, reactor.showReactions ? 0 : 16) - if showReactions { + if reactor.showReactions { VStack { FeedReactionView( feedItemId: feedItemId, - reactionOptionGroups: reactionOptions, - reactions: reactions + reactor: _reactor ) } .padding( @@ -75,7 +99,7 @@ struct FeedYouTubeVideoView: View { // Required to prevent highlighting the button then dragging the scroll // view from causing the button to be pressed. .simultaneousGesture(TapGesture()) - .disabled(!redactionReasons.isEmpty) + .disabled(!redactionReasons.isEmpty && hasPaywallEntitlement) .background(parraTheme.palette.secondaryBackground) .applyCornerRadii(size: .xl, from: parraTheme) .buttonStyle(.plain) @@ -86,8 +110,7 @@ struct FeedYouTubeVideoView: View { FeedYouTubeVideoDetailView( youtubeVideo: youtubeVideo, feedItemId: feedItemId, - reactionOptions: reactionOptions, - reactions: reactions + reactor: reactor ) } } @@ -108,8 +131,11 @@ struct FeedYouTubeVideoView: View { @Environment(\.parraComponentFactory) private var componentFactory @Environment(\.parraTheme) private var parraTheme + @Environment(\.parraUserEntitlements) private var entitlements @Environment(\.redactionReasons) private var redactionReasons + @ObservedObject private var reactor: FeedItemReactor + @State private var isPresentingModal: Bool = false @ViewBuilder private var thumbnail: some View {