Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class BlogDashboardCardFrameView: UIView {
button.accessibilityTraits = .button
button.isHidden = true
button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
button.on(.touchUpInside) { [weak self] _ in
button.on([.touchUpInside, .menuActionTriggered]) { [weak self] _ in
self?.onEllipsisButtonTap?()
}
return button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typedef NS_ENUM(NSUInteger, BlogDetailsSectionCategory) {
BlogDetailsSectionCategoryExternal,
BlogDetailsSectionCategoryRemoveSite,
BlogDetailsSectionCategoryMigrationSuccess,
BlogDetailsSectionCategoryJetpackBrandingCard,
};

typedef NS_ENUM(NSUInteger, BlogDetailsSubsection) {
Expand All @@ -40,6 +41,7 @@ typedef NS_ENUM(NSUInteger, BlogDetailsSubsection) {
BlogDetailsSubsectionPlugins,
BlogDetailsSubsectionHome,
BlogDetailsSubsectionMigrationSuccess,
BlogDetailsSubsectionJetpackBrandingCard,
};


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
static NSString *const BlogDetailsQuickStartCellIdentifier = @"BlogDetailsQuickStartCell";
static NSString *const BlogDetailsSectionFooterIdentifier = @"BlogDetailsSectionFooterView";
static NSString *const BlogDetailsMigrationSuccessCellIdentifier = @"BlogDetailsMigrationSuccessCell";
static NSString *const BlogDetailsJetpackBrandingCardCellIdentifier = @"BlogDetailsJetpackBrandingCardCellIdentifier";

NSString * const WPBlogDetailsRestorationID = @"WPBlogDetailsID";
NSString * const WPBlogDetailsBlogKey = @"WPBlogDetailsBlogKey";
Expand Down Expand Up @@ -355,6 +356,7 @@ - (void)viewDidLoad
[self.tableView registerClass:[QuickStartCell class] forCellReuseIdentifier:BlogDetailsQuickStartCellIdentifier];
[self.tableView registerClass:[BlogDetailsSectionFooterView class] forHeaderFooterViewReuseIdentifier:BlogDetailsSectionFooterIdentifier];
[self.tableView registerClass:[MigrationSuccessCell class] forCellReuseIdentifier:BlogDetailsMigrationSuccessCellIdentifier];
[self.tableView registerClass:[JetpackBrandingMenuCardCell class] forCellReuseIdentifier:BlogDetailsJetpackBrandingCardCellIdentifier];

self.hasLoggedDomainCreditPromptShownEvent = NO;

Expand Down Expand Up @@ -451,6 +453,7 @@ - (void)showDetailViewForSubsection:(BlogDetailsSubsection)section
[self showDashboard];
break;
case BlogDetailsSubsectionQuickStart:
case BlogDetailsSubsectionJetpackBrandingCard:
self.restorableSelectedIndexPath = indexPath;
[self.tableView selectRowAtIndexPath:indexPath
animated:NO
Expand Down Expand Up @@ -558,6 +561,7 @@ - (NSIndexPath *)indexPathForSubsection:(BlogDetailsSubsection)subsection
case BlogDetailsSubsectionReminders:
case BlogDetailsSubsectionHome:
case BlogDetailsSubsectionMigrationSuccess:
case BlogDetailsSubsectionJetpackBrandingCard:
return [NSIndexPath indexPathForRow:0 inSection:section];
case BlogDetailsSubsectionDomainCredit:
return [NSIndexPath indexPathForRow:0 inSection:section];
Expand Down Expand Up @@ -738,6 +742,9 @@ - (void)configureTableViewData
if (MigrationSuccessCardView.shouldShowMigrationSuccessCard == YES) {
[marr addObject:[self migrationSuccessSectionViewModel]];
}
if (JetpackBrandingMenuCardCoordinator.shouldShowCard == YES) {
[marr addObject:[self jetpackCardSectionViewModel]];
}

if ([DomainCreditEligibilityChecker canRedeemDomainCreditWithBlog:self.blog]) {
if (!self.hasLoggedDomainCreditPromptShownEvent) {
Expand Down Expand Up @@ -1165,6 +1172,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
[cell configureWithViewController:self];
return cell;
}

if (section.category == BlogDetailsSectionCategoryJetpackBrandingCard) {
JetpackBrandingMenuCardCell *cell = [tableView dequeueReusableCellWithIdentifier:BlogDetailsJetpackBrandingCardCellIdentifier];
[cell configureWithViewController:self];
return cell;
}

BlogDetailsRow *row = [section.rows objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:row.identifier];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

extension BlogDetailsViewController {

@objc func jetpackCardSectionViewModel() -> BlogDetailsSection {
let row = BlogDetailsRow()
row.callback = {}

let section = BlogDetailsSection(title: nil,
rows: [row],
footerTitle: nil,
category: .jetpackBrandingCard)
return section
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
import UIKit
import Lottie

class JetpackBrandingMenuCardCell: UITableViewCell {

// MARK: Private Variables

private weak var viewController: UIViewController?

/// Sets the animation based on the language orientation
private var animation: Animation? {
traitCollection.layoutDirection == .leftToRight ?
Animation.named(Constants.animationLtr) :
Animation.named(Constants.animationRtl)
}

// MARK: Lazy Loading Views

private lazy var cardFrameView: BlogDashboardCardFrameView = {
let frameView = BlogDashboardCardFrameView()
frameView.translatesAutoresizingMaskIntoConstraints = false
frameView.configureButtonContainerStackView()
frameView.hideHeader()

frameView.onEllipsisButtonTap = {
// TODO: Track menu shown
}
frameView.ellipsisButton.showsMenuAsPrimaryAction = true
frameView.ellipsisButton.menu = contextMenu

return frameView
}()

private lazy var containerStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = Metrics.spacing
stackView.layoutMargins = Metrics.containerMargins
stackView.isLayoutMarginsRelativeArrangement = true
stackView.addArrangedSubviews([logosSuperview, descriptionLabel, learnMoreSuperview])
return stackView
}()

private lazy var logosSuperview: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
view.addSubview(logosAnimationView)

view.topAnchor.constraint(equalTo: logosAnimationView.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: logosAnimationView.bottomAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: logosAnimationView.leadingAnchor).isActive = true

return view
}()

private lazy var logosAnimationView: AnimationView = {
let view = AnimationView()
view.translatesAutoresizingMaskIntoConstraints = false
view.animation = animation

// Height Constraint
view.heightAnchor.constraint(equalToConstant: Metrics.animationsViewHeight).isActive = true

// Width constraint to achieve aspect ratio
let animationSize = animation?.size ?? .init(width: 1, height: 1)
let ratio = animationSize.width / animationSize.height
view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: ratio).isActive = true

return view
}()

private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = Metrics.descriptionFont
label.numberOfLines = 0
label.adjustsFontForContentSizeCategory = true

return label
}()

private lazy var learnMoreSuperview: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
view.addSubview(learnMoreButton)

view.topAnchor.constraint(equalTo: learnMoreButton.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: learnMoreButton.bottomAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: learnMoreButton.leadingAnchor).isActive = true

return view
}()

private lazy var learnMoreButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = Metrics.learnMoreButtonTextColor
button.titleLabel?.font = WPStyleGuide.fontForTextStyle(.body, fontWeight: .regular)
button.titleLabel?.adjustsFontForContentSizeCategory = true
button.setTitle(Strings.learnMoreButtonText, for: .normal)
button.addTarget(self, action: #selector(learnMoreButtonTapped), for: .touchUpInside)

if #available(iOS 15.0, *) {
var learnMoreButtonConfig: UIButton.Configuration = .plain()
learnMoreButtonConfig.contentInsets = Metrics.learnMoreButtonContentInsets
button.configuration = learnMoreButtonConfig
} else {
button.contentEdgeInsets = Metrics.learnMoreButtonContentEdgeInsets
button.flipInsetsForRightToLeftLayoutDirection()
}

return button
}()

// MARK: Initializers

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}

required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}

