mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 00:01:00 +00:00
wip
This commit is contained in:
parent
5ae1d9be9a
commit
4806c43202
11 changed files with 122 additions and 44 deletions
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import ViewModels
|
|||
|
||||
struct CompositionAttachmentContentConfiguration {
|
||||
let viewModel: CompositionAttachmentViewModel
|
||||
let parentViewModel: CompositionViewModel
|
||||
}
|
||||
|
||||
extension CompositionAttachmentContentConfiguration: UIContentConfiguration {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue