From 15c2fcbe1b58e62ff6e0e7a6fe20b8fe0f44fbac Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 12 Sep 2024 18:12:02 -0300 Subject: [PATCH 01/13] Interactive Prompt Attributions (#23586) * BloggingPromptsAttribution: New externalURL and trailingImage properties * Dashboard: Pressing the prompt attribution now opens Safari * Removes extra spaces * BloggingPromptsAttribution: Dropping Gridicons --- .../Prompts/BloggingPromptsAttribution.swift | 28 +++++++++++++++++-- .../Prompts/DashboardPromptsCardCell.swift | 24 +++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift index ce3ad902c6b8..ca73bf0ea434 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift @@ -1,5 +1,6 @@ import UIKit import WordPressUI +import Gridicons enum BloggingPromptsAttribution: String { case dayone @@ -11,7 +12,9 @@ enum BloggingPromptsAttribution: String { guard let range = baseText.range(of: source) else { return attributedText } - attributedText.addAttributes(Constants.sourceAttributes, range: NSRange(range, in: baseText)) + + let nsRange = NSRange(range, in: baseText) + attributedText.addAttributes(Constants.sourceAttributes, range: nsRange) return attributedText } @@ -30,6 +33,21 @@ enum BloggingPromptsAttribution: String { } } + var externalURL: URL? { + switch self { + case .dayone: return Constants.dayOneURL + case .bloganuary: return nil + } + } + + var trailingImage: UIImage? { + guard let _ = externalURL else { + return nil + } + + return Constants.linkIcon + } + private struct Strings { static let fromTextFormat = NSLocalizedString("From %1$@", comment: "Format for blogging prompts attribution. %1$@ is the attribution source.") static let dayOne = "Day One" @@ -45,8 +63,12 @@ enum BloggingPromptsAttribution: String { .font: WPStyleGuide.fontForTextStyle(.caption1, fontWeight: .medium), .foregroundColor: UIColor.label, ] - static let iconSize = CGSize(width: 18, height: 18) - static let dayOneIcon = UIImage(named: "logo-dayone")?.resized(to: Constants.iconSize) + static let dayOneIconSize = CGSize(width: 18, height: 18) + static let dayOneIcon = UIImage(named: "logo-dayone")?.resized(to: Constants.dayOneIconSize) + static let dayOneURL = URL(string: "https://dayoneapp.com/?utm_source=jetpack&utm_medium=prompts") + + static let linkIconSize = CGFloat(10) + static let linkIcon = UIImage(systemName: "link", withConfiguration: UIImage.SymbolConfiguration(pointSize: linkIconSize)) /// This is computed so it can react accordingly on color scheme changes. static var bloganuaryIcon: UIImage? { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift index df5579a1c3dd..aa3883230b70 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift @@ -1,4 +1,5 @@ import UIKit +import SafariServices import WordPressShared import WordPressFlux @@ -182,6 +183,17 @@ class DashboardPromptsCardCell: UICollectionViewCell, Reusable { WPAnalytics.track(.promptsOtherAnswersTapped) } + @IBAction + private func didTapAttribution() { + guard let targetURL = prompt?.promptAttribution?.externalURL else { + return + } + + let safariViewController = SFSafariViewController(url: targetURL) + safariViewController.modalPresentationStyle = .pageSheet + presenterViewController?.present(safariViewController, animated: true) + } + private var answerInfoView: UIView { let stackView = UIStackView() stackView.translatesAutoresizingMaskIntoConstraints = false @@ -212,9 +224,16 @@ class DashboardPromptsCardCell: UICollectionViewCell, Reusable { return label }() + private lazy var attributionTrailingImage = UIImageView() + private lazy var attributionStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [attributionIcon, attributionSourceLabel]) + let stackView = UIStackView(arrangedSubviews: [attributionIcon, attributionSourceLabel, attributionTrailingImage]) + stackView.setCustomSpacing(Constants.attributionTrailingImageSpacing, after: attributionSourceLabel) stackView.alignment = .center + + let recognizer = UITapGestureRecognizer(target: self, action: #selector(didTapAttribution)) + stackView.addGestureRecognizer(recognizer) + return stackView }() @@ -411,7 +430,9 @@ private extension DashboardPromptsCardCell { if let attribution = prompt?.promptAttribution { attributionIcon.image = attribution.iconImage + attributionTrailingImage.image = attribution.trailingImage attributionSourceLabel.attributedText = attribution.attributedText + containerStackView.addArrangedSubview(attributionStackView) } @@ -554,6 +575,7 @@ private extension DashboardPromptsCardCell { static let spacing: CGFloat = 12 static let answeredButtonsSpacing: CGFloat = 16 static let answerInfoViewSpacing: CGFloat = 6 + static let attributionTrailingImageSpacing: CGFloat = 6 static let containerMargins = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) static let maxAvatarCount = 3 static let exampleAnswerCount = 19 From 298c53ce288f5bb1ccf2a014d624f5c38fcc2825 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 12 Sep 2024 20:55:44 -0400 Subject: [PATCH 02/13] Sidebar: Show Site Settings in modal on iPad (#23582) * Show Site Settings in a modal form * Add presentedSiteSettingsViewController --- .../Blog Details/BlogDetailsViewController.m | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m index 83d362905495..7e428c0f0ffb 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m @@ -227,7 +227,7 @@ - (instancetype)initWithTitle:(NSString *)title #pragma mark - -@interface BlogDetailsViewController () +@interface BlogDetailsViewController () @property (nonatomic, strong) NSArray *headerViewHorizontalConstraints; @property (nonatomic, strong) NSArray *tableSections; @@ -242,6 +242,8 @@ @interface BlogDetailsViewController () Date: Thu, 12 Sep 2024 21:51:43 -0400 Subject: [PATCH 03/13] Remove unused willDisplayPostSignupFlow (#23590) --- WordPress/Classes/System/RootViewPresenter.swift | 1 - .../Classes/System/SplitViewRootPresenter.swift | 4 ---- .../Classes/System/StaticScreensTabBarWrapper.swift | 4 ---- .../WPTabBarController+RootViewPresenter.swift | 4 ---- .../Blog/My Site/MySiteViewController.swift | 12 +----------- .../System/Coordinators/MySitesCoordinator.swift | 4 ---- 6 files changed, 1 insertion(+), 28 deletions(-) diff --git a/WordPress/Classes/System/RootViewPresenter.swift b/WordPress/Classes/System/RootViewPresenter.swift index 0e21904fb3e8..4b2684509db0 100644 --- a/WordPress/Classes/System/RootViewPresenter.swift +++ b/WordPress/Classes/System/RootViewPresenter.swift @@ -10,7 +10,6 @@ protocol RootViewPresenter: AnyObject { func getMeScenePresenter() -> ScenePresenter func currentlySelectedScreen() -> String func currentlyVisibleBlog() -> Blog? - func willDisplayPostSignupFlow() // MARK: Reader diff --git a/WordPress/Classes/System/SplitViewRootPresenter.swift b/WordPress/Classes/System/SplitViewRootPresenter.swift index f1d7329491c7..f21b51b532fb 100644 --- a/WordPress/Classes/System/SplitViewRootPresenter.swift +++ b/WordPress/Classes/System/SplitViewRootPresenter.swift @@ -257,10 +257,6 @@ final class SplitViewRootPresenter: RootViewPresenter { return try? ContextManager.shared.mainContext.existingObject(with: id) } - func willDisplayPostSignupFlow() { - fatalError() - } - var readerTabViewController: ReaderTabViewController? var readerCoordinator: ReaderCoordinator? diff --git a/WordPress/Classes/System/StaticScreensTabBarWrapper.swift b/WordPress/Classes/System/StaticScreensTabBarWrapper.swift index 905645b3756c..055257cb5b62 100644 --- a/WordPress/Classes/System/StaticScreensTabBarWrapper.swift +++ b/WordPress/Classes/System/StaticScreensTabBarWrapper.swift @@ -35,10 +35,6 @@ class StaticScreensTabBarWrapper: RootViewPresenter { tabBarController.currentlyVisibleBlog() } - func willDisplayPostSignupFlow() { - tabBarController.willDisplayPostSignupFlow() - } - // MARK: Reader var readerTabViewController: ReaderTabViewController? { diff --git a/WordPress/Classes/System/WPTabBarController+RootViewPresenter.swift b/WordPress/Classes/System/WPTabBarController+RootViewPresenter.swift index 6e186247013d..e17d9ac7f272 100644 --- a/WordPress/Classes/System/WPTabBarController+RootViewPresenter.swift +++ b/WordPress/Classes/System/WPTabBarController+RootViewPresenter.swift @@ -29,10 +29,6 @@ extension WPTabBarController: RootViewPresenter { return mySitesCoordinator.currentBlog } - func willDisplayPostSignupFlow() { - mySitesCoordinator.willDisplayPostSignupFlow() - } - func showNotificationsTab(completion: ((NotificationsViewController) -> Void)?) { self.selectedIndex = WPTab.notifications.rawValue completion?(self.notificationsViewController!) diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index 3bbbbd64be70..41bc21df430f 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -57,9 +57,6 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite return refreshControl }() - /// A boolean indicating whether a site creation or adding self-hosted site flow has been initiated but not yet displayed. - var willDisplayPostSignupFlow: Bool = false - private var isSidebarModeEnabled = false private var createButtonCoordinator: CreateButtonCoordinator? @@ -607,12 +604,6 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite WordPressAuthenticator.showLoginForSelfHostedSite(self) } - @objc - func launchSiteCreationFromNotification() { - self.launchSiteCreation(source: "signup_epilogue") - willDisplayPostSignupFlow = false - } - func launchSiteCreation(source: String) { JetpackFeaturesRemovalCoordinator.presentSiteCreationOverlayIfNeeded(in: self, source: source, onDidDismiss: { guard JetpackFeaturesRemovalCoordinator.siteCreationPhase() != .two else { @@ -633,7 +624,6 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite @objc private func showAddSelfHostedSite() { WordPressAuthenticator.showLoginForSelfHostedSite(self) - willDisplayPostSignupFlow = false } // MARK: - Blog Details UI Logic @@ -946,7 +936,7 @@ extension MySiteViewController: BlogDetailsPresentationDelegate { private extension MySiteViewController { @objc func displayOverlayIfNeeded() { - if isViewOnScreen() && !willDisplayPostSignupFlow && !RootViewCoordinator.shared.isSiteCreationActive { + if isViewOnScreen() && !RootViewCoordinator.shared.isSiteCreationActive { let didReloadUI = RootViewCoordinator.shared.reloadUIIfNeeded(blog: self.blog) if !didReloadUI { let phase = JetpackFeaturesRemovalCoordinator.generalPhase() diff --git a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift index c344ba99d2bb..f87ae52c5645 100644 --- a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift +++ b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift @@ -151,10 +151,6 @@ class MySitesCoordinator: NSObject { // MARK: - Adding a new site - func willDisplayPostSignupFlow() { - mySiteViewController.willDisplayPostSignupFlow = true - } - func showSiteCreation() { showRootViewController() mySiteViewController.launchSiteCreation(source: "my_site") From f614ecacc634b372015f9fe591871928bd03ef1f Mon Sep 17 00:00:00 2001 From: Ian Guedes Maia Date: Fri, 13 Sep 2024 18:43:03 +0200 Subject: [PATCH 04/13] Update GHA concurrency setting to not cancel other currently running jobs (#23596) --- .github/workflows/run-danger.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-danger.yml b/.github/workflows/run-danger.yml index 46cb77cb4100..bd1a52d7c6f4 100644 --- a/.github/workflows/run-danger.yml +++ b/.github/workflows/run-danger.yml @@ -7,11 +7,12 @@ on: jobs: dangermattic: if: ${{ (github.event.pull_request.draft == false) }} - uses: Automattic/dangermattic/.github/workflows/reusable-retry-buildkite-step-on-events.yml@v1.1.0 + uses: Automattic/dangermattic/.github/workflows/reusable-retry-buildkite-step-on-events.yml@v1.1.2 with: - org-slug: "automattic" - pipeline-slug: "wordpress-ios" - retry-step-key: "danger" - build-commit-sha: "${{ github.event.pull_request.head.sha }}" + org-slug: automattic + pipeline-slug: wordpress-ios + retry-step-key: danger + build-commit-sha: ${{ github.event.pull_request.head.sha }} + cancel-running-github-jobs: false secrets: buildkite-api-token: ${{ secrets.TRIGGER_BK_BUILD_TOKEN }} From d57b1ef8be7edb7195730168c27ea1028d9d36ff Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 13 Sep 2024 13:55:11 -0400 Subject: [PATCH 05/13] Remove some redundant isPad checks --- .../Cards/BlogDashboardPersonalizeCardCell.swift | 4 +--- .../Header/HomeSiteHeaderViewController+SiteActions.swift | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift index 6ce086d1937a..94a1c8047b38 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift @@ -80,9 +80,7 @@ final class BlogDashboardPersonalizeCardCell: DashboardCollectionViewCell { let viewController = UIHostingController(rootView: NavigationView { BlogDashboardPersonalizationView(viewModel: .init(blog: blog, service: .init(siteID: siteID))) }.navigationViewStyle(.stack)) // .stack is required for iPad - if UIDevice.isPad() { - viewController.modalPresentationStyle = .formSheet - } + viewController.modalPresentationStyle = .formSheet presentingViewController?.present(viewController, animated: true) } } diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/Header/HomeSiteHeaderViewController+SiteActions.swift b/WordPress/Classes/ViewRelated/Blog/My Site/Header/HomeSiteHeaderViewController+SiteActions.swift index 16da3f645bed..268302d7c00e 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/Header/HomeSiteHeaderViewController+SiteActions.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/Header/HomeSiteHeaderViewController+SiteActions.swift @@ -87,9 +87,7 @@ extension HomeSiteHeaderViewController { let viewController = UIHostingController(rootView: NavigationView { BlogDashboardPersonalizationView(viewModel: .init(blog: self.blog, service: .init(siteID: siteID))) }.navigationViewStyle(.stack)) // .stack is required for iPad - if UIDevice.isPad() { - viewController.modalPresentationStyle = .formSheet - } + viewController.modalPresentationStyle = .formSheet present(viewController, animated: true) WPAnalytics.trackEvent(.mySiteHeaderPersonalizeHomeTapped) From db5d49a842024a63f9da6a41bbda789d83db6a9c Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 13 Sep 2024 19:47:47 -0400 Subject: [PATCH 06/13] feat: Remote editor (#23563) * feat: Provide site URL to editor Simplify top level site access. * feat: Only use WPCOM API for Simple sites Potentially simplify request authentication by relying upon the WP CORE API for all requests -- both core endpoints and plugin-registered endpoints. * build: Update GutenbergKit reference * feat: Add feature flag toggling block editor plugin support * feat: Manually retrieve blog password It appears the application password is not currently stored when adding a self-hosted site in the current login flow. This workaround change should not be merged into the `trunk` branch. * build: Update GutenbergKit ref * build: Update GutenbergKit ref * fix: Limit remote editor to self-hosted sites Currently, sites relying upon a WPCOM account utilize the WPCOM API and an OAuth token. The remote editor requires a third-party endpoint on the core WP API and an application token. Eventually, we hope to widen support by landing the endpoint in Gutenberg/core. * Revert "feat: Manually retrieve blog password" This reverts commit 1a28f0facca2758dafba0455f8238c4a5db3ec45. * build: Update GutenbergKit ref * build: Update GutenbergKit ref --- Modules/Package.swift | 2 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Classes/Utility/BuildInformation/FeatureFlag.swift | 4 ++++ .../NewGutenberg/NewGutenbergViewController.swift | 7 +++++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index e100b3c35d8f..85e0b0f9432e 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -47,7 +47,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-swift-20240813"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "da722c56773fdbf40648a914275ffb8316c1c84c"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "0987aa05587342f531b646dbb36d5622df12cfea"), .package(url: "https://github.com/Automattic/color-studio", branch: "add/swift-file-output"), ], targets: XcodeSupport.targets + [ diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index 59e3ae5a79bd..b0dbb67f4274 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -149,7 +149,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "da722c56773fdbf40648a914275ffb8316c1c84c" + "revision" : "0987aa05587342f531b646dbb36d5622df12cfea" } }, { diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index ec680216fd75..eea466b32680 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -16,6 +16,7 @@ enum FeatureFlag: Int, CaseIterable { case sidebar case newGutenberg case newGutenbergThemeStyles + case newGutenbergPlugins case serif /// Returns a boolean indicating if the feature is enabled @@ -53,6 +54,8 @@ enum FeatureFlag: Int, CaseIterable { return false case .newGutenbergThemeStyles: return false + case .newGutenbergPlugins: + return false case .serif: return false } @@ -91,6 +94,7 @@ extension FeatureFlag { case .sidebar: "Sidebar" case .newGutenberg: "Experimental Block Editor" case .newGutenbergThemeStyles: "Experimental Block Editor Styles" + case .newGutenbergPlugins: "Experimental Block Editor Plugins" case .serif: "Serif" } } diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 8b8cc7eb38f6..6f02afed8920 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -134,7 +134,8 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor let networkClient = NewGutenbergNetworkClient(blog: post.blog) let selfHostedApiUrl = post.blog.url(withPath: "wp-json/") - let siteApiRoot = post.blog.isAccessibleThroughWPCom() ? post.blog.wordPressComRestApi()?.baseURL.absoluteString : selfHostedApiUrl + let isSelfHosted = !post.blog.isHostedAtWPcom && !post.blog.isAtomic() + let siteApiRoot = post.blog.isAccessibleThroughWPCom() && !isSelfHosted ? post.blog.wordPressComRestApi()?.baseURL.absoluteString : selfHostedApiUrl let siteId = post.blog.dotComID?.stringValue let authToken = post.blog.authToken ?? "" var authHeader = "Bearer \(authToken)" @@ -149,7 +150,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor } } - let siteApiNamespace = post.blog.dotComID != nil && applicationPassword == nil ? "sites/\(siteId ?? "")" : "" + let siteApiNamespace = post.blog.dotComID != nil && !isSelfHosted && applicationPassword == nil ? "sites/\(siteId ?? "")" : "" let postType = post is Page ? "page" : "post" let postId: Int? = post.postID?.intValue != -1 ? post.postID?.intValue : nil @@ -160,6 +161,8 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor content: post.content ?? "", service: GutenbergKit.EditorService(client: networkClient), themeStyles: FeatureFlag.newGutenbergThemeStyles.enabled, + plugins: FeatureFlag.newGutenbergPlugins.enabled && isSelfHosted, + siteURL: post.blog.url ?? "", siteApiRoot: siteApiRoot!, siteApiNamespace: siteApiNamespace, authHeader: authHeader From cda2ee247d2e8bfde5f618238cdc042124e042a8 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Sun, 15 Sep 2024 17:06:07 -0400 Subject: [PATCH 07/13] Revert post & pages readable content guide change (#23600) --- .../ViewRelated/Post/AbstractPostListViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift index 4a545130d709..27eb162084db 100644 --- a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift @@ -163,7 +163,6 @@ class AbstractPostListViewController: UIViewController, tableView.estimatedRowHeight = 110 tableView.rowHeight = UITableView.automaticDimension tableView.refreshControl = refreshControl - tableView.cellLayoutMarginsFollowReadableWidth = true refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged) } From 833cdae785bd9ef3f819e9806fa76a4e098ff0bb Mon Sep 17 00:00:00 2001 From: Tony Li Date: Mon, 16 Sep 2024 09:11:00 +1200 Subject: [PATCH 08/13] Minor changes to `MySiteCoordinator` (#23594) * Remove `StartRoute` "/start" is not registered in https://wordpress.com/.well-known/apple-app-site-association * Remove a no longer used function * No need to go back to the root level if a given site is not found --- .../Universal Links/Routes+MySites.swift | 1 - .../Universal Links/Routes+Start.swift | 21 ------------------- .../Universal Links/Routes+Stats.swift | 1 - .../Universal Links/UniversalLinkRouter.swift | 7 +------ .../Coordinators/MySitesCoordinator.swift | 7 ------- WordPress/WordPress.xcodeproj/project.pbxproj | 6 ------ 6 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 WordPress/Classes/Utility/Universal Links/Routes+Start.swift diff --git a/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift b/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift index 2b82d0b53b48..e6bb18b2592e 100644 --- a/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift +++ b/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift @@ -99,7 +99,6 @@ extension MySitesRoute: NavigationAction { } if failAndBounce(values) == false { - coordinator.showRootViewController() postFailureNotice(title: NSLocalizedString("Site not found", comment: "Error notice shown if the app can't find a specific site belonging to the user")) } diff --git a/WordPress/Classes/Utility/Universal Links/Routes+Start.swift b/WordPress/Classes/Utility/Universal Links/Routes+Start.swift deleted file mode 100644 index 87ffc531aaa4..000000000000 --- a/WordPress/Classes/Utility/Universal Links/Routes+Start.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -struct StartRoute: Route, NavigationAction { - let path = "/start" - - let section: DeepLinkSection? = .siteCreation - - var action: NavigationAction { - return self - } - - let jetpackPowered: Bool = true - - func perform(_ values: [String: String], source: UIViewController?, router: LinkRouter) { - guard AccountHelper.isDotcomAvailable() else { - return - } - - RootViewCoordinator.sharedPresenter.mySitesCoordinator.showSiteCreation() - } -} diff --git a/WordPress/Classes/Utility/Universal Links/Routes+Stats.swift b/WordPress/Classes/Utility/Universal Links/Routes+Stats.swift index b23f2d4013f2..6c225bffc27f 100644 --- a/WordPress/Classes/Utility/Universal Links/Routes+Stats.swift +++ b/WordPress/Classes/Utility/Universal Links/Routes+Stats.swift @@ -140,7 +140,6 @@ extension StatsRoute: NavigationAction { WPAppAnalytics.track(.deepLinkFailed, withProperties: ["route": path]) if failAndBounce(values) == false { - coordinator.showRootViewController() postFailureNotice(title: NSLocalizedString("Site not found", comment: "Error notice shown if the app can't find a specific site belonging to the user")) } diff --git a/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift b/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift index 29d382633b29..19995f429379 100644 --- a/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift +++ b/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift @@ -39,8 +39,7 @@ struct UniversalLinkRouter: LinkRouter { readerRoutes + statsRoutes + mySitesRoutes + - appBannerRoutes + - startRoutes + appBannerRoutes static let meRoutes: [Route] = [ MeRoute(), @@ -105,10 +104,6 @@ struct UniversalLinkRouter: LinkRouter { AppBannerRoute() ] - static let startRoutes: [Route] = [ - StartRoute() - ] - static let redirects: [Route] = [ MbarRoute() ] diff --git a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift index f87ae52c5645..7baa227bae80 100644 --- a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift +++ b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift @@ -149,13 +149,6 @@ class MySitesCoordinator: NSObject { showBlogDetails(for: blog, then: .activity) } - // MARK: - Adding a new site - - func showSiteCreation() { - showRootViewController() - mySiteViewController.launchSiteCreation(source: "my_site") - } - // MARK: - Post creation func showCreateSheet(for blog: Blog?) { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 5c93942889f3..19726f5be9e5 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -786,8 +786,6 @@ 1746D7771D2165AE00B11D77 /* ForcePopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1746D7761D2165AE00B11D77 /* ForcePopoverPresenter.swift */; }; 1749965F2271BF08007021BD /* WordPressAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1749965E2271BF08007021BD /* WordPressAppDelegate.swift */; }; 174C116F2624603400346EC6 /* MBarRouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174C116E2624603400346EC6 /* MBarRouteTests.swift */; }; - 174C11932624C78900346EC6 /* Routes+Start.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174C11922624C78900346EC6 /* Routes+Start.swift */; }; - 174C11942624C78900346EC6 /* Routes+Start.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174C11922624C78900346EC6 /* Routes+Start.swift */; }; 1750BD6D201144DB0050F13A /* MediaNoticeNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1750BD6C201144DB0050F13A /* MediaNoticeNavigationCoordinator.swift */; }; 1751E5911CE0E552000CA08D /* KeyValueDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */; }; 1751E5931CE23801000CA08D /* NSAttributedString+StyledHTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751E5921CE23801000CA08D /* NSAttributedString+StyledHTML.swift */; }; @@ -6651,7 +6649,6 @@ 1746D7761D2165AE00B11D77 /* ForcePopoverPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForcePopoverPresenter.swift; sourceTree = ""; }; 1749965E2271BF08007021BD /* WordPressAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressAppDelegate.swift; sourceTree = ""; }; 174C116E2624603400346EC6 /* MBarRouteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBarRouteTests.swift; sourceTree = ""; }; - 174C11922624C78900346EC6 /* Routes+Start.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Start.swift"; sourceTree = ""; }; 1750BD6C201144DB0050F13A /* MediaNoticeNavigationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaNoticeNavigationCoordinator.swift; sourceTree = ""; }; 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueDatabase.swift; sourceTree = ""; }; 1751E5921CE23801000CA08D /* NSAttributedString+StyledHTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+StyledHTML.swift"; sourceTree = ""; }; @@ -11177,7 +11174,6 @@ 17B7C8C020EE2A870042E260 /* Routes+Notifications.swift */, 1703D04B20ECD93800D292E9 /* Routes+Post.swift */, 17A4A36820EE51870071C2CA /* Routes+Reader.swift */, - 174C11922624C78900346EC6 /* Routes+Start.swift */, 1715179120F4B2EB002C4A38 /* Routes+Stats.swift */, 17B7C89D20EC1D0D0042E260 /* UniversalLinkRouter.swift */, ); @@ -22653,7 +22649,6 @@ E1B23B081BFB3B370006559B /* MyProfileViewController.swift in Sources */, F532AE1C253E55D40013B42E /* CreateButtonActionSheet.swift in Sources */, 8BCB83D124C21063001581BD /* ReaderStreamViewController+Ghost.swift in Sources */, - 174C11932624C78900346EC6 /* Routes+Start.swift in Sources */, 4AA33F04299A1F93005B6E23 /* ReaderTagTopic+Lookup.swift in Sources */, FA347AED26EB6E300096604B /* GrowAudienceCell.swift in Sources */, FE6AFE472B1A351F00F76520 /* SOTWCardView.swift in Sources */, @@ -25615,7 +25610,6 @@ 3FE3D1FF26A6F56700F3CD10 /* Comment+Interface.swift in Sources */, FABB24492602FC2C00C8785C /* CreateButtonActionSheet.swift in Sources */, FABB244A2602FC2C00C8785C /* ReaderStreamViewController+Ghost.swift in Sources */, - 174C11942624C78900346EC6 /* Routes+Start.swift in Sources */, FABB244B2602FC2C00C8785C /* Constants.m in Sources */, FAB9826F2697038700B172A3 /* StatsViewController+JetpackSettings.swift in Sources */, FABB244D2602FC2C00C8785C /* RegisterDomainDetailsErrorSectionFooter.swift in Sources */, From be047ba47c97d36a634f452d3250e51044b4f10f Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Sun, 15 Sep 2024 17:31:58 -0400 Subject: [PATCH 09/13] Sidebar: Fixes and Improvements (#23593) * Show InsightsManagementViewController in a form * Update MigrationSuccessCell for sidebar mode (see screenshots) * Fix navigation bar background in NotificationCommentDetailViewController * Add support for readbleContentGuide in the remaining screens in Stats * Fix cell buttons * Use wpl-phone icon in Me screen when running on iPhone * Fix hover effects in Site Menu * Fix sidebar not refreshing the site list and not updating the title when site title changes * Fix placeholder site icon text color durign sidebar selection * Remove TODOs that are no longer needed * Fix Reader sidebar tint color * Fix an issue with quick actions not show in compact presentation * Fix Media hiding back button on iPad split view * Fix an issue with hidesBottomBarWhenPushed not working on iPad split view (because of the nested split view controllers) * Fix an issue with empty state layout (had no constraints) * Revert "Fix hover effects in Site Menu" This reverts commit e2b275dc873687eefd28420ebdcfc66b2bceda7d. * Simplify how insets on home page are set * Fix comment alignment --- .../WPStyleGuide+ApplicationStyles.swift | 2 +- .../BlogDashboardViewController.swift | 20 ++++++++--------- .../ViewModel/BlogDashboardViewModel.swift | 22 ++++++++----------- .../Blog Details/BlogDetailsViewController.m | 10 +++++---- .../BlogList/BlogListViewModel.swift | 2 +- .../Site Picker/BlogList/SiteIconView.swift | 4 +++- .../Me/Me Main/MeViewController.swift | 2 +- .../SiteMedia/SiteMediaViewController.swift | 2 +- ...ificationCommentDetailViewController.swift | 2 ++ .../People/PeopleViewController.swift | 8 +++---- .../Reader/ReaderSidebarViewController.swift | 1 - .../SiteStatsBaseTableViewController.swift | 1 + ...SiteStatsInsightsTableViewController.swift | 1 + .../Coordinators/MySitesCoordinator.swift | 8 ++++--- .../Sidebar/SidebarViewController.swift | 13 +++++++++-- .../System/Sidebar/SidebarViewModel.swift | 20 ++++++++++++++++- .../Table View/MigrationSuccessCell.swift | 6 +++++ 17 files changed, 79 insertions(+), 45 deletions(-) diff --git a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift index 4d0f78c4ffd3..7ea54e486a0d 100644 --- a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift +++ b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift @@ -164,7 +164,7 @@ extension WPStyleGuide { @objc class func configureTableViewActionCell(_ cell: UITableViewCell?) { configureTableViewCell(cell) - cell?.textLabel?.textColor = UIAppColor.primary + cell?.textLabel?.textColor = UIAppColor.brand } @objc diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift index 995da1b083a5..e9ed8c89506e 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift @@ -208,14 +208,16 @@ extension BlogDashboardViewController { let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) - let isQuickActionSection = viewModel.isQuickActionsSection(sectionIndex) - let isMigrationSuccessCardSection = viewModel.isMigrationSuccessCardSection(sectionIndex) let horizontalInset = Constants.horizontalSectionInset - let bottomInset = isQuickActionSection || isMigrationSuccessCardSection ? 0 : Constants.bottomSectionInset - section.contentInsets = NSDirectionalEdgeInsets(top: Constants.verticalSectionInset, - leading: horizontalInset, - bottom: bottomInset, - trailing: horizontalInset) + let isLast = (sectionIndex == collectionView.numberOfSections - 1) + // More on .compact to match the FAB. + let bottomInset = (isLast && traitCollection.horizontalSizeClass == .compact) ? 86 : 20 + section.contentInsets = NSDirectionalEdgeInsets( + top: Constants.verticalSectionInset, + leading: horizontalInset, + bottom: CGFloat(bottomInset), + trailing: horizontalInset + ) section.interGroupSpacing = Constants.cellSpacing section.contentInsetsReference = .readableContent @@ -240,10 +242,6 @@ extension BlogDashboardViewController { static let estimatedHeight: CGFloat = 44 static let horizontalSectionInset: CGFloat = 12 static let verticalSectionInset: CGFloat = 20 - static var bottomSectionInset: CGFloat { - // Make room for FAB on iPhone - WPDeviceIdentification.isiPad() ? verticalSectionInset : 86 - } static let cellSpacing: CGFloat = 20 } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift index 16ed789785c7..eb01457cf3a4 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift @@ -166,17 +166,6 @@ final class BlogDashboardViewModel { let cards = service.fetchLocal(blog: blog) updateCurrentCards(cards: cards) } - - func isQuickActionsSection(_ sectionIndex: Int) -> Bool { - let showMigration = MigrationSuccessCardView.shouldShowMigrationSuccessCard && !WPDeviceIdentification.isiPad() - let targetIndex = showMigration ? DashboardSection.quickActions.rawValue : DashboardSection.quickActions.rawValue - 1 - return sectionIndex == targetIndex - } - - func isMigrationSuccessCardSection(_ sectionIndex: Int) -> Bool { - let showMigration = MigrationSuccessCardView.shouldShowMigrationSuccessCard && !WPDeviceIdentification.isiPad() - return showMigration ? sectionIndex == DashboardSection.migrationSuccess.rawValue : false - } } // MARK: - Private methods @@ -214,14 +203,21 @@ private extension BlogDashboardViewModel { } func createSnapshot(from cards: [DashboardCardModel]) -> DashboardSnapshot { + let isShowingQuickActions: Bool = { + guard Feature.enabled(.sidebar) else { + return !WPDeviceIdentification.isiPad() + } + return viewController?.traitCollection.horizontalSizeClass == .compact + }() + let items = cards.map { DashboardItem.cards($0) } let dotComID = blog.dotComID?.intValue ?? 0 var snapshot = DashboardSnapshot() - if MigrationSuccessCardView.shouldShowMigrationSuccessCard, !WPDeviceIdentification.isiPad() { + if MigrationSuccessCardView.shouldShowMigrationSuccessCard, isShowingQuickActions { snapshot.appendSections([.migrationSuccess]) snapshot.appendItems([.migrationSuccess], toSection: .migrationSuccess) } - if !WPDeviceIdentification.isiPad() { + if isShowingQuickActions { snapshot.appendSections([.quickActions]) snapshot.appendItems([.quickActions(dotComID)], toSection: .quickActions) } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m index 7e428c0f0ffb..2723a7aad8c0 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m @@ -977,7 +977,7 @@ - (void)configureTableViewData [marr addNullableObject:[self sotw2023SectionViewModel]]; } - if (MigrationSuccessCardView.shouldShowMigrationSuccessCard == YES) { + if (MigrationSuccessCardView.shouldShowMigrationSuccessCard == YES && ![Feature enabled:FeatureFlagSidebar]) { [marr addNullableObject:[self migrationSuccessSectionViewModel]]; } @@ -1028,7 +1028,6 @@ - (void)configureTableViewData self.tableSections = [NSArray arrayWithArray:marr]; } -// TODO: (wpsidebar) Remove when WPSPlitViewController is removed on iPhone - (Boolean)isSplitViewDisplayed { if (self.isSidebarModeEnabled) { return true; @@ -1551,12 +1550,15 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } - if (section.category == BlogDetailsSectionCategoryMigrationSuccess) { + if (section.category == BlogDetailsSectionCategoryMigrationSuccess) { MigrationSuccessCell *cell = [tableView dequeueReusableCellWithIdentifier:BlogDetailsMigrationSuccessCellIdentifier]; + if (self.isSidebarModeEnabled) { + [cell configureForSidebarMode]; + } [cell configureWithViewController:self]; return cell; } - + if (section.category == BlogDetailsSectionCategoryJetpackBrandingCard) { JetpackBrandingMenuCardCell *cell = [tableView dequeueReusableCellWithIdentifier:BlogDetailsJetpackBrandingCardCellIdentifier]; [cell configureWithViewController:self]; diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift index 2106f6f22bd4..af5c1265b00f 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/BlogListViewModel.swift @@ -80,7 +80,7 @@ final class BlogListViewModel: NSObject, ObservableObject { updateDisplayedSites() } - private func updateDisplayedSites() { + func updateDisplayedSites() { rawSites = getFilteredSites(from: fetchedResultsController) var sitesByURL: [String: Blog] = [:] diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/SiteIconView.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/SiteIconView.swift index ed051dd72305..42b3b32be7f6 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/SiteIconView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/BlogList/SiteIconView.swift @@ -34,9 +34,11 @@ struct SiteIconView: View { private var noIconView: some View { backgroundColor.overlay { if let firstLetter = viewModel.firstLetter { + // - warning: important to use `.foregroundColor` and not + // `.foregroundStyle` to avoid it changing in sidebar on selection Text(firstLetter.uppercased()) .font(.system(size: iconFontSize(for: viewModel.size), weight: .medium, design: .rounded)) - .foregroundStyle(.secondary.opacity(0.8)) + .foregroundColor(.secondary.opacity(0.8)) } else { failureStateView } diff --git a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift index 61dfe5253a96..7d0d5bf284da 100644 --- a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift @@ -129,7 +129,7 @@ class MeViewController: UITableViewController { return NavigationItemRow( title: RowTitles.appSettings, - icon: UIImage(named: "wpl-tablet")?.withRenderingMode(.alwaysTemplate), + icon: UIImage(named: UIDevice.isPad() ? "wpl-tablet" : "wpl-phone")?.withRenderingMode(.alwaysTemplate), tintColor: .label, accessoryType: accessoryType, action: pushAppSettings(), diff --git a/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift b/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift index dc906e8caeed..d6dab88f2dc8 100644 --- a/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift +++ b/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift @@ -87,7 +87,7 @@ final class SiteMediaViewController: UIViewController, SiteMediaCollectionViewCo let button = UIButton.makeMenu(title: Strings.title, menu: menu) self.buttonFilter = button - if UIDevice.isPad() { + if traitCollection.horizontalSizeClass == .regular { navigationItem.leftBarButtonItem = UIBarButtonItem(customView: button) } else { navigationItem.titleView = button diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationCommentDetailViewController.swift b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationCommentDetailViewController.swift index 208570486fe9..44226b28aee5 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationCommentDetailViewController.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationCommentDetailViewController.swift @@ -101,7 +101,9 @@ class NotificationCommentDetailViewController: UIViewController, NoResultsViewHo override func viewDidLoad() { super.viewDidLoad() + configureNavBar() + WPStyleGuide.disableScrollEdgeAppearance(for: self) view.backgroundColor = .systemBackground loadComment() } diff --git a/WordPress/Classes/ViewRelated/People/PeopleViewController.swift b/WordPress/Classes/ViewRelated/People/PeopleViewController.swift index bde01a62b4ba..4086d4336258 100644 --- a/WordPress/Classes/ViewRelated/People/PeopleViewController.swift +++ b/WordPress/Classes/ViewRelated/People/PeopleViewController.swift @@ -463,17 +463,15 @@ private extension PeopleViewController { let accessoryView = isLoading ? NoResultsViewController.loadingAccessoryView() : nil noResultsViewController.configure(title: noResultsTitle(), accessoryView: accessoryView) - // Set the NRV top as the filterBar bottom so the NRV - // adjusts correctly when refreshControl is active. - let filterBarBottom = filterBar.frame.origin.y + filterBar.frame.size.height - noResultsViewController.view.frame.origin.y = filterBarBottom - guard noResultsViewController.parent == nil else { noResultsViewController.updateView() return } addChild(noResultsViewController) tableView.addSubview(withFadeAnimation: noResultsViewController.view) + noResultsViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.pinSubviewToSafeArea(noResultsViewController.view) + noResultsViewController.didMove(toParent: self) } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSidebarViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSidebarViewController.swift index f94fcf23ec0b..bd7d9d2d59c7 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSidebarViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSidebarViewController.swift @@ -175,7 +175,6 @@ private struct ReaderSidebarView: View { .toolbar { EditButton() } - .tint(Color(UIAppColor.primary)) } @ViewBuilder diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsBaseTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsBaseTableViewController.swift index fc9b4c32ef3c..3bd927749911 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsBaseTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsBaseTableViewController.swift @@ -25,6 +25,7 @@ class SiteStatsBaseTableViewController: UIViewController { func initTableView() { tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.cellLayoutMarginsFollowReadableWidth = true view.addSubview(tableView) view.pinSubviewToAllEdges(tableView) diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift index 83308561d9c0..62c67ca42bdf 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift @@ -111,6 +111,7 @@ class SiteStatsInsightsTableViewController: SiteStatsBaseTableViewController, St let controller = InsightsManagementViewController(insightsDelegate: self, insightsManagementDelegate: self, insightsShown: insightsToShow.compactMap { $0.statSection }) let navigationController = UINavigationController(rootViewController: controller) + navigationController.modalPresentationStyle = .formSheet // Has to be set before the delegate navigationController.presentationController?.delegate = self present(navigationController, animated: true, completion: nil) } diff --git a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift index 7baa227bae80..2b11ab637549 100644 --- a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift +++ b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift @@ -5,7 +5,6 @@ import WordPressAuthenticator class MySitesCoordinator: NSObject { let meScenePresenter: ScenePresenter - // TODO: (wpsidebar) move logic to RootViewPresenter let becomeActiveTab: () -> Void @objc @@ -43,9 +42,12 @@ class MySitesCoordinator: NSObject { } } - // TODO: (wpsidebar) remove @objc class var isSplitViewEnabled: Bool { - UIDevice.current.userInterfaceIdiom == .pad + if Feature.enabled(.sidebar) { + return false + } else { + return UIDevice.current.userInterfaceIdiom == .pad + } } @objc diff --git a/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewController.swift b/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewController.swift index c2e160b4d784..5cb7a2ddc8c5 100644 --- a/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewController.swift +++ b/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewController.swift @@ -6,19 +6,28 @@ import WordPressUI /// The sidebar for the iPad version of the app. final class SidebarViewController: UIHostingController { + private let viewModel: SidebarViewModel + init(viewModel: SidebarViewModel) { - super.init(rootView: AnyView(SidebarView(viewModel: viewModel))) + self.viewModel = viewModel + super.init(rootView: AnyView(SidebarView(viewModel: viewModel, blogListViewModel: viewModel.blogListViewModel))) self.title = Strings.sectionMySites } required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewModel.onAppear() + } } private struct SidebarView: View { @ObservedObject var viewModel: SidebarViewModel - @StateObject private var blogListViewModel = BlogListViewModel() + @ObservedObject var blogListViewModel: BlogListViewModel @StateObject private var notificationsButtonViewModel = NotificationsButtonViewModel() static let displayedSiteLimit = 4 diff --git a/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewModel.swift b/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewModel.swift index 951a891f4de7..a1bef85cc802 100644 --- a/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewModel.swift +++ b/WordPress/Classes/ViewRelated/System/Sidebar/SidebarViewModel.swift @@ -23,20 +23,38 @@ final class SidebarViewModel: ObservableObject { @Published var selection: SidebarSelection? @Published private(set) var account: WPAccount? + let blogListViewModel = BlogListViewModel() + var navigate: (SidebarNavigationStep) -> Void = { _ in } private let contextManager: CoreDataStackSwift + private var previousReloadTimestamp: Date? private var cancellables: [AnyCancellable] = [] init(contextManager: CoreDataStackSwift = ContextManager.shared) { self.contextManager = contextManager - // TODO: (wpsidebar) can it change during the root presenter lifetime? account = try? WPAccount.lookupDefaultWordPressComAccount(in: contextManager.mainContext) resetSelection() setupObservers() } + func onAppear() { + reloadMenuIfNeeded() + } + + private func reloadMenuIfNeeded() { + blogListViewModel.updateDisplayedSites() + + if Date.now.timeIntervalSince(previousReloadTimestamp ?? .distantPast) > 60 { + previousReloadTimestamp = .now + + Task { + try? await blogListViewModel.refresh() + } + } + } + private func setupObservers() { NotificationCenter.default .publisher(for: MySiteViewController.didPickSiteNotification) diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Success card/Table View/MigrationSuccessCell.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Success card/Table View/MigrationSuccessCell.swift index 40a357594279..7a5eceab91ee 100644 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Success card/Table View/MigrationSuccessCell.swift +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Success card/Table View/MigrationSuccessCell.swift @@ -4,6 +4,7 @@ import UIKit class MigrationSuccessCell: UITableViewCell { var onTap: (() -> Void)? + var cardView: MigrationSuccessCardView? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -21,6 +22,11 @@ class MigrationSuccessCell: UITableViewCell { view.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(view) contentView.pinSubviewToAllEdges(view) + cardView = view + } + + @objc func configureForSidebarMode() { + cardView?.backgroundColor = .clear } } From e303a86376ffbe747270517cca81ead30cf9a1c0 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Sun, 15 Sep 2024 17:36:38 -0400 Subject: [PATCH 10/13] Fix an issue with sidebar showing incorrect selection (#23595) --- WordPress/Classes/System/SplitViewRootPresenter.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/System/SplitViewRootPresenter.swift b/WordPress/Classes/System/SplitViewRootPresenter.swift index f21b51b532fb..9fe0a28d1ba1 100644 --- a/WordPress/Classes/System/SplitViewRootPresenter.swift +++ b/WordPress/Classes/System/SplitViewRootPresenter.swift @@ -92,7 +92,9 @@ final class SplitViewRootPresenter: RootViewPresenter { sidebarVC.showInitialSelection() } - splitVC.hide(.primary) + DispatchQueue.main.async { + self.splitVC.hide(.primary) + } } private func showNoSitesScreen() { From 9a59c28c25df661c62223bec876031adc2a3320a Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Sun, 15 Sep 2024 17:59:01 -0400 Subject: [PATCH 11/13] Simpler ghost views in Reader (#23599) --- .../Reader/ReaderStreamViewController+Ghost.swift | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift index 0de2ab883c27..2c5e659a155d 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift @@ -51,24 +51,21 @@ private final class ReaderGhostCell: UITableViewCell { view.widthAnchor.constraint(equalToConstant: width).withPriority(.defaultLow), view.heightAnchor.constraint(equalToConstant: height).withPriority(.defaultHigh), ]) + view.layer.cornerRadius = 4 + view.layer.masksToBounds = true return view } let imageView = makeLeafView(height: 320, width: 1200) imageView.layer.cornerRadius = 8 - imageView.layer.masksToBounds = true imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5).isActive = true let insets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) - let stackView = UIStackView(axis: .vertical, alignment: .leading, spacing: 14, insets: insets, [ - makeLeafView(height: 10, width: .random(in: 140...200)), - makeLeafView(height: 18, width: .random(in: 160...320)), - UIStackView(axis: .vertical, alignment: .leading, spacing: 4, [ - makeLeafView(height: 10, width: 2000), - makeLeafView(height: 10, width: .random(in: 200...600)) - ]), + let stackView = UIStackView(axis: .vertical, alignment: .leading, spacing: 16, insets: insets, [ + makeLeafView(height: 16, width: .random(in: 140...200)), + makeLeafView(height: 24, width: .random(in: 200...600)), imageView, - makeLeafView(height: 10, width: .random(in: 200...240)) + makeLeafView(height: 16, width: .random(in: 200...240)) ]) contentView.addSubview(stackView) stackView.translatesAutoresizingMaskIntoConstraints = false From f19f4b32fe18745678dc7f2b2485595accc63c94 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Mon, 16 Sep 2024 11:04:05 +1200 Subject: [PATCH 12/13] Display previously viewed screen when switching back to Site, Notification, and Reader (#23578) * Display last screen when switching back to Site, Notification, and Reader * Remove Notifications and Reader upon low memory warning * Add an accidentally deleted line back * Add WelcomeSplitViewContent * Move updating UI code into the `configure(selection:)` function * Rename `SplitViewDisplayable` properties * Remove `SplitViewDisplayable.selection` * Remove handling low memory warning --- ...SplitViewRootPresenter+Notifications.swift | 20 +++ .../SplitViewRootPresenter+Reader.swift | 23 +++ .../System/SplitViewRootPresenter+Site.swift | 39 ++++ .../SplitViewRootPresenter+Welcome.swift | 25 +++ .../System/SplitViewRootPresenter.swift | 170 ++++++++++-------- .../Sidebar/SiteMenuViewController.swift | 2 +- WordPress/WordPress.xcodeproj/project.pbxproj | 24 +++ 7 files changed, 226 insertions(+), 77 deletions(-) create mode 100644 WordPress/Classes/System/SplitViewRootPresenter+Notifications.swift create mode 100644 WordPress/Classes/System/SplitViewRootPresenter+Reader.swift create mode 100644 WordPress/Classes/System/SplitViewRootPresenter+Site.swift create mode 100644 WordPress/Classes/System/SplitViewRootPresenter+Welcome.swift diff --git a/WordPress/Classes/System/SplitViewRootPresenter+Notifications.swift b/WordPress/Classes/System/SplitViewRootPresenter+Notifications.swift new file mode 100644 index 000000000000..d75202e1ff4e --- /dev/null +++ b/WordPress/Classes/System/SplitViewRootPresenter+Notifications.swift @@ -0,0 +1,20 @@ +import Foundation +import UIKit + +class NotificationsSplitViewContent: SplitViewDisplayable { + let supplementary: UINavigationController + let notificationsViewController: NotificationsViewController + var secondary: UINavigationController + + init() { + notificationsViewController = UIStoryboard(name: "Notifications", bundle: nil).instantiateInitialViewController() as! NotificationsViewController + supplementary = UINavigationController(rootViewController: notificationsViewController) + secondary = UINavigationController() + + notificationsViewController.isSidebarModeEnabled = true + } + + func displayed(in splitVC: UISplitViewController) { + // Do nothing + } +} diff --git a/WordPress/Classes/System/SplitViewRootPresenter+Reader.swift b/WordPress/Classes/System/SplitViewRootPresenter+Reader.swift new file mode 100644 index 000000000000..1b7f7ffe219b --- /dev/null +++ b/WordPress/Classes/System/SplitViewRootPresenter+Reader.swift @@ -0,0 +1,23 @@ +import Foundation +import UIKit + +class ReaderSplitViewContent: SplitViewDisplayable { + let sidebar: ReaderSidebarViewController + let supplementary: UINavigationController + var secondary: UINavigationController + + init() { + secondary = UINavigationController() + let viewModel = ReaderSidebarViewModel() + sidebar = ReaderSidebarViewController(viewModel: viewModel) + sidebar.navigationItem.largeTitleDisplayMode = .automatic + supplementary = UINavigationController(rootViewController: sidebar) + supplementary .navigationBar.prefersLargeTitles = true + } + + func displayed(in splitVC: UISplitViewController) { + if secondary.viewControllers.isEmpty { + sidebar.showInitialSelection() + } + } +} diff --git a/WordPress/Classes/System/SplitViewRootPresenter+Site.swift b/WordPress/Classes/System/SplitViewRootPresenter+Site.swift new file mode 100644 index 000000000000..bd60ce12188b --- /dev/null +++ b/WordPress/Classes/System/SplitViewRootPresenter+Site.swift @@ -0,0 +1,39 @@ +import Foundation +import UIKit + +class SiteSplitViewContent: SiteMenuViewControllerDelegate, SplitViewDisplayable { + let siteMenu: SiteMenuViewController + let supplementary: UINavigationController + var secondary: UINavigationController + + var blog: Blog { + siteMenu.blog + } + + init(blog: Blog) { + siteMenu = SiteMenuViewController(blog: blog) + supplementary = UINavigationController(rootViewController: siteMenu) + secondary = UINavigationController() + siteMenu.delegate = self + } + + func displayed(in splitVC: UISplitViewController) { + RecentSitesService().touch(blog: blog) + + // TODO: (wpsidebar) Refactor this (initial .secondary vc managed based on the VC presentation) + _ = siteMenu.view + } + + func siteMenuViewController(_ siteMenuViewController: SiteMenuViewController, showDetailsViewController viewController: UIViewController) { + guard siteMenu === siteMenuViewController, let splitVC = siteMenuViewController.splitViewController else { return } + + if viewController is UINavigationController || + viewController is UISplitViewController { + splitVC.setViewController(viewController, for: .secondary) + } else { + // Reset previous navigation or split stack + let navigationVC = UINavigationController(rootViewController: viewController) + splitVC.setViewController(navigationVC, for: .secondary) + } + } +} diff --git a/WordPress/Classes/System/SplitViewRootPresenter+Welcome.swift b/WordPress/Classes/System/SplitViewRootPresenter+Welcome.swift new file mode 100644 index 000000000000..bcf70d958a4f --- /dev/null +++ b/WordPress/Classes/System/SplitViewRootPresenter+Welcome.swift @@ -0,0 +1,25 @@ +import Foundation +import UIKit +import SwiftUI + +class WelcomeSplitViewContent: SplitViewDisplayable { + let supplementary: UINavigationController + var secondary: UINavigationController + + init(addSite: @escaping (AddSiteMenuViewModel.Selection) -> Void) { + supplementary = UINavigationController(rootViewController: UnifiedPrologueViewController()) + + let addSiteViewModel = AddSiteMenuViewModel(context: .shared, onSelection: addSite) + let noSitesViewModel = NoSitesViewModel(appUIType: JetpackFeaturesRemovalCoordinator.currentAppUIType, account: nil) + let noSiteView = NoSitesView(addSiteViewModel: addSiteViewModel, viewModel: noSitesViewModel) + let noSitesVC = UIHostingController(rootView: noSiteView) + noSitesVC.view.backgroundColor = .systemBackground + secondary = UINavigationController(rootViewController: noSitesVC) + + supplementary.isNavigationBarHidden = true + } + + func displayed(in splitVC: UISplitViewController) { + // Do nothing + } +} diff --git a/WordPress/Classes/System/SplitViewRootPresenter.swift b/WordPress/Classes/System/SplitViewRootPresenter.swift index 9fe0a28d1ba1..c92529b2b542 100644 --- a/WordPress/Classes/System/SplitViewRootPresenter.swift +++ b/WordPress/Classes/System/SplitViewRootPresenter.swift @@ -1,7 +1,8 @@ import UIKit import Combine -import WordPressAuthenticator import SwiftUI +import WordPressAuthenticator +import WordPressUI /// The presenter that uses triple-column navigation for `.regular` size classes /// and a tab-bar based navigation for `.compact` size class. @@ -12,10 +13,20 @@ final class SplitViewRootPresenter: RootViewPresenter { private weak var sitePickerPopoverVC: UIViewController? private var cancellables: [AnyCancellable] = [] - private var notifications: UINavigationController? { - didSet { - assert(notifications == nil || notifications?.viewControllers.first is NotificationsViewController) - } + private var siteContent: SiteSplitViewContent? + private var notificationsContent: NotificationsSplitViewContent? + private var readerContent: ReaderSplitViewContent? + private var welcomeContent: WelcomeSplitViewContent? + + private var displayingContent: SplitViewDisplayable? { + let possibleContent: [SplitViewDisplayable?] = [siteContent, notificationsContent, readerContent, welcomeContent] + let displaying = possibleContent + .compactMap { $0 } + .filter { $0.isDisplaying(in: splitVC) } + + wpAssert(displaying.count <= 1) + + return displaying.first } /// Is the app displaying tab bar UI instead of the full split view UI (with sidebar). @@ -72,60 +83,52 @@ final class SplitViewRootPresenter: RootViewPresenter { splitVC.preferredSupplementaryColumnWidth = UISplitViewController.automaticDimension } + let content: SplitViewDisplayable switch selection { case .welcome: - showNoSitesScreen() + if let welcomeContent { + content = welcomeContent + } else { + welcomeContent = WelcomeSplitViewContent { [weak self] in self?.navigate(to: .addSite(selection: $0)) } + content = welcomeContent! + } case .blog(let objectID): - do { - let site = try ContextManager.shared.mainContext.existingObject(with: objectID) - showDetails(for: site) - } catch { - // TODO: (wpsidebar) switch to a different blog? + if let siteContent, siteContent.blog.objectID == objectID.objectID { + content = siteContent + } else { + do { + let site = try ContextManager.shared.mainContext.existingObject(with: objectID) + siteContent = SiteSplitViewContent(blog: site) + content = siteContent! + } catch { + // TODO: (wpsidebar) switch to a different blog? + return + } } case .notifications: - showNotificationsTab(completion: nil) + // TODO: (wpsidebar) update tab bar item when new notifications arrive + if let notificationsContent { + content = notificationsContent + } else { + notificationsContent = NotificationsSplitViewContent() + content = notificationsContent! + } case .reader: - let viewModel = ReaderSidebarViewModel() - let sidebarVC = ReaderSidebarViewController(viewModel: viewModel) - splitVC.setViewController(makeRootNavigationController(with: sidebarVC), for: .supplementary) - - sidebarVC.showInitialSelection() + if let readerContent { + content = readerContent + } else { + readerContent = ReaderSplitViewContent() + content = readerContent! + } } + display(content: content) + DispatchQueue.main.async { self.splitVC.hide(.primary) } } - private func showNoSitesScreen() { - splitVC.setViewController(UnifiedPrologueViewController(), for: .supplementary) - - let addSiteViewModel = AddSiteMenuViewModel { [weak self] in - self?.navigate(to: .addSite(selection: $0)) - } - let noSitesViewModel = NoSitesViewModel(appUIType: JetpackFeaturesRemovalCoordinator.currentAppUIType, account: nil) - let noSiteView = NoSitesView(addSiteViewModel: addSiteViewModel, viewModel: noSitesViewModel) - let noSitesVC = UIHostingController(rootView: noSiteView) - noSitesVC.view.backgroundColor = .systemBackground - let navigationVC = UINavigationController(rootViewController: noSitesVC) - splitVC.setViewController(navigationVC, for: .secondary) - } - - private func showDetails(for site: Blog) { - RecentSitesService().touch(blog: site) - - let siteMenuVC = SiteMenuViewController(blog: site) - siteMenuVC.delegate = self - let navigationVC = UINavigationController(rootViewController: siteMenuVC) - splitVC.setViewController(navigationVC, for: .supplementary) - - // Reset navigation stack - splitVC.setViewController(UINavigationController(), for: .secondary) - - // TODO: (wpsidebar) Refactor this (initial .secondary vc managed based on the VC presentation) - _ = siteMenuVC.view - } - private func makeRootNavigationController(with viewController: UIViewController) -> UINavigationController { let navigationVC = UINavigationController(rootViewController: viewController) viewController.navigationItem.largeTitleDisplayMode = .automatic @@ -252,11 +255,7 @@ final class SplitViewRootPresenter: RootViewPresenter { func currentlyVisibleBlog() -> Blog? { assert(Thread.isMainThread) - guard case let .blog(id) = sidebarViewModel.selection else { - return nil - } - - return try? ContextManager.shared.mainContext.existingObject(with: id) + return siteContent?.blog } var readerTabViewController: ReaderTabViewController? @@ -266,7 +265,7 @@ final class SplitViewRootPresenter: RootViewPresenter { var readerNavigationController: UINavigationController? func showReaderTab() { - fatalError() + sidebarViewModel.selection = .reader } func showReaderTab(forPost: NSNumber, onBlog: NSNumber) { @@ -308,7 +307,11 @@ final class SplitViewRootPresenter: RootViewPresenter { var mySitesCoordinator: MySitesCoordinator func showMySitesTab() { - fatalError() + guard let blog = currentlyVisibleBlog() else { + // Do nothing. + return + } + sidebarViewModel.selection = .blog(TaggedManagedObjectID(blog)) } func showPages(for blog: Blog) { @@ -324,21 +327,11 @@ final class SplitViewRootPresenter: RootViewPresenter { } func showNotificationsTab(completion: ((NotificationsViewController) -> Void)?) { - // TODO: (wpsidebar) update tab bar item when new notifications arrive - let navigationController: UINavigationController - if let notifications = self.notifications { - navigationController = notifications - } else { - let notificationsVC = UIStoryboard(name: "Notifications", bundle: nil).instantiateInitialViewController() as! NotificationsViewController - notificationsVC.isSidebarModeEnabled = true - let navigationVC = UINavigationController(rootViewController: notificationsVC) - self.notifications = navigationVC + sidebarViewModel.selection = .notifications - navigationController = navigationVC + if let notifications = self.notificationsContent { + completion?(notifications.notificationsViewController) } - - splitVC.setViewController(navigationController, for: .supplementary) - completion?(navigationController.viewControllers.first as! NotificationsViewController) } var meViewController: MeViewController? @@ -375,15 +368,40 @@ extension SplitViewRootPresenter: UISplitViewControllerDelegate { } } -extension SplitViewRootPresenter: SiteMenuViewControllerDelegate { - func siteMenuViewController(_ siteMenuViewController: SiteMenuViewController, showDetailsViewController viewController: UIViewController) { - if viewController is UINavigationController || - viewController is UISplitViewController { - splitVC.setViewController(viewController, for: .secondary) - } else { - // Reset previous navigation or split stack - let navigationVC = UINavigationController(rootViewController: viewController) - splitVC.setViewController(navigationVC, for: .secondary) - } +// MARK: - Content displayed within the split view, alongside the sidebar + +/// This protocol is an abstraction of the `supplementary` and `secondary` columns in a split view. +/// +/// When in full-screen mode, `SplitViewRootPresenter` presents a triple-column split view. The sidebar is displayed in +/// the primary column, which is always accessible. The `supplementary` and `secondary` columns display different +/// content, depending on what users choose from the sidebar. +protocol SplitViewDisplayable: AnyObject { + var supplementary: UINavigationController { get } + var secondary: UINavigationController { get set } + + func displayed(in splitVC: UISplitViewController) +} + +extension SplitViewDisplayable { + func isDisplaying(in splitVC: UISplitViewController) -> Bool { + splitVC.viewController(for: .supplementary) === self.supplementary + } + + func refresh(with splitVC: UISplitViewController) { + guard isDisplaying(in: splitVC) else { return } + guard let currentContent = splitVC.viewController(for: .secondary) as? UINavigationController else { return } + + self.secondary = currentContent + } +} + +private extension SplitViewRootPresenter { + func display(content: SplitViewDisplayable) { + displayingContent?.refresh(with: splitVC) + + splitVC.setViewController(content.supplementary, for: .supplementary) + splitVC.setViewController(content.secondary, for: .secondary) + + content.displayed(in: splitVC) } } diff --git a/WordPress/Classes/ViewRelated/System/Sidebar/SiteMenuViewController.swift b/WordPress/Classes/ViewRelated/System/Sidebar/SiteMenuViewController.swift index 5cd065c9911b..61e7d30637a6 100644 --- a/WordPress/Classes/ViewRelated/System/Sidebar/SiteMenuViewController.swift +++ b/WordPress/Classes/ViewRelated/System/Sidebar/SiteMenuViewController.swift @@ -6,7 +6,7 @@ protocol SiteMenuViewControllerDelegate: AnyObject { /// The site menu for the split view navigation. final class SiteMenuViewController: UIViewController { - private let blog: Blog + let blog: Blog private let blogDetailsVC = SiteMenuListViewController() weak var delegate: SiteMenuViewControllerDelegate? diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 19726f5be9e5..1493a362cd8e 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1372,6 +1372,14 @@ 4A2172F928EAACFF0006F4F1 /* BlogQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2172F728EAACFF0006F4F1 /* BlogQuery.swift */; }; 4A266B8F282B05210089CF3D /* JSONObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A266B8E282B05210089CF3D /* JSONObjectTests.swift */; }; 4A266B91282B13A70089CF3D /* CoreDataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A266B90282B13A70089CF3D /* CoreDataTestCase.swift */; }; + 4A26E98E2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E98D2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift */; }; + 4A26E98F2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E98D2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift */; }; + 4A26E9912C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E9902C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift */; }; + 4A26E9922C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E9902C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift */; }; + 4A26E9942C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E9932C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift */; }; + 4A26E9952C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E9932C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift */; }; + 4A26E99A2C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E9992C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift */; }; + 4A26E99B2C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A26E9992C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift */; }; 4A2C73E12A943D9000ACE79E /* TaggedManagedObjectID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2C73E02A943D8F00ACE79E /* TaggedManagedObjectID.swift */; }; 4A2C73E22A943D9000ACE79E /* TaggedManagedObjectID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2C73E02A943D8F00ACE79E /* TaggedManagedObjectID.swift */; }; 4A2C73E42A943DEA00ACE79E /* TaggedManagedObjectIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2C73E32A943DEA00ACE79E /* TaggedManagedObjectIDTests.swift */; }; @@ -7223,6 +7231,10 @@ 4A2172F728EAACFF0006F4F1 /* BlogQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogQuery.swift; sourceTree = ""; }; 4A266B8E282B05210089CF3D /* JSONObjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONObjectTests.swift; sourceTree = ""; }; 4A266B90282B13A70089CF3D /* CoreDataTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataTestCase.swift; sourceTree = ""; }; + 4A26E98D2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SplitViewRootPresenter+Notifications.swift"; sourceTree = ""; }; + 4A26E9902C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SplitViewRootPresenter+Site.swift"; sourceTree = ""; }; + 4A26E9932C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SplitViewRootPresenter+Reader.swift"; sourceTree = ""; }; + 4A26E9992C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SplitViewRootPresenter+Welcome.swift"; sourceTree = ""; }; 4A2C73E02A943D8F00ACE79E /* TaggedManagedObjectID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaggedManagedObjectID.swift; sourceTree = ""; }; 4A2C73E32A943DEA00ACE79E /* TaggedManagedObjectIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaggedManagedObjectIDTests.swift; sourceTree = ""; }; 4A2C73EC2A9577C000ACE79E /* PostHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostHelper.m; sourceTree = ""; }; @@ -14262,6 +14274,10 @@ 803BB985295A873800B3F6D6 /* RootViewPresenter+MeNavigation.swift */, 803BB988295B80D300B3F6D6 /* RootViewPresenter+EditorNavigation.swift */, 0CCC13C82C6FA10D0010940A /* SplitViewRootPresenter.swift */, + 4A26E9902C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift */, + 4A26E98D2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift */, + 4A26E9932C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift */, + 4A26E9992C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift */, 803BB97F295957CF00B3F6D6 /* WPTabBarController+RootViewPresenter.swift */, 803BB982295957F600B3F6D6 /* MySitesCoordinator+RootViewPresenter.swift */, 80A2153C29C35197002FE8EB /* StaticScreensTabBarWrapper.swift */, @@ -21987,6 +22003,7 @@ 017008452B35C25C00C80490 /* SiteDomainsViewModel.swift in Sources */, E14A52371E39F43E00EE203E /* AppRatingsUtility.swift in Sources */, 46638DF6244904A3006E8439 /* GutenbergBlockProcessor.swift in Sources */, + 4A26E99B2C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift in Sources */, 46241C0F2540BD01002B8A12 /* SiteDesignStep.swift in Sources */, 8350E49611D2C71E00A7B073 /* Media.m in Sources */, D8B9B58F204F4EA1003C6042 /* NetworkAware.swift in Sources */, @@ -22078,6 +22095,7 @@ 08AAD69F1CBEA47D002B2418 /* MenusService.m in Sources */, 3F4370412893207C00475B6E /* JetpackOverlayView.swift in Sources */, 1788106F260E488B00A98BD8 /* UnifiedPrologueNotificationsContentView.swift in Sources */, + 4A26E98E2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift in Sources */, 3223393C24FEC18100BDD4BF /* ReaderDetailFeaturedImageView.swift in Sources */, 98B52AE121F7AF4A006FF6B4 /* StatsDataHelper.swift in Sources */, 4A1E77C92988997C006281CC /* PublicizeConnection+Creation.swift in Sources */, @@ -22740,6 +22758,7 @@ 83317ED62BC71BA8001AD2F4 /* ReaderTagCardCell.swift in Sources */, E14694071F3459E2004052C8 /* PluginListViewController.swift in Sources */, FAD3DE812AE2965A00A3B031 /* AbstractPostMenuHelper.swift in Sources */, + 4A26E9952C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift in Sources */, FF0D8146205809C8000EE505 /* PostCoordinator.swift in Sources */, B0B89DC02A1E882F003D5295 /* DomainResultView.swift in Sources */, 7EA30DB621ADA20F0092F894 /* AztecAttachmentDelegate.swift in Sources */, @@ -22949,6 +22968,7 @@ E1823E6C1E42231C00C19F53 /* UIEdgeInsets.swift in Sources */, F42A1D9729928B360059CC70 /* BlockedAuthor.swift in Sources */, 069A4AA62664448F00413FA9 /* GutenbergFeaturedImageHelper.swift in Sources */, + 4A26E9912C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift in Sources */, B54C02241F38F50100574572 /* String+RegEx.swift in Sources */, FFB1FA9E1BF0EB840090C761 /* UIImage+Exporters.swift in Sources */, F4F7B2552AFA60DA00207282 /* DomainDetailsWebViewController.swift in Sources */, @@ -24897,6 +24917,7 @@ F41D98E52B39CAAA004EC050 /* BlogDashboardDynamicCardCoordinator.swift in Sources */, FABB22552602FC2C00C8785C /* StoreContainer.swift in Sources */, FABB22572602FC2C00C8785C /* StartOverViewController.swift in Sources */, + 4A26E9922C90EEBA00C1D5DE /* SplitViewRootPresenter+Site.swift in Sources */, 934098C4271957A600B3E77E /* SiteStatsInsightsDelegate.swift in Sources */, FABB22592602FC2C00C8785C /* UINavigationController+Helpers.swift in Sources */, FABB225A2602FC2C00C8785C /* UnifiedProloguePages.swift in Sources */, @@ -25057,6 +25078,7 @@ FABB22CB2602FC2C00C8785C /* FilterBarView.swift in Sources */, 98B88453261E4E09007ED7F8 /* LikeUserTableViewCell.swift in Sources */, FABB22CC2602FC2C00C8785C /* BlogToBlogMigration_61_62.swift in Sources */, + 4A26E9942C90EEBF00C1D5DE /* SplitViewRootPresenter+Reader.swift in Sources */, 01ABF1712AD578B3004331BD /* WidgetAnalytics.swift in Sources */, FABB22CD2602FC2C00C8785C /* WebKitViewController.swift in Sources */, FABB22CE2602FC2C00C8785C /* AnnouncementsDataSource.swift in Sources */, @@ -25131,6 +25153,7 @@ FABB22FB2602FC2C00C8785C /* ReaderFollowAction.swift in Sources */, FABB22FC2602FC2C00C8785C /* SheetActions.swift in Sources */, FABB22FD2602FC2C00C8785C /* ReaderTracker.swift in Sources */, + 4A26E99A2C93AEE200C1D5DE /* SplitViewRootPresenter+Welcome.swift in Sources */, FEC1B0CF2BE41E1C00CB4A3D /* AdaptiveCollectionViewFlowLayout.swift in Sources */, FABB22FE2602FC2C00C8785C /* PlanFeature.swift in Sources */, 17C1D7DD26735631006C8970 /* EmojiRenderer.swift in Sources */, @@ -25284,6 +25307,7 @@ 2448ABAF2C5D515700DC0091 /* URL+AVKit.swift in Sources */, FABB23612602FC2C00C8785C /* ReaderTabItemsStore.swift in Sources */, FABB23622602FC2C00C8785C /* NoResultsViewController+Model.swift in Sources */, + 4A26E98F2C90EE8400C1D5DE /* SplitViewRootPresenter+Notifications.swift in Sources */, 3F435220289B2B2B00CE19ED /* JetpackBrandingCoordinator.swift in Sources */, 0822C3FA2BA1EA2100C53B50 /* SiteSwitcherView.swift in Sources */, FABB23642602FC2C00C8785C /* UIAlertController+Helpers.swift in Sources */, From 09d82f188955292b96c3baa0ce09b5f3242956b5 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 17 Sep 2024 10:25:30 +1200 Subject: [PATCH 13/13] Fix a crash which may be caused by a nil WPAccount.email (#23601) --- WordPress/Classes/Networking/WordPressClient.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/Networking/WordPressClient.swift b/WordPress/Classes/Networking/WordPressClient.swift index 456172899dd3..bca6ef678bdb 100644 --- a/WordPress/Classes/Networking/WordPressClient.swift +++ b/WordPress/Classes/Networking/WordPressClient.swift @@ -4,7 +4,7 @@ import Network struct WordPressSite { enum SiteType { - case dotCom(emailAddress: String, authToken: String) + case dotCom(authToken: String) case selfHosted(username: String, authToken: String) } @@ -19,10 +19,7 @@ struct WordPressSite { static func from(blog: Blog) throws -> WordPressSite { let url = try ParsedUrl.parse(input: blog.getUrlString()) if let account = blog.account { - return WordPressSite(baseUrl: url, type: .dotCom( - emailAddress: account.email, - authToken: account.authToken - )) + return WordPressSite(baseUrl: url, type: .dotCom(authToken: account.authToken)) } else { return WordPressSite(baseUrl: url, type: .selfHosted( username: try blog.getUsername(), @@ -52,7 +49,7 @@ actor WordPressClient { let parsedUrl = try ParsedUrl.parse(input: site.baseUrl) switch site.type { - case let .dotCom(_, authToken): + case let .dotCom(authToken): let api = WordPressAPI(urlSession: session, baseUrl: parsedUrl, authenticationStategy: .authorizationHeader(token: authToken)) return WordPressClient(api: api, rootUrl: parsedUrl) case .selfHosted(let username, let authToken):