Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Product Details: Display cover tag on the first product image #15041

Merged
merged 8 commits into from
Feb 5, 2025
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,33 @@ final class ProductImageCollectionViewCell: UICollectionViewCell {

var cancellableTask: Task<Void, Never>?

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?) {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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()
}
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,19 @@ 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
}
}

// 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):
Expand All @@ -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()
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
}
}