private func commonInit() {
setupViews()
setupContent()
// TODO: Track card shown
}

// MARK: Helpers

private func setupViews() {
contentView.addSubview(cardFrameView)
contentView.pinSubviewToAllEdges(cardFrameView, priority: Metrics.cardFrameConstraintPriority)
cardFrameView.add(subview: containerStackView)
}

private func setupContent() {
logosAnimationView.play()
let config = JetpackBrandingMenuCardCoordinator.cardConfig
descriptionLabel.text = config?.description
learnMoreSuperview.isHidden = config?.learnMoreButtonURL == nil
}

// MARK: Actions

@objc private func learnMoreButtonTapped() {
guard let config = JetpackBrandingMenuCardCoordinator.cardConfig,
let urlString = config.learnMoreButtonURL,
let url = URL(string: urlString) else {
return
}

let webViewController = WebViewControllerFactory.controller(url: url, source: Constants.analyticsSource)
let navController = UINavigationController(rootViewController: webViewController)
viewController?.present(navController, animated: true)
// TODO: Track button tapped
}
}

// MARK: Contexual Menu

private extension JetpackBrandingMenuCardCell {

// MARK: Items

// Defines the structure of the contextual menu items.
private var contextMenuItems: [MenuItem] {
return [.remindLater(remindMeLaterTapped), .hide(hideThisTapped)]
}

// MARK: Menu Creation

private var contextMenu: UIMenu {
let actions = contextMenuItems.map { $0.toAction }
return .init(title: String(), options: .displayInline, children: actions)
}

// MARK: Actions

private func remindMeLaterTapped() {
// TODO: Implement this
}

private func hideThisTapped() {
// TODO: Implement this
}
}

