diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index a3ba500..6f963b5 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; }; D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; }; D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + D0EA59402522AC8700804347 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA593F2522AC8700804347 /* CardView.swift */; }; + D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA59472522B8B600804347 /* ViewConstants.swift */; }; D0F0B10E251A868200942152 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B10D251A868200942152 /* AccountView.swift */; }; D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B112251A86A000942152 /* AccountContentConfiguration.swift */; }; D0F0B126251A90F400942152 /* AccountListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0B125251A90F400942152 /* AccountListCell.swift */; }; @@ -150,6 +152,8 @@ D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = ""; }; + D0EA593F2522AC8700804347 /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = ""; }; + D0EA59472522B8B600804347 /* ViewConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewConstants.swift; sourceTree = ""; }; D0F0B10D251A868200942152 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; D0F0B112251A86A000942152 /* AccountContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountContentConfiguration.swift; sourceTree = ""; }; D0F0B125251A90F400942152 /* AccountListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListCell.swift; sourceTree = ""; }; @@ -236,6 +240,7 @@ D0625E55250F086B00502611 /* Status */ = { isa = PBXGroup; children = ( + D0EA593F2522AC8700804347 /* CardView.swift */, D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */, D0625E58250F092900502611 /* StatusListCell.swift */, D0625E5E250F0CFF00502611 /* StatusView.swift */, @@ -277,7 +282,6 @@ D0F0B10D251A868200942152 /* AccountView.swift */, D0C7D42424F76169001EBDBB /* AddIdentityView.swift */, D01F41E024F8885900D55A2D /* Attachments */, - D0C7D42524F76169001EBDBB /* TableView.swift */, D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */, D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */, D0BEB20424FA1107001B0F04 /* FiltersView.swift */, @@ -292,8 +296,10 @@ D02E1F94250B13210071AD56 /* SafariView.swift */, D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */, D0625E55250F086B00502611 /* Status */, + D0C7D42524F76169001EBDBB /* TableView.swift */, D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */, D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */, + D0EA59472522B8B600804347 /* ViewConstants.swift */, D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */, ); path = Views; @@ -545,10 +551,12 @@ D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */, D01C6FAC252024BD003D0300 /* Array+Extensions.swift in Sources */, D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */, + D0EA59402522AC8700804347 /* CardView.swift in Sources */, D0F0B10E251A868200942152 /* AccountView.swift in Sources */, D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */, D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */, D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */, + D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */, D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */, D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */, D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */, diff --git a/ViewModels/Sources/ViewModels/CardViewModel.swift b/ViewModels/Sources/ViewModels/CardViewModel.swift new file mode 100644 index 0000000..83bd4e9 --- /dev/null +++ b/ViewModels/Sources/ViewModels/CardViewModel.swift @@ -0,0 +1,22 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation +import Mastodon + +public struct CardViewModel { + private let card: Card + + init(card: Card) { + self.card = card + } +} + +public extension CardViewModel { + var url: URL { card.url } + + var title: String { card.title } + + var description: String { card.description } + + var imageURL: URL? { card.image } +} diff --git a/ViewModels/Sources/ViewModels/StatusViewModel.swift b/ViewModels/Sources/ViewModels/StatusViewModel.swift index 2f10a4e..01e1901 100644 --- a/ViewModels/Sources/ViewModels/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusViewModel.swift @@ -90,13 +90,13 @@ public extension StatusViewModel { var sharingURL: URL? { statusService.status.displayStatus.url } - var cardURL: URL? { statusService.status.displayStatus.card?.url } - - var cardTitle: String? { statusService.status.displayStatus.card?.title } - - var cardDescription: String? { statusService.status.displayStatus.card?.description } - - var cardImageURL: URL? { statusService.status.displayStatus.card?.image } + var cardViewModel: CardViewModel? { + if let card = statusService.status.displayStatus.card { + return CardViewModel(card: card) + } else { + return nil + } + } var canBeReblogged: Bool { switch statusService.status.displayStatus.visibility { diff --git a/Views/Status/CardView.swift b/Views/Status/CardView.swift new file mode 100644 index 0000000..aa5039a --- /dev/null +++ b/Views/Status/CardView.swift @@ -0,0 +1,106 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Kingfisher +import UIKit +import ViewModels + +final class CardView: UIView { + let imageView = UIImageView() + let titleLabel = UILabel() + let descriptionLabel = UILabel() + let urlLabel = UILabel() + let button = UIButton() + + var viewModel: CardViewModel? { + didSet { + guard let viewModel = viewModel else { return } + + imageView.isHidden = viewModel.imageURL == nil + imageView.kf.setImage(with: viewModel.imageURL) + + titleLabel.text = viewModel.title + descriptionLabel.text = viewModel.description + descriptionLabel.isHidden = descriptionLabel.text == "" || descriptionLabel.text == titleLabel.text + + if + let host = viewModel.url.host, host.hasPrefix("www."), + let withoutWww = host.components(separatedBy: "www.").last { + urlLabel.text = withoutWww + } else { + urlLabel.text = viewModel.url.host + } + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + initializationActions() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + + initializationActions() + } +} + +private extension CardView { + // swiftlint:disable:next function_body_length + func initializationActions() { + backgroundColor = .secondarySystemBackground + layer.cornerRadius = .defaultCornerRadius + clipsToBounds = true + + let stackView = UIStackView() + let innerStackView = UIStackView() + + addSubview(stackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + + addSubview(button) + button.translatesAutoresizingMaskIntoConstraints = false + button.setBackgroundImage(.highlightedButtonBackground, for: .highlighted) + + stackView.addArrangedSubview(imageView) + stackView.addArrangedSubview(innerStackView) + + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + + innerStackView.axis = .vertical + innerStackView.isLayoutMarginsRelativeArrangement = true + innerStackView.directionalLayoutMargins = + .init(top: .defaultSpacing, leading: .defaultSpacing, bottom: .defaultSpacing, trailing: .defaultSpacing) + innerStackView.spacing = .compactSpacing + innerStackView.addArrangedSubview(titleLabel) + innerStackView.addArrangedSubview(descriptionLabel) + innerStackView.addArrangedSubview(urlLabel) + + titleLabel.font = .preferredFont(forTextStyle: .headline) + titleLabel.adjustsFontForContentSizeCategory = true + titleLabel.setContentCompressionResistancePriority(.required, for: .vertical) + descriptionLabel.font = .preferredFont(forTextStyle: .subheadline) + descriptionLabel.adjustsFontForContentSizeCategory = true + descriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) + urlLabel.font = .preferredFont(forTextStyle: .footnote) + urlLabel.adjustsFontForContentSizeCategory = true + urlLabel.setContentCompressionResistancePriority(.required, for: .vertical) + urlLabel.textColor = .secondaryLabel + + NSLayoutConstraint.activate([ + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), + stackView.topAnchor.constraint(equalTo: topAnchor), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor), + button.leadingAnchor.constraint(equalTo: leadingAnchor), + button.trailingAnchor.constraint(equalTo: trailingAnchor), + button.topAnchor.constraint(equalTo: topAnchor), + button.bottomAnchor.constraint(equalTo: bottomAnchor), + imageView.heightAnchor.constraint(equalTo: innerStackView.heightAnchor), + imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor) + ]) + } +} diff --git a/Views/Status/StatusView.swift b/Views/Status/StatusView.swift index a8cb6ef..a219558 100644 --- a/Views/Status/StatusView.swift +++ b/Views/Status/StatusView.swift @@ -20,12 +20,7 @@ class StatusView: UIView { @IBOutlet weak var favoriteButton: UIButton! @IBOutlet weak var shareButton: UIButton! @IBOutlet weak var attachmentsView: AttachmentsView! - @IBOutlet weak var cardView: UIView! - @IBOutlet weak var cardImageView: UIImageView! - @IBOutlet weak var cardTitleLabel: UILabel! - @IBOutlet weak var cardDescriptionLabel: UILabel! - @IBOutlet weak var cardURLLabel: UILabel! - @IBOutlet weak var cardButton: UIButton! + @IBOutlet weak var cardView: CardView! @IBOutlet weak var sensitiveContentView: UIStackView! @IBOutlet weak var hasReplyFollowingView: UIView! @IBOutlet weak var inReplyToView: UIView! @@ -86,7 +81,6 @@ extension StatusView: UIContentView { avatarImageView.kf.cancelDownloadTask() contextParentAvatarImageView.kf.cancelDownloadTask() - cardImageView.kf.cancelDownloadTask() applyStatusConfiguration() } } @@ -135,13 +129,11 @@ private extension StatusView { avatarImageView.kf.indicatorType = .activity contextParentAvatarImageView.kf.indicatorType = .activity - cardImageView.kf.indicatorType = .activity contentTextView.delegate = self let highlightedButtonBackgroundImage = UIColor(white: 0, alpha: 0.5).image() - cardButton.setBackgroundImage(highlightedButtonBackgroundImage, for: .highlighted) avatarButton.setBackgroundImage(highlightedButtonBackgroundImage, for: .highlighted) contextParentAvatarButton.setBackgroundImage(highlightedButtonBackgroundImage, for: .highlighted) @@ -150,6 +142,17 @@ private extension StatusView { avatarButton.addAction(accountAction, for: .touchUpInside) contextParentAvatarButton.addAction(accountAction, for: .touchUpInside) + cardView.button.addAction( + UIAction { [weak self] _ in + guard + let viewModel = self?.statusConfiguration.viewModel, + let url = viewModel.cardViewModel?.url + else { return } + + viewModel.urlSelected(url) + }, + for: .touchUpInside) + let favoriteAction = UIAction { [weak self] _ in self?.statusConfiguration.viewModel.toggleFavorited() } favoriteButton.addAction(favoriteAction, for: .touchUpInside) @@ -287,29 +290,8 @@ private extension StatusView { attachmentsView.attachmentViewModels = viewModel.attachmentViewModels setNeedsLayout() - if let cardURL = viewModel.cardURL { - cardTitleLabel.text = viewModel.cardTitle - cardDescriptionLabel.text = viewModel.cardDescription - cardDescriptionLabel.isHidden = cardDescriptionLabel.text == "" - || cardDescriptionLabel.text == cardTitleLabel.text - if - let host = cardURL.host, host.hasPrefix("www."), - let withoutWww = cardURL.host?.components(separatedBy: "www.").last { - cardURLLabel.text = withoutWww - } else { - cardURLLabel.text = cardURL.host - } - - if let cardImageURL = viewModel.cardImageURL { - cardImageView.isHidden = false - cardImageView.kf.setImage(with: cardImageURL) - } else { - cardImageView.isHidden = true - } - cardView.isHidden = false - } else { - cardView.isHidden = true - } + cardView.viewModel = viewModel.cardViewModel + cardView.isHidden = viewModel.cardViewModel == nil sensitiveContentView.isHidden = !viewModel.shouldDisplaySensitiveContent diff --git a/Views/Status/StatusView.xib b/Views/Status/StatusView.xib index 19379fd..9f2be47 100644 --- a/Views/Status/StatusView.xib +++ b/Views/Status/StatusView.xib @@ -1,8 +1,8 @@ - + - + @@ -18,11 +18,6 @@ - - - - - @@ -72,19 +67,19 @@ - + - + - + @@ -96,7 +91,7 @@ - + @@ -130,31 +125,31 @@ - + - + - + @@ -220,13 +215,13 @@ - - - - - - - - - - - - - - - - - + + - +