This commit is contained in:
Justin Mazzocchi 2021-01-02 17:22:17 -08:00
parent 5ae1d9be9a
commit 4806c43202
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
11 changed files with 122 additions and 44 deletions

View file

@ -9,10 +9,11 @@ final class CompositionAttachmentsDataSource: UICollectionViewDiffableDataSource
DispatchQueue(label: "com.metabolist.metatext.composition-attachments-data-source.update-queue")
init(collectionView: UICollectionView,
viewModelProvider: @escaping (IndexPath) -> CompositionAttachmentViewModel?) {
viewModelProvider: @escaping (IndexPath) -> (CompositionAttachmentViewModel, CompositionViewModel)) {
let registration = UICollectionView.CellRegistration
<CompositionAttachmentCollectionViewCell, CompositionAttachmentViewModel> {
$0.viewModel = $2
<CompositionAttachmentCollectionViewCell, (CompositionAttachmentViewModel, CompositionViewModel)> {
$0.viewModel = $2.0
$0.parentViewModel = $2.1
}
super.init(collectionView: collectionView) { collectionView, indexPath, _ in

View file

@ -38,6 +38,8 @@
"camera-access.description" = "Open system settings to allow camera access";
"camera-access.open-system-settings" = "Open system settings";
"cancel" = "Cancel";
"compose.attachment.uploading" = "Uploading";
"compose.attachment.remove" = "Remove";
"error" = "Error";
"favorites" = "Favorites";
"registration.review-terms-of-use-and-privacy-policy-%@" = "Please review %@'s Terms of Use and Privacy Policy to continue";

View file

@ -44,4 +44,19 @@ public struct Attachment: Codable, Hashable {
public extension Attachment {
typealias Id = String
var aspectRatio: Double? {
if
let info = meta?.original,
let width = info.width,
let height = info.height,
width != 0,
height != 0 {
let aspectRatio = Double(width) / Double(height)
return aspectRatio.isNaN ? nil : aspectRatio
}
return nil
}
}

View file

@ -22,21 +22,6 @@ public extension AttachmentViewModel {
attachment.id.appending(status.id).hashValue
}
var aspectRatio: Double? {
if
let info = attachment.meta?.original,
let width = info.width,
let height = info.height,
width != 0,
height != 0 {
let aspectRatio = Double(width) / Double(height)
return aspectRatio.isNaN ? nil : aspectRatio
}
return nil
}
var shouldAutoplay: Bool {
switch attachment.type {
case .video:

View file

@ -46,8 +46,8 @@ public extension CompositionViewModel {
visibility: visibility)
}
func attachmentViewModel(indexPath: IndexPath) -> CompositionAttachmentViewModel {
attachmentViewModels[indexPath.item]
func remove(attachmentViewModel: CompositionAttachmentViewModel) {
attachmentViewModels.removeAll { $0 === attachmentViewModel }
}
}

View file

@ -5,6 +5,7 @@ import UIKit
import ViewModels
final class AttachmentUploadView: UIView {
let label = UILabel()
let progressView = UIProgressView(progressViewStyle: .default)
private var progressCancellable: AnyCancellable?
@ -24,13 +25,26 @@ final class AttachmentUploadView: UIView {
init() {
super.init(frame: .zero)
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontForContentSizeCategory = true
label.font = .preferredFont(forTextStyle: .callout)
label.textAlignment = .center
label.text = NSLocalizedString("compose.attachment.uploading", comment: "")
label.textColor = .secondaryLabel
label.numberOfLines = 0
addSubview(progressView)
progressView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
progressView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: .defaultSpacing),
progressView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
progressView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
progressView.centerYAnchor.constraint(equalTo: centerYAnchor)
progressView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
])
}

View file

@ -5,11 +5,15 @@ import ViewModels
class CompositionAttachmentCollectionViewCell: UICollectionViewCell {
var viewModel: CompositionAttachmentViewModel?
var parentViewModel: CompositionViewModel?
override func updateConfiguration(using state: UICellConfigurationState) {
guard let viewModel = viewModel else { return }
guard let viewModel = viewModel, let parentViewModel = parentViewModel else { return }
contentConfiguration = CompositionAttachmentContentConfiguration(viewModel: viewModel).updated(for: state)
contentConfiguration = CompositionAttachmentContentConfiguration(
viewModel: viewModel,
parentViewModel: parentViewModel)
.updated(for: state)
backgroundConfiguration = UIBackgroundConfiguration.clear().updated(for: state)
}
}

View file

@ -5,6 +5,7 @@ import ViewModels
struct CompositionAttachmentContentConfiguration {
let viewModel: CompositionAttachmentViewModel
let parentViewModel: CompositionViewModel
}
extension CompositionAttachmentContentConfiguration: UIContentConfiguration {

View file

@ -6,11 +6,16 @@ import ViewModels
class CompositionAttachmentView: UIView {
let imageView = UIImageView()
let removeButton = UIButton()
let editButton = UIButton()
private var compositionAttachmentConfiguration: CompositionAttachmentContentConfiguration
private var aspectRatioConstraint: NSLayoutConstraint
init(configuration: CompositionAttachmentContentConfiguration) {
self.compositionAttachmentConfiguration = configuration
aspectRatioConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: 2)
super.init(frame: .zero)
initialSetup()
@ -38,22 +43,70 @@ extension CompositionAttachmentView: UIContentView {
}
private extension CompositionAttachmentView {
// swiftlint:disable:next function_body_length
func initialSetup() {
backgroundColor = .secondarySystemBackground
layer.cornerRadius = .defaultCornerRadius
clipsToBounds = true
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = .defaultCornerRadius
imageView.clipsToBounds = true
imageView.kf.indicatorType = .activity
addSubview(removeButton)
removeButton.translatesAutoresizingMaskIntoConstraints = false
removeButton.setImage(
UIImage(
systemName: "xmark.circle.fill",
withConfiguration: UIImage.SymbolConfiguration(scale: .large)),
for: .normal)
removeButton.showsMenuAsPrimaryAction = true
removeButton.menu = UIMenu(
children: [
UIAction(
title: NSLocalizedString("compose.attachment.remove", comment: ""),
image: UIImage(systemName: "xmark.circle.fill"),
attributes: .destructive, handler: { [weak self] _ in
guard let self = self else { return }
self.compositionAttachmentConfiguration.parentViewModel.remove(
attachmentViewModel: self.compositionAttachmentConfiguration.viewModel)
})])
addSubview(editButton)
editButton.translatesAutoresizingMaskIntoConstraints = false
editButton.setImage(
UIImage(
systemName: "pencil.circle.fill",
withConfiguration: UIImage.SymbolConfiguration(scale: .large)),
for: .normal)
editButton.addAction(UIAction { [weak self] _ in }, for: .touchUpInside)
NSLayoutConstraint.activate([
aspectRatioConstraint,
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: bottomAnchor)
imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
removeButton.topAnchor.constraint(equalTo: topAnchor),
removeButton.trailingAnchor.constraint(equalTo: trailingAnchor),
removeButton.heightAnchor.constraint(equalToConstant: .minimumButtonDimension),
removeButton.widthAnchor.constraint(equalToConstant: .minimumButtonDimension),
editButton.trailingAnchor.constraint(equalTo: trailingAnchor),
editButton.bottomAnchor.constraint(equalTo: bottomAnchor),
editButton.heightAnchor.constraint(equalToConstant: .minimumButtonDimension),
editButton.widthAnchor.constraint(equalToConstant: .minimumButtonDimension)
])
}
func applyCompositionAttachmentConfiguration() {
imageView.kf.setImage(with: compositionAttachmentConfiguration.viewModel.attachment.previewUrl)
aspectRatioConstraint.isActive = false
aspectRatioConstraint = imageView.widthAnchor.constraint(
equalTo: imageView.heightAnchor,
multiplier: CGFloat(compositionAttachmentConfiguration.viewModel.attachment.aspectRatio ?? 1))
aspectRatioConstraint.priority = .justBelowMax
aspectRatioConstraint.isActive = true
}
}

View file

@ -17,9 +17,10 @@ final class CompositionView: UIView {
private var cancellables = Set<AnyCancellable>()
private lazy var attachmentsDataSource: CompositionAttachmentsDataSource = {
CompositionAttachmentsDataSource(
collectionView: attachmentsCollectionView) { [weak self] in
self?.viewModel.attachmentViewModel(indexPath: $0)
let vm = viewModel
return .init(collectionView: attachmentsCollectionView) {
(vm.attachmentViewModels[$0.item], vm)
}
}()
@ -28,20 +29,25 @@ final class CompositionView: UIView {
self.parentViewModel = parentViewModel
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
widthDimension: .estimated(Self.attachmentCollectionViewHeight),
heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2))
widthDimension: .estimated(Self.attachmentCollectionViewHeight),
heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitems: [item])
group.interItemSpacing = .fixed(.defaultSpacing)
let section = NSCollectionLayoutSection(group: group)
let attachmentsLayout = UICollectionViewCompositionalLayout(section: section)
section.interGroupSpacing = .defaultSpacing
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
let attachmentsLayout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)
attachmentsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: attachmentsLayout)
super.init(frame: .zero)
@ -66,7 +72,7 @@ extension CompositionView: UITextViewDelegate {
}
private extension CompositionView {
static let attachmentUploadViewHeight: CGFloat = 100
static let attachmentCollectionViewHeight: CGFloat = 200
// swiftlint:disable:next function_body_length
func initialSetup() {
@ -114,6 +120,7 @@ private extension CompositionView {
stackView.addArrangedSubview(attachmentsCollectionView)
attachmentsCollectionView.dataSource = attachmentsDataSource
attachmentsCollectionView.backgroundColor = .clear
stackView.addArrangedSubview(attachmentUploadView)
@ -139,7 +146,6 @@ private extension CompositionView {
.store(in: &cancellables)
viewModel.$attachmentViewModels
.receive(on: DispatchQueue.main) // hack to punt to next run loop, consider refactoring
.sink { [weak self] in
self?.attachmentsDataSource.apply($0.map(\.attachment).snapshot())
self?.attachmentsCollectionView.isHidden = $0.isEmpty
@ -161,10 +167,7 @@ private extension CompositionView {
stackView.topAnchor.constraint(equalTo: guide.topAnchor),
stackView.trailingAnchor.constraint(equalTo: guide.trailingAnchor),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: guide.bottomAnchor),
attachmentsCollectionView.heightAnchor.constraint(
equalTo: attachmentsCollectionView.widthAnchor,
multiplier: 1 / 4),
attachmentUploadView.heightAnchor.constraint(equalToConstant: Self.attachmentUploadViewHeight)
attachmentsCollectionView.heightAnchor.constraint(equalToConstant: Self.attachmentCollectionViewHeight)
]
if UIDevice.current.userInterfaceIdiom == .pad {

View file

@ -47,7 +47,7 @@ final class StatusAttachmentsView: UIView {
let newAspectRatio: CGFloat
if attachmentCount == 1, let aspectRatio = attachmentViewModels.first?.aspectRatio {
if attachmentCount == 1, let aspectRatio = attachmentViewModels.first?.attachment.aspectRatio {
newAspectRatio = max(CGFloat(aspectRatio), 16 / 9)
} else {
newAspectRatio = 16 / 9