-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Jetpack Focus: Adds Jetpack menu card #19782
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
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
a122066
Add: Basic implementation of the menu card view
hassaanelgarem adca58c
Add: display the new card in the menu screen
hassaanelgarem 3726739
Remove: unneeded code from `MigrationSuccessCell`
hassaanelgarem 6102ef8
Add: logos view to the menu card
hassaanelgarem e7cff19
Add: create `JetpackBrandingMenuCardCoordinator` with card config logic
hassaanelgarem f019a90
Add: connect should display card logic
hassaanelgarem 21f9f7e
Add: correctly configure the description label
hassaanelgarem 06167b8
Add: learn more button to menu card
hassaanelgarem 4c8c08e
Add: learn more button action
hassaanelgarem e724302
Update: make labels respect dynamic types
hassaanelgarem d24c7cd
Update: fix wrong bottom spacing for the menu card
hassaanelgarem 76e7e4d
Add: context menu implementation
hassaanelgarem 0d7ed8c
Update: Remove dependency on `UIDeferredMenuElement.uncached`
hassaanelgarem 9080abc
Add: TODO statements for missing functionality
hassaanelgarem 0888393
Refactor: move `BlogDetailsViewController` extension to its own file
hassaanelgarem 832c0c1
Update: revert change made for testing
hassaanelgarem 3fed2b8
Fix: Jetpack build error
hassaanelgarem 43ba84b
Add: organizational mark
hassaanelgarem File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...elated/Jetpack/Branding/Menu Card/BlogDetailsViewController+JetpackBrandingMenuCard.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| } |
282 changes: 282 additions & 0 deletions
282
WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension JetpackBrandingMenuCardCell { | ||
|
|
||
| @objc(configureWithViewController:) | ||
| func configure(with viewController: UIViewController) { | ||
| self.viewController = viewController | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slick 🤩