diff --git a/WordPress/Classes/System/WindowManager.swift b/WordPress/Classes/System/WindowManager.swift index 3553b64bbe3d..e36bd6b5fc93 100644 --- a/WordPress/Classes/System/WindowManager.swift +++ b/WordPress/Classes/System/WindowManager.swift @@ -82,7 +82,7 @@ class WindowManager: NSObject { /// Shows the specified VC as the root VC for the managed window. Takes care of animating the transition whenever the existing /// root VC isn't `nil` (this is because a `nil` VC means we're showing the initial VC on a call to this method). /// - func show(_ viewController: UIViewController, completion: Completion?) { + func show(_ viewController: UIViewController, completion: Completion? = nil) { // When the App is launched, the root VC will be `nil`. // When this is the case we'll simply show the VC without any type of animation. guard window.rootViewController != nil else { diff --git a/WordPress/Jetpack/Classes/System/JetpackWindowManager.swift b/WordPress/Jetpack/Classes/System/JetpackWindowManager.swift index 80d03e98aba7..ae05026d55bb 100644 --- a/WordPress/Jetpack/Classes/System/JetpackWindowManager.swift +++ b/WordPress/Jetpack/Classes/System/JetpackWindowManager.swift @@ -1,10 +1,14 @@ +import Combine import Foundation class JetpackWindowManager: WindowManager { + /// receives migration flow updates in order to dismiss it when needed. + private var cancellable: AnyCancellable? + override func showUI(for blog: Blog?) { // If the user is logged in and has blogs sync'd to their account if AccountHelper.isLoggedIn && AccountHelper.hasBlogs { - showAppUI(for: blog) + shouldShowMigrationUI ? showMigrationUI(blog) : showAppUI(for: blog) return } @@ -18,4 +22,27 @@ class JetpackWindowManager: WindowManager { // the `logOutDefaultWordPressComAccount` method will trigger the `showSignInUI` automatically AccountHelper.logOutDefaultWordPressComAccount() } + + private func showMigrationUI(_ blog: Blog?) { + let container = MigrationDependencyContainer() + cancellable = container.migrationCoordinator.$currentStep + .receive(on: DispatchQueue.main) + .sink { [weak self] step in + guard step == .dismiss else { + return + } + self?.switchToAppUI(for: blog) + } + self.show(container.makeInitialViewController()) + } + + private func switchToAppUI(for blog: Blog?) { + cancellable = nil + showAppUI(for: blog) + } + + // TODO: Add logic in here to trigger migration UI if needed + private var shouldShowMigrationUI: Bool { + false + } } diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationFlowCoordinator.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationFlowCoordinator.swift deleted file mode 100644 index 7ebbef0cffcf..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationFlowCoordinator.swift +++ /dev/null @@ -1,85 +0,0 @@ -import UIKit - -final class MigrationFlowCoordinator { - - // MARK: - Properties - - private let account: WPAccount - - private(set) lazy var navigationController: UINavigationController = { - let rootViewController = self.viewController(for: currentStep) - let navigationController = UINavigationController(rootViewController: rootViewController) - self.configure(navigationController: navigationController) - return navigationController - }() - - // MARK: - State - - private var currentStep = MigrationStep.welcome { - didSet { - self.didUpdateCurrentStep(currentStep) - } - } - - // MARK: - Init - - init(account: WPAccount) { - self.account = account - } - - // MARK: - Step Updates - - private func didUpdateCurrentStep(_ newValue: MigrationStep) { - let viewController = self.viewController(for: newValue) - self.navigationController.setViewControllers([viewController], animated: true) - } - - // MARK: - User Interaction - - private func didTapContinue(in step: MigrationStep) { - if let nextStep = Self.nextStep(from: step) { - self.currentStep = nextStep - } else { - // Migration Flow is done - } - } - - // MARK: - View Controllers - - private func viewController(for step: MigrationStep) -> UIViewController { - switch step { - case .welcome: - let viewModel = MigrationWelcomeViewModel(account: account, primaryAction: { self.didTapContinue(in: step) }) - return MigrationWelcomeViewController(viewModel: viewModel) - default: - // This is temporary - let viewController = UIViewController() - viewController.view.backgroundColor = .systemBackground - return viewController - } - } - - private func configure(navigationController: UINavigationController) { - let navigationBar = navigationController.navigationBar - let standardAppearance = UINavigationBarAppearance() - standardAppearance.configureWithDefaultBackground() - let scrollEdgeAppearance = UINavigationBarAppearance() - scrollEdgeAppearance.configureWithTransparentBackground() - navigationBar.standardAppearance = standardAppearance - navigationBar.scrollEdgeAppearance = scrollEdgeAppearance - navigationBar.compactAppearance = standardAppearance - if #available(iOS 15.0, *) { - navigationBar.compactScrollEdgeAppearance = scrollEdgeAppearance - } - navigationBar.isTranslucent = true - } - - // MARK: - Factories - - private static func nextStep(from step: MigrationStep) -> MigrationStep? { - switch step { - case .welcome: return .notification - case .notification: return nil - } - } -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationStep.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationStep.swift deleted file mode 100644 index 29f6a4037862..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationStep.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -enum MigrationStep { - case welcome - case notification -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationStepViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationStepViewModel.swift deleted file mode 100644 index deb660d3792b..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Types/MigrationStepViewModel.swift +++ /dev/null @@ -1,37 +0,0 @@ -import UIKit - -class MigrationStepViewModel { - - // MARK: - Properties - - let image: UIImage? - let title: String - let descriptions: Descriptions - let actions: Actions - - // MARK: - Init - - init(title: String, image: UIImage?, descriptions: Descriptions, actions: Actions) { - self.title = title - self.image = image - self.descriptions = descriptions - self.actions = actions - } - - // MARK: - Types - - struct Actions { - let primary: Action - let secondary: Action - } - - struct Descriptions { - let primary: String - let secondary: String - } - - struct Action { - let title: String - let handler: () -> Void - } -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeViewModel.swift deleted file mode 100644 index ec08a1dd90a2..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeViewModel.swift +++ /dev/null @@ -1,75 +0,0 @@ -import UIKit - -final class MigrationWelcomeViewModel: MigrationStepViewModel { - - // MARK: - Properties - - let gravatarEmail: String - - let blogListDataSource: BlogListDataSource - - // MARK: - Init - - init(account: WPAccount, primaryAction: @escaping () -> Void) { - self.gravatarEmail = account.email - - self.blogListDataSource = BlogListDataSource() - self.blogListDataSource.loggedIn = true - self.blogListDataSource.account = account - - let primaryAction = Action(title: Strings.primaryButtonTitle, handler: primaryAction) - let secondaryAction = Action(title: Strings.secondaryButtonTitle, handler: {}) - let actions = Actions(primary: primaryAction, secondary: secondaryAction) - - let secondaryDescription = Strings.secondaryDescription(plural: blogListDataSource.visibleBlogsCount > 1) - let descriptions = Descriptions(primary: Strings.primaryDescription, secondary: secondaryDescription) - - super.init( - title: Strings.title, - image: UIImage(named: "wp-migration-welcome"), - descriptions: descriptions, - actions: actions - ) - } - - // MARK: - Constants - - struct Strings { - - static let title = NSLocalizedString( - "migration.welcome.title", - value: "Welcome to Jetpack!", - comment: "The title in the migration welcome screen" - ) - - static let primaryDescription = NSLocalizedString( - "migration.welcome.primaryDescription", - value: "It looks like you’re switching from the WordPress app.", - comment: "The primary description in the migration welcome screen" - ) - - static func secondaryDescription(plural: Bool) -> String { - let comment = "The secondary description in the migration welcome screen" - let siteWord = plural ? "sites" : "site" - let value = "We found your \(siteWord). Continue to transfer all your data and sign in to Jetpack automatically." - if plural { - let comment = "The plural form of the secondary description in the migration welcome screen" - return NSLocalizedString("migration.welcome.secondaryDescription.plural", value: value, comment: comment) - } else { - let comment = "The singular form of the secondary description in the migration welcome screen" - return NSLocalizedString("migration.welcome.secondaryDescription.singular", value: value, comment: comment) - } - } - - static let primaryButtonTitle = NSLocalizedString( - "Continue", - value: "Continue", - comment: "The primary button title in the migration welcome screen" - ) - - static let secondaryButtonTitle = NSLocalizedString( - "Need help?", - comment: "The secondary button title in the migration welcome screen" - ) - } -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/All done/MigrationDoneViewController.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/All done/MigrationDoneViewController.swift new file mode 100644 index 000000000000..f04210ed55c3 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/All done/MigrationDoneViewController.swift @@ -0,0 +1,26 @@ +import UIKit + +class MigrationDoneViewController: UIViewController { + + private let viewModel: MigrationDoneViewModel + + init(viewModel: MigrationDoneViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + // TODO: replace this blank center view with the actual content + let centerView = UIView() + centerView.translatesAutoresizingMaskIntoConstraints = false + centerView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .vertical) + + view = MigrationStepView(headerView: MigrationHeaderView(configuration: viewModel.configuration.headerConfiguration), + actionsView: MigrationActionsView(configuration: viewModel.configuration.actionsConfiguration), + centerView: centerView) + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/All done/MigrationDoneViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/All done/MigrationDoneViewModel.swift new file mode 100644 index 000000000000..1ed1986d7d9e --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/All done/MigrationDoneViewModel.swift @@ -0,0 +1,15 @@ +class MigrationDoneViewModel { + + let configuration: MigrationStepConfiguration + + init(coordinator: MigrationFlowCoordinator) { + + let headerConfiguration = MigrationHeaderConfiguration(step: .done) + + let actionsConfiguration = MigrationActionsViewConfiguration(step: .done, primaryHandler: { [weak coordinator] in + coordinator?.transitionToNextStep() + }) + configuration = MigrationStepConfiguration(headerConfiguration: headerConfiguration, + actionsConfiguration: actionsConfiguration) + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/MigrationDependencyContainer.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/MigrationDependencyContainer.swift new file mode 100644 index 000000000000..f37424fb1a17 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/MigrationDependencyContainer.swift @@ -0,0 +1,70 @@ +struct MigrationDependencyContainer { + + let migrationCoordinator = MigrationFlowCoordinator() + + func makeInitialViewController() -> UIViewController { + MigrationNavigationController(coordinator: migrationCoordinator, + factory: MigrationViewControllerFactory(coordinator: migrationCoordinator)) + } +} + +struct MigrationViewControllerFactory { + + let coordinator: MigrationFlowCoordinator + + init(coordinator: MigrationFlowCoordinator) { + self.coordinator = coordinator + } + + func viewController(for step: MigrationStep) -> UIViewController? { + switch step { + case .welcome: + return makeWelcomeViewController() + case .notifications: + return makeNotificationsViewController() + case .done: + return makeDoneViewController() + case .dismiss: + return nil + } + } + + func initialViewController() -> UIViewController? { + viewController(for: coordinator.currentStep) + } + + private func makeAccount() -> WPAccount? { + + let context = ContextManager.shared.mainContext + do { + return try WPAccount.lookupDefaultWordPressComAccount(in: context) + } catch { + DDLogError("Account lookup failed with error: \(error)") + return nil + } + } + + private func makeWelcomeViewModel() -> MigrationWelcomeViewModel { + MigrationWelcomeViewModel(account: makeAccount(), coordinator: coordinator) + } + + private func makeWelcomeViewController() -> UIViewController { + MigrationWelcomeViewController(viewModel: makeWelcomeViewModel()) + } + + private func makeNotificationsViewModel() -> MigrationNotificationsViewModel { + MigrationNotificationsViewModel(coordinator: coordinator) + } + + private func makeNotificationsViewController() -> UIViewController { + MigrationNotificationsViewController(viewModel: makeNotificationsViewModel()) + } + + private func makeDoneViewModel() -> MigrationDoneViewModel { + MigrationDoneViewModel(coordinator: coordinator) + } + + private func makeDoneViewController() -> UIViewController { + MigrationDoneViewController(viewModel: makeDoneViewModel()) + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationFlowCoordinator.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationFlowCoordinator.swift new file mode 100644 index 000000000000..6bf3a0d8e589 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationFlowCoordinator.swift @@ -0,0 +1,38 @@ +import Combine +import UserNotifications + +/// Coordinator for the migration to jetpack flow +final class MigrationFlowCoordinator: ObservableObject { + // beware that changes won't be published on the main thread, + // so always make sure to return to the main thread for UI updates + // related to this property. + @Published private(set) var currentStep = MigrationStep.welcome + + func transitionToNextStep() { + Task { [weak self] in + if let nextStep = await Self.nextStep(from: currentStep) { + self?.currentStep = nextStep + } + } + } + + private static func shouldSkipNotificationsScreen() async -> Bool { + let settings = await UNUserNotificationCenter.current().notificationSettings() + let authStatus = settings.authorizationStatus + return authStatus == .authorized || authStatus == .denied + } + + private static func nextStep(from step: MigrationStep) async -> MigrationStep? { + switch step { + case .welcome: + let shouldSkipNotifications = await shouldSkipNotificationsScreen() + return shouldSkipNotifications ? .done : .notifications + case .notifications: + return .done + case .done: + return .dismiss + case .dismiss: + return nil + } + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationNavigationController.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationNavigationController.swift new file mode 100644 index 000000000000..ee334108bfb7 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationNavigationController.swift @@ -0,0 +1,73 @@ +import Combine +import UIKit + +class MigrationNavigationController: UINavigationController { + /// Navigation coordinator + private let coordinator: MigrationFlowCoordinator + /// The view controller factory used to push view controllers on the stack + private let factory: MigrationViewControllerFactory + /// Receives state changes to set the navigation stack accordingly + private var cancellable: AnyCancellable? + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + if WPDeviceIdentification.isiPhone() { + return .portrait + } else { + return .allButUpsideDown + } + } + + override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + .portrait + } + + init(coordinator: MigrationFlowCoordinator, factory: MigrationViewControllerFactory) { + self.coordinator = coordinator + self.factory = factory + if let initialViewController = factory.initialViewController() { + super.init(rootViewController: initialViewController) + } else { + super.init(nibName: nil, bundle: nil) + } + configure() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configure() { + let navigationBar = self.navigationBar + let standardAppearance = UINavigationBarAppearance() + standardAppearance.configureWithDefaultBackground() + let scrollEdgeAppearance = UINavigationBarAppearance() + scrollEdgeAppearance.configureWithTransparentBackground() + navigationBar.standardAppearance = standardAppearance + navigationBar.scrollEdgeAppearance = scrollEdgeAppearance + navigationBar.compactAppearance = standardAppearance + if #available(iOS 15.0, *) { + navigationBar.compactScrollEdgeAppearance = scrollEdgeAppearance + } + navigationBar.isTranslucent = true + listenForStateChanges() + } + + private func listenForStateChanges() { + cancellable = coordinator.$currentStep + .dropFirst() + .receive(on: DispatchQueue.main) + .sink { [weak self] step in + self?.updateStack(for: step) + } + } + + private func updateStack(for step: MigrationStep) { + // sets the stack for the next navigation step, if there's one + guard let viewController = factory.viewController(for: step) else { + return + } + // if we want to support backwards navigation, we need to set + // also the previous steps in the stack + setViewControllers([viewController], animated: true) + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationStep.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationStep.swift new file mode 100644 index 000000000000..3edc828bb24b --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationStep.swift @@ -0,0 +1,8 @@ +import Foundation + +enum MigrationStep: String { + case welcome + case notifications + case done + case dismiss +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationActionsConfiguration.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationActionsConfiguration.swift new file mode 100644 index 000000000000..2a70d2144ee7 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationActionsConfiguration.swift @@ -0,0 +1,58 @@ +struct MigrationActionsViewConfiguration { + let step: MigrationStep + let primaryTitle: String? + let secondaryTitle: String? + let primaryHandler: (() -> Void)? + let secondaryHandler: (() -> Void)? + + init(step: MigrationStep, primaryHandler: (() -> Void)? = nil, secondaryHandler: (() -> Void)? = nil) { + self.step = step + self.primaryHandler = primaryHandler + self.secondaryHandler = secondaryHandler + self.primaryTitle = Appearance.primaryTitle(for: step) + self.secondaryTitle = Appearance.secondaryTitle(for: step) + } +} + +private extension MigrationActionsViewConfiguration { + + enum Appearance { + + static func primaryTitle(for step: MigrationStep) -> String? { + switch step { + case .welcome, .notifications: + return Appearance.defaultPrimaryTitle + case .done: + return Appearance.donePrimaryTitle + case.dismiss: + return nil + } + } + + static func secondaryTitle(for step: MigrationStep) -> String? { + switch step { + case .welcome: + return Appearance.welcomeSecondaryTitle + case .notifications: + return Appearance.notificationsSecondaryTitle + default: + return nil + } + } + + static let defaultPrimaryTitle = NSLocalizedString("Continue", + value: "Continue", + comment: "The primary button title in the migration welcome and notifications screens.") + + static let donePrimaryTitle = NSLocalizedString("migration.done.actions.primary.title", + value: "Finish", + comment: "Primary button title in the migration done screen.") + + static let welcomeSecondaryTitle = NSLocalizedString("Need help?", + comment: "The secondary button title in the migration welcome screen") + + static let notificationsSecondaryTitle = NSLocalizedString("migration.notifications.actions.secondary.title", + value: "Decide later", + comment: "Secondary button title in the migration notifications screen.") + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationHeaderConfiguration.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationHeaderConfiguration.swift new file mode 100644 index 000000000000..e0949caa9b16 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationHeaderConfiguration.swift @@ -0,0 +1,115 @@ +struct MigrationHeaderConfiguration { + + let step: MigrationStep + let title: String? + let image: UIImage? + let primaryDescription: String? + let secondaryDescription: String? + + init(step: MigrationStep, multiSite: Bool = false) { + self.step = step + + image = Appearance.image(for: step) + title = Appearance.title(for: step) + primaryDescription = Appearance.primaryDescription(for: step) + secondaryDescription = Appearance.secondaryDescription(for: step, multiSite: multiSite) + } +} + +private extension MigrationHeaderConfiguration { + + enum Appearance { + // TODO: Set the right images for notification and done states + static func image(for step: MigrationStep) -> UIImage? { + switch step { + case .welcome: + return UIImage(named: "wp-migration-welcome") + case .notifications: + return UIImage(named: "wp-migration-welcome") + case .done: + return UIImage(named: "wp-migration-welcome") + case .dismiss: + return nil + } + } + + static func title(for step: MigrationStep) -> String? { + switch step { + case .welcome: + return welcomeTitle + case .notifications: + return notificationsTitle + case .done: + return doneTitle + case .dismiss: + return nil + } + } + + static func primaryDescription(for step: MigrationStep) -> String? { + switch step { + case .welcome: + return welcomePrimaryDescription + case .notifications: + return notificationsPrimaryDescription + case .done: + return donePrimaryDescription + case .dismiss: + return nil + } + } + + static func secondaryDescription(for step: MigrationStep, multiSite: Bool = false) -> String? { + switch step { + case .welcome: + return welcomeSecondaryDescription(plural: multiSite) + case .notifications: + return notificationsSecondaryDescription + case .done: + return nil + case .dismiss: + return nil + } + } + + static let welcomeTitle = NSLocalizedString("migration.welcome.title", + value: "Welcome to Jetpack!", + comment: "The title in the migration welcome screen") + + static let notificationsTitle = NSLocalizedString("migration.notifications.title", + value: "Allow notifications to keep up with your site", + comment: "Title of the migration notifications screen.") + + static let doneTitle = NSLocalizedString("migration.done.title", + value: "Thanks for switching to Jetpack!", + comment: "Title of the migration done screen.") + + static let welcomePrimaryDescription = NSLocalizedString("migration.welcome.primaryDescription", + value: "It looks like you’re switching from the WordPress app.", + comment: "The primary description in the migration welcome screen") + + static let notificationsPrimaryDescription = NSLocalizedString("migration.notifications.primaryDescription", + value: "You’ll get all the same notifications but now they’ll come from the Jetpack app.", + comment: "Primary description in the migration notifications screen.") + + static let donePrimaryDescription = NSLocalizedString("migration.done.primaryDescription", + value: "We’ve transferred all your data and settings. Everything is right where you left it.", + comment: "Primary description in the migration done screen.") + + static let notificationsSecondaryDescription = NSLocalizedString("migration.notifications.secondaryDescription", + value: "We’ve disabled notifications for the WordPress app.", + comment: "Secondary description in the migration notifications screen") + + static func welcomeSecondaryDescription(plural: Bool) -> String { + let siteWord = plural ? "sites" : "site" + let value = "We found your \(siteWord). Continue to transfer all your data and sign in to Jetpack automatically." + if plural { + let comment = "The plural form of the secondary description in the migration welcome screen" + return NSLocalizedString("migration.welcome.secondaryDescription.plural", value: value, comment: comment) + } else { + let comment = "The singular form of the secondary description in the migration welcome screen" + return NSLocalizedString("migration.welcome.secondaryDescription.singular", value: value, comment: comment) + } + } + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationStepConfiguration.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationStepConfiguration.swift new file mode 100644 index 000000000000..406eb1a22185 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/Configuration/MigrationStepConfiguration.swift @@ -0,0 +1,4 @@ +struct MigrationStepConfiguration { + let headerConfiguration: MigrationHeaderConfiguration + let actionsConfiguration: MigrationActionsViewConfiguration +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Views/MigrationActionsView.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationActionsView.swift similarity index 82% rename from WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Views/MigrationActionsView.swift rename to WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationActionsView.swift index c37cb298218d..d426ff5450b2 100644 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Views/MigrationActionsView.swift +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationActionsView.swift @@ -5,6 +5,8 @@ final class MigrationActionsView: UIView { // MARK: - Views + private let configuration: MigrationActionsViewConfiguration + let primaryButton: UIButton = MigrationActionsView.primaryButton() let secondaryButton: UIButton = MigrationActionsView.secondaryButton() @@ -34,16 +36,12 @@ final class MigrationActionsView: UIView { // MARK: - Init - init() { + init(configuration: MigrationActionsViewConfiguration) { + self.configuration = configuration super.init(frame: .zero) self.setup() } - override init(frame: CGRect) { - super.init(frame: frame) - self.setup() - } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -68,6 +66,7 @@ final class MigrationActionsView: UIView { // Layout buttons self.stackView.addArrangedSubviews([primaryButton, secondaryButton]) self.addSubview(stackView) + configureButtons() NSLayoutConstraint.activate([ primaryButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor), primaryButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor), @@ -78,6 +77,21 @@ final class MigrationActionsView: UIView { ]) } + private func configureButtons() { + primaryButton.setTitle(configuration.primaryTitle, for: .normal) + primaryButton.addTarget(self, action: #selector(didTapPrimaryButton), for: .touchUpInside) + secondaryButton.setTitle(configuration.secondaryTitle, for: .normal) + secondaryButton.addTarget(self, action: #selector(didTapSecondaryButton), for: .touchUpInside) + } + + @objc private func didTapPrimaryButton() { + configuration.primaryHandler?() + } + + @objc private func didTapSecondaryButton() { + configuration.secondaryHandler?() + } + // MARK: - Button Factory private static func primaryButton() -> UIButton { diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Views/MigrationHeaderView.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationHeaderView.swift similarity index 71% rename from WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Views/MigrationHeaderView.swift rename to WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationHeaderView.swift index de3718f4df1f..9df33dcc944c 100644 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Views/MigrationHeaderView.swift +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationHeaderView.swift @@ -6,6 +6,8 @@ final class MigrationHeaderView: UIView { let imageView = UIImageView() + private let configuration: MigrationHeaderConfiguration + let titleLabel: UILabel = { let label = UILabel() label.font = Constants.titleFont @@ -32,31 +34,30 @@ final class MigrationHeaderView: UIView { // MARK: - Init - init() { + init(configuration: MigrationHeaderConfiguration) { + self.configuration = configuration super.init(frame: .zero) self.setup() } - override init(frame: CGRect) { - super.init(frame: frame) - self.setup() - } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setup() { // Set subviews - let labelsStackView = Self.verticalStackView(arrangedSubviews: [titleLabel, primaryDescriptionLabel, secondaryDescriptionLabel]) + let labelsStackView = verticalStackView(arrangedSubviews: [titleLabel, primaryDescriptionLabel, secondaryDescriptionLabel]) + labelsStackView.translatesAutoresizingMaskIntoConstraints = false labelsStackView.spacing = Constants.labelsSpacing - let mainStackView = Self.verticalStackView(arrangedSubviews: [imageView, labelsStackView]) + let mainStackView = verticalStackView(arrangedSubviews: [imageView, labelsStackView]) mainStackView.spacing = Constants.spacing + mainStackView.alignment = .leading mainStackView.translatesAutoresizingMaskIntoConstraints = false self.addSubview(mainStackView) - + configureAppearance() // Set constraints NSLayoutConstraint.activate([ + labelsStackView.widthAnchor.constraint(equalTo: mainStackView.widthAnchor), mainStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), mainStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), mainStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), @@ -66,14 +67,19 @@ final class MigrationHeaderView: UIView { // MARK: - Views Factory - private static func verticalStackView(arrangedSubviews: [UIView]) -> UIStackView { + private func verticalStackView(arrangedSubviews: [UIView]) -> UIStackView { let stackView = UIStackView(arrangedSubviews: arrangedSubviews) stackView.axis = .vertical - stackView.alignment = .leading - stackView.distribution = .fill return stackView } + private func configureAppearance() { + imageView.image = configuration.image + titleLabel.text = configuration.title + primaryDescriptionLabel.text = configuration.primaryDescription + secondaryDescriptionLabel.text = configuration.secondaryDescription + } + // MARK: - Types private struct Constants { diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationStepView.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationStepView.swift new file mode 100644 index 000000000000..cca338a1ffc7 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Views/MigrationStepView.swift @@ -0,0 +1,35 @@ +import UIKit + +class MigrationStepView: UIView { + + private let headerView: MigrationHeaderView + private let centerView: UIView + private let actionsView: MigrationActionsView + + private lazy var mainStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [headerView, centerView, actionsView]) + stackView.axis = .vertical + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + init(headerView: MigrationHeaderView, + actionsView: MigrationActionsView, + centerView: UIView) { + + self.headerView = headerView + headerView.directionalLayoutMargins = Self.headerViewMargins + self.centerView = centerView + self.actionsView = actionsView + super.init(frame: .zero) + backgroundColor = .systemBackground + addSubview(mainStackView) + pinSubviewToAllEdges(mainStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + static let headerViewMargins = NSDirectionalEdgeInsets(top: 0, leading: 30, bottom: 30, trailing: 30) +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Notifications Permission/MigrationNotificationsViewController.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Notifications Permission/MigrationNotificationsViewController.swift new file mode 100644 index 000000000000..ae75f70bfa52 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Notifications Permission/MigrationNotificationsViewController.swift @@ -0,0 +1,26 @@ +import UIKit + +class MigrationNotificationsViewController: UIViewController { + + private let viewModel: MigrationNotificationsViewModel + + init(viewModel: MigrationNotificationsViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + // TODO: replace this blank center view with the actual content + let centerView = UIView() + centerView.translatesAutoresizingMaskIntoConstraints = false + centerView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .vertical) + + view = MigrationStepView(headerView: MigrationHeaderView(configuration: viewModel.configuration.headerConfiguration), + actionsView: MigrationActionsView(configuration: viewModel.configuration.actionsConfiguration), + centerView: centerView) + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Notifications Permission/MigrationNotificationsViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Notifications Permission/MigrationNotificationsViewModel.swift new file mode 100644 index 000000000000..de8d5e0491e7 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Notifications Permission/MigrationNotificationsViewModel.swift @@ -0,0 +1,23 @@ +import Foundation + +class MigrationNotificationsViewModel { + + let configuration: MigrationStepConfiguration + + init(coordinator: MigrationFlowCoordinator) { + + let headerConfiguration = MigrationHeaderConfiguration(step: .notifications) + + let actionsConfiguration = MigrationActionsViewConfiguration(step: .notifications, + primaryHandler: { + InteractiveNotificationsManager.shared.requestAuthorization { [weak coordinator] authorized in + coordinator?.transitionToNextStep() + } + }, + secondaryHandler: { [weak coordinator] in + coordinator?.transitionToNextStep() + }) + + configuration = MigrationStepConfiguration(headerConfiguration: headerConfiguration, actionsConfiguration: actionsConfiguration) + } +} diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeBlogTableViewCell.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeBlogTableViewCell.swift similarity index 100% rename from WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeBlogTableViewCell.swift rename to WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeBlogTableViewCell.swift diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeViewController.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewController.swift similarity index 79% rename from WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeViewController.swift rename to WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewController.swift index fa06ea1a8e91..a3d20166d9b6 100644 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress Migration/Welcome/MigrationWelcomeViewController.swift +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewController.swift @@ -10,14 +10,18 @@ final class MigrationWelcomeViewController: UIViewController { private let tableView = UITableView(frame: .zero, style: .plain) - private let headerView: MigrationHeaderView = { - let view = MigrationHeaderView() + private lazy var headerView: MigrationHeaderView = { + let view = MigrationHeaderView(configuration: viewModel.configuration.headerConfiguration) view.translatesAutoresizingMaskIntoConstraints = true view.directionalLayoutMargins = Constants.tableHeaderViewMargins return view }() - private let bottomSheet = MigrationActionsView() + private lazy var bottomSheet: MigrationActionsView = { + let actionsView = MigrationActionsView(configuration: viewModel.configuration.actionsConfiguration) + actionsView.translatesAutoresizingMaskIntoConstraints = false + return actionsView + }() // MARK: - Lifecycle @@ -39,7 +43,6 @@ final class MigrationWelcomeViewController: UIViewController { self.setupTableView() self.setupBottomSheet() self.setupNavigationBar() - self.updateTableHeaderView(with: viewModel) } override func viewDidLayoutSubviews() { @@ -66,8 +69,6 @@ final class MigrationWelcomeViewController: UIViewController { } private func setupBottomSheet() { - self.bottomSheet.translatesAutoresizingMaskIntoConstraints = false - self.bottomSheet.primaryButton.addTarget(self, action: #selector(didTapContinue), for: .touchUpInside) self.view.addSubview(bottomSheet) NSLayoutConstraint.activate([ bottomSheet.leadingAnchor.constraint(equalTo: view.leadingAnchor), @@ -76,15 +77,6 @@ final class MigrationWelcomeViewController: UIViewController { ]) } - private func updateTableHeaderView(with viewModel: MigrationWelcomeViewModel) { - self.headerView.imageView.image = viewModel.image - self.headerView.titleLabel.text = viewModel.title - self.headerView.primaryDescriptionLabel.text = viewModel.descriptions.primary - self.headerView.secondaryDescriptionLabel.text = viewModel.descriptions.secondary - self.bottomSheet.primaryButton.setTitle(viewModel.actions.primary.title, for: .normal) - self.bottomSheet.secondaryButton.setTitle(viewModel.actions.secondary.title, for: .normal) - } - /// Increases the tableView's bottom inset so it doesn't cover the bottom actions sheet. private func updateTableViewContentInset() { let bottomInset = -view.safeAreaInsets.bottom + bottomSheet.bounds.height @@ -92,12 +84,6 @@ final class MigrationWelcomeViewController: UIViewController { self.tableView.verticalScrollIndicatorInsets.bottom = bottomInset } - // MARK: - User Interaction - - @objc private func didTapContinue() { - self.viewModel.actions.primary.handler() - } - // MARK: - Constants private struct Constants { diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewModel.swift new file mode 100644 index 000000000000..1c267c0f62c6 --- /dev/null +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewModel.swift @@ -0,0 +1,35 @@ +import UIKit + +final class MigrationWelcomeViewModel { + + // MARK: - Properties + + var gravatarEmail: String? + + let blogListDataSource: BlogListDataSource + + let configuration: MigrationStepConfiguration + + // MARK: - Init + + init(account: WPAccount?, coordinator: MigrationFlowCoordinator) { + if let account { + self.gravatarEmail = account.email + } + + self.blogListDataSource = BlogListDataSource() + self.blogListDataSource.loggedIn = true + self.blogListDataSource.account = account + + let headerConfiguration = MigrationHeaderConfiguration(step: .welcome, + multiSite: blogListDataSource.visibleBlogsCount > 1) + + let actionsConfiguration = MigrationActionsViewConfiguration(step: .welcome, + primaryHandler: { [weak coordinator] in + coordinator?.transitionToNextStep() + }, + secondaryHandler: { }) + + configuration = MigrationStepConfiguration(headerConfiguration: headerConfiguration, actionsConfiguration: actionsConfiguration) + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index dbe195a34579..dee1018fb096 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -833,6 +833,16 @@ 3FEC241525D73E8B007AFE63 /* ConfettiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC241425D73E8B007AFE63 /* ConfettiView.swift */; }; 3FF1A853242D5FCB00373F5D /* WPTabBarController+ReaderTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF1A852242D5FCB00373F5D /* WPTabBarController+ReaderTabNavigation.swift */; }; 3FFA5ED22876152E00830E28 /* JetpackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFA5ED12876152E00830E28 /* JetpackButton.swift */; }; + 3FFDEF7829177D7500B625CE /* MigrationNotificationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF7729177D7500B625CE /* MigrationNotificationsViewModel.swift */; }; + 3FFDEF7A29177D8C00B625CE /* MigrationNotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF7929177D8C00B625CE /* MigrationNotificationsViewController.swift */; }; + 3FFDEF7F29177FB100B625CE /* MigrationStepConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF7E29177FB100B625CE /* MigrationStepConfiguration.swift */; }; + 3FFDEF812917882800B625CE /* MigrationNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF802917882800B625CE /* MigrationNavigationController.swift */; }; + 3FFDEF8329179CD000B625CE /* MigrationDependencyContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF8229179CD000B625CE /* MigrationDependencyContainer.swift */; }; + 3FFDEF852918215700B625CE /* MigrationStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF842918215700B625CE /* MigrationStepView.swift */; }; + 3FFDEF882918596B00B625CE /* MigrationDoneViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF872918596B00B625CE /* MigrationDoneViewModel.swift */; }; + 3FFDEF8A2918597700B625CE /* MigrationDoneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF892918597700B625CE /* MigrationDoneViewController.swift */; }; + 3FFDEF8F29187F1200B625CE /* MigrationHeaderConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF8E29187F1200B625CE /* MigrationHeaderConfiguration.swift */; }; + 3FFDEF9129187F2100B625CE /* MigrationActionsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFDEF9029187F2100B625CE /* MigrationActionsConfiguration.swift */; }; 3FFE3C0828FE00D10021BB96 /* StatsSegmentedControlDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFE3C0728FE00D10021BB96 /* StatsSegmentedControlDataTests.swift */; }; 400199AB222590E100EB0906 /* AllTimeStatsRecordValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400199AA222590E100EB0906 /* AllTimeStatsRecordValueTests.swift */; }; 400199AD22259FF300EB0906 /* AnnualAndMostPopularTimeStatsRecordValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400199AC22259FF300EB0906 /* AnnualAndMostPopularTimeStatsRecordValueTests.swift */; }; @@ -3216,7 +3226,6 @@ F41BDD73290BBDCA00B7F2B0 /* MigrationActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41BDD72290BBDCA00B7F2B0 /* MigrationActionsView.swift */; }; F41BDD792910AFCA00B7F2B0 /* MigrationFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41BDD782910AFCA00B7F2B0 /* MigrationFlowCoordinator.swift */; }; F41BDD7B29114E2400B7F2B0 /* MigrationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41BDD7A29114E2400B7F2B0 /* MigrationStep.swift */; }; - F41BDD7D29114E9700B7F2B0 /* MigrationStepViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41BDD7C29114E9700B7F2B0 /* MigrationStepViewModel.swift */; }; F41E32FE287B47A500F89082 /* SuggestionsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41E32FD287B47A500F89082 /* SuggestionsListViewModel.swift */; }; F41E32FF287B47A500F89082 /* SuggestionsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41E32FD287B47A500F89082 /* SuggestionsListViewModel.swift */; }; F41E3301287B5FE500F89082 /* SuggestionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41E3300287B5FE500F89082 /* SuggestionViewModel.swift */; }; @@ -6084,6 +6093,16 @@ 3FEC241425D73E8B007AFE63 /* ConfettiView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfettiView.swift; sourceTree = ""; }; 3FF1A852242D5FCB00373F5D /* WPTabBarController+ReaderTabNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WPTabBarController+ReaderTabNavigation.swift"; sourceTree = ""; }; 3FFA5ED12876152E00830E28 /* JetpackButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackButton.swift; sourceTree = ""; }; + 3FFDEF7729177D7500B625CE /* MigrationNotificationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationNotificationsViewModel.swift; sourceTree = ""; }; + 3FFDEF7929177D8C00B625CE /* MigrationNotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationNotificationsViewController.swift; sourceTree = ""; }; + 3FFDEF7E29177FB100B625CE /* MigrationStepConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationStepConfiguration.swift; sourceTree = ""; }; + 3FFDEF802917882800B625CE /* MigrationNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationNavigationController.swift; sourceTree = ""; }; + 3FFDEF8229179CD000B625CE /* MigrationDependencyContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationDependencyContainer.swift; sourceTree = ""; }; + 3FFDEF842918215700B625CE /* MigrationStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationStepView.swift; sourceTree = ""; }; + 3FFDEF872918596B00B625CE /* MigrationDoneViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationDoneViewModel.swift; sourceTree = ""; }; + 3FFDEF892918597700B625CE /* MigrationDoneViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationDoneViewController.swift; sourceTree = ""; }; + 3FFDEF8E29187F1200B625CE /* MigrationHeaderConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationHeaderConfiguration.swift; sourceTree = ""; }; + 3FFDEF9029187F2100B625CE /* MigrationActionsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationActionsConfiguration.swift; sourceTree = ""; }; 3FFE3C0728FE00D10021BB96 /* StatsSegmentedControlDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSegmentedControlDataTests.swift; sourceTree = ""; }; 400199AA222590E100EB0906 /* AllTimeStatsRecordValueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllTimeStatsRecordValueTests.swift; sourceTree = ""; }; 400199AC22259FF300EB0906 /* AnnualAndMostPopularTimeStatsRecordValueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnualAndMostPopularTimeStatsRecordValueTests.swift; sourceTree = ""; }; @@ -8318,7 +8337,6 @@ F41BDD72290BBDCA00B7F2B0 /* MigrationActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationActionsView.swift; sourceTree = ""; }; F41BDD782910AFCA00B7F2B0 /* MigrationFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationFlowCoordinator.swift; sourceTree = ""; }; F41BDD7A29114E2400B7F2B0 /* MigrationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationStep.swift; sourceTree = ""; }; - F41BDD7C29114E9700B7F2B0 /* MigrationStepViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationStepViewModel.swift; sourceTree = ""; }; F41E32FD287B47A500F89082 /* SuggestionsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionsListViewModel.swift; sourceTree = ""; }; F41E3300287B5FE500F89082 /* SuggestionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionViewModel.swift; sourceTree = ""; }; F41E4E8B28F18B7B001880C6 /* AppIconListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconListViewModelTests.swift; sourceTree = ""; }; @@ -10149,6 +10167,15 @@ path = Stats; sourceTree = ""; }; + 3F00739D2915BAA100A37FD1 /* Notifications Permission */ = { + isa = PBXGroup; + children = ( + 3FFDEF7929177D8C00B625CE /* MigrationNotificationsViewController.swift */, + 3FFDEF7729177D7500B625CE /* MigrationNotificationsViewModel.swift */, + ); + path = "Notifications Permission"; + sourceTree = ""; + }; 3F09CCA62428FE8600D00A8C /* Tab Navigation */ = { isa = PBXGroup; children = ( @@ -10861,6 +10888,35 @@ path = Widgets; sourceTree = ""; }; + 3FFDEF7D29177F4200B625CE /* Common */ = { + isa = PBXGroup; + children = ( + 3FFDEF8229179CD000B625CE /* MigrationDependencyContainer.swift */, + F41BDD772910AFB900B7F2B0 /* Navigation */, + F4F9D5EE29096D0400502576 /* Views */, + ); + path = Common; + sourceTree = ""; + }; + 3FFDEF862918595200B625CE /* All done */ = { + isa = PBXGroup; + children = ( + 3FFDEF892918597700B625CE /* MigrationDoneViewController.swift */, + 3FFDEF872918596B00B625CE /* MigrationDoneViewModel.swift */, + ); + path = "All done"; + sourceTree = ""; + }; + 3FFDEF8D2918625600B625CE /* Configuration */ = { + isa = PBXGroup; + children = ( + 3FFDEF9029187F2100B625CE /* MigrationActionsConfiguration.swift */, + 3FFDEF8E29187F1200B625CE /* MigrationHeaderConfiguration.swift */, + 3FFDEF7E29177FB100B625CE /* MigrationStepConfiguration.swift */, + ); + path = Configuration; + sourceTree = ""; + }; 400A2C8D2217AAA1000A8A59 /* Time-based data */ = { isa = PBXGroup; children = ( @@ -14895,7 +14951,7 @@ C7F7AC72261CF1C900CE547F /* ViewRelated */ = { isa = PBXGroup; children = ( - F4F9D5E82909615D00502576 /* WordPress Migration */, + F4F9D5E82909615D00502576 /* WordPress-to-Jetpack Migration */, C72A4FB12641837A009CA633 /* Login Error */, ); path = ViewRelated; @@ -15974,14 +16030,14 @@ path = WordPressIntents; sourceTree = ""; }; - F41BDD772910AFB900B7F2B0 /* Types */ = { + F41BDD772910AFB900B7F2B0 /* Navigation */ = { isa = PBXGroup; children = ( F41BDD782910AFCA00B7F2B0 /* MigrationFlowCoordinator.swift */, + 3FFDEF802917882800B625CE /* MigrationNavigationController.swift */, F41BDD7A29114E2400B7F2B0 /* MigrationStep.swift */, - F41BDD7C29114E9700B7F2B0 /* MigrationStepViewModel.swift */, ); - path = Types; + path = Navigation; sourceTree = ""; }; F41E4E8F28F1949D001880C6 /* App Icons */ = { @@ -16329,14 +16385,15 @@ path = "white-on-pink"; sourceTree = ""; }; - F4F9D5E82909615D00502576 /* WordPress Migration */ = { + F4F9D5E82909615D00502576 /* WordPress-to-Jetpack Migration */ = { isa = PBXGroup; children = ( - F41BDD772910AFB900B7F2B0 /* Types */, - F4F9D5EE29096D0400502576 /* Views */, + 3FFDEF7D29177F4200B625CE /* Common */, F4F9D5ED29096CFC00502576 /* Welcome */, + 3F00739D2915BAA100A37FD1 /* Notifications Permission */, + 3FFDEF862918595200B625CE /* All done */, ); - path = "WordPress Migration"; + path = "WordPress-to-Jetpack Migration"; sourceTree = ""; }; F4F9D5ED29096CFC00502576 /* Welcome */ = { @@ -16352,8 +16409,10 @@ F4F9D5EE29096D0400502576 /* Views */ = { isa = PBXGroup; children = ( - F4F9D5EB29096CF500502576 /* MigrationHeaderView.swift */, F41BDD72290BBDCA00B7F2B0 /* MigrationActionsView.swift */, + F4F9D5EB29096CF500502576 /* MigrationHeaderView.swift */, + 3FFDEF842918215700B625CE /* MigrationStepView.swift */, + 3FFDEF8D2918625600B625CE /* Configuration */, ); path = Views; sourceTree = ""; @@ -22379,6 +22438,7 @@ F1585419267D3B5700A2E966 /* BloggingRemindersScheduler.swift in Sources */, FABB21812602FC2C00C8785C /* DiffContentValue.swift in Sources */, FABB21822602FC2C00C8785C /* FormattableTextContent.swift in Sources */, + 3FFDEF7A29177D8C00B625CE /* MigrationNotificationsViewController.swift in Sources */, FABB21832602FC2C00C8785C /* WordPress-61-62.xcmappingmodel in Sources */, FABB21842602FC2C00C8785C /* WhatIsNewView.swift in Sources */, FABB21852602FC2C00C8785C /* GravatarService.swift in Sources */, @@ -22545,6 +22605,7 @@ 3F946C5A2684DD8E00B946F6 /* BloggingRemindersActions.swift in Sources */, C387B7A22638D66F00BDEF86 /* PostAuthorSelectorViewController.swift in Sources */, 171096CC270F01EA001BCDD6 /* DomainSuggestionsTableViewController.swift in Sources */, + 3FFDEF7F29177FB100B625CE /* MigrationStepConfiguration.swift in Sources */, FABB22052602FC2C00C8785C /* SubjectContentGroup.swift in Sources */, FABB22062602FC2C00C8785C /* PluginListRow.swift in Sources */, FABB22072602FC2C00C8785C /* BlogSettings+DateAndTimeFormat.swift in Sources */, @@ -22875,6 +22936,7 @@ FABB230B2602FC2C00C8785C /* GutenbergViewController.swift in Sources */, FABB230C2602FC2C00C8785C /* EditPageViewController.swift in Sources */, FABB230D2602FC2C00C8785C /* SiteCreator.swift in Sources */, + 3FFDEF8329179CD000B625CE /* MigrationDependencyContainer.swift in Sources */, 3F5B9B45288B0761001D17E9 /* DashboardBadgeCell.swift in Sources */, FABB230E2602FC2C00C8785C /* CalendarMonthView.swift in Sources */, FABB230F2602FC2C00C8785C /* RestoreStatusFailedView.swift in Sources */, @@ -22922,6 +22984,7 @@ FABB23342602FC2C00C8785C /* BlogDetailsSectionFooterView.swift in Sources */, 3F435221289B2B5100CE19ED /* JetpackOverlayViewController.swift in Sources */, FABB23362602FC2C00C8785C /* TenorGIFCollection.swift in Sources */, + 3FFDEF7829177D7500B625CE /* MigrationNotificationsViewModel.swift in Sources */, FABB23372602FC2C00C8785C /* WebNavigationDelegate.swift in Sources */, FAFC065227D27241002F0483 /* BlogDetailsViewController+Dashboard.swift in Sources */, 8B55FAAD2614FC87007D618E /* Text+BoldSubString.swift in Sources */, @@ -23001,6 +23064,7 @@ C9F1D4B82706ED7C00BDF917 /* EditHomepageViewController.swift in Sources */, FABB23772602FC2C00C8785C /* MediaImageExporter.swift in Sources */, FABB23782602FC2C00C8785C /* GutenbergViewController+InformativeDialog.swift in Sources */, + 3FFDEF8A2918597700B625CE /* MigrationDoneViewController.swift in Sources */, 8B55F9DC2614D902007D618E /* CircledIcon.swift in Sources */, FABB23792602FC2C00C8785C /* ChangePasswordViewController.swift in Sources */, 3FE3D1FD26A6F34900F3CD10 /* WPStyleGuide+List.swift in Sources */, @@ -23027,6 +23091,7 @@ FABB23892602FC2C00C8785C /* FormattableUserContent.swift in Sources */, FABB238A2602FC2C00C8785C /* RegisterDomainDetailsViewController+HeaderFooter.swift in Sources */, FABB238B2602FC2C00C8785C /* ReaderSpacerView.swift in Sources */, + 3FFDEF852918215700B625CE /* MigrationStepView.swift in Sources */, FABB238D2602FC2C00C8785C /* WPStyleGuide+SiteCreation.swift in Sources */, F41E32FF287B47A500F89082 /* SuggestionsListViewModel.swift in Sources */, FABB238E2602FC2C00C8785C /* NotificationSettingsService.swift in Sources */, @@ -23155,6 +23220,7 @@ 3F2ABE1D277118CC005D8916 /* WPMediaAsset+VideoLimits.swift in Sources */, FABB23FB2602FC2C00C8785C /* WPTabBarController+MeNavigation.swift in Sources */, 80EF928B280D28140064A971 /* Atomic.swift in Sources */, + 3FFDEF9129187F2100B625CE /* MigrationActionsConfiguration.swift in Sources */, FABB23FC2602FC2C00C8785C /* SitePromptView.swift in Sources */, 8B4EDADE27DF9D5E004073B6 /* Blog+MySite.swift in Sources */, FABB23FD2602FC2C00C8785C /* BaseRestoreCompleteViewController.swift in Sources */, @@ -23206,6 +23272,7 @@ 830A58D92793AB4500CDE94F /* LoginEpilogueAnimator.swift in Sources */, FABB24262602FC2C00C8785C /* SiteAssemblyStep.swift in Sources */, FABB24272602FC2C00C8785C /* SiteSettingsViewController+Swift.swift in Sources */, + 3FFDEF8F29187F1200B625CE /* MigrationHeaderConfiguration.swift in Sources */, FABB24282602FC2C00C8785C /* FilterTableData.swift in Sources */, FABB24292602FC2C00C8785C /* ReaderGapMarker.m in Sources */, FABB242A2602FC2C00C8785C /* Pattern.swift in Sources */, @@ -23297,6 +23364,7 @@ FABB246C2602FC2C00C8785C /* ActivityPostRange.swift in Sources */, FABB246D2602FC2C00C8785C /* ReaderSelectInterestsViewController.swift in Sources */, FABB246E2602FC2C00C8785C /* ReaderShowMenuAction.swift in Sources */, + 3FFDEF882918596B00B625CE /* MigrationDoneViewModel.swift in Sources */, FABB246F2602FC2C00C8785C /* NSURLCache+Helpers.swift in Sources */, FABB24702602FC2C00C8785C /* String+Extensions.swift in Sources */, FABB24712602FC2C00C8785C /* CollapsableHeaderFilterBar.swift in Sources */, @@ -23353,7 +23421,6 @@ DC772AF2282009BA00664C02 /* InsightsLineChart.swift in Sources */, 176CE91727FB44C100F1E32B /* StatsBaseCell.swift in Sources */, 934098C12719577D00B3E77E /* InsightType.swift in Sources */, - F41BDD7D29114E9700B7F2B0 /* MigrationStepViewModel.swift in Sources */, FABB24992602FC2C00C8785C /* StreakStatsRecordValue+CoreDataProperties.swift in Sources */, 46F583AA2624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift in Sources */, FABB249A2602FC2C00C8785C /* Header+WordPress.swift in Sources */, @@ -23717,6 +23784,7 @@ FABB25B92602FC2C00C8785C /* UITextField+WorkaroundContinueIssue.swift in Sources */, FABB25BA2602FC2C00C8785C /* StoreKit+Debug.swift in Sources */, FABB25BB2602FC2C00C8785C /* PrivacySettingsViewController.swift in Sources */, + 3FFDEF812917882800B625CE /* MigrationNavigationController.swift in Sources */, FABB25BC2602FC2C00C8785C /* SharingDetailViewController.m in Sources */, 837B49DE283C2AE80061A657 /* BloggingPromptSettingsReminderDays+CoreDataProperties.swift in Sources */, FABB25BD2602FC2C00C8785C /* OtherAndTotalViewsCount+CoreDataProperties.swift in Sources */,