diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f23a4280a6c..45a6083e923 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -2,6 +2,7 @@ *** Use [*****] to indicate smoke tests of all critical flows should be run on the final IPA before release (e.g. major library or OS update). 21.7 ----- +- [*] Product Details: Display cover tag on the first product image [https://github.com/woocommerce/woocommerce-ios/pull/15041] - [*] Payments: Update learn more links to open Stripe-specific docs when using that gateway [https://github.com/woocommerce/woocommerce-ios/pull/15035] - [x] Now, usernames and emails in text fields across multiple login views are no longer capitalized. [https://github.com/woocommerce/woocommerce-ios/pull/15002] diff --git a/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/Collection View Cells/ProductImageCollectionViewCell.swift b/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/Collection View Cells/ProductImageCollectionViewCell.swift index c1488eb44f5..3492edb76d3 100644 --- a/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/Collection View Cells/ProductImageCollectionViewCell.swift +++ b/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/Collection View Cells/ProductImageCollectionViewCell.swift @@ -7,11 +7,33 @@ final class ProductImageCollectionViewCell: UICollectionViewCell { var cancellableTask: Task? + private(set) lazy var coverTagView: UIView = { + let containerView = UIView(frame: .zero) + containerView.backgroundColor = UIColor.primary + containerView.clipsToBounds = true + containerView.isHidden = true + containerView.layer.cornerRadius = Constants.tagCornerRadius + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(tagLabel) + containerView.pinSubviewToAllEdges(tagLabel, insets: Constants.tagEdgeInsets) + return containerView + }() + + private lazy var tagLabel: UILabel = { + let label = UILabel(frame: .zero) + label.translatesAutoresizingMaskIntoConstraints = false + label.applyCaption1Style() + label.textColor = UIColor(light: .white, dark: .black) + label.text = Localization.tagLabel + return label + }() + override func awakeFromNib() { super.awakeFromNib() configureBackground() configureImageView() configureCellAppearance() + configureCoverTagView() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -46,6 +68,14 @@ private extension ProductImageCollectionViewCell { contentView.layer.borderColor = Colors.borderColor.cgColor contentView.layer.masksToBounds = Settings.maskToBounds } + + func configureCoverTagView() { + contentView.addSubview(coverTagView) + NSLayoutConstraint.activate([ + contentView.leadingAnchor.constraint(equalTo: coverTagView.leadingAnchor, constant: -Constants.tagPadding), + contentView.topAnchor.constraint(equalTo: coverTagView.topAnchor, constant: -Constants.tagPadding), + ]) + } } /// Constants @@ -54,6 +84,9 @@ private extension ProductImageCollectionViewCell { enum Constants { static let cornerRadius = CGFloat(2.0) static let borderWidth = CGFloat(0.5) + static let tagPadding = CGFloat(8) + static let tagCornerRadius = CGFloat(4) + static let tagEdgeInsets = UIEdgeInsets(top: 2, left: 4, bottom: 2, right: 4) } enum Colors { @@ -65,4 +98,12 @@ private extension ProductImageCollectionViewCell { static let imageContentMode = ContentMode.center static let maskToBounds = true } + + enum Localization { + static let tagLabel = NSLocalizedString( + "productImageCollectionViewCell.tagLabel.text", + value: "Cover", + comment: "Label indicating the cover image of a product" + ) + } } diff --git a/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/ProductImagesCollectionViewDataSource.swift b/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/ProductImagesCollectionViewDataSource.swift index 0e08647fe42..0ae96663d8b 100644 --- a/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/ProductImagesCollectionViewDataSource.swift +++ b/WooCommerce/Classes/ViewRelated/Products/Cells/Product Images/ProductImagesCollectionViewDataSource.swift @@ -38,7 +38,8 @@ private extension ProductImagesCollectionViewDataSource { func configure(collectionView: UICollectionView, _ cell: UICollectionViewCell, for item: ProductImagesItem, at indexPath: IndexPath) { switch item { case .image(let status): - configureImageCell(cell, productImageStatus: status) + let isFirstImage = indexPath.item == 0 + configureImageCell(cell, productImageStatus: status, isFirstImage: isFirstImage) case .extendedAddImage(let isVariation): if let cell = cell as? ExtendedAddProductImageCollectionViewCell { cell.configurePlaceholderLabelForProductImages(isVariation: isVariation) @@ -48,10 +49,10 @@ private extension ProductImagesCollectionViewDataSource { } } - func configureImageCell(_ cell: UICollectionViewCell, productImageStatus: ProductImageStatus) { + func configureImageCell(_ cell: UICollectionViewCell, productImageStatus: ProductImageStatus, isFirstImage: Bool) { switch productImageStatus { case .remote(let image): - configureRemoteImageCell(cell, productImage: image) + configureRemoteImageCell(cell, productImage: image, isFirstImage: isFirstImage) case .uploading(let asset): switch asset { case .phAsset(let asset): @@ -62,7 +63,7 @@ private extension ProductImagesCollectionViewDataSource { } } - func configureRemoteImageCell(_ cell: UICollectionViewCell, productImage: ProductImage) { + func configureRemoteImageCell(_ cell: UICollectionViewCell, productImage: ProductImage, isFirstImage: Bool) { guard let cell = cell as? ProductImageCollectionViewCell else { fatalError() } @@ -83,6 +84,7 @@ private extension ProductImagesCollectionViewDataSource { cell?.imageView.contentMode = .scaleAspectFit cell?.imageView.image = image } + cell.coverTagView.isHidden = !isFirstImage } func configureUploadingImageCell(_ cell: UICollectionViewCell, asset: PHAsset) { diff --git a/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesCollectionViewController.swift b/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesCollectionViewController.swift index 9f4aa5d7cb2..bc1ed2c2f8b 100644 --- a/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesCollectionViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesCollectionViewController.swift @@ -72,7 +72,8 @@ extension ProductImagesCollectionViewController { let productImageStatus = productImageStatuses[indexPath.row] let cell = collectionView.dequeueReusableCell(withReuseIdentifier: productImageStatus.cellReuseIdentifier, for: indexPath) - configureCell(cell, productImageStatus: productImageStatus) + let isFirstImage = indexPath.row == 0 + configureCell(cell, productImageStatus: productImageStatus, isFirstImage: isFirstImage) return cell } } @@ -80,10 +81,10 @@ extension ProductImagesCollectionViewController { // MARK: Cell configurations // private extension ProductImagesCollectionViewController { - func configureCell(_ cell: UICollectionViewCell, productImageStatus: ProductImageStatus) { + func configureCell(_ cell: UICollectionViewCell, productImageStatus: ProductImageStatus, isFirstImage: Bool) { switch productImageStatus { case .remote(let image): - configureRemoteImageCell(cell, productImage: image) + configureRemoteImageCell(cell, productImage: image, isFirstImage: isFirstImage) case .uploading(let asset): switch asset { case .phAsset(let asset): @@ -94,7 +95,7 @@ private extension ProductImagesCollectionViewController { } } - func configureRemoteImageCell(_ cell: UICollectionViewCell, productImage: ProductImage) { + func configureRemoteImageCell(_ cell: UICollectionViewCell, productImage: ProductImage, isFirstImage: Bool) { guard let cell = cell as? ProductImageCollectionViewCell else { fatalError() } @@ -116,6 +117,7 @@ private extension ProductImagesCollectionViewController { cell.imageView.contentMode = .scaleAspectFit cell.imageView.image = image } + cell.coverTagView.isHidden = !isFirstImage } func configureUploadingImageCell(_ cell: UICollectionViewCell, asset: PHAsset) { @@ -254,7 +256,9 @@ extension ProductImagesCollectionViewController: UICollectionViewDragDelegate, U }, completion: { [weak self] _ in // [Workaround] Reload the collection view if there are more than // one type of cells, for example, when there are any pending upload. - self?.reloadCollectionViewIfNeeded() + // Reloading is also necessary when the first image is updated. + let firstImageUpdated = item.sourceIndexPath?.item == 0 || destinationIndexPath.item == 0 + self?.reloadCollectionViewIfNeeded(firstImageUpdated: firstImageUpdated) }) coordinator.drop(item.dragItem, toItemAt: destinationIndexPath) @@ -264,9 +268,10 @@ extension ProductImagesCollectionViewController: UICollectionViewDragDelegate, U /// Reloads collection view only if there is any pending upload. /// This makes sure that cells for pending uploads are reloaded properly /// to remove their overlays after uploading is done. + /// If the first image is updated, reloading is also necessary to update the Cover tag. /// - private func reloadCollectionViewIfNeeded() { - if productImageStatuses.hasPendingUpload { + private func reloadCollectionViewIfNeeded(firstImageUpdated: Bool) { + if firstImageUpdated || productImageStatuses.hasPendingUpload { collectionView.reloadData() } } diff --git a/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesViewController.swift b/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesViewController.swift index d537d55419a..2968a225d69 100644 --- a/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Products/Media/ProductImagesViewController.swift @@ -371,7 +371,10 @@ private extension ProductImagesViewController { static let replacePhoto = NSLocalizedString("Replace Photo", comment: "Action to replace one photo on the Product images screen") static let variableProductHelperText = NSLocalizedString("Only one photo can be displayed by variation", comment: "Helper text above photo list in Product images screen") - static let dragAndDropHelperText = NSLocalizedString("Drag and drop to re-order photos", - comment: "Drag and drop helper text above photo list in Product images screen") + static let dragAndDropHelperText = NSLocalizedString( + "productImagesViewController.dragAndDropHelperText", + value: "Drag and drop to re-order photos. The first photo will be set as the cover.", + comment: "Drag and drop helper text above photo list in Product images screen" + ) } }