Skip to content

Commit

Permalink
Merge pull request #20369 from kean/feature/personalize-home-tab-screen
Browse files Browse the repository at this point in the history
Hiding dashboard cards: add Personalize Home Tab screen
  • Loading branch information
guarani authored Mar 30, 2023
2 parents 43e088d + fb7d8d6 commit dd5e2a2
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 4 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* [**] [internal] Refactor updating account related Core Data operations, which ususally happens during log in and out of the app. [#20394]
* [***] [internal] Refactor uploading photos (from the device photo, the Free Photo library, and other sources) to the WordPress Media Library. Affected areas are where you can choose a photo and upload, including the "Media" screen, adding images to a post, updating site icon, etc. [#20322]
* [**] [WordPress-only] Warns user about sites with only individual plugins not supporting core app features and offers the option to switch to the Jetpack app. [#20408]
* [**] Add a "Personalize Home Tab" button to the bottom of the Home tab that allows changing cards visibility. [#20369]

22.0
-----
Expand Down
5 changes: 5 additions & 0 deletions WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ enum FeatureFlag: Int, CaseIterable {
case jetpackIndividualPluginSupport
case siteCreationDomainPurchasing
case readerUserBlocking
case personalizeHomeTab

/// Returns a boolean indicating if the feature is enabled
var enabled: Bool {
Expand Down Expand Up @@ -125,6 +126,8 @@ enum FeatureFlag: Int, CaseIterable {
return false
case .readerUserBlocking:
return true
case .personalizeHomeTab:
return false
}
}

Expand Down Expand Up @@ -221,6 +224,8 @@ extension FeatureFlag {
return "Site Creation Domain Purchasing"
case .readerUserBlocking:
return "Reader User Blocking"
case .personalizeHomeTab:
return "Personalize Home Tab"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import UIKit
import SwiftUI

final class BlogDashboardPersonalizeCardCell: DashboardCollectionViewCell {
private var blog: Blog?
private weak var presentingViewController: BlogDashboardViewController?

private let personalizeButton = UIButton(type: .system)

// MARK: - Initializers

override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - View setup

private func setupView() {
let titleLabel = UILabel()
titleLabel.text = Strings.buttonTitle
titleLabel.font = WPStyleGuide.fontForTextStyle(.subheadline, fontWeight: .semibold)
titleLabel.adjustsFontForContentSizeCategory = true

let imageView = UIImageView(image: UIImage(named: "personalize")?.withRenderingMode(.alwaysTemplate))
imageView.tintColor = .label

let spacer = UIView()
spacer.translatesAutoresizingMaskIntoConstraints = false
spacer.widthAnchor.constraint(greaterThanOrEqualToConstant: 8).isActive = true

let contents = UIStackView(arrangedSubviews: [titleLabel, spacer, imageView])
contents.alignment = .center
contents.isUserInteractionEnabled = false

personalizeButton.accessibilityLabel = Strings.buttonTitle
personalizeButton.setBackgroundImage(.renderBackgroundImage(fill: .tertiarySystemFill), for: .normal)
personalizeButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

let container = UIView()
container.layer.cornerRadius = 10
container.addSubview(personalizeButton)
container.addSubview(contents)

personalizeButton.translatesAutoresizingMaskIntoConstraints = false
container.pinSubviewToAllEdges(personalizeButton)

contents.translatesAutoresizingMaskIntoConstraints = false
container.pinSubviewToAllEdges(contents, insets: .init(allEdges: 16))

contentView.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
contentView.pinSubviewToAllEdges(container)
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)

personalizeButton.setBackgroundImage(.renderBackgroundImage(fill: .tertiarySystemFill), for: .normal)
}

// MARK: - BlogDashboardCardConfigurable

func configure(blog: Blog, viewController: BlogDashboardViewController?, apiResponse: BlogDashboardRemoteEntity?) {
self.blog = blog
self.presentingViewController = viewController
}

// MARK: - Actions

@objc private func buttonTapped() {
guard let blog = blog, let siteID = blog.dotComID?.intValue else {
return DDLogError("Failed to show dashboard personalization screen: siteID is missing")
}
WPAnalytics.track(.dashboardCardItemTapped, properties: ["type": DashboardCard.personalize.rawValue], blog: blog)
let viewController = UIHostingController(rootView: NavigationView {
BlogDashboardPersonalizationView(viewModel: .init(service: .init(siteID: siteID)))
}.navigationViewStyle(.stack)) // .stack is required for iPad
if UIDevice.isPad() {
viewController.modalPresentationStyle = .formSheet
}
presentingViewController?.present(viewController, animated: true)
}
}

private extension BlogDashboardPersonalizeCardCell {
struct Strings {
static let buttonTitle = NSLocalizedString("dasboard.personalizeHomeButtonTitle", value: "Personalize your home tab", comment: "Personialize home tab button title")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ enum DashboardCard: String, CaseIterable {
case nextPost = "create_next"
case createPost = "create_first"
case jetpackBadge

// Card placeholder for when loading data
/// Card placeholder for when loading data
case ghost
case failure
/// A "Personalize Home Tab" button
case personalize

var cell: DashboardCollectionViewCell.Type {
switch self {
Expand Down Expand Up @@ -52,6 +53,8 @@ enum DashboardCard: String, CaseIterable {
case .domainsDashboardCard:
/// TODO
return DashboardFailureCardCell.self
case .personalize:
return BlogDashboardPersonalizeCardCell.self
}
}

Expand Down Expand Up @@ -88,6 +91,8 @@ enum DashboardCard: String, CaseIterable {
return BlazeHelper.shouldShowCard(for: blog)
case .domainsDashboardCard:
return DomainsDashboardCardHelper.shouldShowCard(for: blog)
case .personalize:
return AppConfiguration.isJetpack && FeatureFlag.personalizeHomeTab.enabled
}
}

Expand All @@ -111,6 +116,15 @@ enum DashboardCard: String, CaseIterable {
}
}

/// A list of cards that can be shown/hidden on a "Personalize Home Tab" screen.
static let personalizableCards: [DashboardCard] = [
.todaysStats,
.draftPosts,
.scheduledPosts,
.blaze,
.prompts
]

/// Includes all cards that should be fetched from the backend
/// The `String` should match its identifier on the backend.
enum RemoteDashboardCard: String, CaseIterable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ private func makeKey(for card: DashboardCard) -> String? {
// avoid having to migrate data.
return "prompts-enabled-site-settings"
case .domainsDashboardCard:
return "domains-dashboard-enabled-site-settings"
case .quickStart, .jetpackBadge, .jetpackInstall, .nextPost, .createPost, .failure, .ghost:
return "domains-dashboard-card-enabled-site-settings"
case .quickStart, .jetpackBadge, .jetpackInstall, .nextPost, .createPost, .failure, .ghost, .personalize:
return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import SwiftUI

struct BlogDashboardPersonalizationView: View {
@StateObject var viewModel: BlogDashboardPersonalizationViewModel

@SwiftUI.Environment(\.presentationMode) var presentationMode

var body: some View {
List {
Section(content: {
ForEach(viewModel.cards, content: BlogDashboardPersonalizationCardCell.init)
}, header: {
Text(Strings.sectionHeader)
}, footer: {
Text(Strings.sectionFooter)
})
}
.listStyle(.insetGrouped)
.navigationTitle(Strings.title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) { closeButton }
}
}

private var closeButton: some View {
Button(action: { presentationMode.wrappedValue.dismiss() }) {
Image(systemName: "xmark")
.font(.body.weight(.medium))
.foregroundColor(Color.primary)
}
}
}

private struct BlogDashboardPersonalizationCardCell: View {
@ObservedObject var viewModel: BlogDashboardPersonalizationCardCellViewModel

var body: some View {
Toggle(viewModel.title, isOn: $viewModel.isOn)
}
}

private extension BlogDashboardPersonalizationView {
struct Strings {
static let title = NSLocalizedString("personalizeHome.title", value: "Personalize Home Tab", comment: "Page title")
static let sectionHeader = NSLocalizedString("personalizeHome.cardsSectionHeader", value: "Add or hide cards", comment: "Section header")
static let sectionFooter = NSLocalizedString("personalizeHome.cardsSectionFooter", value: "Cards may show different content depending on what's happening on your site. We're working on more cards and controls.", comment: "Section footer displayed below the list of toggles")
}
}

#if DEBUG
struct BlogDashboardPersonalizationView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
BlogDashboardPersonalizationView(viewModel: .init(service: .init(repository: UserDefaults.standard, siteID: 1)))
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import SwiftUI

final class BlogDashboardPersonalizationViewModel: ObservableObject {
let cards: [BlogDashboardPersonalizationCardCellViewModel]

init(service: BlogDashboardPersonalizationService) {
self.cards = DashboardCard.personalizableCards.map {
BlogDashboardPersonalizationCardCellViewModel(card: $0, service: service)
}
}
}

final class BlogDashboardPersonalizationCardCellViewModel: ObservableObject, Identifiable {
private let card: DashboardCard
private let service: BlogDashboardPersonalizationService

var id: DashboardCard { card }
var title: String { card.localizedTitle }

var isOn: Bool {
get { service.isEnabled(card) }
set {
objectWillChange.send()
service.setEnabled(newValue, for: card)
}
}

init(card: DashboardCard, service: BlogDashboardPersonalizationService) {
self.card = card
self.service = service
}
}

private extension DashboardCard {
var localizedTitle: String {
switch self {
case .prompts:
return NSLocalizedString("personalizeHome.dashboardCard.prompts", value: "Blogging prompts", comment: "Card title for the pesonalization menu")
case .blaze:
return NSLocalizedString("personalizeHome.dashboardCard.blaze", value: "Blaze", comment: "Card title for the pesonalization menu")
case .todaysStats:
return NSLocalizedString("personalizeHome.dashboardCard.todaysStats", value: "Today's stats", comment: "Card title for the pesonalization menu")
case .draftPosts:
return NSLocalizedString("personalizeHome.dashboardCard.draftPosts", value: "Draft posts", comment: "Card title for the pesonalization menu")
case .scheduledPosts:
return NSLocalizedString("personalizeHome.dashboardCard.scheduledPosts", value: "Scheduled posts", comment: "Card title for the pesonalization menu")
case .quickStart, .nextPost, .createPost, .ghost, .failure, .personalize, .jetpackBadge, .jetpackInstall, .domainsDashboardCard:
assertionFailure("\(self) card should not appear in the personalization menus")
return "" // These cards don't appear in the personalization menus
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "personalize.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Loading

0 comments on commit dd5e2a2

Please sign in to comment.