private extension JetpackBrandingMenuCardCell {

enum Metrics {
// General
static let spacing: CGFloat = 10
static let containerMargins = UIEdgeInsets(top: 20, left: 20, bottom: 12, right: 20)
static let cardFrameConstraintPriority = UILayoutPriority(999)

// Animation view
static let animationsViewHeight: CGFloat = 32

// Description Label
static var descriptionFont: UIFont {
let maximumFontPointSize: CGFloat = 16
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
let font = UIFont(descriptor: fontDescriptor, size: min(fontDescriptor.pointSize, maximumFontPointSize))
return UIFontMetrics.default.scaledFont(for: font)
}

// Learn more button
static let learnMoreButtonContentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 24)
static let learnMoreButtonContentEdgeInsets = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 24)
static let learnMoreButtonTextColor: UIColor = UIColor.muriel(color: .jetpackGreen, .shade40)
}

enum Constants {
static let animationLtr = "JetpackAllFeaturesLogosAnimation_ltr"
static let animationRtl = "JetpackAllFeaturesLogosAnimation_rtl"
static let analyticsSource = "jetpack_menu_card"
static let remindMeLaterSystemImageName = "alarm"
static let hideThisLaterSystemImageName = "eye.slash"
}

enum Strings {
static let learnMoreButtonText = NSLocalizedString("jetpack.menuCard.learnMore",
value: "Learn more",
comment: "Title of a button that displays a blog post in a web view.")
static let remindMeLaterMenuItemTitle = NSLocalizedString("jetpack.menuCard.remindLater",
value: "Remind me later",
comment: "Menu item title to hide the card for now and show it later.")
static let hideCardMenuItemTitle = NSLocalizedString("jetpack.menuCard.hide",
value: "Hide this",
comment: "Menu item title to hide the card.")
}

enum MenuItem {
case remindLater(_ handler: () -> Void)
case hide(_ handler: () -> Void)

var title: String {
switch self {
case .remindLater:
return Strings.remindMeLaterMenuItemTitle
case .hide:
return Strings.hideCardMenuItemTitle
}
}

var image: UIImage? {
switch self {
case .remindLater:
return .init(systemName: Constants.remindMeLaterSystemImageName)
case .hide:
return .init(systemName: Constants.hideThisLaterSystemImageName)
}
}

var toAction: UIAction {
switch self {
case .remindLater(let handler),
.hide(let handler):
return UIAction(title: title, image: image, attributes: []) { _ in
handler()
}
}
}
Comment on lines +264 to +272
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slick 🤩

}
}

extension JetpackBrandingMenuCardCell {

@objc(configureWithViewController:)
func configure(with viewController: UIViewController) {
self.viewController = viewController
}
}
Loading