From e6e1816e112b559bea761eede5334ed9a30fc3ec Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Fri, 18 Dec 2020 22:30:19 -0800 Subject: [PATCH] wip --- .../CompositionAttachmentsDataSource.swift | 29 +++++++-- .../Endpoints/StatusEndpoint.swift | 20 ++++++- Metatext.xcodeproj/project.pbxproj | 12 ++++ .../Entities/StatusComponents.swift | 5 ++ .../Services/IdentityService.swift | 27 +-------- .../CompositionAttachmentViewModel.swift | 2 +- .../ViewModels/CompositionViewModel.swift | 23 +++++--- .../Entities/AttachmentUpload.swift | 9 +++ .../ViewModels/NewStatusViewModel.swift | 37 +++++++++--- Views/AttachmentUploadView.swift | 2 +- ...positionAttachmentCollectionViewCell.swift | 10 +++- ...sitionAttachmentContentConfiguration.swift | 18 ++++++ Views/CompositionAttachmentView.swift | 59 +++++++++++++++++++ Views/CompositionView.swift | 45 ++++++++++++-- 14 files changed, 240 insertions(+), 58 deletions(-) create mode 100644 ServiceLayer/Sources/ServiceLayer/Entities/StatusComponents.swift create mode 100644 ViewModels/Sources/ViewModels/Entities/AttachmentUpload.swift create mode 100644 Views/CompositionAttachmentContentConfiguration.swift create mode 100644 Views/CompositionAttachmentView.swift diff --git a/Data Sources/CompositionAttachmentsDataSource.swift b/Data Sources/CompositionAttachmentsDataSource.swift index bc6d786..5050e63 100644 --- a/Data Sources/CompositionAttachmentsDataSource.swift +++ b/Data Sources/CompositionAttachmentsDataSource.swift @@ -5,9 +5,28 @@ import UIKit import ViewModels final class CompositionAttachmentsDataSource: UICollectionViewDiffableDataSource { -// init(collectionView: UICollectionView, composition: Composition) { -// super.init(collectionView: collectionView) { collectionView, indexPath, attachment in -// -// } -// } + private let updateQueue = + DispatchQueue(label: "com.metabolist.metatext.composition-attachments-data-source.update-queue") + + init(collectionView: UICollectionView, viewModelProvider: @escaping (IndexPath) -> CompositionAttachmentViewModel) { + let registration = UICollectionView.CellRegistration + { + $0.viewModel = $2 + } + + super.init(collectionView: collectionView) { collectionView, indexPath, _ in + collectionView.dequeueConfiguredReusableCell( + using: registration, + for: indexPath, + item: viewModelProvider(indexPath)) + } + } + + override func apply(_ snapshot: NSDiffableDataSourceSnapshot, + animatingDifferences: Bool = true, + completion: (() -> Void)? = nil) { + updateQueue.async { + super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion) + } + } } diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift index 0062ab0..6abc9a9 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusEndpoint.swift @@ -15,9 +15,15 @@ public enum StatusEndpoint { public extension StatusEndpoint { struct Components { - public var text: String? + public let inReplyToId: Status.Id? + public let text: String + public let mediaIds: [Attachment.Id] - public init() {} + public init(inReplyToId: Status.Id?, text: String, mediaIds: [Attachment.Id]) { + self.inReplyToId = inReplyToId + self.text = text + self.mediaIds = mediaIds + } } } @@ -25,7 +31,15 @@ extension StatusEndpoint.Components { var jsonBody: [String: Any]? { var params = [String: Any]() - params["status"] = text + if !text.isEmpty { + params["status"] = text + } + + if !mediaIds.isEmpty { + params["media_ids"] = mediaIds + } + + params["in_reply_to_id"] = inReplyToId return params } diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 7d6bf8f..f9b6f9c 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -33,6 +33,10 @@ D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065965A25899DAE0096AC5D /* CompositionAttachmentsDataSource.swift */; }; D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; }; D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; }; + D0804133258D902900AD6139 /* CompositionAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0804132258D902900AD6139 /* CompositionAttachmentView.swift */; }; + D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0804132258D902900AD6139 /* CompositionAttachmentView.swift */; }; + D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; }; + D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */; }; D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */; }; D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */; }; D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */; }; @@ -191,6 +195,8 @@ D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; + D0804132258D902900AD6139 /* CompositionAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentView.swift; sourceTree = ""; }; + D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentContentConfiguration.swift; sourceTree = ""; }; D085C3BB25008DEC008A6C5E /* DB */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DB; sourceTree = ""; }; D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePageViewController.swift; sourceTree = ""; }; @@ -444,6 +450,8 @@ D0C7D42424F76169001EBDBB /* AddIdentityView.swift */, D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */, D065966025899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift */, + D080413D258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift */, + D0804132258D902900AD6139 /* CompositionAttachmentView.swift */, D08E52E2257D747400FA2C5F /* CompositionContentConfiguration.swift */, D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */, D08E52DB257D742B00FA2C5F /* CompositionListCell.swift */, @@ -767,6 +775,7 @@ D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */, D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */, D0B32F50250B373600311912 /* RegistrationView.swift in Sources */, + D0804133258D902900AD6139 /* CompositionAttachmentView.swift in Sources */, D08B8D612540DE3B00B1EBEF /* ZoomDismissalInteractionController.swift in Sources */, D036AA07254B6118009094DF /* NotificationView.swift in Sources */, D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */, @@ -791,6 +800,7 @@ D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */, D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */, D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */, + D080413E258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */, D0625E59250F092900502611 /* StatusListCell.swift in Sources */, D0E569DB2529319100FA1D72 /* LoadMoreView.swift in Sources */, D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */, @@ -867,11 +877,13 @@ D0E7AD4225870C79005F5E2D /* UIVIewController+Extensions.swift in Sources */, D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */, D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */, + D080413F258D904400AD6139 /* CompositionAttachmentContentConfiguration.swift in Sources */, D0F2D5452581ABAB00986197 /* KingfisherOptionsInfo+Extensions.swift in Sources */, D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */, D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */, D0F2D4D6257EED6100986197 /* NewStatusDataSource.swift in Sources */, D065966725899E910096AC5D /* CompositionAttachmentsDataSource.swift in Sources */, + D0804134258D902900AD6139 /* CompositionAttachmentView.swift in Sources */, D08E52FD257D78CB00FA2C5F /* UIColor+Extensions.swift in Sources */, D08E52E4257D747400FA2C5F /* CompositionContentConfiguration.swift in Sources */, D065966225899E890096AC5D /* CompositionAttachmentCollectionViewCell.swift in Sources */, diff --git a/ServiceLayer/Sources/ServiceLayer/Entities/StatusComponents.swift b/ServiceLayer/Sources/ServiceLayer/Entities/StatusComponents.swift new file mode 100644 index 0000000..9246d9d --- /dev/null +++ b/ServiceLayer/Sources/ServiceLayer/Entities/StatusComponents.swift @@ -0,0 +1,5 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import MastodonAPI + +public typealias StatusComponents = StatusEndpoint.Components diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index b40523f..607bbea 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -212,30 +212,9 @@ public extension IdentityService { progress: progress) } -// func post(compositions: [Composition]) -> AnyPublisher { -// fatalError() -// guard let composition = compositions.first else { fatalError() } - -// guard let attachment = composition.attachments.first else { fatalError() } -// return mastodonAPIClient.request(AttachmentEndpoint.create( -// data: attachment.data, -// mimeType: attachment.mimeType, -// description: attachment.description, -// focus: attachment.focus)) -// .print() -// .ignoreOutput() -// .eraseToAnyPublisher() - -// var components = StatusEndpoint.Components() -// -// if !composition.text.isEmpty { -// components.text = composition.text -// } -// -// return mastodonAPIClient.request(StatusEndpoint.post(components)) -// .ignoreOutput() -// .eraseToAnyPublisher() -// } + func post(statusComponents: StatusComponents) -> AnyPublisher { + mastodonAPIClient.request(StatusEndpoint.post(statusComponents)).map(\.id).eraseToAnyPublisher() + } func service(timeline: Timeline) -> TimelineService { TimelineService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) diff --git a/ViewModels/Sources/ViewModels/CompositionAttachmentViewModel.swift b/ViewModels/Sources/ViewModels/CompositionAttachmentViewModel.swift index 3ecdc67..c6c06e4 100644 --- a/ViewModels/Sources/ViewModels/CompositionAttachmentViewModel.swift +++ b/ViewModels/Sources/ViewModels/CompositionAttachmentViewModel.swift @@ -6,7 +6,7 @@ import Mastodon import ServiceLayer public final class CompositionAttachmentViewModel: ObservableObject { - public var attachment: Attachment + public let attachment: Attachment init(attachment: Attachment) { self.attachment = attachment diff --git a/ViewModels/Sources/ViewModels/CompositionViewModel.swift b/ViewModels/Sources/ViewModels/CompositionViewModel.swift index 6f1ccc6..c6a32ed 100644 --- a/ViewModels/Sources/ViewModels/CompositionViewModel.swift +++ b/ViewModels/Sources/ViewModels/CompositionViewModel.swift @@ -7,8 +7,9 @@ import ServiceLayer public final class CompositionViewModel: ObservableObject { public let id = Id() + public var isPosted = false @Published public var text = "" - @Published public private(set) var attachments = [Attachment]() + @Published public private(set) var attachmentViewModels = [CompositionAttachmentViewModel]() @Published public private(set) var isPostable = false @Published public private(set) var identification: Identification @Published public private(set) var attachmentUpload: AttachmentUpload? @@ -35,10 +36,11 @@ public extension CompositionViewModel { case error(Error) } - struct AttachmentUpload { - public let progress: Progress - public let data: Data - public let mimeType: String + func components(inReplyToId: Status.Id?) -> StatusComponents { + StatusComponents( + inReplyToId: inReplyToId, + text: text, + mediaIds: attachmentViewModels.map(\.attachment.id)) } func presentMediaPicker() { @@ -63,17 +65,20 @@ public extension CompositionViewModel { return self.identification.service.uploadAttachment(data: data, mimeType: mimeType, progress: progress) } .print() + .receive(on: DispatchQueue.main) .sink { [weak self] in - DispatchQueue.main.async { - self?.attachmentUpload = nil - } + self?.attachmentUpload = nil if case let .failure(error) = $0 { self?.eventsSubject.send(.error(error)) } } receiveValue: { [weak self] in - self?.attachments.append($0) + self?.attachmentViewModels.append(CompositionAttachmentViewModel(attachment: $0)) } .store(in: &cancellables) } + + func attachmentViewModel(indexPath: IndexPath) -> CompositionAttachmentViewModel { + attachmentViewModels[indexPath.item] + } } diff --git a/ViewModels/Sources/ViewModels/Entities/AttachmentUpload.swift b/ViewModels/Sources/ViewModels/Entities/AttachmentUpload.swift new file mode 100644 index 0000000..d1e6b72 --- /dev/null +++ b/ViewModels/Sources/ViewModels/Entities/AttachmentUpload.swift @@ -0,0 +1,9 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation + +public struct AttachmentUpload: Hashable { + public let progress: Progress + public let data: Data + public let mimeType: String +} diff --git a/ViewModels/Sources/ViewModels/NewStatusViewModel.swift b/ViewModels/Sources/ViewModels/NewStatusViewModel.swift index 9d16de5..badf110 100644 --- a/ViewModels/Sources/ViewModels/NewStatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/NewStatusViewModel.swift @@ -67,14 +67,9 @@ public extension NewStatusViewModel { } func post() { -// identification.service.post(compositions: compositionViewModels.map(\.composition)) -// .receive(on: DispatchQueue.main) -// .handleEvents( -// receiveSubscription: { [weak self] _ in self?.loading = true }, -// receiveCompletion: { [weak self] _ in self?.loading = false }) -// .assignErrorsToAlertItem(to: \.alertItem, on: self) -// .sink { _ in } -// .store(in: &cancellables) + guard let unposted = compositionViewModels.first(where: { !$0.isPosted }) else { return } + + post(viewModel: unposted, inReplyToId: nil) } } @@ -104,4 +99,30 @@ private extension NewStatusViewModel { eventsSubject.send(event) } } + + func post(viewModel: CompositionViewModel, inReplyToId: Status.Id?) { + loading = true + identification.service.post(statusComponents: viewModel.components(inReplyToId: inReplyToId)) + .receive(on: DispatchQueue.main) + .sink { [weak self] in + guard let self = self else { return } + + switch $0 { + case .finished: + self.loading = self.compositionViewModels.allSatisfy(\.isPosted) + case let .failure(error): + self.alertItem = AlertItem(error: error) + self.loading = false + } + } receiveValue: { [weak self] in + guard let self = self else { return } + + viewModel.isPosted = true + + if let unposted = self.compositionViewModels.first(where: { !$0.isPosted }) { + self.post(viewModel: unposted, inReplyToId: $0) + } + } + .store(in: &cancellables) + } } diff --git a/Views/AttachmentUploadView.swift b/Views/AttachmentUploadView.swift index 701ca16..bbd8ad9 100644 --- a/Views/AttachmentUploadView.swift +++ b/Views/AttachmentUploadView.swift @@ -8,7 +8,7 @@ final class AttachmentUploadView: UIView { let progressView = UIProgressView(progressViewStyle: .default) private var progressCancellable: AnyCancellable? - var attachmentUpload: CompositionViewModel.AttachmentUpload? { + var attachmentUpload: AttachmentUpload? { didSet { if let attachmentUpload = attachmentUpload { progressCancellable = attachmentUpload.progress.publisher(for: \.fractionCompleted) diff --git a/Views/CompositionAttachmentCollectionViewCell.swift b/Views/CompositionAttachmentCollectionViewCell.swift index 3aaee27..433aa0c 100644 --- a/Views/CompositionAttachmentCollectionViewCell.swift +++ b/Views/CompositionAttachmentCollectionViewCell.swift @@ -1,7 +1,15 @@ // Copyright © 2020 Metabolist. All rights reserved. import UIKit +import ViewModels class CompositionAttachmentCollectionViewCell: UICollectionViewCell { - + var viewModel: CompositionAttachmentViewModel? + + override func updateConfiguration(using state: UICellConfigurationState) { + guard let viewModel = viewModel else { return } + + contentConfiguration = CompositionAttachmentContentConfiguration(viewModel: viewModel).updated(for: state) + backgroundConfiguration = UIBackgroundConfiguration.clear().updated(for: state) + } } diff --git a/Views/CompositionAttachmentContentConfiguration.swift b/Views/CompositionAttachmentContentConfiguration.swift new file mode 100644 index 0000000..a1796b8 --- /dev/null +++ b/Views/CompositionAttachmentContentConfiguration.swift @@ -0,0 +1,18 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import UIKit +import ViewModels + +struct CompositionAttachmentContentConfiguration { + let viewModel: CompositionAttachmentViewModel +} + +extension CompositionAttachmentContentConfiguration: UIContentConfiguration { + func makeContentView() -> UIView & UIContentView { + CompositionAttachmentView(configuration: self) + } + + func updated(for state: UIConfigurationState) -> CompositionAttachmentContentConfiguration { + self + } +} diff --git a/Views/CompositionAttachmentView.swift b/Views/CompositionAttachmentView.swift new file mode 100644 index 0000000..d46d335 --- /dev/null +++ b/Views/CompositionAttachmentView.swift @@ -0,0 +1,59 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Kingfisher +import UIKit +import ViewModels + +class CompositionAttachmentView: UIView { + let imageView = UIImageView() + private var compositionAttachmentConfiguration: CompositionAttachmentContentConfiguration + + init(configuration: CompositionAttachmentContentConfiguration) { + self.compositionAttachmentConfiguration = configuration + + super.init(frame: .zero) + + initialSetup() + applyCompositionAttachmentConfiguration() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension CompositionAttachmentView: UIContentView { + var configuration: UIContentConfiguration { + get { compositionAttachmentConfiguration } + set { + guard let compositionAttachmentConfiguration = newValue as? CompositionAttachmentContentConfiguration + else { return } + + self.compositionAttachmentConfiguration = compositionAttachmentConfiguration + + applyCompositionAttachmentConfiguration() + } + } +} + +private extension CompositionAttachmentView { + func initialSetup() { + addSubview(imageView) + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFill + imageView.layer.cornerRadius = .defaultCornerRadius + imageView.clipsToBounds = true + + NSLayoutConstraint.activate([ + imageView.leadingAnchor.constraint(equalTo: leadingAnchor), + imageView.topAnchor.constraint(equalTo: topAnchor), + imageView.trailingAnchor.constraint(equalTo: trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + } + + func applyCompositionAttachmentConfiguration() { + imageView.kf.setImage(with: compositionAttachmentConfiguration.viewModel.attachment.previewUrl) + } +} diff --git a/Views/CompositionView.swift b/Views/CompositionView.swift index ad9a3dc..49bfa4a 100644 --- a/Views/CompositionView.swift +++ b/Views/CompositionView.swift @@ -4,18 +4,41 @@ import Combine import Kingfisher import UIKit -class CompositionView: UIView { +final class CompositionView: UIView { let avatarImageView = UIImageView() let textView = UITextView() let attachmentUploadView = AttachmentUploadView() -// let attachmentsCollectionView = UICollectionView() + let attachmentsCollectionView: UICollectionView private var compositionConfiguration: CompositionContentConfiguration private var cancellables = Set() + private lazy var attachmentsDataSource: CompositionAttachmentsDataSource = { + CompositionAttachmentsDataSource( + collectionView: attachmentsCollectionView, + viewModelProvider: compositionConfiguration.viewModel.attachmentViewModel(indexPath:)) + }() + init(configuration: CompositionContentConfiguration) { self.compositionConfiguration = configuration + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(0.2), + heightDimension: .fractionalHeight(1.0)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalWidth(0.2)) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item]) + + group.interItemSpacing = .fixed(.defaultSpacing) + + let section = NSCollectionLayoutSection(group: group) + let attachmentsLayout = UICollectionViewCompositionalLayout(section: section) + attachmentsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: attachmentsLayout) + super.init(frame: .zero) initialSetup() @@ -48,7 +71,7 @@ extension CompositionView: UITextViewDelegate { } private extension CompositionView { - static let attachmentsCollectionViewHeight: CGFloat = 100 + static let attachmentUploadViewHeight: CGFloat = 100 func initialSetup() { addSubview(avatarImageView) @@ -71,7 +94,8 @@ private extension CompositionView { textView.inputAccessoryView?.sizeToFit() textView.delegate = self -// stackView.addArrangedSubview(attachmentsCollectionView) + stackView.addArrangedSubview(attachmentsCollectionView) + attachmentsCollectionView.dataSource = attachmentsDataSource stackView.addArrangedSubview(attachmentUploadView) @@ -85,8 +109,10 @@ private extension CompositionView { stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor), stackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor), -// attachmentsCollectionView.heightAnchor.constraint(equalToConstant: Self.attachmentsCollectionViewHeight) - attachmentUploadView.heightAnchor.constraint(equalToConstant: Self.attachmentsCollectionViewHeight) + attachmentsCollectionView.heightAnchor.constraint( + equalTo: attachmentsCollectionView.widthAnchor, + multiplier: 1 / 4), + attachmentUploadView.heightAnchor.constraint(equalToConstant: Self.attachmentUploadViewHeight) ] for constraint in constraints { @@ -99,6 +125,13 @@ private extension CompositionView { .sink { [weak self] in self?.avatarImageView.kf.setImage(with: $0) } .store(in: &cancellables) + compositionConfiguration.viewModel.$attachmentViewModels + .sink { [weak self] in + self?.attachmentsDataSource.apply([$0.map(\.attachment)].snapshot()) + self?.attachmentsCollectionView.isHidden = $0.isEmpty + } + .store(in: &cancellables) + compositionConfiguration.viewModel.$attachmentUpload .sink { [weak self] in self?.attachmentUploadView.attachmentUpload = $0 } .store(in: &cancellables)