diff --git a/.buildkite/cache-builder.yml b/.buildkite/cache-builder.yml index 405b5c32eae3..0cf900094f06 100644 --- a/.buildkite/cache-builder.yml +++ b/.buildkite/cache-builder.yml @@ -14,7 +14,7 @@ common_params: # Common environment values to use with the `env` key. - &common_env # Be sure to also update the `.xcode-version` file when updating the Xcode image/version here - IMAGE_ID: xcode-14.3.1 + IMAGE_ID: xcode-15.1 steps: diff --git a/.buildkite/commands/checkout-release-branch.sh b/.buildkite/commands/checkout-release-branch.sh new file mode 100755 index 000000000000..394e18560bb9 --- /dev/null +++ b/.buildkite/commands/checkout-release-branch.sh @@ -0,0 +1,14 @@ +#!/bin/bash -eu + +RELEASE_NUMBER=$1 + +if [[ -z "${RELEASE_NUMBER}" ]]; then + echo "Usage $0 " + exit 1 +fi + +# Buildkite, by default, checks out a specific commit. +# For many release actions, we need to be on a release branch instead. +BRANCH_NAME="release/${RELEASE_NUMBER}" +git fetch origin "$BRANCH_NAME" +git checkout "$BRANCH_NAME" diff --git a/.buildkite/commands/complete-code-freeze.sh b/.buildkite/commands/complete-code-freeze.sh new file mode 100755 index 000000000000..94f6bc0366d6 --- /dev/null +++ b/.buildkite/commands/complete-code-freeze.sh @@ -0,0 +1,23 @@ +#!/bin/bash -eu + +RELEASE_NUMBER=$1 + +if [[ -z "${RELEASE_NUMBER}" ]]; then + echo "Usage $0 " + exit 1 +fi + +echo '--- :git: Configure Git for release management' +.buildkite/commands/configure-git-for-release-management.sh + +echo '--- :git: Checkout release branch' +.buildkite/commands/checkout-release-branch.sh "$RELEASE_NUMBER" + +echo '--- :ruby: Setup Ruby tools' +install_gems + +echo '--- :closed_lock_with_key: Access secrets' +bundle exec fastlane run configure_apply + +echo '--- :shipit: Complete code freeze' +bundle exec fastlane complete_code_freeze skip_confirm:true diff --git a/.buildkite/commands/configure-git-for-release-management.sh b/.buildkite/commands/configure-git-for-release-management.sh new file mode 100755 index 000000000000..8d9a770a236e --- /dev/null +++ b/.buildkite/commands/configure-git-for-release-management.sh @@ -0,0 +1,13 @@ +#!/bin/bash -eu + +# The Git command line client is not configured in Buildkite. +# At the moment, steps that need Git access can configure it on deman using this script. +# Later on, we should be able to configure it on the agent instead. + +curl -L https://api.github.com/meta | jq -r '.ssh_keys | .[]' | sed -e 's/^/github.com /' >> ~/.ssh/known_hosts +git config --global user.email "mobile+wpmobilebot@automattic.com" +git config --global user.name "Automattic Release Bot" + +# Buildkite is currently using the HTTPS URL to checkout. +# We need to override it to be able to use the deploy key. +git remote set-url origin git@github.com:wordpress-mobile/WordPress-iOS.git diff --git a/.buildkite/commands/finalize-release.sh b/.buildkite/commands/finalize-release.sh new file mode 100755 index 000000000000..2f3a7c7b78ce --- /dev/null +++ b/.buildkite/commands/finalize-release.sh @@ -0,0 +1,23 @@ +#!/bin/bash -eu + +RELEASE_NUMBER=$1 + +if [[ -z "${RELEASE_NUMBER}" ]]; then + echo "Usage $0 " + exit 1 +fi + +echo '--- :git: Configure Git for release management' +.buildkite/commands/configure-git-for-release-management.sh + +echo '--- :git: Checkout release branch' +.buildkite/commands/checkout-release-branch.sh "$RELEASE_NUMBER" + +echo '--- :ruby: Setup Ruby tools' +install_gems + +echo '--- :closed_lock_with_key: Access secrets' +bundle exec fastlane run configure_apply + +echo '--- :shipit: Finalize release' +bundle exec fastlane finalize_release skip_confirm:true diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 0307d3d4d2c8..70b34aa58b1d 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -3,13 +3,10 @@ common_params: # Common plugin settings to use with the `plugins` key. - &common_plugins - automattic/a8c-ci-toolkit#2.18.1 - - automattic/git-s3-cache#1.1.4: - bucket: "a8c-repo-mirrors" - repo: "automattic/wordpress-ios/" # Common environment values to use with the `env` key. - &common_env # Be sure to also update the `.xcode-version` file when updating the Xcode image/version here - IMAGE_ID: xcode-14.3.1 + IMAGE_ID: xcode-15.1 # This is the default pipeline – it will build and test the app steps: @@ -80,7 +77,7 @@ steps: - group: "🔬 UI Tests" steps: - label: "🔬 :jetpack: UI Tests (iPhone)" - command: .buildkite/commands/run-ui-tests.sh 'iPhone SE (3rd generation)' + command: .buildkite/commands/run-ui-tests.sh 'iPhone 15' depends_on: "build_jetpack" env: *common_env plugins: *common_plugins @@ -92,7 +89,7 @@ steps: context: "UI Tests (iPhone)" - label: "🔬 :jetpack: UI Tests (iPad)" - command: .buildkite/commands/run-ui-tests.sh 'iPad Air (5th generation)' + command: .buildkite/commands/run-ui-tests.sh 'iPad Pro (12.9-inch) (6th generation)' depends_on: "build_jetpack" env: *common_env plugins: *common_plugins diff --git a/.buildkite/release-builds.yml b/.buildkite/release-builds.yml index 53447e466cdc..d1eeeb4fda69 100644 --- a/.buildkite/release-builds.yml +++ b/.buildkite/release-builds.yml @@ -11,7 +11,7 @@ common_params: # Common environment values to use with the `env` key. - &common_env # Be sure to also update the `.xcode-version` file when updating the Xcode image/version here - IMAGE_ID: xcode-14.3.1 + IMAGE_ID: xcode-15.1 steps: diff --git a/.buildkite/release-pipelines/code-freeze.yml b/.buildkite/release-pipelines/code-freeze.yml new file mode 100644 index 000000000000..0e11af8022c0 --- /dev/null +++ b/.buildkite/release-pipelines/code-freeze.yml @@ -0,0 +1,20 @@ +steps: + - label: Code Freeze + plugins: + - automattic/a8c-ci-toolkit#2.18.2 + # The first client to implement releases in CI was Android so the automation works in that queue. + # We might want to move it to a leaner one in the future. + agents: + queue: android + command: | + echo '--- :git: Configure Git for release management' + .buildkite/commands/configure-git-for-release-management.sh + + echo '--- :ruby: Setup Ruby tools' + install_gems + + echo '--- :closed_lock_with_key: Access secrets' + bundle exec fastlane run configure_apply + + echo '--- :shipit: Run code freeze' + bundle exec fastlane code_freeze skip_confirm:true diff --git a/.buildkite/release-pipelines/complete-code-freeze.yml b/.buildkite/release-pipelines/complete-code-freeze.yml new file mode 100644 index 000000000000..77fa242f1371 --- /dev/null +++ b/.buildkite/release-pipelines/complete-code-freeze.yml @@ -0,0 +1,10 @@ +steps: + - label: Complete Code Freeze + plugins: + - automattic/a8c-ci-toolkit#2.18.2 + # The code freeze completion needs to run on macOS because it uses genstrings under the hood + agents: + queue: mac + env: + IMAGE_ID: xcode-15.1 + command: ".buildkite/commands/complete-code-freeze.sh $RELEASE_VERSION" diff --git a/.buildkite/release-pipelines/finalize-release.yml b/.buildkite/release-pipelines/finalize-release.yml new file mode 100644 index 000000000000..4a94c6f2b517 --- /dev/null +++ b/.buildkite/release-pipelines/finalize-release.yml @@ -0,0 +1,10 @@ +steps: + - label: Finalize Release + plugins: + - automattic/a8c-ci-toolkit#2.18.2 + # The finalization needs to run on macOS because of localization linting + agents: + queue: mac + env: + IMAGE_ID: xcode-15.1 + command: ".buildkite/commands/finalize-release.sh $RELEASE_VERSION" diff --git a/.buildkite/release-pipelines/new-beta-release.yml b/.buildkite/release-pipelines/new-beta-release.yml new file mode 100644 index 000000000000..d93ed039b321 --- /dev/null +++ b/.buildkite/release-pipelines/new-beta-release.yml @@ -0,0 +1,21 @@ +steps: + - label: New Beta Deployment + plugins: + - automattic/a8c-ci-toolkit#2.18.2 + # The beta needs to run on macOS because it uses genstrings under the hood + agents: + queue: mac + env: + IMAGE_ID: xcode-15.1 + command: | + echo '--- :git: Configure Git for release management' + .buildkite/commands/configure-git-for-release-management.sh + + echo '--- :ruby: Setup Ruby tools' + install_gems + + echo '--- :closed_lock_with_key: Access secrets' + bundle exec fastlane run configure_apply + + echo '--- :shipit: Deploy new beta' + bundle exec fastlane new_beta_release skip_confirm:true diff --git a/.buildkite/release-pipelines/update-app-store-strings.yml b/.buildkite/release-pipelines/update-app-store-strings.yml new file mode 100644 index 000000000000..4d4a898d7c7e --- /dev/null +++ b/.buildkite/release-pipelines/update-app-store-strings.yml @@ -0,0 +1,20 @@ +steps: + - label: Update App Store Strings + plugins: + - automattic/a8c-ci-toolkit#2.18.2 + # The first client to implement releases in CI was Android so the automation works in that queue. + # We might want to move it to a leaner one in the future. + agents: + queue: android + command: | + echo '--- :git: Configure Git for release management' + .buildkite/commands/configure-git-for-release-management.sh + + echo '--- :ruby: Setup Ruby tools' + install_gems + + echo '--- :closed_lock_with_key: Access secrets' + bundle exec fastlane run configure_apply + + echo '--- :shipit: Update relaese notes and other App Store metadata' + bundle exec fastlane update_appstore_strings skip_confirm:true diff --git a/.xcode-version b/.xcode-version index 6dfe8b1298c0..adbc6d2b1bde 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -14.3.1 +15.1 diff --git a/API-Mocks/WordPressMocks/src/main/assets/mocks/mappings/wpcom/stats/stats_visits-year.json b/API-Mocks/WordPressMocks/src/main/assets/mocks/mappings/wpcom/stats/stats_visits-year.json index 44426144b0c2..2da5f0249f3c 100644 --- a/API-Mocks/WordPressMocks/src/main/assets/mocks/mappings/wpcom/stats/stats_visits-year.json +++ b/API-Mocks/WordPressMocks/src/main/assets/mocks/mappings/wpcom/stats/stats_visits-year.json @@ -20,7 +20,7 @@ "response": { "status": 200, "jsonBody": { - "date": "2019-07-16", + "date": "{{now format='yyyy-MM-dd'}}", "unit": "year", "fields": [ "period", @@ -33,7 +33,7 @@ ], "data": [ [ - "2005-01-01", + "{{now offset='-7 years' format='yyyy-MM-dd'}}", 0, 0, 0, @@ -42,7 +42,7 @@ 0 ], [ - "2006-01-01", + "{{now offset='-6 years' format='yyyy-MM-dd'}}", 0, 0, 0, @@ -51,7 +51,7 @@ 0 ], [ - "2007-01-01", + "{{now offset='-5 years' format='yyyy-MM-dd'}}", 0, 0, 0, @@ -60,7 +60,7 @@ 0 ], [ - "2008-01-01", + "{{now offset='-4 years' format='yyyy-MM-dd'}}", 0, 0, 0, @@ -69,70 +69,7 @@ 0 ], [ - "2009-01-01", - 0, - 0, - 0, - 0, - 0, - 0 - ], - [ - "2010-01-01", - 0, - 0, - 0, - 0, - 0, - 0 - ], - [ - "2011-01-01", - 0, - 0, - 0, - 0, - 0, - 0 - ], - [ - "2012-01-01", - 0, - 0, - 0, - 0, - 0, - 0 - ], - [ - "2013-01-01", - 0, - 0, - 0, - 0, - 0, - 0 - ], - [ - "2014-01-01", - 0, - 0, - 0, - 0, - 0, - 0 - ], - [ - "2015-01-01", - 0, - 0, - 0, - 0, - 0, - 0 - ], - [ - "2016-01-01", + "{{now offset='-3 years' format='yyyy-MM-dd'}}", 48, 12, 0, @@ -141,7 +78,7 @@ 13 ], [ - "2017-01-01", + "{{now offset='-2 years' format='yyyy-MM-dd'}}", 788, 465, 0, @@ -150,7 +87,7 @@ 3 ], [ - "2018-01-01", + "{{now offset='-1 years' format='yyyy-MM-dd'}}", 1215, 632, 0, @@ -159,7 +96,7 @@ 3 ], [ - "2019-01-01", + "{{now format='yyyy-MM-dd'}}", 9148, 4216, 1351, diff --git a/Gemfile b/Gemfile index 9aa96efb1f7c..8a11ae824018 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,8 @@ gem 'cocoapods', '~> 1.14' gem 'commonmarker' gem 'danger-dangermattic', git: 'https://github.com/Automattic/dangermattic' gem 'dotenv' -gem 'fastlane', '~> 2.216' +# 2.217.0 includes a fix for Xcode 15 test results parsing in CI +gem 'fastlane', '~> 2.217' gem 'fastlane-plugin-appcenter', '~> 2.1' gem 'fastlane-plugin-sentry' # This comment avoids typing to switch to a development version for testing. diff --git a/Gemfile.lock b/Gemfile.lock index 31bf340e114e..6a16ac1d447c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -405,7 +405,7 @@ DEPENDENCIES commonmarker danger-dangermattic! dotenv - fastlane (~> 2.216) + fastlane (~> 2.217) fastlane-plugin-appcenter (~> 2.1) fastlane-plugin-sentry fastlane-plugin-wpmreleasetoolkit (~> 9.1) diff --git a/Gutenberg/config.yml b/Gutenberg/config.yml index afc3c080c954..1d1311433fcf 100644 --- a/Gutenberg/config.yml +++ b/Gutenberg/config.yml @@ -9,6 +9,6 @@ # # LOCAL_GUTENBERG=../my-gutenberg-fork bundle exec pod install ref: - tag: v1.110.0-alpha2 + tag: v1.110.0 github_org: wordpress-mobile repo_name: gutenberg-mobile diff --git a/Modules/Sources/DesignSystem/Foundation/Colors.xcassets/Foundation/Background/backgroundBrandWP.colorset/Contents.json b/Modules/Sources/DesignSystem/Foundation/Colors.xcassets/Foundation/Background/backgroundBrandWP.colorset/Contents.json index 339429db211c..15a845b25ba1 100644 --- a/Modules/Sources/DesignSystem/Foundation/Colors.xcassets/Foundation/Background/backgroundBrandWP.colorset/Contents.json +++ b/Modules/Sources/DesignSystem/Foundation/Colors.xcassets/Foundation/Background/backgroundBrandWP.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xC4", - "green" : "0x75", - "red" : "0x06" + "blue" : "0xE3", + "green" : "0x58", + "red" : "0x38" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xDB", - "green" : "0x89", - "red" : "0x16" + "blue" : "0xE3", + "green" : "0x58", + "red" : "0x38" } }, "idiom" : "universal" diff --git a/WordPress/JetpackStatsWidgets/Model/ThisWeekWidgetStats.swift b/Modules/Sources/JetpackStatsWidgetsCore/Model/ThisWeekWidgetStats.swift similarity index 60% rename from WordPress/JetpackStatsWidgets/Model/ThisWeekWidgetStats.swift rename to Modules/Sources/JetpackStatsWidgetsCore/Model/ThisWeekWidgetStats.swift index 6bcbc22a79bd..fb61c140a7f5 100644 --- a/WordPress/JetpackStatsWidgets/Model/ThisWeekWidgetStats.swift +++ b/Modules/Sources/JetpackStatsWidgetsCore/Model/ThisWeekWidgetStats.swift @@ -1,39 +1,48 @@ import Foundation -import WordPressKit /// This struct contains data for 'Views This Week' stats to be displayed in the corresponding widget. /// -struct ThisWeekWidgetStats: Codable { - let days: [ThisWeekWidgetDay] +public struct ThisWeekWidgetStats: Codable { + public let days: [ThisWeekWidgetDay] - init(days: [ThisWeekWidgetDay]? = []) { + public init(days: [ThisWeekWidgetDay]? = []) { self.days = days ?? [] } } -struct ThisWeekWidgetDay: Codable, Hashable { - let date: Date - let viewsCount: Int - let dailyChangePercent: Float +public struct ThisWeekWidgetDay: Codable, Hashable { + public let date: Date + public let viewsCount: Int + public let dailyChangePercent: Float - init(date: Date, viewsCount: Int, dailyChangePercent: Float) { + public init(date: Date, viewsCount: Int, dailyChangePercent: Float) { self.date = date self.viewsCount = viewsCount self.dailyChangePercent = dailyChangePercent } } -extension ThisWeekWidgetStats { +public extension ThisWeekWidgetStats { + struct Input { + public let periodStartDate: Date + public let viewsCount: Int + + public init(periodStartDate: Date, viewsCount: Int) { + self.periodStartDate = periodStartDate + self.viewsCount = viewsCount + } + } + static var maxDaysToDisplay: Int { return 7 } - static func daysFrom(summaryData: [StatsSummaryData]) -> [ThisWeekWidgetDay] { + static func daysFrom(summaryData: [ThisWeekWidgetStats.Input]) -> [ThisWeekWidgetDay] { var days = [ThisWeekWidgetDay]() for index in 0.. Bool { + public static func == (lhs: ThisWeekWidgetStats, rhs: ThisWeekWidgetStats) -> Bool { return lhs.days.elementsEqual(rhs.days) } } extension ThisWeekWidgetDay: Equatable { - static func == (lhs: ThisWeekWidgetDay, rhs: ThisWeekWidgetDay) -> Bool { + public static func == (lhs: ThisWeekWidgetDay, rhs: ThisWeekWidgetDay) -> Bool { return lhs.date == rhs.date && lhs.viewsCount == rhs.viewsCount && lhs.dailyChangePercent == rhs.dailyChangePercent diff --git a/Modules/Tests/JetpackStatsWidgetsCoreTests/ThisWeekWidgetStatsTests.swift b/Modules/Tests/JetpackStatsWidgetsCoreTests/ThisWeekWidgetStatsTests.swift new file mode 100644 index 000000000000..2d3fffb5911a --- /dev/null +++ b/Modules/Tests/JetpackStatsWidgetsCoreTests/ThisWeekWidgetStatsTests.swift @@ -0,0 +1,32 @@ +import XCTest +import JetpackStatsWidgetsCore + +final class ThisWeekWidgetStatsTests: XCTestCase { + func testDaysFromSummaryData_moreSummaryData() { + var summaryData: [ThisWeekWidgetStats.Input] = [] + + // Given there's summary data for more than max days to display + for _ in 0.. 9.0' - pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '32b5a9fdd097b82934a5ce679c915a1fc3f21848' + pod 'WordPressKit', '~> 9.0', '>= 9.0.2' + # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: 'trunk' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', tag: '' # pod 'WordPressKit', path: '../WordPressKit-iOS' @@ -142,8 +142,8 @@ abstract_target 'Apps' do pod 'NSURL+IDN', '~> 0.4' - # pod 'WordPressAuthenticator', '~> 8.0' - pod 'WordPressAuthenticator', git: 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', commit: 'b51b4b238103f7812db0aa1a265995e1c6d1e355' + pod 'WordPressAuthenticator', '~> 8.0', '>= 8.0.1' + # pod 'WordPressAuthenticator', git: 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', commit: '' # pod 'WordPressAuthenticator', git: 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', branch: '' # pod 'WordPressAuthenticator', path: '../WordPressAuthenticator-iOS' diff --git a/Podfile.lock b/Podfile.lock index f88a1b3fd588..daab523c6c77 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -14,7 +14,7 @@ PODS: - AppCenter/Core - AppCenter/Distribute (5.0.3): - AppCenter/Core - - Automattic-Tracks-iOS (3.0.0): + - Automattic-Tracks-iOS (3.2.0): - Sentry (~> 8.0) - Sodium (>= 0.9.1) - UIDeviceIdentifier (~> 2.0) @@ -26,7 +26,7 @@ PODS: - FSInteractiveMap (0.1.0) - Gifu (3.3.1) - Gridicons (1.2.0) - - Gutenberg (1.109.2) + - Gutenberg (1.110.0) - JTAppleCalendar (8.0.5) - Kanvas (1.4.9): - CropViewController @@ -49,12 +49,12 @@ PODS: - OHHTTPStubs/Swift (9.1.0): - OHHTTPStubs/Default - Reachability (3.2) - - Sentry (8.16.1): - - Sentry/Core (= 8.16.1) - - SentryPrivate (= 8.16.1) - - Sentry/Core (8.16.1): - - SentryPrivate (= 8.16.1) - - SentryPrivate (8.16.1) + - Sentry (8.18.0): + - Sentry/Core (= 8.18.0) + - SentryPrivate (= 8.18.0) + - Sentry/Core (8.18.0): + - SentryPrivate (= 8.18.0) + - SentryPrivate (8.18.0) - Sodium (0.9.1) - Starscream (3.0.6) - SVProgressHUD (2.2.5) @@ -63,14 +63,14 @@ PODS: - WordPress-Aztec-iOS (1.19.9) - WordPress-Editor-iOS (1.19.9): - WordPress-Aztec-iOS (= 1.19.9) - - WordPressAuthenticator (8.0.0): + - WordPressAuthenticator (8.0.1): - Gridicons (~> 1.0) - "NSURL+IDN (= 0.4)" - SVProgressHUD (~> 2.2.5) - WordPressKit (~> 9.0.0) - WordPressShared (~> 2.1-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (9.0.1): + - WordPressKit (9.0.2): - Alamofire (~> 4.8.0) - NSObject-SafeExpectations (~> 0.0.4) - UIDeviceIdentifier (~> 2.0) @@ -107,7 +107,7 @@ DEPENDENCIES: - FSInteractiveMap (from `https://github.com/wordpress-mobile/FSInteractiveMap.git`, tag `0.2.0`) - Gifu (= 3.3.1) - Gridicons (~> 1.2) - - Gutenberg (from `https://cdn.a8c-ci.services/gutenberg-mobile/Gutenberg-v1.110.0-alpha2.podspec`) + - Gutenberg (from `https://cdn.a8c-ci.services/gutenberg-mobile/Gutenberg-v1.110.0.podspec`) - JTAppleCalendar (~> 8.0.5) - Kanvas (~> 1.4.4) - MediaEditor (>= 1.2.2, ~> 1.2) @@ -120,14 +120,16 @@ DEPENDENCIES: - SVProgressHUD (= 2.2.5) - SwiftLint (~> 0.50) - WordPress-Editor-iOS (~> 1.19.9) - - WordPressAuthenticator (from `https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git`, commit `b51b4b238103f7812db0aa1a265995e1c6d1e355`) - - WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, commit `32b5a9fdd097b82934a5ce679c915a1fc3f21848`) + - WordPressAuthenticator (>= 8.0.1, ~> 8.0) + - WordPressKit (>= 9.0.2, ~> 9.0) - WordPressShared (~> 2.2) - WordPressUI (~> 1.15) - ZendeskSupportSDK (= 5.3.0) - ZIPFoundation (~> 0.9.8) SPEC REPOS: + https://github.com/wordpress-mobile/cocoapods-specs.git: + - WordPressAuthenticator trunk: - Alamofire - AlamofireImage @@ -156,6 +158,7 @@ SPEC REPOS: - UIDeviceIdentifier - WordPress-Aztec-iOS - WordPress-Editor-iOS + - WordPressKit - WordPressShared - WordPressUI - wpxmlrpc @@ -173,13 +176,7 @@ EXTERNAL SOURCES: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 Gutenberg: - :podspec: https://cdn.a8c-ci.services/gutenberg-mobile/Gutenberg-v1.110.0-alpha2.podspec - WordPressAuthenticator: - :commit: b51b4b238103f7812db0aa1a265995e1c6d1e355 - :git: https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git - WordPressKit: - :commit: 32b5a9fdd097b82934a5ce679c915a1fc3f21848 - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git + :podspec: https://cdn.a8c-ci.services/gutenberg-mobile/Gutenberg-v1.110.0.podspec CHECKOUT OPTIONS: FSInteractiveMap: @@ -189,26 +186,20 @@ CHECKOUT OPTIONS: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true :tag: v1.100.2 - WordPressAuthenticator: - :commit: b51b4b238103f7812db0aa1a265995e1c6d1e355 - :git: https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git - WordPressKit: - :commit: 32b5a9fdd097b82934a5ce679c915a1fc3f21848 - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git SPEC CHECKSUMS: Alamofire: 3ec537f71edc9804815215393ae2b1a8ea33a844 AlamofireImage: 63cfe3baf1370be6c498149687cf6db3e3b00999 AlamofireNetworkActivityIndicator: 9acc3de3ca6645bf0efed462396b0df13dd3e7b8 AppCenter: a4070ec3d4418b5539067a51f57155012e486ebd - Automattic-Tracks-iOS: f30bf3362a77010ccb9fe9aded453645089f6ccb + Automattic-Tracks-iOS: baa126f98d2ce26fd54ee2534bef6e2d46480a5c CocoaLumberjack: 78abfb691154e2a9df8ded4350d504ee19d90732 CropViewController: a5c143548a0fabcd6cc25f2d26e40460cfb8c78c Down: 71bf4af3c04fa093e65dffa25c4b64fa61287373 FSInteractiveMap: a396f610f48b76cb540baa87139d056429abda86 Gifu: 416d4e38c4c2fed012f019e0a1d3ffcb58e5b842 Gridicons: 4455b9f366960121430e45997e32112ae49ffe1d - Gutenberg: fd7f055a7ec5c27c993e176c3c2b2a6548789b8d + Gutenberg: 0e64ef7d9c46ba0a681c82f5969622f3db9bf033 JTAppleCalendar: 16c6501b22cb27520372c28b0a2e0b12c8d0cd73 Kanvas: cc027f8058de881a4ae2b5aa5f05037b6d054d08 MediaEditor: d08314cfcbfac74361071a306b4bc3a39b3356ae @@ -217,8 +208,8 @@ SPEC CHECKSUMS: OCMock: 43565190abc78977ad44a61c0d20d7f0784d35ab OHHTTPStubs: 90eac6d8f2c18317baeca36698523dc67c513831 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - Sentry: fcd7073d05654d9f0214d0226fca3d56d6530379 - SentryPrivate: 1cf54bae8be81dee04672b4c14ad5de52efb909f + Sentry: 8984a4ffb2b9bd2894d74fb36e6f5833865bc18e + SentryPrivate: 2f0c9ba4c3fc993f70eab6ca95673509561e0085 Sodium: 23d11554ecd556196d313cf6130d406dfe7ac6da Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5 SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 @@ -226,8 +217,8 @@ SPEC CHECKSUMS: UIDeviceIdentifier: 442b65b4ff1832d4ca9c2a157815cb29ad981b17 WordPress-Aztec-iOS: fbebd569c61baa252b3f5058c0a2a9a6ada686bb WordPress-Editor-iOS: bda9f7f942212589b890329a0cb22547311749ef - WordPressAuthenticator: 738d6423d11db1927b4f30dec8c4b90a442df8fc - WordPressKit: 304725187d755db8d5ff73a58d27eda0071771c1 + WordPressAuthenticator: fd2e1d340680faffffd9d675fc2df5ed19e26ea2 + WordPressKit: 23d0ffb43f2ccdad2debd6799e62d39790a5ffad WordPressShared: 87f3ee89b0a3e83106106f13a8b71605fb8eb6d2 WordPressUI: a491454affda3b0fb812812e637dc5e8f8f6bd06 wpxmlrpc: 68db063041e85d186db21f674adf08d9c70627fd @@ -240,6 +231,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: d170fa8e270b2a32bef9dcdcabff5b8f1a5deced -PODFILE CHECKSUM: 15b140d335cb977dc0159e0edc13eaf0f761502a +PODFILE CHECKSUM: 9567ce349333fd257fea44c201727b4180efb961 COCOAPODS: 1.14.2 diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f16dd3078b68..1082c73dc706 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,8 +1,26 @@ +24.1 +----- + + 24.0 ----- * [**] [internal] A minor refactor in authentication flow, including but not limited to social sign-in and two factor authentication. [#22086] * [***] [Jetpack-only] Plans: Upgrade to a WPCOM plan from domains dashboard in Jetpack app. [#22261] * [**] [internal] Refactor domain selection flows to use the same domain selection UI. [22254] +* [**] Re-enable the support for using Security Keys as a second factor during login [#22258] +* [*] Fix crash in editor that sometimes happens after modifying tags or categories [#22265] +* [**] Updated login screen's colors to highlight WordPress - Jetpack brand relationship +* [*] Add defensive code to make sure the retain cycles in the editor don't lead to crashes [#22252] +* [*] [Jetpack-only] Updated Site Domains screen to make domains management more convenient [#22294, #22311] +* [**] [internal] [Jetpack-only] Adds support for dynamic dashboard cards driven by the backend [#22326] +* [**] [internal] Add support for the Phase One Fast Media Uploads banner [#22330] +* [*] [internal] Remove personalizeHomeTab feature flag [#22280] +* [*] Fix a rare crash in post search related to tags [#22275] +* [*] Fix a rare crash when deleting posts [#22277] +* [*] Fix a rare crash in Site Media prefetching cancellation [#22278] +* [*] Fix an issue with BlogDashboardPersonalizationService being used on the background thread [#22335] +* [***] Block Editor: Avoid keyboard dismiss when interacting with text blocks [https://github.com/WordPress/gutenberg/pull/57070] +* [**] Block Editor: Auto-scroll upon block insertion [https://github.com/WordPress/gutenberg/pull/57273] 23.9 ----- @@ -16,6 +34,8 @@ * [*] [Jetpack-only] Fix an occasional crash when changing Reader comment status [#22155] * [*] [Jetpack-only] Fix an occasional crash when logging out after interacting with Reader [#22147] * [*] Fix an issue where the Compliance Popover breaks other screens presentation. [#22085] +* [*] [Jetpack-only] Fix an occassional crash when logging out after interacting with Reader [#22147] +* [*] [Jetpack-only] Fix an issue where VoiceOver could lose focus when liking posts in Reader. [#22232] 23.8 ----- diff --git a/WordPress/Classes/Extensions/Colors and Styles/UIColor+MurielColors.swift b/WordPress/Classes/Extensions/Colors and Styles/UIColor+MurielColors.swift index 1d5817c6324c..e42bfb8d7fa1 100644 --- a/WordPress/Classes/Extensions/Colors and Styles/UIColor+MurielColors.swift +++ b/WordPress/Classes/Extensions/Colors and Styles/UIColor+MurielColors.swift @@ -43,13 +43,9 @@ extension UIColor { /// Muriel brand color static var brand = muriel(color: .brand) - class func brand(_ shade: MurielColorShade) -> UIColor { - return muriel(color: .brand, shade) - } /// Muriel error color static var error = muriel(color: .error) - static var errorDark = muriel(color: .error, .shade70) class func error(_ shade: MurielColorShade) -> UIColor { return muriel(color: .error, shade) } @@ -64,15 +60,9 @@ extension UIColor { /// Muriel editor primary color static var editorPrimary = muriel(color: .editorPrimary) - class func editorPrimary(_ shade: MurielColorShade) -> UIColor { - return muriel(color: .editorPrimary, shade) - } /// Muriel success color static var success = muriel(color: .success) - class func success(_ shade: MurielColorShade) -> UIColor { - return muriel(color: .success, shade) - } /// Muriel warning color static var warning = muriel(color: .warning) @@ -197,10 +187,6 @@ extension UIColor { return .separator } - static var primaryButtonBorder: UIColor { - return .opaqueSeparator - } - /// WP color for table foregrounds (cells, etc) static var listForeground: UIColor { return .secondarySystemGroupedBackground @@ -232,10 +218,6 @@ extension UIColor { return .systemGray } - static var buttonIcon: UIColor { - return .systemGray2 - } - /// For icons that are present in a toolbar or similar view static var toolbarInactive: UIColor { return .secondaryLabel diff --git a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Aztec.swift b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Aztec.swift index 1113a7253722..eaf58fd5cc42 100644 --- a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Aztec.swift +++ b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Aztec.swift @@ -11,8 +11,6 @@ extension WPStyleGuide { static let aztecFormatBarDividerColor: UIColor = .divider - static let aztecFormatBarBackgroundColor = UIColor.basicBackground - static var aztecFormatPickerSelectedCellBackgroundColor: UIColor { get { return (UIDevice.isPad()) ? .neutral(.shade0) : .neutral(.shade5) diff --git a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Search.swift b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Search.swift index ebad5bb1c5a6..b9a854a84ff0 100644 --- a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Search.swift +++ b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+Search.swift @@ -4,8 +4,6 @@ import UIKit extension WPStyleGuide { - fileprivate static let barTintColor: UIColor = .neutral(.shade10) - public class func configureSearchBar(_ searchBar: UISearchBar, backgroundColor: UIColor, returnKeyType: UIReturnKeyType) { searchBar.accessibilityIdentifier = "Search" searchBar.autocapitalizationType = .none diff --git a/WordPress/Classes/Extensions/Interpolation.swift b/WordPress/Classes/Extensions/Interpolation.swift index ae019dca6a7c..775eb498017e 100644 --- a/WordPress/Classes/Extensions/Interpolation.swift +++ b/WordPress/Classes/Extensions/Interpolation.swift @@ -1,10 +1,6 @@ import Foundation extension CGFloat { - static func interpolated(from fromValue: CGFloat, to toValue: CGFloat, progress: CGFloat) -> CGFloat { - return fromValue.interpolated(to: toValue, with: progress) - } - /// Interpolates a CGFloat /// - Parameters: /// - toValue: The to value diff --git a/WordPress/Classes/Extensions/UINavigationBar+Appearance.swift b/WordPress/Classes/Extensions/UINavigationBar+Appearance.swift deleted file mode 100644 index 45cc4b0d84a9..000000000000 --- a/WordPress/Classes/Extensions/UINavigationBar+Appearance.swift +++ /dev/null @@ -1,7 +0,0 @@ -import UIKit - -extension UINavigationBar { - class func standardTitleTextAttributes() -> [NSAttributedString.Key: Any] { - return appearance().standardAppearance.titleTextAttributes - } -} diff --git a/WordPress/Classes/Models/Blog+JetpackSocial.swift b/WordPress/Classes/Models/Blog+JetpackSocial.swift index 18cb9ffb0a20..5a5737e2c712 100644 --- a/WordPress/Classes/Models/Blog+JetpackSocial.swift +++ b/WordPress/Classes/Models/Blog+JetpackSocial.swift @@ -7,8 +7,8 @@ extension Blog { /// Whether the blog has Social auto-sharing limited. /// Note that sites hosted at WP.com has no Social sharing limitations. var isSocialSharingLimited: Bool { - let features = planActiveFeatures ?? [] - return !isHostedAtWPcom && !features.contains(Constants.socialSharingFeature) + let hasUnlimitedSharing = (planActiveFeatures ?? []).contains(Constants.unlimitedSharingFeatureKey) + return !(isHostedAtWPcom || isAtomic() || hasUnlimitedSharing) } /// The auto-sharing limit information for the blog. @@ -26,6 +26,6 @@ extension Blog { private enum Constants { /// The feature key listed in the blog's plan's features. At the moment, `social-shares-1000` means unlimited /// sharing, but in the future we might introduce a proper differentiation between 1000 and unlimited. - static let socialSharingFeature = "social-shares-1000" + static let unlimitedSharingFeatureKey = "social-shares-1000" } } diff --git a/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift b/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift index 14694a2917f3..9cccb837b720 100644 --- a/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift +++ b/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift @@ -48,12 +48,13 @@ public class BloggingPromptSettings: NSManagedObject { } private func updatePromptSettingsIfNecessary(siteID: Int, enabled: Bool) { - let service = BlogDashboardPersonalizationService(siteID: siteID) - if !service.hasPreference(for: .prompts) { - service.setEnabled(enabled, for: .prompts) + DispatchQueue.main.async { + let service = BlogDashboardPersonalizationService(siteID: siteID) + if !service.hasPreference(for: .prompts) { + service.setEnabled(enabled, for: .prompts) + } } } - } extension RemoteBloggingPromptsSettings { diff --git a/WordPress/Classes/Models/Comment+CoreDataClass.swift b/WordPress/Classes/Models/Comment+CoreDataClass.swift index 39ab676683a3..8bc04ae781fc 100644 --- a/WordPress/Classes/Models/Comment+CoreDataClass.swift +++ b/WordPress/Classes/Models/Comment+CoreDataClass.swift @@ -58,10 +58,6 @@ public class Comment: NSManagedObject { return Int(likeCount) } - func hasAuthorUrl() -> Bool { - return !author_url.isEmpty - } - func canEditAuthorData() -> Bool { // If the authorID is zero, the user is unregistered. Therefore, the data can be edited. return authorID == 0 diff --git a/WordPress/Classes/Models/Notifications/NotificationSettings.swift b/WordPress/Classes/Models/Notifications/NotificationSettings.swift index d03d8caaf613..b3c4c6832490 100644 --- a/WordPress/Classes/Models/Notifications/NotificationSettings.swift +++ b/WordPress/Classes/Models/Notifications/NotificationSettings.swift @@ -144,8 +144,6 @@ open class NotificationSettings { return NSLocalizedString("Push Notifications", comment: "Mobile Push Notifications") } } - - static let allValues = [ Timeline, Email, Device ] } } diff --git a/WordPress/Classes/Models/Revisions/Revision.swift b/WordPress/Classes/Models/Revisions/Revision.swift index ae6636c9bd08..e4e31a44f98d 100644 --- a/WordPress/Classes/Models/Revisions/Revision.swift +++ b/WordPress/Classes/Models/Revisions/Revision.swift @@ -31,10 +31,6 @@ class Revision: NSManagedObject { return revisionFormatter.date(from: postDateGmt ?? "") ?? Date() } - var revisionModifiedDate: Date { - return revisionFormatter.date(from: postModifiedGmt ?? "") ?? Date() - } - @objc var revisionDateForSection: String { return revisionDate.longUTCStringWithoutTime() } diff --git a/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift b/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift index 7af84a15507b..f1794ca90b5d 100644 --- a/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift +++ b/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift @@ -5,7 +5,6 @@ import Foundation /// extension MediaHost { enum ReaderPostContentProviderError: Swift.Error { - case noDefaultWordPressComAccount case baseInitializerError(error: Error, readerPostContentProvider: ReaderPostContentProvider) } diff --git a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift index 68c74ad84a6b..5132eab8fd04 100644 --- a/WordPress/Classes/Networking/MediaRequestAuthenticator.swift +++ b/WordPress/Classes/Networking/MediaRequestAuthenticator.swift @@ -22,7 +22,6 @@ class MediaRequestAuthenticator { /// Errors conditions that this class can find. /// enum Error: Swift.Error { - case cannotFindSiteIDForSiteAvailableThroughWPCom(blog: Blog) case cannotBreakDownURLIntoComponents(url: URL) case cannotCreateAtomicURL(components: URLComponents) case cannotCreateAtomicProxyURL(components: URLComponents) diff --git a/WordPress/Classes/Services/AccountSettingsService.swift b/WordPress/Classes/Services/AccountSettingsService.swift index 927854ca06cb..bf6184a1e617 100644 --- a/WordPress/Classes/Services/AccountSettingsService.swift +++ b/WordPress/Classes/Services/AccountSettingsService.swift @@ -38,7 +38,6 @@ class AccountSettingsService { struct Defaults { static let stallTimeout = 4.0 static let maxRetries = 3 - static let pollingInterval = 60.0 } let remote: AccountSettingsRemoteInterface diff --git a/WordPress/Classes/Services/Domains/DomainsService+AllDomains.swift b/WordPress/Classes/Services/Domains/DomainsService+AllDomains.swift index 1e28799adba8..8b092afdd25a 100644 --- a/WordPress/Classes/Services/Domains/DomainsService+AllDomains.swift +++ b/WordPress/Classes/Services/Domains/DomainsService+AllDomains.swift @@ -1,7 +1,11 @@ import Foundation import WordPressKit -extension DomainsService { +protocol DomainsServiceAllDomainsFetching { + func fetchAllDomains(resolveStatus: Bool, noWPCOM: Bool, completion: @escaping (DomainsServiceRemote.AllDomainsEndpointResult) -> Void) +} + +extension DomainsService: DomainsServiceAllDomainsFetching { /// Makes a GET request to `/v1.1/all-domains` endpoint and returns a list of domain objects. /// diff --git a/WordPress/Classes/Services/MediaCoordinator.swift b/WordPress/Classes/Services/MediaCoordinator.swift index 849d2e130805..4bffc4622698 100644 --- a/WordPress/Classes/Services/MediaCoordinator.swift +++ b/WordPress/Classes/Services/MediaCoordinator.swift @@ -456,15 +456,6 @@ class MediaCoordinator: NSObject { return cachedCoordinator(for: post)?.totalProgress ?? 0 } - /// Returns the error associated to media if any - /// - /// - Parameter media: the media object from where to fetch the associated error. - /// - Returns: the error associated to media if any - /// - func error(for media: Media) -> NSError? { - return coordinator(for: media).error(forMediaID: media.uploadID) - } - /// Returns the media object for the specified uploadID. /// /// - Parameter uploadID: the identifier for an ongoing upload diff --git a/WordPress/Classes/Services/MediaService.m b/WordPress/Classes/Services/MediaService.m index afa36666ecdc..355b7c9adac3 100644 --- a/WordPress/Classes/Services/MediaService.m +++ b/WordPress/Classes/Services/MediaService.m @@ -82,7 +82,7 @@ - (void)uploadMedia:(Media *)media void (^failureBlock)(NSError *error) = ^(NSError *error) { [self.managedObjectContext performBlock:^{ if (error) { - [self trackUploadError:error]; + [self trackUploadError:error blog:blog]; DDLogError(@"Error uploading media: %@", error); } NSError *customError = [self customMediaUploadError:error remote:remote]; @@ -165,11 +165,12 @@ - (void)uploadMedia:(Media *)media #pragma mark - Private helpers - (void)trackUploadError:(NSError *)error + blog:(Blog *)blog { if (error.code == NSURLErrorCancelled) { - [WPAppAnalytics track:WPAnalyticsStatMediaServiceUploadCanceled]; + [WPAppAnalytics track:WPAnalyticsStatMediaServiceUploadCanceled withBlog:blog]; } else { - [WPAppAnalytics track:WPAnalyticsStatMediaServiceUploadFailed error:error]; + [WPAppAnalytics track:WPAnalyticsStatMediaServiceUploadFailed error:error withBlogID:blog.dotComID]; } } diff --git a/WordPress/Classes/Services/MediaSettings.swift b/WordPress/Classes/Services/MediaSettings.swift index 9dea2d6c1822..8b01674c3d3b 100644 --- a/WordPress/Classes/Services/MediaSettings.swift +++ b/WordPress/Classes/Services/MediaSettings.swift @@ -103,23 +103,6 @@ class MediaSettings: NSObject { return 5 } } - - static func videoResolution(from value: Int) -> MediaSettings.VideoResolution { - switch value { - case 1: - return .size640x480 - case 2: - return .size1280x720 - case 3: - return .size1920x1080 - case 4: - return .size3840x2160 - case 5: - return .sizeOriginal - default: - return .sizeOriginal - } - } } // MARK: - Internal variables diff --git a/WordPress/Classes/Services/PostCoordinator.swift b/WordPress/Classes/Services/PostCoordinator.swift index 087d9f8f55be..fdabb87cd4ff 100644 --- a/WordPress/Classes/Services/PostCoordinator.swift +++ b/WordPress/Classes/Services/PostCoordinator.swift @@ -520,7 +520,8 @@ class PostCoordinator: NSObject { ActionDispatcher.dispatch(NoticeAction.dismiss) ActionDispatcher.dispatch(NoticeAction.post(notice)) - setPendingDeletion(false, post: post) + // No need to notify as the object gets deleted + setPendingDeletion(false, post: post, notify: false) } catch { if let error = error as NSError?, error.code == Constants.httpCodeForbidden { delegate?.postCoordinator(self, promptForPasswordForBlog: post.blog) @@ -532,15 +533,17 @@ class PostCoordinator: NSObject { } } - private func setPendingDeletion(_ isDeleting: Bool, post: AbstractPost) { + private func setPendingDeletion(_ isDeleting: Bool, post: AbstractPost, notify: Bool = true) { if isDeleting { pendingDeletionPostIDs.insert(post.objectID) } else { pendingDeletionPostIDs.remove(post.objectID) } - NotificationCenter.default.post(name: .postCoordinatorDidUpdate, object: self, userInfo: [ - NSUpdatedObjectsKey: Set([post]) - ]) + if notify { + NotificationCenter.default.post(name: .postCoordinatorDidUpdate, object: self, userInfo: [ + NSUpdatedObjectsKey: Set([post]) + ]) + } } private func propertiesForAnalytics(for post: AbstractPost) -> [String: AnyObject] { diff --git a/WordPress/Classes/Services/PostRepository.swift b/WordPress/Classes/Services/PostRepository.swift index 5b7679920c1a..c38c9abdc044 100644 --- a/WordPress/Classes/Services/PostRepository.swift +++ b/WordPress/Classes/Services/PostRepository.swift @@ -497,9 +497,11 @@ extension PostRepository { purgeExisting: deleteOtherLocalPosts, in: context ) - return updatedPosts.map { - guard let post = $0 as? P else { - fatalError("Expecting a \(postType) as \(type), but got \($0)") + return updatedPosts.compactMap { aPost -> TaggedManagedObjectID

? in + guard let post = aPost as? P else { + // FIXME: This issue is tracked in https://github.com/wordpress-mobile/WordPress-iOS/issues/22255 + DDLogWarn("Expecting a \(postType) as \(type), but got \(aPost)") + return nil } return TaggedManagedObjectID(post) } diff --git a/WordPress/Classes/Services/SiteVerticalsService.swift b/WordPress/Classes/Services/SiteVerticalsService.swift deleted file mode 100644 index 5e064b3a119f..000000000000 --- a/WordPress/Classes/Services/SiteVerticalsService.swift +++ /dev/null @@ -1,68 +0,0 @@ -import AutomatticTracks - -// MARK: - SiteVerticalsService - -/// Advises the caller of results related to requests for a specific site vertical. -/// -/// - success: the site vertical request succeeded with the accompanying result. -/// - failure: the site vertical request failed due to the accompanying error. -/// -public enum SiteVerticalRequestResult { - case success(SiteVertical) - case failure(SiteVerticalsError) -} - -typealias SiteVerticalRequestCompletion = (SiteVerticalRequestResult) -> () - -/// Abstracts retrieval of site verticals. -/// -protocol SiteVerticalsService { - func retrieveVertical(named verticalName: String, completion: @escaping SiteVerticalRequestCompletion) - func retrieveVerticals(request: SiteVerticalsRequest, completion: @escaping SiteVerticalsServiceCompletion) -} - -// MARK: - SiteCreationVerticalsService - -/// Retrieves candidate Site Verticals used to create a new site. -/// -final class SiteCreationVerticalsService: SiteVerticalsService { - - // MARK: Properties - - /// A facade for WPCOM services. - private let remoteService: WordPressComServiceRemote - - init(coreDataStack: CoreDataStack) { - let api = coreDataStack.performQuery({ context in - try? WPAccount.lookupDefaultWordPressComAccount(in: context)?.wordPressComRestV2Api - }) ?? WordPressComRestApi.anonymousApi(userAgent: WPUserAgent.wordPress(), localeKey: WordPressComRestApi.LocaleKeyV2) - self.remoteService = WordPressComServiceRemote(wordPressComRestApi: api) - } - - // MARK: SiteVerticalsService - - func retrieveVertical(named verticalName: String, completion: @escaping SiteVerticalRequestCompletion) { - let request = SiteVerticalsRequest(search: verticalName, limit: 1) - - remoteService.retrieveVerticals(request: request) { result in - switch result { - case .success(let verticals): - guard let vertical = verticals.first else { - WordPressAppDelegate.crashLogging?.logMessage("The verticals service should always return at least 1 match for the precise term queried.", level: .error) - completion(.failure(.serviceFailure)) - return - } - - completion(.success(vertical)) - case .failure(let error): - completion(.failure(error)) - } - } - } - - func retrieveVerticals(request: SiteVerticalsRequest, completion: @escaping SiteVerticalsServiceCompletion) { - remoteService.retrieveVerticals(request: request) { result in - completion(result) - } - } -} diff --git a/WordPress/Classes/Stores/AccountSettingsStore.swift b/WordPress/Classes/Stores/AccountSettingsStore.swift index 50cf059c504d..a6cd34f432a9 100644 --- a/WordPress/Classes/Stores/AccountSettingsStore.swift +++ b/WordPress/Classes/Stores/AccountSettingsStore.swift @@ -22,15 +22,6 @@ enum AccountSettingsState: Equatable { var succeeded: Bool { return self == .success } - - var failureMessage: String? { - switch self { - case .failure(let error): - return error - default: - return nil - } - } } enum AccountSettingsAction: Action { diff --git a/WordPress/Classes/Stores/StatsInsightsStore.swift b/WordPress/Classes/Stores/StatsInsightsStore.swift index 68bf85252461..4e5be4ae60ff 100644 --- a/WordPress/Classes/Stores/StatsInsightsStore.swift +++ b/WordPress/Classes/Stores/StatsInsightsStore.swift @@ -838,9 +838,6 @@ extension StatsInsightsStore { return state.topTagsAndCategories } - func getPostingActivity() -> StatsPostingStreakInsight? { - return state.postingActivity - } /// Summarizes the daily posting count for the month in the given date. /// Returns an array containing every day of the month and associated post count. /// @@ -947,10 +944,6 @@ extension StatsInsightsStore { return state.annualAndMostPopularTimeStatus } - var isFetchingLastPostSummary: Bool { - return lastPostSummaryStatus == .loading - } - var isFetchingOverview: Bool { /* * Use reflection here to inspect all the members of type StoreFetchingStatus diff --git a/WordPress/Classes/Stores/StatsWidgetsStore.swift b/WordPress/Classes/Stores/StatsWidgetsStore.swift index 19a713764892..ce197c2bf7df 100644 --- a/WordPress/Classes/Stores/StatsWidgetsStore.swift +++ b/WordPress/Classes/Stores/StatsWidgetsStore.swift @@ -251,7 +251,7 @@ extension StatsWidgetsStore { } let summaryData = Array(summary?.summaryData.reversed().prefix(ThisWeekWidgetStats.maxDaysToDisplay + 1) ?? []) - let stats = ThisWeekWidgetStats(days: ThisWeekWidgetStats.daysFrom(summaryData: summaryData)) + let stats = ThisWeekWidgetStats(days: ThisWeekWidgetStats.daysFrom(summaryData: summaryData.map { ThisWeekWidgetStats.Input(periodStartDate: $0.periodStartDate, viewsCount: $0.viewsCount) })) StoreContainer.shared.statsWidgets.storeHomeWidgetData(widgetType: HomeWidgetThisWeekData.self, stats: stats) case .week: WidgetCenter.shared.reloadThisWeekTimelines() diff --git a/WordPress/Classes/Stores/UserPersistentStore.swift b/WordPress/Classes/Stores/UserPersistentStore.swift deleted file mode 100644 index a4d8341ac36f..000000000000 --- a/WordPress/Classes/Stores/UserPersistentStore.swift +++ /dev/null @@ -1,126 +0,0 @@ -class UserPersistentStore: UserPersistentRepository { - static let standard = UserPersistentStore(defaultsSuiteName: defaultsSuiteName)! - private static let defaultsSuiteName = WPAppGroupName // TBD - - private let userDefaults: UserDefaults - - init?(defaultsSuiteName: String) { - guard let suiteDefaults = UserDefaults(suiteName: defaultsSuiteName) else { - return nil - } - userDefaults = suiteDefaults - } - - // MARK: - UserPeresistentRepositoryReader - func object(forKey key: String) -> Any? { - if let object = userDefaults.object(forKey: key) { - return object - } - - return UserDefaults.standard.object(forKey: key) - } - - func string(forKey key: String) -> String? { - if let string = userDefaults.string(forKey: key) { - return string - } - - return UserDefaults.standard.string(forKey: key) - } - - func bool(forKey key: String) -> Bool { - userDefaults.bool(forKey: key) || UserDefaults.standard.bool(forKey: key) - } - - func integer(forKey key: String) -> Int { - let suiteValue = userDefaults.integer(forKey: key) - if suiteValue != 0 { - return suiteValue - } - - return UserDefaults.standard.integer(forKey: key) - } - - func float(forKey key: String) -> Float { - let suiteValue = userDefaults.float(forKey: key) - if suiteValue != 0 { - return suiteValue - } - - return UserDefaults.standard.float(forKey: key) - } - - func double(forKey key: String) -> Double { - let suiteValue = userDefaults.double(forKey: key) - if suiteValue != 0 { - return suiteValue - } - - return UserDefaults.standard.double(forKey: key) - } - - func array(forKey key: String) -> [Any]? { - let suiteValue = userDefaults.array(forKey: key) - if suiteValue != nil { - return suiteValue - } - - return UserDefaults.standard.array(forKey: key) - } - - func dictionary(forKey key: String) -> [String: Any]? { - let suiteValue = userDefaults.dictionary(forKey: key) - if suiteValue != nil { - return suiteValue - } - - return UserDefaults.standard.dictionary(forKey: key) - } - - func url(forKey key: String) -> URL? { - if let url = userDefaults.url(forKey: key) { - return url - } - - return UserDefaults.standard.url(forKey: key) - } - - func dictionaryRepresentation() -> [String: Any] { - return userDefaults.dictionaryRepresentation() - } - - // MARK: - UserPersistentRepositoryWriter - func set(_ value: Any?, forKey key: String) { - userDefaults.set(value, forKey: key) - UserDefaults.standard.removeObject(forKey: key) - } - - func set(_ value: Int, forKey key: String) { - userDefaults.set(value, forKey: key) - UserDefaults.standard.removeObject(forKey: key) - } - - func set(_ value: Float, forKey key: String) { - userDefaults.set(value, forKey: key) - UserDefaults.standard.removeObject(forKey: key) - } - - func set(_ value: Double, forKey key: String) { - userDefaults.set(value, forKey: key) - UserDefaults.standard.removeObject(forKey: key) - } - - func set(_ value: Bool, forKey key: String) { - userDefaults.set(value, forKey: key) - UserDefaults.standard.removeObject(forKey: key) - } - - func set(_ url: URL?, forKey key: String) { - userDefaults.set(url, forKey: key) - UserDefaults.standard.removeObject(forKey: key) - } - - func removeObject(forKey key: String) { - userDefaults.removeObject(forKey: key) - } -} diff --git a/WordPress/Classes/System/3DTouch/WP3DTouchShortcutHandler.swift b/WordPress/Classes/System/3DTouch/WP3DTouchShortcutHandler.swift index b7f5b13e85eb..775d5d02c244 100644 --- a/WordPress/Classes/System/3DTouch/WP3DTouchShortcutHandler.swift +++ b/WordPress/Classes/System/3DTouch/WP3DTouchShortcutHandler.swift @@ -8,14 +8,6 @@ open class WP3DTouchShortcutHandler: NSObject { case Stats case Notifications - init?(fullType: String) { - guard let last = fullType.components(separatedBy: ".").last else { - return nil - } - - self.init(rawValue: last) - } - var type: String { return Bundle.main.bundleIdentifier! + ".\(self.rawValue)" } diff --git a/WordPress/Classes/System/RootViewPresenter+MeNavigation.swift b/WordPress/Classes/System/RootViewPresenter+MeNavigation.swift index 54d04943f372..e96ef7a90dba 100644 --- a/WordPress/Classes/System/RootViewPresenter+MeNavigation.swift +++ b/WordPress/Classes/System/RootViewPresenter+MeNavigation.swift @@ -24,6 +24,15 @@ extension RootViewPresenter { } } + func navigateToAllDomains() { + CATransaction.perform { + showMeScreen() + } completion: { + self.meViewController?.navigateToAllDomains() + } + } + + func navigateToAppSettings() { CATransaction.perform { showMeScreen() diff --git a/WordPress/Classes/System/WPGUIConstants.h b/WordPress/Classes/System/WPGUIConstants.h index e8cc1ba4159e..f1ae58cbb097 100644 --- a/WordPress/Classes/System/WPGUIConstants.h +++ b/WordPress/Classes/System/WPGUIConstants.h @@ -3,14 +3,6 @@ extern const CGFloat WPAlphaFull; extern const CGFloat WPAlphaZero; -extern const CGFloat WPColorFull; -extern const CGFloat WPColorZero; - -extern const NSTimeInterval WPAnimationDurationSlow; extern const NSTimeInterval WPAnimationDurationDefault; -extern const NSTimeInterval WPAnimationDurationFast; -extern const NSTimeInterval WPAnimationDurationFaster; -extern const CGFloat WPTableViewTopMargin; extern const CGFloat WPTableViewDefaultRowHeight; -extern const UIEdgeInsets WPTableViewContentInsets; diff --git a/WordPress/Classes/System/WPGUIConstants.m b/WordPress/Classes/System/WPGUIConstants.m index 94b6f9dd7f63..90e3f9161ba2 100644 --- a/WordPress/Classes/System/WPGUIConstants.m +++ b/WordPress/Classes/System/WPGUIConstants.m @@ -3,14 +3,6 @@ const CGFloat WPAlphaFull = 1.0; const CGFloat WPAlphaZero = 0.0; -const CGFloat WPColorFull = 1.0; -const CGFloat WPColorZero = 0.0; - -const NSTimeInterval WPAnimationDurationSlow = 0.6; const NSTimeInterval WPAnimationDurationDefault = 0.33; -const NSTimeInterval WPAnimationDurationFast = 0.15; -const NSTimeInterval WPAnimationDurationFaster = 0.07; -const CGFloat WPTableViewTopMargin = 40.0; const CGFloat WPTableViewDefaultRowHeight = 44.0; -const UIEdgeInsets WPTableViewContentInsets = {40.0, 0.0, 0.0, 0.0}; diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index 1c1b578192f0..fd46661e248e 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -17,7 +17,6 @@ #import "CommentService.h" #import "CommentsViewController+Network.h" -#import "Confirmable.h" #import "Constants.h" #import "CoreDataStack.h" #import "Coordinate.h" diff --git a/WordPress/Classes/System/WordPressAppDelegate.swift b/WordPress/Classes/System/WordPressAppDelegate.swift index 84733d44fa2f..68417181fd29 100644 --- a/WordPress/Classes/System/WordPressAppDelegate.swift +++ b/WordPress/Classes/System/WordPressAppDelegate.swift @@ -344,10 +344,6 @@ class WordPressAppDelegate: UIResponder, UIApplicationDelegate { pingHubManager = PingHubManager() } - private func setupShortcutCreator() { - shortcutCreator = WP3DTouchShortcutCreator() - } - private func setupNoticePresenter() { noticePresenter = NoticePresenter() } @@ -643,20 +639,6 @@ extension WordPressAppDelegate { } } - var isWelcomeScreenVisible: Bool { - get { - guard let presentedViewController = window?.rootViewController?.presentedViewController as? UINavigationController else { - return false - } - - guard let visibleViewController = presentedViewController.visibleViewController else { - return false - } - - return WordPressAuthenticator.isAuthenticationViewController(visibleViewController) - } - } - @objc func trackLogoutIfNeeded() { if AccountHelper.isLoggedIn == false { WPAnalytics.track(.logout) diff --git a/WordPress/Classes/Utility/Analytics/DashboardDynamicCardAnalyticsEvent.swift b/WordPress/Classes/Utility/Analytics/DashboardDynamicCardAnalyticsEvent.swift new file mode 100644 index 000000000000..e61bbc6e6b92 --- /dev/null +++ b/WordPress/Classes/Utility/Analytics/DashboardDynamicCardAnalyticsEvent.swift @@ -0,0 +1,32 @@ +enum DashboardDynamicCardAnalyticsEvent: Hashable { + + case cardShown(id: String) + case cardTapped(id: String, url: String?) + case cardCtaTapped(id: String, url: String?) + + var name: String { + switch self { + case .cardShown: return "dynamic_dashboard_card_shown" + case .cardTapped: return "dynamic_dashboard_card_tapped" + case .cardCtaTapped: return "dynamic_dashboard_card_cta_tapped" + } + } + + var properties: [String: String] { + switch self { + case .cardShown(let id): + return [Keys.id: id] + case .cardTapped(let id, let url), .cardCtaTapped(let id, let url): + var props = [Keys.id: id] + if let url { + props[Keys.url] = url + } + return props + } + } + + private enum Keys { + static let id = "id" + static let url = "url" + } +} diff --git a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift index a8e11e6d9c18..bf617ab86ea4 100644 --- a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift +++ b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift @@ -251,7 +251,7 @@ import Foundation case addDomainTapped case domainsSearchTransferDomainTapped case domainsSearchRowSelected - case siteSwitcherDomainSiteSelected + case siteSwitcherSiteSelected case purchaseDomainScreenShown case purchaseDomainGetDomainTapped case purchaseDomainChooseSiteTapped @@ -268,6 +268,7 @@ import Foundation // My Site: Header Actions case mySiteHeaderMoreTapped + case mySiteHeaderAddSiteTapped case mySiteHeaderPersonalizeHomeTapped // Site Switcher @@ -999,8 +1000,8 @@ import Foundation return "domains_dashboard_domains_search_transfer_domain_tapped" case .domainsSearchRowSelected: return "domain_management_domains_search_row_selected" - case .siteSwitcherDomainSiteSelected: - return "site_switcher_domain_site_selected" + case .siteSwitcherSiteSelected: + return "site_switcher_site_selected" case .purchaseDomainScreenShown: return "domain_management_purchase_domain_screen_shown" case .purchaseDomainGetDomainTapped: @@ -1027,6 +1028,8 @@ import Foundation // My Site Header Actions case .mySiteHeaderMoreTapped: return "my_site_header_more_tapped" + case .mySiteHeaderAddSiteTapped: + return "my_site_header_add_site_tapped" case .mySiteHeaderPersonalizeHomeTapped: return "my_site_header_personalize_home_tapped" diff --git a/WordPress/Classes/Utility/Analytics/WPAppAnalytics.h b/WordPress/Classes/Utility/Analytics/WPAppAnalytics.h index c8aae9f55d0d..8d4e5fcf8192 100644 --- a/WordPress/Classes/Utility/Analytics/WPAppAnalytics.h +++ b/WordPress/Classes/Utility/Analytics/WPAppAnalytics.h @@ -141,4 +141,8 @@ extern NSString * const WPAppAnalyticsValueSiteTypeP2; */ + (void)track:(WPAnalyticsStat)stat error:(NSError *)error; +/** + * @brief Track Anaylytics with associate error that is translated to properties, along with available blog details + */ ++ (void)track:(WPAnalyticsStat)stat error:(NSError *)error withBlogID:(NSNumber *)blogID; @end diff --git a/WordPress/Classes/Utility/Analytics/WPAppAnalytics.m b/WordPress/Classes/Utility/Analytics/WPAppAnalytics.m index b8ff12e99628..526b06ca2bbc 100644 --- a/WordPress/Classes/Utility/Analytics/WPAppAnalytics.m +++ b/WordPress/Classes/Utility/Analytics/WPAppAnalytics.m @@ -338,14 +338,18 @@ + (void)track:(WPAnalyticsStat)stat withProperties:(NSDictionary *)properties { [WPAnalytics track:stat withProperties:properties]; } -+ (void)track:(WPAnalyticsStat)stat error:(NSError * _Nonnull)error { ++ (void)track:(WPAnalyticsStat)stat error:(NSError * _Nonnull)error withBlogID:(NSNumber *)blogID { NSError *err = [self sanitizedErrorFromError:error]; NSDictionary *properties = @{ @"error_code": [@(err.code) stringValue], @"error_domain": err.domain, @"error_description": err.description }; - [self track:stat withProperties: properties]; + [self track:stat withProperties: properties withBlogID:blogID]; +} + ++ (void)track:(WPAnalyticsStat)stat error:(NSError * _Nonnull)error { + [self track:stat error:error withBlogID:nil]; } /** diff --git a/WordPress/Classes/Utility/App Configuration/AppStyleGuide.swift b/WordPress/Classes/Utility/App Configuration/AppStyleGuide.swift index 0853ed209f2f..cfea5805e207 100644 --- a/WordPress/Classes/Utility/App Configuration/AppStyleGuide.swift +++ b/WordPress/Classes/Utility/App Configuration/AppStyleGuide.swift @@ -29,7 +29,6 @@ extension AppStyleGuide { // MARK: - Images extension AppStyleGuide { static let mySiteTabIcon = UIImage(named: "icon-tab-mysites") - static let aboutAppIcon = UIImage(named: "icon-wp") static let quickStartExistingSite = UIImage(named: "wp-illustration-quickstart-existing-site") } diff --git a/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift b/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift index f7ee5cd7d8d6..0e8aac2ac1b0 100644 --- a/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift +++ b/WordPress/Classes/Utility/BackgroundTasks/WeeklyRoundupBackgroundTask.swift @@ -324,7 +324,6 @@ class WeeklyRoundupBackgroundTask: BackgroundTask { enum RunError: Error { case unableToScheduleDynamicNotification(reason: String) - case staticNotificationAlreadyDelivered } private let eventTracker: NotificationEventTracker diff --git a/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift b/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift index 1dadb8d2c0f4..ea03ec22d95a 100644 --- a/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift +++ b/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift @@ -29,9 +29,7 @@ class BloggingRemindersScheduler { // MARK: - Error Handling enum Error: Swift.Error { - case cantRetrieveContainerForAppGroup(appGroupName: String) case needsPermissionForPushNotifications - case noPreviousScheduleAttempt } // MARK: - Schedule Data Containers diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index 41848500b11e..8e5336fa6f94 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -8,10 +8,10 @@ enum FeatureFlag: Int, CaseIterable { case siteIconCreator case statsNewInsights case betaSiteDesigns - case personalizeHomeTab case commentModerationUpdate case compliancePopover case googleDomainsCard + case newTabIcons /// Returns a boolean indicating if the feature is enabled var enabled: Bool { @@ -32,14 +32,14 @@ enum FeatureFlag: Int, CaseIterable { return AppConfiguration.statsRevampV2Enabled case .betaSiteDesigns: return false - case .personalizeHomeTab: - return AppConfiguration.isJetpack case .commentModerationUpdate: return false case .compliancePopover: return true case .googleDomainsCard: return false + case .newTabIcons: + return BuildConfiguration.current == .localDeveloper } } @@ -74,14 +74,14 @@ extension FeatureFlag { return "New Cards for Stats Insights" case .betaSiteDesigns: return "Fetch Beta Site Designs" - case .personalizeHomeTab: - return "Personalize Home Tab" case .commentModerationUpdate: return "Comments Moderation Update" case .compliancePopover: return "Compliance Popover" case .googleDomainsCard: return "Google Domains Promotional Card" + case .newTabIcons: + return "New Tab Icons" } } } diff --git a/WordPress/Classes/Utility/ContentCoordinator.swift b/WordPress/Classes/Utility/ContentCoordinator.swift index 99c4bd1fd2e7..1a1ced6ec2c5 100644 --- a/WordPress/Classes/Utility/ContentCoordinator.swift +++ b/WordPress/Classes/Utility/ContentCoordinator.swift @@ -19,7 +19,6 @@ protocol ContentCoordinator { struct DefaultContentCoordinator: ContentCoordinator { enum DisplayError: Error { case missingParameter - case unsupportedFeature case unsupportedType } diff --git a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift index 1932117b4bc5..ad6751cab2e6 100644 --- a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift +++ b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift @@ -99,10 +99,6 @@ class GutenbergSettings { return database.bool(forKey: Key.showPhase2Dialog(forBlogURL: blog.url)) } - func setShowPhase2Dialog(_ showDialog: Bool, for blog: Blog) { - setShowPhase2Dialog(showDialog, forBlogURL: blog.url) - } - func setShowPhase2Dialog(_ showDialog: Bool, forBlogURL url: String?) { database.set(showDialog, forKey: Key.showPhase2Dialog(forBlogURL: url)) } diff --git a/WordPress/Classes/Utility/FormattableContent/FormattableRangesFactory.swift b/WordPress/Classes/Utility/FormattableContent/FormattableRangesFactory.swift index 6d798afb428f..47cf514d73ce 100644 --- a/WordPress/Classes/Utility/FormattableContent/FormattableRangesFactory.swift +++ b/WordPress/Classes/Utility/FormattableContent/FormattableRangesFactory.swift @@ -27,7 +27,6 @@ private enum RangeKeys { static let url = "url" static let indices = "indices" static let id = "id" - static let value = "value" static let siteId = "site_id" static let postId = "post_id" } diff --git a/WordPress/Classes/Utility/FormattableContent/FormattableTextContent.swift b/WordPress/Classes/Utility/FormattableContent/FormattableTextContent.swift index 112c0764a2f4..41267c6b027d 100644 --- a/WordPress/Classes/Utility/FormattableContent/FormattableTextContent.swift +++ b/WordPress/Classes/Utility/FormattableContent/FormattableTextContent.swift @@ -25,13 +25,3 @@ class FormattableTextContent: FormattableContent { self.ranges = ranges } } - -extension FormattableMediaItem { - fileprivate enum MediaKeys { - static let RawType = "type" - static let URL = "url" - static let Indices = "indices" - static let Width = "width" - static let Height = "height" - } -} diff --git a/WordPress/Classes/Utility/ImmuTableViewController.swift b/WordPress/Classes/Utility/ImmuTableViewController.swift index dfdb008dcae5..b69f7aa90289 100644 --- a/WordPress/Classes/Utility/ImmuTableViewController.swift +++ b/WordPress/Classes/Utility/ImmuTableViewController.swift @@ -26,15 +26,6 @@ extension ImmuTablePresenter where Self: UIViewController { } } -extension ImmuTablePresenter { - func prompt(_ controllerGenerator: @escaping (ImmuTableRow) -> T) -> ImmuTableAction where T: Confirmable { - return present({ - let controller = controllerGenerator($0) - return PromptViewController(controller) - }) - } -} - protocol ImmuTableController { var title: String { get } var immuTableRows: [ImmuTableRow.Type] { get } diff --git a/WordPress/Classes/Utility/Kanvas/KanvasCameraCustomUI.swift b/WordPress/Classes/Utility/Kanvas/KanvasCameraCustomUI.swift index 0e28c9a937b5..82c331a32aea 100644 --- a/WordPress/Classes/Utility/Kanvas/KanvasCameraCustomUI.swift +++ b/WordPress/Classes/Utility/Kanvas/KanvasCameraCustomUI.swift @@ -7,13 +7,9 @@ public class KanvasCustomUI { public static let shared = KanvasCustomUI() private static let brightBlue = UIColor.muriel(color: MurielColor(name: .blue)).color(for: UITraitCollection(userInterfaceStyle: .dark)) - private static let brightPurple = UIColor.muriel(color: MurielColor(name: .purple)).color(for: UITraitCollection(userInterfaceStyle: .dark)) private static let brightPink = UIColor.muriel(color: MurielColor(name: .pink)).color(for: UITraitCollection(userInterfaceStyle: .dark)) - private static let brightYellow = UIColor.muriel(color: MurielColor(name: .yellow)).color(for: UITraitCollection(userInterfaceStyle: .dark)) - private static let brightGreen = UIColor.muriel(color: MurielColor(name: .green)).color(for: UITraitCollection(userInterfaceStyle: .dark)) private static let brightRed = UIColor.muriel(color: MurielColor(name: .red)).color(for: UITraitCollection(userInterfaceStyle: .dark)) private static let brightOrange = UIColor.muriel(color: MurielColor(name: .orange)).color(for: UITraitCollection(userInterfaceStyle: .dark)) - private static let white = UIColor.white static private var firstPrimary: UIColor { return KanvasCustomUI.primaryColors.first ?? UIColor.blue @@ -107,72 +103,6 @@ public class KanvasCustomUI { } } -enum CustomKanvasFonts: CaseIterable { - case libreBaskerville - case nunitoBold - case pacifico - case oswaldUpper - case shrikhand - case spaceMonoBold - - struct Shadow { - let radius: CGFloat - let offset: CGPoint - let color: UIColor - } - - var name: String { - switch self { - case .libreBaskerville: - return "LibreBaskerville-Regular" - case .nunitoBold: - return "Nunito-Bold" - case .pacifico: - return "Pacifico-Regular" - case .oswaldUpper: - return "Oswald-Regular" - case .shrikhand: - return "Shrikhand-Regular" - case .spaceMonoBold: - return "SpaceMono-Bold" - } - } - - var size: Int { - switch self { - case .libreBaskerville: - return 20 - case .nunitoBold: - return 24 - case .pacifico: - return 24 - case .oswaldUpper: - return 22 - case .shrikhand: - return 22 - case .spaceMonoBold: - return 20 - } - } - - var shadow: Shadow? { - switch self { - case .libreBaskerville: - return nil - case .nunitoBold: - return Shadow(radius: 1, offset: CGPoint(x: 0, y: 2), color: UIColor.black.withAlphaComponent(75)) - case .pacifico: - return Shadow(radius: 5, offset: .zero, color: UIColor.white.withAlphaComponent(50)) - case .oswaldUpper: - return nil - case .shrikhand: - return Shadow(radius: 1, offset: CGPoint(x: 1, y: 2), color: UIColor.black.withAlphaComponent(75)) - case .spaceMonoBold: - return nil - } - } -} - extension UIFont { static func libreBaskerville(fontSize: CGFloat) -> UIFont { diff --git a/WordPress/Classes/Utility/Media/ImageDecoder.swift b/WordPress/Classes/Utility/Media/ImageDecoder.swift index da5be0db9221..cfdca0840cc9 100644 --- a/WordPress/Classes/Utility/Media/ImageDecoder.swift +++ b/WordPress/Classes/Utility/Media/ImageDecoder.swift @@ -69,9 +69,3 @@ private extension Data { } } } - -private extension UIImage { - var sizeInPixels: CGSize { - size.scaled(by: scale) - } -} diff --git a/WordPress/Classes/Utility/Media/ImageDownloader.swift b/WordPress/Classes/Utility/Media/ImageDownloader.swift index 00a879badf9e..fc4634120c48 100644 --- a/WordPress/Classes/Utility/Media/ImageDownloader.swift +++ b/WordPress/Classes/Utility/Media/ImageDownloader.swift @@ -111,7 +111,7 @@ actor ImageDownloader { private func validate(response: URLResponse) throws { guard let response = response as? HTTPURLResponse else { - throw ImageDownloaderError.unexpectedResponse + return // The request was made not over HTTP, e.g. a `file://` request } guard (200..<400).contains(response.statusCode) else { throw ImageDownloaderError.unacceptableStatusCode(response.statusCode) @@ -183,7 +183,6 @@ private struct AnonymousImageDownloadTask: ImageDownloaderTask { } enum ImageDownloaderError: Error { - case unexpectedResponse case unacceptableStatusCode(_ statusCode: Int?) } diff --git a/WordPress/Classes/Utility/Media/ImageLoader.swift b/WordPress/Classes/Utility/Media/ImageLoader.swift index 228296da8d69..4b57d1089f8e 100644 --- a/WordPress/Classes/Utility/Media/ImageLoader.swift +++ b/WordPress/Classes/Utility/Media/ImageLoader.swift @@ -39,14 +39,6 @@ import AutomatticTracks private var placeholder: UIImage? private var selectedPhotonQuality: UInt = Constants.defaultPhotonQuality - private lazy var assetRequestOptions: PHImageRequestOptions = { - let requestOptions = PHImageRequestOptions() - requestOptions.resizeMode = .fast - requestOptions.deliveryMode = .opportunistic - requestOptions.isNetworkAccessAllowed = true - return requestOptions - }() - @objc convenience init(imageView: CachedAnimatedImageView, gifStrategy: GIFStrategy = .mediumGIFs) { self.init(imageView: imageView, gifStrategy: gifStrategy, loadingIndicator: nil) } @@ -315,11 +307,6 @@ import AutomatticTracks self.errorHandler?(error) } } - - private func createError(description: String, key: String = NSLocalizedFailureReasonErrorKey) -> NSError { - let userInfo = [key: description] - return NSError(domain: ImageLoader.classNameWithoutNamespaces(), code: 0, userInfo: userInfo) - } } // MARK: - Loading Media object diff --git a/WordPress/Classes/Utility/Media/MediaThumbnailExporter.swift b/WordPress/Classes/Utility/Media/MediaThumbnailExporter.swift index 54843b33fb96..e78344779d7d 100644 --- a/WordPress/Classes/Utility/Media/MediaThumbnailExporter.swift +++ b/WordPress/Classes/Utility/Media/MediaThumbnailExporter.swift @@ -171,17 +171,6 @@ class MediaThumbnailExporter: MediaExporter { } } - /// Export an existing image as a thumbnail image, based on the exporter options. - /// - @discardableResult func exportThumbnail(forImage image: UIImage, onCompletion: @escaping OnThumbnailExport, onError: @escaping OnExportError) -> Progress { - let exporter = MediaImageExporter(image: image, filename: UUID().uuidString) - exporter.mediaDirectoryType = .temporary - exporter.options = imageExporterOptions - return exporter.export(onCompletion: { (export) in - self.exportImageToThumbnailCache(export, onCompletion: onCompletion, onError: onError) - }, onError: onError) - } - /// Export a known video at the URL, being either a file URL or a remote URL. /// @discardableResult func exportThumbnail(forVideoURL url: URL, onCompletion: @escaping OnThumbnailExport, onError: @escaping OnExportError) -> Progress { @@ -303,21 +292,6 @@ extension MediaThumbnailExporter { token.progress?.cancel() } } - - func exportThumbnail(forImage image: UIImage) async throws -> (ThumbnailIdentifier, MediaExport) { - let token = MediaExportCancelationToken() - return try await withTaskCancellationHandler { - try await withUnsafeThrowingContinuation { continuation in - token.progress = exportThumbnail(forImage: image, onCompletion: { - continuation.resume(returning: ($0, $1)) - }, onError: { - continuation.resume(throwing: $0) - }) - } - } onCancel: { - token.progress?.cancel() - } - } } private final class MediaExportCancelationToken { diff --git a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift index 2a77e064c6e5..490c310f5d74 100644 --- a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift +++ b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift @@ -10,10 +10,6 @@ import Foundation /// class RequestAuthenticator: NSObject { - enum Error: Swift.Error { - case atomicSiteWithoutDotComID(blog: Blog) - } - enum DotComAuthenticationType { case regular case regularMapped(siteID: Int) diff --git a/WordPress/Classes/Utility/PushNotificationsManager.swift b/WordPress/Classes/Utility/PushNotificationsManager.swift index ee24f7d720ae..84be3f3dc2d9 100644 --- a/WordPress/Classes/Utility/PushNotificationsManager.swift +++ b/WordPress/Classes/Utility/PushNotificationsManager.swift @@ -424,7 +424,6 @@ extension PushNotificationsManager { static let originKey = "origin" static let badgeResetValue = "badge-reset" static let local = "qs-local-notification" - static let bloggingPrompts = "blogging-prompts-notification" } enum Tracking { diff --git a/WordPress/Classes/Utility/Universal Links/Route.swift b/WordPress/Classes/Utility/Universal Links/Route.swift index a17391cca58e..2be5f73392ae 100644 --- a/WordPress/Classes/Utility/Universal Links/Route.swift +++ b/WordPress/Classes/Utility/Universal Links/Route.swift @@ -91,7 +91,7 @@ extension Route { /// enum DeepLinkSource: Equatable { case link - case banner + case banner(campaign: String? = nil) case email(campaign: String) case widget case lockScreenWidget @@ -123,6 +123,8 @@ enum DeepLinkSource: Equatable { switch self { case .email(let campaign): return campaign + case .banner(let campaign): + return campaign default: return nil } diff --git a/WordPress/Classes/Utility/Universal Links/Routes+Banners.swift b/WordPress/Classes/Utility/Universal Links/Routes+Banners.swift index 14f4408c840c..08f5a67a44ff 100644 --- a/WordPress/Classes/Utility/Universal Links/Routes+Banners.swift +++ b/WordPress/Classes/Utility/Universal Links/Routes+Banners.swift @@ -12,7 +12,7 @@ import Foundation struct AppBannerRoute: Route { let path = "/get" let section: DeepLinkSection? = nil - let source: DeepLinkSource = .banner + let source: DeepLinkSource = .banner() let shouldTrack: Bool = false let jetpackPowered: Bool = false @@ -28,15 +28,34 @@ extension AppBannerRoute: NavigationAction { return } + let campaign = AppBannerCampaign.getCampaign(from: values) + // Convert the fragment into a URL and ask the link router to handle // it like a normal route. var components = URLComponents() components.scheme = "https" components.host = "wordpress.com" components.path = fragment + if let campaign { + components.queryItems = [ + URLQueryItem(name: "campaign", value: campaign) + ] + } if let url = components.url { - router.handle(url: url, shouldTrack: true, source: .banner) + router.handle(url: url, shouldTrack: true, source: .banner(campaign: campaign)) + } + } +} + +enum AppBannerCampaign: String { + case qrCodeMedia = "qr-code-media" + + static func getCampaign(from values: [String: String]) -> String? { + guard let url = values[MatchedRouteURLComponentKey.url.rawValue], + let queryItems = URLComponents(string: url)?.queryItems else { + return nil } + return queryItems.first(where: { $0.name == "campaign" })?.value } } diff --git a/WordPress/Classes/Utility/Universal Links/Routes+Me.swift b/WordPress/Classes/Utility/Universal Links/Routes+Me.swift index 87258b3c84f9..9216bffc5f00 100644 --- a/WordPress/Classes/Utility/Universal Links/Routes+Me.swift +++ b/WordPress/Classes/Utility/Universal Links/Routes+Me.swift @@ -7,6 +7,13 @@ struct MeRoute: Route { let jetpackPowered: Bool = false } +struct MeAllDomainsRoute: Route { + let path = "/domains/manage" + let section: DeepLinkSection? = .me + let action: NavigationAction = MeNavigationAction.allDomains + let jetpackPowered: Bool = true +} + struct MeAccountSettingsRoute: Route { let path = "/me/account" let section: DeepLinkSection? = .me @@ -25,6 +32,7 @@ enum MeNavigationAction: NavigationAction { case root case accountSettings case notificationSettings + case allDomains func perform(_ values: [String: String] = [:], source: UIViewController? = nil, router: LinkRouter) { switch self { @@ -34,6 +42,8 @@ enum MeNavigationAction: NavigationAction { RootViewCoordinator.sharedPresenter.navigateToAccountSettings() case .notificationSettings: RootViewCoordinator.sharedPresenter.switchNotificationsTabToNotificationSettings() + case .allDomains: + RootViewCoordinator.sharedPresenter.navigateToAllDomains() } } } diff --git a/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift b/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift index f8ae48a68001..4c12fcff4a3b 100644 --- a/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift +++ b/WordPress/Classes/Utility/Universal Links/Routes+MySites.swift @@ -66,9 +66,17 @@ extension MySitesRoute: Route { extension MySitesRoute: NavigationAction { func perform(_ values: [String: String], source: UIViewController? = nil, router: LinkRouter) { let coordinator = RootViewCoordinator.sharedPresenter.mySitesCoordinator + let campaign = AppBannerCampaign.getCampaign(from: values) guard let blog = blog(from: values) else { - WPAppAnalytics.track(.deepLinkFailed, withProperties: ["route": path]) + var properties: [AnyHashable: Any] = [ + "route": path, + "error": "invalid_site_id" + ] + if let campaign { + properties["campaign"] = campaign + } + WPAppAnalytics.track(.deepLinkFailed, withProperties: properties) if failAndBounce(values) == false { coordinator.showRootViewController() @@ -84,7 +92,11 @@ extension MySitesRoute: NavigationAction { case .posts: coordinator.showPosts(for: blog) case .media: - coordinator.showMedia(for: blog) + if campaign.flatMap(AppBannerCampaign.init) == .qrCodeMedia { + coordinator.showMediaPicker(for: blog) + } else { + coordinator.showMedia(for: blog) + } case .comments: coordinator.showComments(for: blog) case .sharing: diff --git a/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift b/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift index a1ed4e922c46..510899c684ef 100644 --- a/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift +++ b/WordPress/Classes/Utility/Universal Links/UniversalLinkRouter.swift @@ -45,6 +45,7 @@ struct UniversalLinkRouter: LinkRouter { static let meRoutes: [Route] = [ MeRoute(), MeAccountSettingsRoute(), + MeAllDomainsRoute(), MeNotificationSettingsRoute() ] diff --git a/WordPress/Classes/ViewRelated/Activity/ActivityListViewModel.swift b/WordPress/Classes/ViewRelated/Activity/ActivityListViewModel.swift index 74f0e61434a0..65181c36ae9a 100644 --- a/WordPress/Classes/ViewRelated/Activity/ActivityListViewModel.swift +++ b/WordPress/Classes/ViewRelated/Activity/ActivityListViewModel.swift @@ -25,7 +25,6 @@ class ActivityListViewModel: Observable { private(set) var before: Date? private(set) var selectedGroups: [ActivityGroup] = [] - var errorViewModel: NoResultsViewController.Model? private(set) var refreshing = false { didSet { if refreshing != oldValue { diff --git a/WordPress/Classes/ViewRelated/Activity/BaseActivityListViewController.swift b/WordPress/Classes/ViewRelated/Activity/BaseActivityListViewController.swift index 5567bfee0ca3..a227643b5647 100644 --- a/WordPress/Classes/ViewRelated/Activity/BaseActivityListViewController.swift +++ b/WordPress/Classes/ViewRelated/Activity/BaseActivityListViewController.swift @@ -429,16 +429,6 @@ extension BaseActivityListViewController: ActivityPresenter { } } -// MARK: - Restores handling - -extension BaseActivityListViewController { - - fileprivate func restoreSiteToRewindID(_ rewindID: String) { - navigationController?.popToViewController(self, animated: true) - store.actionDispatcher.dispatch(ActivityAction.rewind(site: site, rewindID: rewindID)) - } -} - // MARK: - NoResults Handling private extension BaseActivityListViewController { diff --git a/WordPress/Classes/ViewRelated/Activity/FormattableContent/FormattableActivity.swift b/WordPress/Classes/ViewRelated/Activity/FormattableContent/FormattableActivity.swift index b125f5a69505..969dd9e0c74c 100644 --- a/WordPress/Classes/ViewRelated/Activity/FormattableContent/FormattableActivity.swift +++ b/WordPress/Classes/ViewRelated/Activity/FormattableContent/FormattableActivity.swift @@ -3,7 +3,6 @@ class FormattableActivity { let activity: Activity private let formatter = FormattableContentFormatter() - private var cachedContentGroup: FormattableContentGroup? = nil private var contentGroup: FormattableContentGroup? { guard let content = activity.content as? [String: AnyObject], content.isEmpty == false else { diff --git a/WordPress/Classes/ViewRelated/Activity/FormattableContent/Ranges/ActivityPluginRange.swift b/WordPress/Classes/ViewRelated/Activity/FormattableContent/Ranges/ActivityPluginRange.swift index 8635a6cb9346..73c79274292f 100644 --- a/WordPress/Classes/ViewRelated/Activity/FormattableContent/Ranges/ActivityPluginRange.swift +++ b/WordPress/Classes/ViewRelated/Activity/FormattableContent/Ranges/ActivityPluginRange.swift @@ -17,9 +17,4 @@ class ActivityPluginRange: ActivityRange { private var urlString: String { return "https://wordpress.com/plugins/\(pluginSlug)/\(siteSlug)" } - - private static func urlWith(pluginSlug: String, siteSlug: String) -> URL? { - let urlString = "https://wordpress.com/plugins/\(pluginSlug)/\(siteSlug)" - return URL(string: urlString) - } } diff --git a/WordPress/Classes/ViewRelated/Activity/WPStyleGuide+Activity.swift b/WordPress/Classes/ViewRelated/Activity/WPStyleGuide+Activity.swift index cc281d7a36dc..5faa20bae5d2 100644 --- a/WordPress/Classes/ViewRelated/Activity/WPStyleGuide+Activity.swift +++ b/WordPress/Classes/ViewRelated/Activity/WPStyleGuide+Activity.swift @@ -24,39 +24,16 @@ extension WPStyleGuide { .foregroundColor: UIColor.text] } - public static func gravatarPlaceholderImage() -> UIImage { - return gravatar - } - public static func summaryRegularStyle() -> [NSAttributedString.Key: Any] { return [.paragraphStyle: summaryParagraph, .font: summaryRegularFont, .foregroundColor: UIColor.text] } - public static func summaryBoldStyle() -> [NSAttributedString.Key: Any] { - return [.paragraphStyle: summaryParagraph, - .font: summaryBoldFont, - .foregroundColor: UIColor.text] - } - - public static func timestampStyle() -> [NSAttributedString.Key: Any] { - return [.font: timestampFont, - .foregroundColor: UIColor.textSubtle] - } - public static func backgroundColor() -> UIColor { return .listForeground } - public static func backgroundDiscardedColor() -> UIColor { - return .neutral(.shade5) - } - - public static func backgroundRewindableColor() -> UIColor { - return .primaryLight - } - public static func getGridiconTypeForActivity(_ activity: Activity) -> GridiconType? { return stringToGridiconTypeMapping[activity.gridicon] } @@ -104,20 +81,10 @@ extension WPStyleGuide { return WPStyleGuide.fontForTextStyle(.body, symbolicTraits: .traitItalic) } - fileprivate static let gravatar = UIImage(named: "gravatar")! - - private static var timestampFont: UIFont { - return WPStyleGuide.fontForTextStyle(.caption1) - } - private static var summaryRegularFont: UIFont { return WPStyleGuide.fontForTextStyle(.footnote) } - private static var summaryBoldFont: UIFont { - return WPStyleGuide.fontForTextStyle(.footnote, fontWeight: .semibold) - } - private static var summaryLineSize: CGFloat { return WPStyleGuide.fontSizeForTextStyle(.footnote) * 1.3 } diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 2f389ab8e7e6..6f7476dbb537 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -2171,10 +2171,6 @@ private extension AztecPostViewController { extension AztecPostViewController { - private func stopEditing() { - view.endEditing(true) - } - func contentByStrippingMediaAttachments() -> String { if editorView.editingMode == .html { setHTML(htmlTextView.text) @@ -3217,9 +3213,6 @@ extension AztecPostViewController { } struct Constants { - static let defaultMargin = CGFloat(20) - static let blogPickerCompactSize = CGSize(width: 125, height: 30) - static let blogPickerRegularSize = CGSize(width: 300, height: 30) static let savingDraftButtonSize = CGSize(width: 130, height: 30) static let uploadingButtonSize = CGSize(width: 150, height: 30) static let moreAttachmentText = "more" @@ -3309,11 +3302,6 @@ extension AztecPostViewController { static let monospace = UIFont(name: "Menlo-Regular", size: 16.0)! } - struct Restoration { - static let restorationIdentifier = "AztecPostViewController" - static let postIdentifierKey = AbstractPost.classNameWithoutNamespaces() - } - struct MediaUploadingCancelAlert { static let title = NSLocalizedString("Cancel media uploads", comment: "Dialog box title for when the user is canceling an upload.") static let message = NSLocalizedString("You are currently uploading media. This action will cancel uploads in progress.\n\nAre you sure?", comment: "This prompt is displayed when the user attempts to stop media uploads in the post editor.") diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift index 58c072266ff0..76726b6c7f5e 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift @@ -29,11 +29,6 @@ final class BlogDashboardViewController: UIViewController { return refreshControl }() - /// The "My Site" parent view controller - var mySiteViewController: MySiteViewController? { - return parent as? MySiteViewController - } - /// The "My Site" main scroll view var mySiteScrollView: UIScrollView? { return view.superview?.superview as? UIScrollView @@ -321,7 +316,6 @@ extension BlogDashboardViewController { private enum Constants { - static let estimatedWidth: CGFloat = 100 static let estimatedHeight: CGFloat = 44 static let horizontalSectionInset: CGFloat = 12 static let verticalSectionInset: CGFloat = 20 diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardCardConfigurable.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardCardConfigurable.swift index e2536e9310ec..086080893a92 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardCardConfigurable.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardCardConfigurable.swift @@ -1,10 +1,29 @@ import Foundation protocol BlogDashboardCardConfigurable { + func configure(blog: Blog, viewController: BlogDashboardViewController?, model: DashboardCardModel) func configure(blog: Blog, viewController: BlogDashboardViewController?, apiResponse: BlogDashboardRemoteEntity?) + func configure(blog: Blog, viewController: BlogDashboardViewController?, model: DashboardDynamicCardModel) var row: Int { get set } } +extension BlogDashboardCardConfigurable { + func configure(blog: Blog, viewController: BlogDashboardViewController?, apiResponse: BlogDashboardRemoteEntity?) { + } + + func configure(blog: Blog, viewController: BlogDashboardViewController?, model: DashboardDynamicCardModel) { + } + + func configure(blog: Blog, viewController: BlogDashboardViewController?, model: DashboardCardModel) { + switch model { + case .normal(let model): + self.configure(blog: blog, viewController: viewController, apiResponse: model.apiResponse) + case .dynamic(let model): + self.configure(blog: blog, viewController: viewController, model: model) + } + } +} + extension BlogDashboardCardConfigurable where Self: UIView { var row: Int { get { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardDynamicCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardDynamicCardCell.swift new file mode 100644 index 000000000000..ce6d654b324b --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardDynamicCardCell.swift @@ -0,0 +1,126 @@ +import UIKit +import SwiftUI + +final class BlogDashboardDynamicCardCell: DashboardCollectionViewCell { + + // MARK: - Properties + + private var coordinator: BlogDashboardDynamicCardCoordinator? + + // MARK: - Views + + private let frameView = BlogDashboardCardFrameView() + private weak var presentingViewController: UIViewController? + private var hostingController: UIHostingController? + + // MARK: - Init + + override init(frame: CGRect) { + super.init(frame: frame) + self.setupFrameView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Configuration + + func configure(blog: Blog, viewController: BlogDashboardViewController?, model: DashboardDynamicCardModel) { + self.coordinator = .init(viewController: viewController, model: model) + + self.presentingViewController = viewController + self.configureMoreButton(for: model, blog: blog) + + self.frameView.setTitle(model.payload.title) + self.frameView.onViewTap = { [weak self] in + self?.didTapCard(with: model) + } + + if let viewController { + self.configureHostingController(with: model, parent: viewController) + } + + self.coordinator?.didAppear() + } + + private func configureHostingController(with model: DashboardDynamicCardModel, parent: UIViewController) { + let content = DynamicDashboardCard(model: model) { [weak self] in + self?.didTapAction(with: model) + } + + if let hostingController { + hostingController.rootView = content + } else { + let hostingController = DynamicDashboardCardViewController(rootView: content) + hostingController.view.backgroundColor = .clear + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + hostingController.willMove(toParent: parent) + parent.addChild(hostingController) + self.frameView.add(subview: hostingController.view) + hostingController.didMove(toParent: parent) + self.hostingController = hostingController + } + + self.hostingController?.view?.invalidateIntrinsicContentSize() + } + + private func setupFrameView() { + self.frameView.ellipsisButton.showsMenuAsPrimaryAction = true + self.frameView.translatesAutoresizingMaskIntoConstraints = false + self.contentView.addSubview(frameView) + self.contentView.pinSubviewToAllEdges(frameView, priority: .defaultHigh) + } + + private func configureMoreButton(for card: DashboardDynamicCardModel, blog: Blog) { + self.frameView.addMoreMenu( + items: + [ + UIMenu( + options: .displayInline, + children: [ + BlogDashboardHelpers.makeHideCardAction(for: card, blog: blog) + ] + ) + ], + card: card + ) + } + + // MARK: - User Interaction + + private func didTapAction(with model: DashboardDynamicCardModel) { + self.coordinator?.didTapCardCTA() + } + + private func didTapCard(with model: DashboardDynamicCardModel) { + self.coordinator?.didTapCard() + } +} + +// MARK: - DynamicDashboardCard Extension + +private extension DynamicDashboardCard { + + init(model: DashboardDynamicCardModel, callback: (() -> Void)?) { + let payload = model.payload + + let featureImageURL = URL(string: payload.featuredImage ?? "") + let rows = (payload.rows ?? []).map { + Input.Row( + title: $0.title, + description: $0.description, + imageURL: URL(string: $0.icon ?? "") + ) + } + let action: Input.Action? = { + guard let title = payload.action, let callback else { + return nil + } + return .init(title: title, callback: callback) + }() + + let input = Input(featureImageURL: featureImageURL, rows: rows, action: action) + self.init(input: input) + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/DashboardDomainRegistrationCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/DashboardDomainRegistrationCardCell.swift index 513123b8c11f..bfe435bcc475 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/DashboardDomainRegistrationCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/DashboardDomainRegistrationCardCell.swift @@ -103,7 +103,7 @@ extension DashboardDomainRegistrationCardCell { comment: "Action to redeem domain credit." ) static let content = NSLocalizedString( - "All WordPress.com plans include a custom domain name. Register your free premium domain now.", + "All WordPress.com annual plans include a custom domain name. Register your free domain now.", comment: "Information about redeeming domain credit on site dashboard." ) static let hideThis = NSLocalizedString( diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/BaseDashboardDomainsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/BaseDashboardDomainsCardCell.swift index 4dbea785636c..8ef9171c1d27 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/BaseDashboardDomainsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/BaseDashboardDomainsCardCell.swift @@ -46,13 +46,6 @@ class BaseDashboardDomainsCardCell: DashboardCollectionViewCell { return stackView }() - private lazy var dashboardIcon: UIImageView = { - let image = UIImage.gridicon(.domains).withTintColor(.white).withRenderingMode(.alwaysOriginal) - let imageView = UIImageView(image: image) - imageView.translatesAutoresizingMaskIntoConstraints = false - return imageView - }() - private lazy var contextMenu: UIMenu = { let hideThisAction = UIAction(title: viewModel.strings.hideThis, image: Style.hideThisImage, diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Dynamic/BlogDashboardDynamicCardCoordinator.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Dynamic/BlogDashboardDynamicCardCoordinator.swift new file mode 100644 index 000000000000..59b669b479c7 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Dynamic/BlogDashboardDynamicCardCoordinator.swift @@ -0,0 +1,91 @@ +import Foundation + +final class BlogDashboardDynamicCardCoordinator { + + // MARK: - Dependencies + + private let analyticsTracker: AnalyticsEventTracking.Type + private let model: DashboardDynamicCardModel + private let linkRouter: LinkRouter + + private weak var viewController: UIViewController? + + // MARK: - Init + + init(viewController: UIViewController?, + model: DashboardDynamicCardModel, + linkRouter: LinkRouter = UniversalLinkRouter.shared, + analyticsTracker: AnalyticsEventTracking.Type = WPAnalytics.self) { + self.viewController = viewController + self.model = model + self.linkRouter = linkRouter + self.analyticsTracker = analyticsTracker + } + + // MARK: - API + + func didAppear() { + self.track(.cardShown(id: model.payload.id), frequency: .oncePerSession) + } + + func didTapCard() { + let payload = model.payload + if let urlString = payload.url, + let url = URL(string: urlString) { + routeToCardDestination(url: url) + } + self.track(.cardTapped(id: payload.id, url: payload.url)) + } + + func didTapCardCTA() { + let payload = model.payload + if let urlString = model.payload.url, + let url = URL(string: urlString) { + routeToCardDestination(url: url) + } + self.track(.cardCtaTapped(id: payload.id, url: payload.url)) + } + + private func routeToCardDestination(url: URL) { + if linkRouter.canHandle(url: url) { + routeToUniversalURL(url: url) + } else { + routeToWebView(url: url) + } + } + + private func routeToWebView(url: URL) { + guard UIApplication.shared.canOpenURL(url) else { + return + } + let configuration = WebViewControllerConfiguration(url: url) + configuration.authenticateWithDefaultAccount() + let controller = WebViewControllerFactory.controller(configuration: configuration, source: "dashboard") + let navController = UINavigationController(rootViewController: controller) + viewController?.present(navController, animated: true) + } + + private func routeToUniversalURL(url: URL) { + linkRouter.handle(url: url, shouldTrack: true, source: nil) + } +} + +// MARK: - Analytics + +private extension BlogDashboardDynamicCardCoordinator { + + private static var firedAnalyticEvents = Set() + + func track(_ event: DashboardDynamicCardAnalyticsEvent, frequency: TrackingFrequency = .multipleTimesPerSession) { + guard frequency == .multipleTimesPerSession || (frequency == .oncePerSession && !Self.firedAnalyticEvents.contains(event)) else { + return + } + self.analyticsTracker.track(.init(name: event.name, properties: event.properties)) + Self.firedAnalyticEvents.insert(event) + } + + enum TrackingFrequency { + case oncePerSession + case multipleTimesPerSession + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Dynamic/DynamicDashboardCard.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Dynamic/DynamicDashboardCard.swift index b41abc548d84..477a27711ac1 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Dynamic/DynamicDashboardCard.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Dynamic/DynamicDashboardCard.swift @@ -37,20 +37,31 @@ struct DynamicDashboardCard: View { rowsVStack actionHStack } + .padding(.bottom, Length.Padding.single) + .padding(.horizontal, Length.Padding.double) + .fixedSize(horizontal: false, vertical: true) } @ViewBuilder var featureImage: some View { if let featureImageURL = input.featureImageURL { - AsyncImage(url: featureImageURL) { image in - image.image? - .resizable() - .aspectRatio(contentMode: .fit) - .clipShape( - RoundedRectangle( - cornerRadius: Length.Radius.small - ) + AsyncImage(url: featureImageURL) { phase in + Group { + if let image = phase.image { + image + .resizable() + .aspectRatio(contentMode: .fit) + } else { + Color.DS.Background.secondary + .frame(maxWidth: .infinity) + .frame(height: 150) + } + } + .clipShape( + RoundedRectangle( + cornerRadius: Length.Radius.small ) + ) } } } @@ -105,6 +116,14 @@ struct DynamicDashboardCard: View { } } +final class DynamicDashboardCardViewController: UIHostingController { + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + self.view.invalidateIntrinsicContentSize() + } +} + // DesignSystem.DSButtonStyle extension to omit `isJetpack` from project target. extension DSButtonStyle { init(emphasis: DSButtonStyle.Emphasis, size: DSButtonStyle.Size) { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift index f5c332503bba..0190e6b4fe02 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift @@ -153,7 +153,6 @@ private extension DashboardPageCreationCell { static let labelsStackViewLayoutMargins: NSDirectionalEdgeInsets = .init(top: 15, leading: 0, bottom: 15, trailing: 0) static let labelsStackViewCompactLayoutMargins: NSDirectionalEdgeInsets = .init(top: 15, leading: 0, bottom: 7, trailing: 0) static let createPageButtonContentInsets = NSDirectionalEdgeInsets.zero - static let createPageButtonContentEdgeInsets = UIEdgeInsets.zero static let promoImageSize: CGSize = .init(width: 110, height: 80) static let promoImageSuperViewInsets: UIEdgeInsets = .init(top: 10, left: 0, bottom: 10, right: 0) static let promoImageCornerRadius: CGFloat = 5 diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/BlogDashboardCardFrameView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/BlogDashboardCardFrameView.swift index 89c6319d17c1..689b4a4e41e2 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/BlogDashboardCardFrameView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/BlogDashboardCardFrameView.swift @@ -185,6 +185,11 @@ class BlogDashboardCardFrameView: UIView { /// Adds the "more" button with the given actions to the corner of the cell. func addMoreMenu(items: [UIMenuElement], card: DashboardCard) { + self.addMoreMenu(items: items, card: card as BlogDashboardAnalyticPropertiesProviding) + } + + /// Adds the "more" button with the given actions to the corner of the cell. + func addMoreMenu(items: [UIMenuElement], card: BlogDashboardAnalyticPropertiesProviding) { onEllipsisButtonTap = { BlogDashboardAnalytics.trackContextualMenuAccessed(for: card) } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift index b42cfa44e9e6..72d4d466a9f1 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift @@ -72,7 +72,7 @@ class DashboardPostsListCardCell: UICollectionViewCell, Reusable { frameView.add(subview: tableView) contentView.addSubview(frameView) - contentView.pinSubviewToAllEdges(frameView, priority: Constants.constraintPriority) + contentView.pinSubviewToAllEdges(frameView, priority: UILayoutPriority(999)) } func trackPostsDisplayed() { @@ -109,8 +109,6 @@ extension DashboardPostsListCardCell { } private func addDraftsContextMenu(card: DashboardCard, blog: Blog) { - guard FeatureFlag.personalizeHomeTab.enabled else { return } - frameView.addMoreMenu(items: [ UIMenu(options: .displayInline, children: [ makeDraftsListMenuAction() @@ -122,8 +120,6 @@ extension DashboardPostsListCardCell { } private func addScheduledContextMenu(card: DashboardCard, blog: Blog) { - guard FeatureFlag.personalizeHomeTab.enabled else { return } - frameView.addMoreMenu(items: [ UIMenu(options: .displayInline, children: [ makeScheduledListMenuAction() @@ -220,10 +216,4 @@ private extension DashboardPostsListCardCell { static let viewAllDrafts = NSLocalizedString("my-sites.drafts.card.viewAllDrafts", value: "View all drafts", comment: "Title for the View all drafts button in the More menu") static let viewAllScheduledPosts = NSLocalizedString("my-sites.scheduled.card.viewAllScheduledPosts", value: "View all scheduled posts", comment: "Title for the View all scheduled drafts button in the More menu") } - - enum Constants { - static let iconSize = CGSize(width: 18, height: 18) - static let constraintPriority = UILayoutPriority(999) - static let numberOfPosts = 3 - } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift index a4c3f6620efb..a8e05c695df2 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift @@ -272,7 +272,6 @@ private extension PostsCardViewModel { enum Constants { static let numberOfPosts = 3 - static let numberOfPostsToSync: NSNumber = 3 } enum Strings { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardBloganuaryCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardBloganuaryCardCell.swift index e267bab74592..87c5e6b13508 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardBloganuaryCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardBloganuaryCardCell.swift @@ -27,14 +27,14 @@ class DashboardBloganuaryCardCell: DashboardCollectionViewCell { } // Check for date eligibility. - let isDateInDecember: Bool = { + let isDateWithinEligibleMonths: Bool = { let components = date.dateAndTimeComponents() guard let month = components.month else { return false } - // NOTE: For simplicity, we're going to hardcode the date check if the date is within December. - return month == 12 + // NOTE: For simplicity, we're going to hardcode the date check if the date is within December or January. + return Constants.eligibleMonths.contains(month) }() // Check if the blog is marked as a potential blogging site. @@ -42,7 +42,7 @@ class DashboardBloganuaryCardCell: DashboardCollectionViewCell { return (try? BloggingPromptSettings.of(blog))?.isPotentialBloggingSite ?? false } - return isDateInDecember && isPotentialBloggingSite + return isDateWithinEligibleMonths && isPotentialBloggingSite } func configure(blog: Blog, viewController: BlogDashboardViewController?, apiResponse: BlogDashboardRemoteEntity?) { @@ -111,6 +111,11 @@ class DashboardBloganuaryCardCell: DashboardCollectionViewCell { return frameView } + + struct Constants { + // Only show the card in December and January. + static let eligibleMonths = [1, 12] + } } // MARK: - SwiftUI @@ -144,7 +149,7 @@ private struct BloganuaryNudgeCardView: View { var textContainer: some View { VStack(alignment: .leading, spacing: 8.0) { - Text(Strings.title) + Text(cardTitle) .font(.headline) .fontWeight(.semibold) Text(Strings.description) @@ -153,6 +158,16 @@ private struct BloganuaryNudgeCardView: View { } } + var cardTitle: String { + let components = Date().dateAndTimeComponents() + guard let month = components.month, + DashboardBloganuaryCardCell.Constants.eligibleMonths.contains(month) else { + return Strings.title + } + + return month == 1 ? Strings.runningTitle : Strings.title + } + struct Strings { static let title = NSLocalizedString( "bloganuary.dashboard.card.title", @@ -160,6 +175,13 @@ private struct BloganuaryNudgeCardView: View { comment: "Title for the Bloganuary dashboard card." ) + // The card title string to be shown while Bloganuary is running + static let runningTitle = NSLocalizedString( + "bloganuary.dashboard.card.runningTitle", + value: "Bloganuary is here!", + comment: "Title for the Bloganuary dashboard card while Bloganuary is running." + ) + static let description = NSLocalizedString( "bloganuary.dashboard.card.description", value: "For the month of January, blogging prompts will come from Bloganuary — our community challenge to build a blogging habit for the new year.", 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 d25c8a3dfd39..d6f01a846376 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift @@ -513,12 +513,6 @@ private extension DashboardPromptsCardCell { BlogDashboardAnalytics.trackHideTapped(for: .prompts) let service = BlogDashboardPersonalizationService(siteID: siteID) service.setEnabled(false, for: .prompts) - if !FeatureFlag.personalizeHomeTab.enabled { - let notice = Notice(title: Strings.promptRemovedTitle, message: Strings.promptRemovedSubtitle, feedbackType: .success, actionTitle: Strings.undoSkipTitle) { _ in - service.setEnabled(true, for: .prompts) - } - ActionDispatcher.dispatch(NoticeAction.post(notice)) - } } func learnMoreTapped() { @@ -547,12 +541,6 @@ private extension DashboardPromptsCardCell { static let errorTitle = NSLocalizedString("Error loading prompt", comment: "Text displayed when there is a failure loading a blogging prompt.") static let promptSkippedTitle = NSLocalizedString("Prompt skipped", comment: "Title of the notification presented when a prompt is skipped") static let undoSkipTitle = NSLocalizedString("Undo", comment: "Button in the notification presented when a prompt is skipped") - static let promptRemovedTitle = NSLocalizedString("prompts.notification.removed.title", - value: "Blogging Prompts hidden", - comment: "Title of the notification when prompts are hidden from the dashboard card") - static let promptRemovedSubtitle = NSLocalizedString("prompts.notification.removed.subtitle", - value: "Visit Site Settings to turn back on", - comment: "Subtitle of the notification when prompts are hidden from the dashboard card") } struct Style { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/DashboardQuickStartCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/DashboardQuickStartCardCell.swift index 853cce02101f..872d0ed9665a 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/DashboardQuickStartCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/DashboardQuickStartCardCell.swift @@ -110,7 +110,6 @@ extension DashboardQuickStartCardCell { } private enum Metrics { - static let iconSize = CGSize(width: 18, height: 18) static let constraintPriority = UILayoutPriority(999) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift index 577c9c27db9b..8574350f062f 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift @@ -76,16 +76,14 @@ extension DashboardStatsCardCell: BlogDashboardCardConfigurable { self.showStats(for: blog, from: viewController) } - if FeatureFlag.personalizeHomeTab.enabled { - frameView.addMoreMenu(items: [ - UIMenu(options: .displayInline, children: [ - makeShowStatsMenuAction(for: blog, in: viewController) - ]), - UIMenu(options: .displayInline, children: [ - BlogDashboardHelpers.makeHideCardAction(for: .todaysStats, blog: blog) - ]) - ], card: .todaysStats) - } + frameView.addMoreMenu(items: [ + UIMenu(options: .displayInline, children: [ + makeShowStatsMenuAction(for: blog, in: viewController) + ]), + UIMenu(options: .displayInline, children: [ + BlogDashboardHelpers.makeHideCardAction(for: .todaysStats, blog: blog) + ]) + ], card: .todaysStats) statsStackView?.views = viewModel?.todaysViews statsStackView?.visitors = viewModel?.todaysVisitors @@ -157,7 +155,6 @@ private extension DashboardStatsCardCell { enum Constants { static let spacing: CGFloat = 20 - static let iconSize = CGSize(width: 18, height: 18) static let constraintPriority = UILayoutPriority(999) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardAnalytics.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardAnalytics.swift index b3e90f6d86bc..9a065d30ee73 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardAnalytics.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardAnalytics.swift @@ -31,11 +31,19 @@ class BlogDashboardAnalytics { } } + static func trackContextualMenuAccessed(for card: BlogDashboardAnalyticPropertiesProviding) { + WPAnalytics.track(.dashboardCardContextualMenuAccessed, properties: card.analyticProperties) + } + + static func trackHideTapped(for card: BlogDashboardAnalyticPropertiesProviding) { + WPAnalytics.track(.dashboardCardHideTapped, properties: card.analyticProperties) + } + static func trackContextualMenuAccessed(for card: DashboardCard) { - WPAnalytics.track(.dashboardCardContextualMenuAccessed, properties: ["card": card.rawValue]) + self.trackContextualMenuAccessed(for: card as BlogDashboardAnalyticPropertiesProviding) } static func trackHideTapped(for card: DashboardCard) { - WPAnalytics.track(.dashboardCardHideTapped, properties: ["card": card.rawValue]) + self.trackHideTapped(for: card as BlogDashboardAnalyticPropertiesProviding) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardHelpers.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardHelpers.swift index 0b3f51757722..e243378aa26a 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardHelpers.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/BlogDashboardHelpers.swift @@ -1,7 +1,13 @@ import Foundation struct BlogDashboardHelpers { + typealias Card = BlogDashboardAnalyticPropertiesProviding & BlogDashboardPersonalizable + static func makeHideCardAction(for card: DashboardCard, blog: Blog) -> UIAction { + Self.makeHideCardAction(for: card as Card, blog: blog) + } + + static func makeHideCardAction(for card: Card, blog: Blog) -> UIAction { makeHideCardAction { BlogDashboardAnalytics.trackHideTapped(for: card) BlogDashboardPersonalizationService(siteID: blog.dotComID?.intValue ?? 0) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/DashboardPostsSyncManager.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/DashboardPostsSyncManager.swift index db82f5eea564..24948b96cc5a 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/DashboardPostsSyncManager.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Helpers/DashboardPostsSyncManager.swift @@ -116,15 +116,6 @@ class DashboardPostsSyncManager { } private extension DashboardPostsSyncManager.PostType { - var postServiceType: PostServiceType { - switch self { - case .post: - return .post - case .page: - return .page - } - } - func statusesNotBeingSynced(_ statuses: [BasePost.Status], for blog: Blog) -> [BasePost.Status] { var currentlySyncing: [BasePost.Status] switch self { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/BlogDashboardAnalyticPropertiesProviding.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/BlogDashboardAnalyticPropertiesProviding.swift new file mode 100644 index 000000000000..4347807ae2e6 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/BlogDashboardAnalyticPropertiesProviding.swift @@ -0,0 +1,6 @@ +import Foundation + +protocol BlogDashboardAnalyticPropertiesProviding { + + var analyticProperties: [AnyHashable: Any] { get } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard+Personalization.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard+Personalization.swift new file mode 100644 index 000000000000..9dea5d3f536c --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard+Personalization.swift @@ -0,0 +1,52 @@ +import Foundation + +extension DashboardCard: BlogDashboardPersonalizable { + + var blogDashboardPersonalizationKey: String? { + switch self { + case .todaysStats: + return "todays-stats-card-enabled-site-settings" + case .draftPosts: + return "draft-posts-card-enabled-site-settings" + case .scheduledPosts: + return "scheduled-posts-card-enabled-site-settings" + case .blaze: + return "blaze-card-enabled-site-settings" + case .bloganuaryNudge: + return "bloganuary-nudge-card-enabled-site-settings" + case .prompts: + // Warning: there is an irregularity with the prompts key that doesn't + // have a "-card" component in the key name. Keeping it like this to + // avoid having to migrate data. + return "prompts-enabled-site-settings" + case .freeToPaidPlansDashboardCard: + return "free-to-paid-plans-dashboard-card-enabled-site-settings" + case .domainRegistration: + return "register-domain-dashboard-card" + case .googleDomains: + return "google-domains-card-enabled-site-settings" + case .activityLog: + return "activity-log-card-enabled-site-settings" + case .pages: + return "pages-card-enabled-site-settings" + case .quickStart: + // The "Quick Start" cell used to use `BlogDashboardPersonalizationService`. + // It no longer does, but it's important to keep the flag around for + // users that hidden it using this flag. + return "quick-start-card-enabled-site-settings" + case .dynamic, .jetpackBadge, .jetpackInstall, .jetpackSocial, .failure, .ghost, .personalize, .empty: + return nil + } + } + + /// Specifies whether the card settings should be applied across + /// different sites or only to a particular site. + var blogDashboardPersonalizationSettingsScope: BlogDashboardPersonalizationService.SettingsScope { + switch self { + case .googleDomains: + return .siteGeneric + default: + return .siteSpecific + } + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard.swift similarity index 87% rename from WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift rename to WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard.swift index 63d44ffbb81c..1068063e1ab6 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard.swift @@ -7,6 +7,7 @@ import Foundation /// /// Remote cards should be separately added to RemoteDashboardCard enum DashboardCard: String, CaseIterable { + case dynamic case jetpackInstall case quickStart case bloganuaryNudge = "bloganuary_nudge" @@ -32,6 +33,8 @@ enum DashboardCard: String, CaseIterable { var cell: DashboardCollectionViewCell.Type { switch self { + case .dynamic: + return BlogDashboardDynamicCardCell.self case .jetpackInstall: return DashboardJetpackInstallCardCell.self case .quickStart: @@ -84,17 +87,6 @@ enum DashboardCard: String, CaseIterable { } } - /// Specifies whether the card settings should be applied across - /// different sites or only to a particular site. - var settingsType: SettingsType { - switch self { - case .googleDomains: - return .siteGeneric - default: - return .siteSpecific - } - } - func shouldShow( for blog: Blog, apiResponse: BlogDashboardRemoteEntity? = nil, @@ -141,7 +133,7 @@ enum DashboardCard: String, CaseIterable { case .empty: return false // Controlled manually based on other cards visibility case .personalize: - return FeatureFlag.personalizeHomeTab.enabled + return true case .pages: return DashboardPagesListCardCell.shouldShowCard(for: blog) && shouldShowRemoteCard(apiResponse: apiResponse) case .activityLog: @@ -150,9 +142,28 @@ enum DashboardCard: String, CaseIterable { return DashboardJetpackSocialCardCell.shouldShowCard(for: blog) case .googleDomains: return FeatureFlag.googleDomainsCard.enabled && isJetpack + case .dynamic: + return false } } + static func shouldShowDynamicCard( + for blog: Blog, + payload: DashboardDynamicCardModel.Payload, + remoteFeatureFlagStore: RemoteFeatureFlagStore, + isJetpack: Bool = AppConfiguration.isJetpack + ) -> Bool { + let remoteFeatureFlagEnabled = { + guard let key = payload.remoteFeatureFlag else { + return true + } + return remoteFeatureFlagStore.value(for: key) ?? false + }() + return isJetpack + && RemoteDashboardCard.dynamic.supported(by: blog) + && remoteFeatureFlagEnabled + } + private func shouldShowRemoteCard(apiResponse: BlogDashboardRemoteEntity?) -> Bool { guard let apiResponse = apiResponse else { return false @@ -191,6 +202,7 @@ enum DashboardCard: String, CaseIterable { case posts case pages case activity + case dynamic func supported(by blog: Blog) -> Bool { switch self { @@ -202,14 +214,11 @@ enum DashboardCard: String, CaseIterable { return DashboardPagesListCardCell.shouldShowCard(for: blog) case .activity: return DashboardActivityLogCardCell.shouldShowCard(for: blog) + case .dynamic: + return RemoteFeatureFlag.dynamicDashboardCards.enabled() } } } - - enum SettingsType { - case siteSpecific - case siteGeneric - } } private extension BlogDashboardRemoteEntity { @@ -233,3 +242,12 @@ private extension BlogDashboardRemoteEntity { return (self.activity?.value?.current?.orderedItems?.count ?? 0) > 0 } } + +// MARK: - BlogDashboardAnalyticPropertiesProviding Protocol Conformance + +extension DashboardCard: BlogDashboardAnalyticPropertiesProviding { + + var analyticProperties: [AnyHashable: Any] { + return ["card": rawValue] + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCardModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCardModel.swift new file mode 100644 index 000000000000..ecbba65b3935 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCardModel.swift @@ -0,0 +1,133 @@ +import Foundation + +enum DashboardCardModel: Hashable { + + case normal(DashboardNormalCardModel) + case dynamic(DashboardDynamicCardModel) + + var cardType: DashboardCard { + switch self { + case .normal(let model): return model.cardType + case .dynamic(let model): return model.cardType + } + } + + func normal() -> DashboardNormalCardModel? { + guard case .normal(let model) = self else { + return nil + } + return model + } + + func dynamic() -> DashboardDynamicCardModel? { + guard case .dynamic(let model) = self else { + return nil + } + return model + } +} + +extension DashboardCardModel: BlogDashboardPersonalizable, BlogDashboardAnalyticPropertiesProviding { + + private var card: BlogDashboardPersonalizable & BlogDashboardAnalyticPropertiesProviding { + switch self { + case .normal(let model): return model + case .dynamic(let model): return model + } + } + + var blogDashboardPersonalizationKey: String? { + return card.blogDashboardPersonalizationKey + } + + var blogDashboardPersonalizationSettingsScope: BlogDashboardPersonalizationService.SettingsScope { + return card.blogDashboardPersonalizationSettingsScope + } + + var analyticProperties: [AnyHashable: Any] { + return card.analyticProperties + } +} + +// MARK: - Normal Card Model + +/// Represents a card in the dashboard collection view +struct DashboardNormalCardModel: Hashable { + let cardType: DashboardCard + let dotComID: Int + let apiResponse: BlogDashboardRemoteEntity? + + /** + Initializes a new DashboardCardModel, used as a model for each dashboard card. + + - Parameters: + - id: The `DashboardCard` id of this card + - dotComID: The blog id for the blog associated with this card + - entity: A `BlogDashboardRemoteEntity?` property + + - Returns: A `DashboardCardModel` that is used by the dashboard diffable collection + view. The `id`, `dotComID` and the `entity` is used to differentiate one + card from the other. + */ + init(cardType: DashboardCard, dotComID: Int, entity: BlogDashboardRemoteEntity? = nil) { + self.cardType = cardType + self.dotComID = dotComID + self.apiResponse = entity + } + + static func == (lhs: DashboardNormalCardModel, rhs: DashboardNormalCardModel) -> Bool { + lhs.cardType == rhs.cardType && + lhs.dotComID == rhs.dotComID && + lhs.apiResponse == rhs.apiResponse + } + + func hash(into hasher: inout Hasher) { + hasher.combine(cardType) + hasher.combine(dotComID) + hasher.combine(apiResponse) + } +} + +extension DashboardNormalCardModel: BlogDashboardPersonalizable, BlogDashboardAnalyticPropertiesProviding { + + var blogDashboardPersonalizationKey: String? { + return cardType.blogDashboardPersonalizationKey + } + + var blogDashboardPersonalizationSettingsScope: BlogDashboardPersonalizationService.SettingsScope { + return cardType.blogDashboardPersonalizationSettingsScope + } + + var analyticProperties: [AnyHashable: Any] { + return cardType.analyticProperties + } +} + +// MARK: - Dynamic Card Model + +struct DashboardDynamicCardModel: Hashable { + + typealias Payload = BlogDashboardRemoteEntity.BlogDashboardDynamic + + let cardType: DashboardCard = .dynamic + let payload: Payload + let dotComID: Int +} + +extension DashboardDynamicCardModel: BlogDashboardPersonalizable, BlogDashboardAnalyticPropertiesProviding { + + var blogDashboardPersonalizationKey: String? { + return "dynamic_card_\(payload.id)" + } + + var blogDashboardPersonalizationSettingsScope: BlogDashboardPersonalizationService.SettingsScope { + return .siteGeneric + } + + var analyticProperties: [AnyHashable: Any] { + let properties: [AnyHashable: Any] = ["id": payload.id] + return cardType.analyticProperties.merging(properties, uniquingKeysWith: { first, second in + return first + }) + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift index 13ae9660a33a..290e2bb01adc 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift @@ -1,5 +1,18 @@ import Foundation +/// `BlogDashboardPersonalizable` is a protocol that defines the requirements for personalizing blog dashboard items. +/// It provides properties to access personalization key and settings scope specific to the blog dashboard. +protocol BlogDashboardPersonalizable { + + /// The personalization key for the blog dashboard. + /// This key is used to identify and retrieve personalization settings specific to a dashboard item. + var blogDashboardPersonalizationKey: String? { get } + + /// The scope of the blog dashboard personalization settings. + /// This defines the extent to which the personalization settings are applied, such as site-agnostic or site-specific. + var blogDashboardPersonalizationSettingsScope: BlogDashboardPersonalizationService.SettingsScope { get } +} + /// Manages dashboard settings such as card visibility. struct BlogDashboardPersonalizationService { private enum Constants { @@ -15,6 +28,47 @@ struct BlogDashboardPersonalizationService { self.siteID = String(siteID) } + // MARK: - Core API + + func setEnabled(_ isEnabled: Bool, forKey key: String, scope: SettingsScope) { + var settings = getSettings(for: key) + let lookUpKey = lookUpKey(from: scope) + settings[lookUpKey] = isEnabled + self.repository.set(settings, forKey: key) + NotificationCenter.default.post(name: .blogDashboardPersonalizationSettingsChanged, object: self) + } + + func isEnabled(_ key: String, scope: SettingsScope) -> Bool { + let settings = getSettings(for: key) + let key = lookUpKey(from: scope) + return settings[key, default: true] + } + + func setEnabled(_ isEnabled: Bool, for item: BlogDashboardPersonalizable) { + guard let key = item.blogDashboardPersonalizationKey else { + return + } + self.setEnabled( + isEnabled, + forKey: key, + scope: item.blogDashboardPersonalizationSettingsScope + ) + } + + func isEnabled(_ item: BlogDashboardPersonalizable) -> Bool { + guard let key = item.blogDashboardPersonalizationKey else { + return true + } + return self.isEnabled(key, scope: item.blogDashboardPersonalizationSettingsScope) + } + + private func lookUpKey(from scope: SettingsScope) -> String { + switch scope { + case .siteSpecific: return siteID + case .siteGeneric: return Constants.siteAgnosticVisibilityKey + } + } + // MARK: - Quick Actions func isEnabled(_ action: DashboardQuickAction) -> Bool { @@ -39,8 +93,7 @@ struct BlogDashboardPersonalizationService { // MARK: - Dashboard Cards func isEnabled(_ card: DashboardCard) -> Bool { - let key = lookUpKey(for: card) - return getSettings(for: card)[key] ?? true + return self.isEnabled(card as BlogDashboardPersonalizable) } func hasPreference(for card: DashboardCard) -> Bool { @@ -57,30 +110,25 @@ struct BlogDashboardPersonalizationService { /// - isEnabled: A Boolean value indicating whether the `DashboardCard` should be enabled or disabled. /// - card: The `DashboardCard` whose setting needs to be updated. func setEnabled(_ isEnabled: Bool, for card: DashboardCard) { - guard let key = makeKey(for: card) else { return } - var settings = getSettings(for: card) - let lookUpKey = lookUpKey(for: card) - settings[lookUpKey] = isEnabled - - repository.set(settings, forKey: key) - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .blogDashboardPersonalizationSettingsChanged, object: self) - } + self.setEnabled(isEnabled, for: card as BlogDashboardPersonalizable) } private func getSettings(for card: DashboardCard) -> [String: Bool] { - guard let key = makeKey(for: card) else { return [:] } + guard let key = card.blogDashboardPersonalizationKey else { + return [:] + } return repository.dictionary(forKey: key) as? [String: Bool] ?? [:] } private func lookUpKey(for card: DashboardCard) -> String { - switch card.settingsType { - case .siteSpecific: - return siteID - case .siteGeneric: - return Constants.siteAgnosticVisibilityKey - } + return lookUpKey(from: card.blogDashboardPersonalizationSettingsScope) + } + + // MARK: - Types + + enum SettingsScope { + case siteSpecific + case siteGeneric } } @@ -88,43 +136,6 @@ private func makeKey(for action: DashboardQuickAction) -> String { "quick-action-\(action.rawValue)-hidden" } -private func makeKey(for card: DashboardCard) -> String? { - switch card { - case .todaysStats: - return "todays-stats-card-enabled-site-settings" - case .draftPosts: - return "draft-posts-card-enabled-site-settings" - case .scheduledPosts: - return "scheduled-posts-card-enabled-site-settings" - case .blaze: - return "blaze-card-enabled-site-settings" - case .bloganuaryNudge: - return "bloganuary-nudge-card-enabled-site-settings" - case .prompts: - // Warning: there is an irregularity with the prompts key that doesn't - // have a "-card" component in the key name. Keeping it like this to - // avoid having to migrate data. - return "prompts-enabled-site-settings" - case .freeToPaidPlansDashboardCard: - return "free-to-paid-plans-dashboard-card-enabled-site-settings" - case .domainRegistration: - return "register-domain-dashboard-card" - case .googleDomains: - return "google-domains-card-enabled-site-settings" - case .activityLog: - return "activity-log-card-enabled-site-settings" - case .pages: - return "pages-card-enabled-site-settings" - case .quickStart: - // The "Quick Start" cell used to use `BlogDashboardPersonalizationService`. - // It no longer does, but it's important to keep the flag around for - // users that hidden it using this flag. - return "quick-start-card-enabled-site-settings" - case .jetpackBadge, .jetpackInstall, .jetpackSocial, .failure, .ghost, .personalize, .empty: - return nil - } -} - extension NSNotification.Name { /// Sent whenever any of the blog settings managed by ``BlogDashboardPersonalizationService`` /// are changed. diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardRemoteEntity.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardRemoteEntity.swift index ef5f94260f58..864184d32edb 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardRemoteEntity.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardRemoteEntity.swift @@ -6,6 +6,7 @@ struct BlogDashboardRemoteEntity: Decodable, Hashable { var todaysStats: FailableDecodable? var pages: FailableDecodable<[BlogDashboardPage]>? var activity: FailableDecodable? + var dynamic: FailableDecodable<[BlogDashboardDynamic]>? struct BlogDashboardPosts: Decodable, Hashable { var hasPublished: Bool? @@ -45,6 +46,46 @@ struct BlogDashboardRemoteEntity: Decodable, Hashable { case todaysStats = "todays_stats" case pages case activity + case dynamic + } +} + +// MARK: - Dynamic Card + +extension BlogDashboardRemoteEntity { + + struct BlogDashboardDynamic: Decodable, Hashable { + + let id: String + let remoteFeatureFlag: String? + let title: String? + let featuredImage: String? + let url: String? + let action: String? + let order: Order? + let rows: [Row]? + + enum Order: String, Decodable { + case top + case bottom + } + + struct Row: Decodable, Hashable { + let title: String? + let description: String? + let icon: String? + } + + private enum CodingKeys: String, CodingKey { + case id + case title + case remoteFeatureFlag = "remote_feature_flag" + case featuredImage = "featured_image" + case url + case action + case order + case rows + } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardService.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardService.swift index 646eeab60bdb..a60b58666451 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardService.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardService.swift @@ -7,6 +7,7 @@ final class BlogDashboardService { private let persistence: BlogDashboardPersistence private let postsParser: BlogDashboardPostsParser private let repository: UserPersistentRepository + private let remoteFeatureFlagStore: RemoteFeatureFlagStore private let isJetpack: Bool private let isDotComAvailable: Bool private let shouldShowJetpackFeatures: Bool @@ -19,7 +20,8 @@ final class BlogDashboardService { remoteService: DashboardServiceRemote? = nil, persistence: BlogDashboardPersistence = BlogDashboardPersistence(), repository: UserPersistentRepository = UserDefaults.standard, - postsParser: BlogDashboardPostsParser? = nil + postsParser: BlogDashboardPostsParser? = nil, + remoteFeatureFlagStore: RemoteFeatureFlagStore = .init() ) { self.isJetpack = isJetpack self.isDotComAvailable = isDotComAvailable @@ -28,6 +30,7 @@ final class BlogDashboardService { self.persistence = persistence self.repository = repository self.postsParser = postsParser ?? BlogDashboardPostsParser(managedObjectContext: managedObjectContext) + self.remoteFeatureFlagStore = remoteFeatureFlagStore } /// Fetch cards from remote @@ -97,27 +100,101 @@ private extension BlogDashboardService { func parse(_ entity: BlogDashboardRemoteEntity?, blog: Blog, dotComID: Int) -> [DashboardCardModel] { let personalizationService = BlogDashboardPersonalizationService(repository: repository, siteID: dotComID) - var cards: [DashboardCardModel] = DashboardCard.allCases.compactMap { card in - guard personalizationService.isEnabled(card) else { + + // Map `DashboardCard` instances to `DashboardCardModel` + var allCards: [DashboardCardModel] = DashboardCard.allCases.compactMap { card -> DashboardCardModel? in + guard card != .dynamic else { return nil } + return self.dashboardCardModel( + from: card, + entity: entity, + blog: blog, + dotComID: dotComID, + personalizationService: personalizationService + ) + } - guard card.shouldShow( - for: blog, - apiResponse: entity, - isJetpack: isJetpack, - isDotComAvailable: isDotComAvailable, - shouldShowJetpackFeatures: shouldShowJetpackFeatures - ) else { - return nil + // Maps dynamic cards to `DashboardCardModel`. + if let dynamic = entity?.dynamic?.value { + let cards = dynamic.compactMap { payload in + return self.dashboardCardModel( + for: blog, + payload: payload, + dotComID: dotComID, + personalizationService: personalizationService + ) } + let cardsByOrder = Dictionary(grouping: cards) { card -> BlogDashboardRemoteEntity.BlogDashboardDynamic.Order in + guard case .dynamic(let model) = card, let order = model.payload.order else { + return .bottom + } + return order + } + let topCards = cardsByOrder[.top, default: []] + let bottomCards = cardsByOrder[.bottom, default: []] + + // Adds "top" cards at the beginning of the list. + allCards = topCards + allCards + + // Adds "bottom" cards at the bottom of the list just before "personalize" card. + if allCards.last?.cardType == .personalize { + allCards.insert(contentsOf: bottomCards, at: allCards.endIndex - 1) + } else { + allCards = allCards + bottomCards + } + } + + // Add "empty" card if the list of cards is empty. + if allCards.isEmpty || allCards.map(\.cardType) == [.personalize] { + let model = DashboardCardModel.normal(.init(cardType: .empty, dotComID: dotComID)) + allCards.insert(model, at: 0) + } + + return allCards + } - return DashboardCardModel(cardType: card, dotComID: dotComID, entity: entity) + func dashboardCardModel( + from card: DashboardCard, + entity: BlogDashboardRemoteEntity?, + blog: Blog, + dotComID: Int, + personalizationService: BlogDashboardPersonalizationService + ) -> DashboardCardModel? { + guard personalizationService.isEnabled(card) else { + return nil } - if cards.isEmpty || cards.map(\.cardType) == [.personalize] { - cards.insert(DashboardCardModel(cardType: .empty, dotComID: dotComID), at: 0) + + guard card.shouldShow( + for: blog, + apiResponse: entity, + isJetpack: isJetpack, + isDotComAvailable: isDotComAvailable, + shouldShowJetpackFeatures: shouldShowJetpackFeatures + ) else { + return nil + } + + return .normal(.init(cardType: card, dotComID: dotComID, entity: entity)) + } + + func dashboardCardModel( + for blog: Blog, + payload: DashboardDynamicCardModel.Payload, + dotComID: Int, + personalizationService: BlogDashboardPersonalizationService + ) -> DashboardCardModel? { + let model = DashboardDynamicCardModel(payload: payload, dotComID: dotComID) + let shouldShow = DashboardCard.shouldShowDynamicCard( + for: blog, + payload: payload, + remoteFeatureFlagStore: remoteFeatureFlagStore, + isJetpack: isJetpack + ) + guard shouldShow, personalizationService.isEnabled(model) else { + return nil } - return cards + return .dynamic(model) } func decode(_ cardsDictionary: NSDictionary, blog: Blog) -> BlogDashboardRemoteEntity? { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift index dc4c29148e74..d47c8e3f1356 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import CoreData +import WordPressKit enum DashboardSection: Int, CaseIterable { case migrationSuccess @@ -70,7 +71,7 @@ final class BlogDashboardViewModel { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellType.defaultReuseID, for: indexPath) if var cellConfigurable = cell as? BlogDashboardCardConfigurable { cellConfigurable.row = indexPath.row - cellConfigurable.configure(blog: blog, viewController: viewController, apiResponse: cardModel.apiResponse) + cellConfigurable.configure(blog: blog, viewController: viewController, model: cardModel) } (cell as? DashboardBlazeCardCell)?.configure(blazeViewModel) return cell diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/DashboardCardModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/DashboardCardModel.swift deleted file mode 100644 index 90631c0b7389..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/DashboardCardModel.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -/// Represents a card in the dashboard collection view -struct DashboardCardModel: Hashable { - let cardType: DashboardCard - let dotComID: Int - let apiResponse: BlogDashboardRemoteEntity? - - /** - Initializes a new DashboardCardModel, used as a model for each dashboard card. - - - Parameters: - - id: The `DashboardCard` id of this card - - dotComID: The blog id for the blog associated with this card - - entity: A `BlogDashboardRemoteEntity?` property - - - Returns: A `DashboardCardModel` that is used by the dashboard diffable collection - view. The `id`, `dotComID` and the `entity` is used to differentiate one - card from the other. - */ - init(cardType: DashboardCard, dotComID: Int, entity: BlogDashboardRemoteEntity? = nil) { - self.cardType = cardType - self.dotComID = dotComID - self.apiResponse = entity - } - - static func == (lhs: DashboardCardModel, rhs: DashboardCardModel) -> Bool { - lhs.cardType == rhs.cardType && - lhs.dotComID == rhs.dotComID && - lhs.apiResponse == rhs.apiResponse - } - - func hash(into hasher: inout Hasher) { - hasher.combine(cardType) - hasher.combine(dotComID) - hasher.combine(apiResponse) - } -} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+FancyAlerts.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+FancyAlerts.swift index 84f9fac07fb9..2a5a60d6992d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+FancyAlerts.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+FancyAlerts.swift @@ -53,15 +53,6 @@ extension BlogDetailsViewController { alertWorkItem = nil } - private var noPresentedViewControllers: Bool { - guard let window = WordPressAppDelegate.shared?.window, - let rootViewController = window.rootViewController, - rootViewController.presentedViewController != nil else { - return true - } - return false - } - private func showNoticeAsNeeded() { let quickStartGuide = QuickStartTourGuide.shared diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+QuickActions.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+QuickActions.swift deleted file mode 100644 index 3be444542c2f..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+QuickActions.swift +++ /dev/null @@ -1,86 +0,0 @@ -import UIKit - -// TODO: Consider completely removing all Quick Action logic -extension BlogDetailsViewController { - - @objc func quickActionsSectionViewModel() -> BlogDetailsSection { - let row = BlogDetailsRow() - row.callback = {} - return BlogDetailsSection(title: nil, - rows: [row], - footerTitle: nil, - category: .quickAction) - } - - @objc func isAccessibilityCategoryEnabled() -> Bool { - tableView.traitCollection.preferredContentSizeCategory.isAccessibilityCategory - } - - @objc func configureQuickActions(cell: QuickActionsCell) { - let actionItems = createActionItems() - - cell.configure(with: actionItems) - } - - private func createActionItems() -> [ActionRow.Item] { - let actionItems: [ActionRow.Item] = [ - .init(image: .gridicon(.statsAlt), title: NSLocalizedString("Stats", comment: "Noun. Abbv. of Statistics. Links to a blog's Stats screen.")) { [weak self] in - self?.tableView.deselectSelectedRowWithAnimation(false) - self?.showStats(from: .button) - }, - .init(image: .gridicon(.posts), title: NSLocalizedString("Posts", comment: "Noun. Title. Links to the blog's Posts screen.")) { [weak self] in - self?.tableView.deselectSelectedRowWithAnimation(false) - self?.showPostList(from: .button) - }, - .init(image: .gridicon(.image), title: NSLocalizedString("Media", comment: "Noun. Title. Links to the blog's Media library.")) { [weak self] in - self?.tableView.deselectSelectedRowWithAnimation(false) - self?.showMediaLibrary(from: .button) - }, - .init(image: .gridicon(.pages), title: NSLocalizedString("Pages", comment: "Noun. Title. Links to the blog's Pages screen.")) { [weak self] in - self?.tableView.deselectSelectedRowWithAnimation(false) - self?.showPageList(from: .button) - } - ] - - return actionItems - } -} - -@objc class QuickActionsCell: UITableViewCell { - private var actionRow: ActionRow! - - func configure(with items: [ActionRow.Item]) { - guard actionRow == nil else { - return - } - - actionRow = ActionRow(items: items) - contentView.addSubview(actionRow) - - setupConstraints() - setupCell() - } - - private func setupConstraints() { - actionRow.translatesAutoresizingMaskIntoConstraints = false - - let widthConstraint = actionRow.widthAnchor.constraint(equalToConstant: Constants.maxQuickActionsWidth) - widthConstraint.priority = .defaultHigh - - NSLayoutConstraint.activate([ - actionRow.topAnchor.constraint(equalTo: contentView.topAnchor), - actionRow.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - actionRow.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor), - actionRow.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - widthConstraint - ]) - } - - private func setupCell() { - selectionStyle = .none - } - - private enum Constants { - static let maxQuickActionsWidth: CGFloat = 390 - } -} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.h b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.h index 40b89868f68e..794d22b2a3ae 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.h +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.h @@ -7,7 +7,6 @@ @protocol BlogDetailHeader; typedef NS_ENUM(NSUInteger, BlogDetailsSectionCategory) { - BlogDetailsSectionCategoryQuickAction, BlogDetailsSectionCategoryReminders, BlogDetailsSectionCategoryDomainCredit, BlogDetailsSectionCategoryQuickStart, @@ -172,6 +171,7 @@ typedef NS_ENUM(NSUInteger, BlogDetailsNavigationSource) { - (id _Nonnull)init; - (void)showDetailViewForSubsection:(BlogDetailsSubsection)section; +- (void)showDetailViewForSubsection:(BlogDetailsSubsection)section userInfo:(nonnull NSDictionary *)userInfo; - (NSIndexPath * _Nonnull)indexPathForSubsection:(BlogDetailsSubsection)subsection; - (void)reloadTableViewPreservingSelection; - (void)configureTableViewData; @@ -186,4 +186,7 @@ typedef NS_ENUM(NSUInteger, BlogDetailsNavigationSource) { - (void)updateTableView:(nullable void(^)(void))completion; - (void)preloadMetadata; - (void)pulledToRefreshWith:(nonnull UIRefreshControl *)refreshControl onCompletion:(nullable void(^)(void))completion; + ++ (nonnull NSString *)userInfoShowPickerKey; + @end diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m index abd093f795a6..8e8c732616bb 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m @@ -24,7 +24,6 @@ static NSString *const BlogDetailsPlanCellIdentifier = @"BlogDetailsPlanCell"; static NSString *const BlogDetailsSettingsCellIdentifier = @"BlogDetailsSettingsCell"; static NSString *const BlogDetailsRemoveSiteCellIdentifier = @"BlogDetailsRemoveSiteCell"; -static NSString *const BlogDetailsQuickActionsCellIdentifier = @"BlogDetailsQuickActionsCell"; static NSString *const BlogDetailsSectionHeaderViewIdentifier = @"BlogDetailsSectionHeaderView"; static NSString *const QuickStartHeaderViewNibName = @"BlogDetailsSectionHeaderView"; static NSString *const BlogDetailsQuickStartCellIdentifier = @"BlogDetailsQuickStartCell"; @@ -377,7 +376,6 @@ - (void)viewDidLoad [self.tableView registerClass:[WPTableViewCellValue1 class] forCellReuseIdentifier:BlogDetailsPlanCellIdentifier]; [self.tableView registerClass:[WPTableViewCellValue1 class] forCellReuseIdentifier:BlogDetailsSettingsCellIdentifier]; [self.tableView registerClass:[WPTableViewCell class] forCellReuseIdentifier:BlogDetailsRemoveSiteCellIdentifier]; - [self.tableView registerClass:[QuickActionsCell class] forCellReuseIdentifier:BlogDetailsQuickActionsCellIdentifier]; UINib *qsHeaderViewNib = [UINib nibWithNibName:QuickStartHeaderViewNibName bundle:[NSBundle mainBundle]]; [self.tableView registerNib:qsHeaderViewNib forHeaderFooterViewReuseIdentifier:BlogDetailsSectionHeaderViewIdentifier]; [self.tableView registerClass:[QuickStartCell class] forCellReuseIdentifier:BlogDetailsQuickStartCellIdentifier]; @@ -475,7 +473,12 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection [self reloadTableViewPreservingSelection]; } -- (void)showDetailViewForSubsection:(BlogDetailsSubsection)section +- (void)showDetailViewForSubsection:(BlogDetailsSubsection)section +{ + [self showDetailViewForSubsection:section userInfo:@{}]; +} + +- (void)showDetailViewForSubsection:(BlogDetailsSubsection)section userInfo:(NSDictionary *)userInfo { NSIndexPath *indexPath = [self indexPathForSubsection:section]; @@ -526,7 +529,8 @@ - (void)showDetailViewForSubsection:(BlogDetailsSubsection)section [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:[self optimumScrollPositionForIndexPath:indexPath]]; - [self showMediaLibraryFromSource:BlogDetailsNavigationSourceLink]; + BOOL showPicker = userInfo[[BlogDetailsViewController userInfoShowPickerKey]] ?: NO; + [self showMediaLibraryFromSource:BlogDetailsNavigationSourceLink showPicker: showPicker]; break; case BlogDetailsSubsectionPages: self.restorableSelectedIndexPath = indexPath; @@ -670,7 +674,6 @@ - (void)setRestorableSelectedIndexPath:(NSIndexPath *)restorableSelectedIndexPat if (restorableSelectedIndexPath != nil && restorableSelectedIndexPath.section < [self.tableSections count]) { BlogDetailsSection *section = [self.tableSections objectAtIndex:restorableSelectedIndexPath.section]; switch (section.category) { - case BlogDetailsSectionCategoryQuickAction: case BlogDetailsSectionCategoryQuickStart: case BlogDetailsSectionCategoryJetpackBrandingCard: case BlogDetailsSectionCategoryDomainCredit: { @@ -979,7 +982,6 @@ - (void)reloadTableViewPreservingSelection // For QuickStart and Use Domain cases we want to select the first row on the next available section switch (section.category) { - case BlogDetailsSectionCategoryQuickAction: case BlogDetailsSectionCategoryQuickStart: case BlogDetailsSectionCategoryJetpackBrandingCard: case BlogDetailsSectionCategoryDomainCredit: { @@ -1549,12 +1551,6 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { BlogDetailsSection *detailSection = [self.tableSections objectAtIndex:section]; - - /// For larger texts we don't show the quick actions row - if (detailSection.category == BlogDetailsSectionCategoryQuickAction && self.isAccessibilityCategoryEnabled) { - return 0; - } - return [detailSection.rows count]; } @@ -1593,11 +1589,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } - if (section.category == BlogDetailsSectionCategoryQuickAction) { - QuickActionsCell *cell = [tableView dequeueReusableCellWithIdentifier:BlogDetailsQuickActionsCellIdentifier]; - [self configureQuickActionsWithCell: cell]; - return cell; - } if (section.category == BlogDetailsSectionCategoryQuickStart) { QuickStartCell *cell = [tableView dequeueReusableCellWithIdentifier:BlogDetailsQuickStartCellIdentifier]; @@ -1833,10 +1824,14 @@ - (void)showPageListFromSource:(BlogDetailsNavigationSource)source [[QuickStartTourGuide shared] visited:QuickStartTourElementPages]; } -- (void)showMediaLibraryFromSource:(BlogDetailsNavigationSource)source +- (void)showMediaLibraryFromSource:(BlogDetailsNavigationSource)source { + [self showMediaLibraryFromSource:source showPicker:false]; +} + +- (void)showMediaLibraryFromSource:(BlogDetailsNavigationSource)source showPicker:(BOOL)showPicker { [self trackEvent:WPAnalyticsStatOpenedMediaLibrary fromSource:source]; - SiteMediaViewController *controller = [[SiteMediaViewController alloc] initWithBlog:self.blog]; + SiteMediaViewController *controller = [[SiteMediaViewController alloc] initWithBlog:self.blog showPicker:showPicker]; [self.presentationDelegate presentBlogDetailsViewController:controller]; [[QuickStartTourGuide shared] visited:QuickStartTourElementMediaScreen]; } @@ -2244,4 +2239,10 @@ - (void)pulledToRefreshWith:(UIRefreshControl *)refreshControl onCompletion:( vo }]; } +#pragma mark - Constants + ++ (NSString *)userInfoShowPickerKey { + return @"show-picker"; +} + @end diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/ActionRow.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/ActionRow.swift deleted file mode 100644 index a05400320d24..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/ActionRow.swift +++ /dev/null @@ -1,119 +0,0 @@ -class ActionButton: UIView { - - private enum Constants { - static let maxButtonSize: CGFloat = 56 - static let spacing: CGFloat = 8 - static let borderColor = UIColor.quickActionButtonBorder - static let backgroundColor = UIColor.quickActionButtonBackground - static let selectedBackgroundColor = UIColor.quickActionSelectedBackground - static let iconColor = UIColor.listIcon - } - - private let button: UIButton = { - let button = RoundedButton(type: .custom) - button.isCircular = true - button.borderColor = Constants.borderColor - button.borderWidth = 1 - button.backgroundColor = Constants.backgroundColor - button.selectedBackgroundColor = Constants.selectedBackgroundColor - button.tintColor = Constants.iconColor - button.imageView?.contentMode = .center - button.imageView?.clipsToBounds = false - return button - }() - - private let titleLabel: UILabel = { - let titleLabel = UILabel() - titleLabel.font = UIFont.preferredFont(forTextStyle: .caption1) - titleLabel.textAlignment = .center - titleLabel.adjustsFontForContentSizeCategory = true - return titleLabel - }() - - private var callback: (() -> Void)? - - convenience init(image: UIImage, title: String, tapped: @escaping () -> Void) { - - self.init(frame: .zero) - - button.setImage(image, for: .normal) - titleLabel.text = title - - button.accessibilityLabel = title - accessibilityElements = [button] - - let stackView = UIStackView(arrangedSubviews: [ - button, - titleLabel - ]) - stackView.alignment = .center - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.spacing = Constants.spacing - stackView.axis = .vertical - - callback = tapped - button.addTarget(self, action: #selector(ActionButton.tapped), for: .touchUpInside) - - NSLayoutConstraint.activate([ - button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: 1), - button.heightAnchor.constraint(lessThanOrEqualToConstant: Constants.maxButtonSize) - ]) - - addSubview(stackView) - - pinSubviewToAllEdges(stackView) - } - - @objc func tapped() { - callback?() - } -} - -class ActionRow: UIStackView { - - enum Constants { - static let minimumSpacing: CGFloat = 8 - static let margins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) - } - - struct Item { - let image: UIImage - let title: String - let tapped: () -> Void - } - - convenience init(items: [Item]) { - - let buttons = items.map({ item in - return ActionButton(image: item.image, title: item.title, tapped: item.tapped) - }) - - self.init(arrangedSubviews: buttons) - - distribution = .equalCentering - spacing = Constants.minimumSpacing - translatesAutoresizingMaskIntoConstraints = false - refreshStackViewVisibility() - - layoutMargins = Constants.margins - isLayoutMarginsRelativeArrangement = true - } - - // MARK: - Accessibility - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - refreshStackViewVisibility() - } - - private func refreshStackViewVisibility() { - for view in arrangedSubviews { - if traitCollection.preferredContentSizeCategory.isAccessibilityCategory { - view.isHidden = true - } else { - view.isHidden = false - } - } - } -} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/BlogDetailHeaderView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/BlogDetailHeaderView.swift index 7c581a3f6f37..b9526c62c861 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/BlogDetailHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/BlogDetailHeaderView.swift @@ -17,7 +17,7 @@ class BlogDetailHeaderView: UIView { // MARK: - Child Views - private let titleView: TitleView + let titleView: TitleView // MARK: - Delegate @@ -100,13 +100,13 @@ class BlogDetailHeaderView: UIView { // MARK: - Initializers - required init(items: [ActionRow.Item], delegate: BlogDetailHeaderViewDelegate) { + required init(delegate: BlogDetailHeaderViewDelegate) { titleView = TitleView(frame: .zero) super.init(frame: .zero) self.delegate = delegate - setupChildViews(items: items) + setupChildViews() } required init?(coder: NSCoder) { @@ -115,7 +115,7 @@ class BlogDetailHeaderView: UIView { // MARK: - Child View Initialization - private func setupChildViews(items: [ActionRow.Item]) { + private func setupChildViews() { assert(delegate != nil) if let siteActionsMenu = delegate?.makeSiteActionsMenu() { @@ -145,17 +145,15 @@ class BlogDetailHeaderView: UIView { addSubview(titleView) - let showsActionRow = items.count > 0 - setupConstraintsForChildViews(showsActionRow) + setupConstraintsForChildViews() } // MARK: - Constraints private var topActionRowConstraint: NSLayoutConstraint? - private func setupConstraintsForChildViews(_ showsActionRow: Bool) { + private func setupConstraintsForChildViews() { let constraints = constraintsForTitleView() - NSLayoutConstraint.activate(constraints) } @@ -203,7 +201,7 @@ class BlogDetailHeaderView: UIView { } } -fileprivate extension BlogDetailHeaderView { +extension BlogDetailHeaderView { class TitleView: UIView { private enum Dimensions { static let siteSwitcherHeight: CGFloat = 36 diff --git a/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListConfiguration.swift b/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListConfiguration.swift index 5785d1df8754..d07bba707513 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListConfiguration.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListConfiguration.swift @@ -8,20 +8,24 @@ import Foundation @objc var backButtonTitle: String @objc var shouldHideSelfHostedSites: Bool @objc var shouldHideBlogsNotSupportingDomains: Bool + @objc var analyticsSource: String? - init(shouldShowCancelButton: Bool, - shouldShowNavBarButtons: Bool, - navigationTitle: String, - backButtonTitle: String, - shouldHideSelfHostedSites: Bool, - shouldHideBlogsNotSupportingDomains: Bool) { + init( + shouldShowCancelButton: Bool, + shouldShowNavBarButtons: Bool, + navigationTitle: String, + backButtonTitle: String, + shouldHideSelfHostedSites: Bool, + shouldHideBlogsNotSupportingDomains: Bool, + analyticsSource: String? = nil + ) { self.shouldShowCancelButton = shouldShowCancelButton self.shouldShowNavBarButtons = shouldShowNavBarButtons self.navigationTitle = navigationTitle self.backButtonTitle = backButtonTitle self.shouldHideSelfHostedSites = shouldHideSelfHostedSites self.shouldHideBlogsNotSupportingDomains = shouldHideBlogsNotSupportingDomains - + self.analyticsSource = analyticsSource super.init() } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController.m b/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController.m index 23dddd96c2c3..bd309ecba5f9 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController.m @@ -418,6 +418,21 @@ - (void)removeBlogItemsFromSpotlight:(Blog *)blog { } } +#pragma mark - Tracks + +- (void)trackEvent:(WPAnalyticsEvent)event properties:(NSDictionary * _Nullable)properties +{ + NSMutableDictionary *mergedProperties = [NSMutableDictionary dictionary]; + + if (self.configuration.analyticsSource) { + mergedProperties[WPAppAnalyticsKeySource] = self.configuration.analyticsSource; + } + + [mergedProperties addEntriesFromDictionary:properties ?: @{}]; + + [WPAnalytics trackEvent:event properties:mergedProperties]; +} + #pragma mark - Header methods - (UIView *)headerView @@ -803,7 +818,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath - (void)setSelectedBlog:(Blog *)selectedBlog { [self setSelectedBlog:selectedBlog animated:[self isViewLoaded]]; - [WPAnalytics trackEvent:WPAnalyticsEventSiteSwitcherDomainSiteSelected]; + [self trackEvent:WPAnalyticsEventSiteSwitcherSiteSelected properties:nil]; } - (void)setSelectedBlog:(Blog *)selectedBlog animated:(BOOL)animated diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift index 7b46435176e0..7d073c66da4f 100644 --- a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift @@ -83,7 +83,11 @@ private extension DashboardCard { return NSLocalizedString("personalizeHome.dashboardCard.activityLog", value: "Recent activity", comment: "Card title for the pesonalization menu") case .pages: return NSLocalizedString("personalizeHome.dashboardCard.pages", value: "Pages", comment: "Card title for the pesonalization menu") - case .quickStart, .ghost, .failure, .personalize, .jetpackBadge, .jetpackInstall, .empty, .freeToPaidPlansDashboardCard, .domainRegistration, .jetpackSocial, .bloganuaryNudge, .googleDomains: + case .dynamic, .quickStart, .ghost, + .failure, .personalize, .jetpackBadge, + .jetpackInstall, .empty, .freeToPaidPlansDashboardCard, + .domainRegistration, .jetpackSocial, .bloganuaryNudge, + .googleDomains: assertionFailure("\(self) card should not appear in the personalization menus") return "" // These cards don't appear in the personalization menus } diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift index d4b383f7ee79..0cc3e988e19f 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/BloggingPromptCoordinator.swift @@ -20,7 +20,6 @@ import UIKit case actionSheetHeader case promptNotification case promptStaticNotification - case unknown var editorEntryPoint: PostEditorEntryPoint { switch self { @@ -32,8 +31,6 @@ import UIKit return .bloggingPromptsActionSheetHeader case .promptNotification, .promptStaticNotification: return .bloggingPromptsNotification - default: - return .unknown } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift index 4dd1995138f0..909befd04e43 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/RootViewCoordinator+BloggingPrompt.swift @@ -45,9 +45,4 @@ private extension RootViewCoordinator { var accountSites: [Blog]? { try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext)?.visibleBlogs } - - struct Constants { - static let featureIntroDisplayedUDKey = "wp_intro_shown_blogging_prompts" - } - } diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/Time Selector/TimeSelectionView.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/Time Selector/TimeSelectionView.swift index 8bd112f7d8f9..09d2167f73a8 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/Time Selector/TimeSelectionView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/Time Selector/TimeSelectionView.swift @@ -19,13 +19,6 @@ class TimeSelectionView: UIView { titleBar.setSelectedTime(timePicker.date.toLocalTime()) } - private lazy var timePickerContainerView: UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(timePicker) - return view - }() - private lazy var titleBar: TimeSelectionButton = { let button = TimeSelectionButton(selectedTime: selectedTime.toLocalTime(), insets: Self.titleInsets) button.translatesAutoresizingMaskIntoConstraints = false diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index 1d93557e8252..a5843a7f2194 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -124,8 +124,8 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite let configuration = AddNewSiteConfiguration( canCreateWPComSite: viewModel.defaultAccount != nil, canAddSelfHostedSite: AppConfiguration.showAddSelfHostedSiteButton, - launchSiteCreation: self.launchSiteCreationFromNoSites, - launchLoginForSelfHostedSite: self.launchLoginForSelfHostedSite + launchSiteCreation: { [weak self] in self?.launchSiteCreationFromNoSites() }, + launchLoginForSelfHostedSite: { [weak self] in self?.launchLoginForSelfHostedSite() } ) let noSiteView = NoSitesView( viewModel: noSitesViewModel, @@ -888,6 +888,10 @@ extension MySiteViewController: BlogDetailsPresentationDelegate { blogDetailsViewController?.showDetailView(for: subsection) } + func showBlogDetailsSubsection(_ subsection: BlogDetailsSubsection, userInfo: [AnyHashable: Any]) { + blogDetailsViewController?.showDetailView(for: subsection, userInfo: userInfo) + } + // TODO: Refactor presentation from routes // More context: https://github.com/wordpress-mobile/WordPress-iOS/issues/21759 func presentBlogDetailsViewController(_ viewController: UIViewController) { diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/NoSitesView.swift b/WordPress/Classes/ViewRelated/Blog/My Site/NoSitesView.swift index 257c450cba65..f09d6dd9e263 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/NoSitesView.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/NoSitesView.swift @@ -145,11 +145,7 @@ extension NoSitesView { func handleAddNewSiteButtonTapped() { WPAnalytics.track(.mySiteNoSitesViewActionTapped) - guard addNewSiteConfiguration.canCreateWPComSite else { - return - } - - guard addNewSiteConfiguration.canAddSelfHostedSite else { + if addNewSiteConfiguration.canCreateWPComSite && !addNewSiteConfiguration.canAddSelfHostedSite { addNewSiteConfiguration.launchSiteCreation() return } @@ -177,18 +173,3 @@ extension NoSitesView { static let addSelfHostedSite = NSLocalizedString("mySite.noSites.actionSheet.addSelfHostedSite", value: "Add self-hosted site", comment: "Action sheet button title. Launches the flow to a add self-hosted site.") } } - -struct NoSitesView_Previews: PreviewProvider { - static var previews: some View { - let configuration = AddNewSiteConfiguration( - canCreateWPComSite: true, - canAddSelfHostedSite: true, - launchSiteCreation: {}, - launchLoginForSelfHostedSite: {} - ) - NoSitesView( - viewModel: NoSitesViewModel(appUIType: .simplified, account: nil), - addNewSiteConfiguration: configuration - ) - } -} diff --git a/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift b/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift index dd0b957235fb..07c11a274576 100644 --- a/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift @@ -134,17 +134,6 @@ private extension QuickStartChecklistViewController { } } -private struct TasksCompleteScreenConfiguration { - var title: String - var subtitle: String - var imageName: String -} - -private struct QuickStartChecklistConfiguration { - var title: String - var tours: [QuickStartTour] -} - private enum Constants { static let analyticsTypeKey = "type" static let closeButtonRadius: CGFloat = 30 diff --git a/WordPress/Classes/ViewRelated/Blog/QuickStartNavigationSettings.swift b/WordPress/Classes/ViewRelated/Blog/QuickStartNavigationSettings.swift index 971967a1bca1..3c7cc0051bc4 100644 --- a/WordPress/Classes/ViewRelated/Blog/QuickStartNavigationSettings.swift +++ b/WordPress/Classes/ViewRelated/Blog/QuickStartNavigationSettings.swift @@ -1,7 +1,4 @@ class QuickStartNavigationSettings: NSObject { - private weak var readerNav: UINavigationController? - private var spotlightView: QuickStartSpotlightView? - func updateWith(navigationController: UINavigationController, andViewController viewController: UIViewController) { switch viewController { @@ -13,33 +10,3 @@ class QuickStartNavigationSettings: NSObject { } } } - -private extension QuickStartNavigationSettings { - - func spotlightReaderBackButton() { - guard let readerNav = readerNav else { - return - } - - let newSpotlightView = QuickStartSpotlightView() - newSpotlightView.translatesAutoresizingMaskIntoConstraints = false - - readerNav.navigationBar.addSubview(newSpotlightView) - readerNav.navigationBar.addConstraints([ - newSpotlightView.leadingAnchor.constraint(equalTo: readerNav.navigationBar.leadingAnchor, constant: 30.0), - newSpotlightView.topAnchor.constraint(equalTo: readerNav.navigationBar.topAnchor, constant: 15.0), - ]) - - spotlightView = newSpotlightView - } - - func removeReaderSpotlight() { - guard let spotlight = spotlightView else { - return - } - - spotlight.removeFromSuperview() - spotlightView = nil - } - -} diff --git a/WordPress/Classes/ViewRelated/Blog/QuickStartSettings.swift b/WordPress/Classes/ViewRelated/Blog/QuickStartSettings.swift index caa6bbf01ec2..5afbcd039af0 100644 --- a/WordPress/Classes/ViewRelated/Blog/QuickStartSettings.swift +++ b/WordPress/Classes/ViewRelated/Blog/QuickStartSettings.swift @@ -10,14 +10,6 @@ final class QuickStartSettings { self.userDefaults = userDefaults } - // MARK: - Quick Start availability - - func isQuickStartAvailable(for blog: Blog) -> Bool { - return blog.isUserCapableOf(.ManageOptions) && - blog.isUserCapableOf(.EditThemeOptions) && - !blog.isWPForTeams() - } - // MARK: - User Defaults Storage func promptWasDismissed(for blog: Blog) -> Bool { diff --git a/WordPress/Classes/ViewRelated/Blog/QuickStartTourGuide.swift b/WordPress/Classes/ViewRelated/Blog/QuickStartTourGuide.swift index 6ffeff6add46..88dba8124653 100644 --- a/WordPress/Classes/ViewRelated/Blog/QuickStartTourGuide.swift +++ b/WordPress/Classes/ViewRelated/Blog/QuickStartTourGuide.swift @@ -507,7 +507,6 @@ private extension QuickStartTourGuide { } private struct Constants { - static let maxSkippedTours = 3 static let suggestionTimeout = 10.0 static let quickStartDelay: DispatchTimeInterval = .milliseconds(500) static let nextStepDelay: DispatchTimeInterval = .milliseconds(1000) diff --git a/WordPress/Classes/ViewRelated/Blog/QuickStartTours.swift b/WordPress/Classes/ViewRelated/Blog/QuickStartTours.swift index 306454eb9c85..a2db082e3aaa 100644 --- a/WordPress/Classes/ViewRelated/Blog/QuickStartTours.swift +++ b/WordPress/Classes/ViewRelated/Blog/QuickStartTours.swift @@ -69,27 +69,6 @@ struct QuickStartSiteMenu { static let waypoint = QuickStartTour.WayPoint(element: .siteMenu, description: descriptionBase.highlighting(phrase: descriptionTarget, icon: nil)) } -struct QuickStartChecklistTour: QuickStartTour { - let key = "quick-start-checklist-tour" - let analyticsKey = "view_list" - let title = NSLocalizedString("Continue with site setup", comment: "Title of a Quick Start Tour") - let titleMarkedCompleted = NSLocalizedString("Completed: Continue with site setup", comment: "The Quick Start Tour title after the user finished the step.") - let description = NSLocalizedString("Time to finish setting up your site! Our checklist walks you through the next steps.", comment: "Description of a Quick Start Tour") - let icon = UIImage.gridicon(.external) - let iconColor = UIColor.systemGray4 - let suggestionNoText = Strings.notNow - let suggestionYesText = Strings.yesShowMe - let possibleEntryPoints: Set = [.blogDetails, .blogDashboard] - - var waypoints: [WayPoint] = { - let descriptionBase = NSLocalizedString("Select %@ to see your checklist", comment: "A step in a guided tour for quick start. %@ will be the name of the item to select.") - let descriptionTarget = NSLocalizedString("Quick Start", comment: "The menu item to select during a guided tour.") - return [(element: .checklist, description: descriptionBase.highlighting(phrase: descriptionTarget, icon: .gridicon(.listCheckmark)))] - }() - - let accessibilityHintText = NSLocalizedString("Guides you through the process of setting up your site.", comment: "This value is used to set the accessibility hint text for setting up the user's site.") -} - struct QuickStartCreateTour: QuickStartTour { let key = "quick-start-create-tour" let analyticsKey = "create_site" @@ -134,27 +113,6 @@ struct QuickStartViewTour: QuickStartTour { } } -struct QuickStartThemeTour: QuickStartTour { - let key = "quick-start-theme-tour" - let analyticsKey = "browse_themes" - let title = NSLocalizedString("Choose a theme", comment: "Title of a Quick Start Tour") - let titleMarkedCompleted = NSLocalizedString("Completed: Choose a theme", comment: "The Quick Start Tour title after the user finished the step.") - let description = NSLocalizedString("Browse all our themes to find your perfect fit.", comment: "Description of a Quick Start Tour") - let icon = UIImage.gridicon(.themes) - let iconColor = UIColor.systemGray4 - let suggestionNoText = Strings.notNow - let suggestionYesText = Strings.yesShowMe - let possibleEntryPoints: Set = [.blogDetails] - - var waypoints: [WayPoint] = { - let descriptionBase = NSLocalizedString("Select %@ to discover new themes", comment: "A step in a guided tour for quick start. %@ will be the name of the item to select.") - let descriptionTarget = NSLocalizedString("Themes", comment: "The menu item to select during a guided tour.") - return [(element: .themes, description: descriptionBase.highlighting(phrase: descriptionTarget, icon: .gridicon(.themes)))] - }() - - let accessibilityHintText = NSLocalizedString("Guides you through the process of choosing a theme for your site.", comment: "This value is used to set the accessibility hint text for choosing a theme for the user's site.") -} - struct QuickStartShareTour: QuickStartTour { let key = "quick-start-share-tour" let analyticsKey = "share_site" @@ -342,27 +300,6 @@ struct QuickStartCheckStatsTour: QuickStartTour { let accessibilityHintText = NSLocalizedString("Guides you through the process of reviewing statistics for your site.", comment: "This value is used to set the accessibility hint text for viewing Stats on the user's site.") } -struct QuickStartExplorePlansTour: QuickStartTour { - let key = "quick-start-explore-plans-tour" - let analyticsKey = "explore_plans" - let title = NSLocalizedString("Explore plans", comment: "Title of a Quick Start Tour") - let titleMarkedCompleted = NSLocalizedString("Completed: Explore plans", comment: "The Quick Start Tour title after the user finished the step.") - let description = NSLocalizedString("Learn about the marketing and SEO tools in our paid plans.", comment: "Description of a Quick Start Tour") - let icon = UIImage.gridicon(.plans) - let iconColor = UIColor.systemGray4 - let suggestionNoText = Strings.notNow - let suggestionYesText = Strings.yesShowMe - let possibleEntryPoints: Set = [.blogDetails] - - var waypoints: [WayPoint] = { - let descriptionBase = NSLocalizedString("Select %@ to see your current plan and other available plans.", comment: "A step in a guided tour for quick start. %@ will be the name of the item to select.") - let descriptionTarget = NSLocalizedString("Plan", comment: "The item to select during a guided tour.") - return [(element: .plans, description: descriptionBase.highlighting(phrase: descriptionTarget, icon: .gridicon(.plans)))] - }() - - let accessibilityHintText = NSLocalizedString("Guides you through the process of exploring plans for your site.", comment: "This value is used to set the accessibility hint text for exploring plans on the user's site.") -} - struct QuickStartNotificationsTour: QuickStartTour { let key = "quick-start-notifications-tour" let analyticsKey = "notifications" diff --git a/WordPress/Classes/ViewRelated/Blog/SharingAuthorizationWebViewController.swift b/WordPress/Classes/ViewRelated/Blog/SharingAuthorizationWebViewController.swift index cf8d2ddd801b..4977c79a89c9 100644 --- a/WordPress/Classes/ViewRelated/Blog/SharingAuthorizationWebViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/SharingAuthorizationWebViewController.swift @@ -114,10 +114,6 @@ class SharingAuthorizationWebViewController: WPWebViewController { // Delegates should expect to handle a false positive. delegate?.authorizeDidSucceed(publicizer) } - - private func displayLoadError(error: NSError) { - delegate?.authorize(self.publicizer, didFailWithError: error) - } } // MARK: - WKNavigationDelegate diff --git a/WordPress/Classes/ViewRelated/Blog/Site Management/SiteTagsViewController.swift b/WordPress/Classes/ViewRelated/Blog/Site Management/SiteTagsViewController.swift index 7f226c051459..585f6669579d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Management/SiteTagsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Management/SiteTagsViewController.swift @@ -6,7 +6,6 @@ final class SiteTagsViewController: UITableViewController { private struct TableConstants { static let cellIdentifier = "TitleBadgeDisclosureCell" static let accesibilityIdentifier = "SiteTagsList" - static let numberOfSections = 1 } private let blog: Blog diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/SiteIconPickerView.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/SiteIconPickerView.swift index 8caa77a1aa57..f85d1d78b917 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/SiteIconPickerView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/SiteIconPickerView.swift @@ -9,7 +9,6 @@ struct SiteIconPickerView: View { @SwiftUI.State private var currentIcon: String? = nil @SwiftUI.State private var currentBackgroundColor: UIColor = .init(hexString: "#969CA1") ?? .gray - @SwiftUI.State private var scrollOffsetColumn: Int? = nil private var hasMadeSelection: Bool { currentIcon != nil diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+SiteActions.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+SiteActions.swift index 1ed02b1c6fcd..4f6ada3551cb 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+SiteActions.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+SiteActions.swift @@ -1,5 +1,6 @@ import UIKit import SwiftUI +import WordPressAuthenticator extension SitePickerViewController { @@ -20,15 +21,19 @@ extension SitePickerViewController { } private func makePrimarySection() -> UIMenu { - UIMenu(options: .displayInline, children: [ - MenuItem.visitSite(visitSiteTapped), - MenuItem.switchSite(siteSwitcherTapped) - ].map { $0.toAction }) + var menuItems = [ + MenuItem.visitSite({ [weak self] in self?.visitSiteTapped() }), + MenuItem.addSite({ [weak self] in self?.addSiteTapped()} ), + ] + if numberOfBlogs() > 1 { + menuItems.append(MenuItem.switchSite({ [weak self] in self?.siteSwitcherTapped() })) + } + return UIMenu(options: .displayInline, children: menuItems.map { $0.toAction }) } private func makeSecondarySection() -> UIMenu { UIMenu(options: .displayInline, children: [ - MenuItem.siteTitle(siteTitleTapped).toAction, + MenuItem.siteTitle({ [weak self] in self?.siteTitleTapped() }).toAction, UIMenu(title: Strings.siteIcon, image: UIImage(systemName: "photo.circle"), children: [ makeSiteIconMenu() ?? UIMenu() ]) @@ -37,10 +42,58 @@ extension SitePickerViewController { private func makeTertiarySection() -> UIMenu { UIMenu(options: .displayInline, children: [ - MenuItem.personalizeHome(personalizeHomeTapped).toAction + MenuItem.personalizeHome({ [weak self] in self?.personalizeHomeTapped() }).toAction ]) } + // MARK: - Add site + + private func addSiteTapped() { + let canCreateWPComSite = defaultAccount() != nil + let canAddSelfHostedSite = AppConfiguration.showAddSelfHostedSiteButton + + // Launch wp.com site creation if the user can't add self-hosted sites + if canCreateWPComSite && !canAddSelfHostedSite { + launchSiteCreation() + return + } + + showAddSiteActionSheet(from: blogDetailHeaderView.titleView.siteActionButton, + canCreateWPComSite: canCreateWPComSite, + canAddSelfHostedSite: canAddSelfHostedSite) + + WPAnalytics.trackEvent(.mySiteHeaderAddSiteTapped) + } + + private func showAddSiteActionSheet(from sourceView: UIView, canCreateWPComSite: Bool, canAddSelfHostedSite: Bool) { + let actionSheet = AddSiteAlertFactory().makeAddSiteAlert( + source: "my_site", + canCreateWPComSite: canCreateWPComSite, + createWPComSite: { [weak self] in self?.launchSiteCreation() }, + canAddSelfHostedSite: canAddSelfHostedSite, + addSelfHostedSite: { [weak self] in self?.launchLoginForSelfHostedSite() } + ) + + actionSheet.popoverPresentationController?.sourceView = sourceView + actionSheet.popoverPresentationController?.sourceRect = sourceView.bounds + actionSheet.popoverPresentationController?.permittedArrowDirections = .up + + parent?.present(actionSheet, animated: true) + } + + private func launchSiteCreation() { + guard let parent = parent as? MySiteViewController else { + return + } + parent.launchSiteCreation(source: "my_site") + } + + private func launchLoginForSelfHostedSite() { + WordPressAuthenticator.showLoginForSelfHostedSite(self) + } + + // MARK: - Personalize home + private func personalizeHomeTapped() { guard let siteID = blog.dotComID?.intValue else { return DDLogError("Failed to show dashboard personalization screen: siteID is missing") @@ -56,10 +109,21 @@ extension SitePickerViewController { WPAnalytics.trackEvent(.mySiteHeaderPersonalizeHomeTapped) } + + // MARK: - Helpers + + private func numberOfBlogs() -> Int { + defaultAccount()?.blogs?.count ?? 0 + } + + private func defaultAccount() -> WPAccount? { + try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext) + } } private enum MenuItem { case visitSite(_ handler: () -> Void) + case addSite(_ handler: () -> Void) case switchSite(_ handler: () -> Void) case siteTitle(_ handler: () -> Void) case personalizeHome(_ handler: () -> Void) @@ -67,6 +131,7 @@ private enum MenuItem { var title: String { switch self { case .visitSite: return Strings.visitSite + case .addSite: return Strings.addSite case .switchSite: return Strings.switchSite case .siteTitle: return Strings.siteTitle case .personalizeHome: return Strings.personalizeHome @@ -76,6 +141,7 @@ private enum MenuItem { var icon: UIImage? { switch self { case .visitSite: return UIImage(systemName: "safari") + case .addSite: return UIImage(systemName: "plus") case .switchSite: return UIImage(systemName: "arrow.triangle.swap") case .siteTitle: return UIImage(systemName: "character") case .personalizeHome: return UIImage(systemName: "slider.horizontal.3") @@ -85,6 +151,7 @@ private enum MenuItem { var toAction: UIAction { switch self { case .visitSite(let handler), + .addSite(let handler), .switchSite(let handler), .siteTitle(let handler), .personalizeHome(let handler): @@ -95,6 +162,7 @@ private enum MenuItem { private enum Strings { static let visitSite = NSLocalizedString("mySite.siteActions.visitSite", value: "Visit site", comment: "Menu title for the visit site option") + static let addSite = NSLocalizedString("mySite.siteActions.addSite", value: "Add site", comment: "Menu title for the add site option") static let switchSite = NSLocalizedString("mySite.siteActions.switchSite", value: "Switch site", comment: "Menu title for the switch site option") static let siteTitle = NSLocalizedString("mySite.siteActions.siteTitle", value: "Change site title", comment: "Menu title for the change site title option") static let siteIcon = NSLocalizedString("mySite.siteActions.siteIcon", value: "Change site icon", comment: "Menu title for the change site icon option") diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift index e3877fef0b0f..8b915c5acbb9 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift @@ -12,7 +12,6 @@ final class SitePickerViewController: UIViewController { } } - var siteIconPresenter: SiteIconPickerPresenter? var siteIconPickerPresenter: SiteIconPickerPresenter? var onBlogSwitched: ((Blog) -> Void)? var onBlogListDismiss: (() -> Void)? @@ -22,7 +21,7 @@ final class SitePickerViewController: UIViewController { let mediaService: MediaService private(set) lazy var blogDetailHeaderView: BlogDetailHeaderView = { - let headerView = BlogDetailHeaderView(items: [], delegate: self) + let headerView = BlogDetailHeaderView(delegate: self) headerView.translatesAutoresizingMaskIntoConstraints = false return headerView }() diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift index 172ac0ef5da1..1e4a25b4759b 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift @@ -20,7 +20,6 @@ final class SiteIconPickerPresenter: NSObject { // MARK: - Private Properties private var dataSource: AnyObject? - private var mediaCapturePresenter: AnyObject? // MARK: - Public methods diff --git a/WordPress/Classes/ViewRelated/Comments/CommentContentTableViewCell.swift b/WordPress/Classes/ViewRelated/Comments/CommentContentTableViewCell.swift index a0598a73a956..7453cf4fbb53 100644 --- a/WordPress/Classes/ViewRelated/Comments/CommentContentTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Comments/CommentContentTableViewCell.swift @@ -87,7 +87,6 @@ class CommentContentTableViewCell: UITableViewCell, NibReusable { // MARK: Constants - private let customBottomSpacing: CGFloat = 10 private let contentButtonsTopSpacing: CGFloat = 15 // MARK: Outlets @@ -151,10 +150,6 @@ class CommentContentTableViewCell: UITableViewCell, NibReusable { } } - private var isReactionBarVisible: Bool { - return isCommentReplyEnabled || isCommentLikesEnabled - } - var shouldHideSeparator = false { didSet { separatorView.isHidden = shouldHideSeparator @@ -531,7 +526,4 @@ private extension String { + "%1$d is a placeholder for the number of Likes.") static let pluralLikesFormat = NSLocalizedString("%1$d Likes", comment: "Plural button title to Like a comment. " + "%1$d is a placeholder for the number of Likes.") - - // pattern that detects empty HTML elements (including HTML comments within). - static let emptyElementRegexPattern = "<[a-z]+>()+<\\/[a-z]+>" } diff --git a/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift b/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift index f9364fc5275d..199fe09c9cf1 100644 --- a/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift +++ b/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift @@ -191,14 +191,6 @@ class CommentDetailViewController: UIViewController, NoResultsViewHost { return appearance }() - /// opaque navigation bar style. - /// this is used for iOS 14 and below, since scrollEdgeAppearance only applies for large title bars, except on iOS 15 where it applies for all navbars. - private lazy var opaqueBarAppearance: UINavigationBarAppearance = { - let appearance = UINavigationBarAppearance() - appearance.configureWithOpaqueBackground() - return appearance - }() - // MARK: Nav Bar Buttons private(set) lazy var editBarButtonItem: UIBarButtonItem = { @@ -351,18 +343,6 @@ private extension CommentDetailViewController { return .init(top: 0, left: -tableView.separatorInset.left, bottom: 0, right: tableView.frame.size.width) } - /// returns the height of the navigation bar + the status bar. - var topBarHeight: CGFloat { - return (view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0.0) + - (navigationController?.navigationBar.frame.height ?? 0.0) - } - - /// determines the threshold for the content offset on whether the content has scrolled. - /// for translucent navigation bars, the content view spans behind the status bar and navigation bar so we'd have to account for that. - var contentScrollThreshold: CGFloat { - (navigationController?.navigationBar.isTranslucent ?? false) ? -topBarHeight : 0 - } - func configureView() { containerStackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(containerStackView) diff --git a/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+CommentDetail.swift b/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+CommentDetail.swift index 9216689530ad..e8a5c8421e2f 100644 --- a/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+CommentDetail.swift +++ b/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+CommentDetail.swift @@ -5,7 +5,6 @@ import UIKit extension WPStyleGuide { public struct CommentDetail { static let tintColor: UIColor = .primary - static let externalIconImage: UIImage = .gridicon(.external).imageFlippedForRightToLeftLayoutDirection() static let textFont = WPStyleGuide.fontForTextStyle(.body) static let textColor = UIColor.text diff --git a/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+Comments.swift b/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+Comments.swift index 1d9d9bdc0261..94f6fa41625f 100644 --- a/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+Comments.swift +++ b/WordPress/Classes/ViewRelated/Comments/WPStyleGuide+Comments.swift @@ -8,23 +8,6 @@ extension WPStyleGuide { public struct Comments { static let gravatarPlaceholderImage = UIImage(named: "gravatar") ?? UIImage() - static let backgroundColor = UIColor.listForeground static let pendingIndicatorColor = UIColor.muriel(color: MurielColor(name: .yellow, shade: .shade20)) - - static let detailFont = WPStyleGuide.fontForTextStyle(.subheadline, fontWeight: .regular) - static let detailTextColor = UIColor.textSubtle - - private static let titleTextColor = UIColor.text - private static let titleTextStyle = UIFont.TextStyle.headline - - static let titleBoldAttributes: [NSAttributedString.Key: Any] = [ - .font: WPStyleGuide.fontForTextStyle(titleTextStyle, fontWeight: .semibold), - .foregroundColor: titleTextColor - ] - - static let titleRegularAttributes: [NSAttributedString.Key: Any] = [ - .font: WPStyleGuide.fontForTextStyle(titleTextStyle, fontWeight: .regular), - .foregroundColor: titleTextColor - ] } } diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewController/RegisterDomainDetailsViewController+LocalizedStrings.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewController/RegisterDomainDetailsViewController+LocalizedStrings.swift index 0ff0f036b69c..a72b44a5906c 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewController/RegisterDomainDetailsViewController+LocalizedStrings.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewController/RegisterDomainDetailsViewController+LocalizedStrings.swift @@ -49,10 +49,6 @@ enum RegisterDomainDetails { static let redemptionError = NSLocalizedString("Problem purchasing your domain. Please try again.", comment: "Register Domain - error displayed when there's a problem when purchasing the domain." ) - static let changingPrimaryDomainError = NSLocalizedString("We've had problems changing the primary domain on your site — but don't worry, your domain was successfully purchased.", - comment: "Register Domain - error displayed when a domain was purchased succesfully, but there was a problem setting it to a primary domain for the site" - ) - static let statesFetchingError = NSLocalizedString( "Error occurred fetching states", diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewModel/RegisterDomainDetailsViewModel+RowDefinitions.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewModel/RegisterDomainDetailsViewModel+RowDefinitions.swift index a4c6db1f1756..e1a9f7718943 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewModel/RegisterDomainDetailsViewModel+RowDefinitions.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainDetails/ViewModel/RegisterDomainDetailsViewModel+RowDefinitions.swift @@ -133,12 +133,6 @@ extension RegisterDomainDetailsViewModel { validationRules.forEach { $0.validate(text: value) } } - func validate(forContext context: ValidationRule.Context) { - validationRules - .filter { $0.context == context } - .forEach { $0.validate(text: value) } - } - func firstRule(forContext context: ValidationRule.Context) -> ValidationRule? { return validationRules.first { $0.context == context } } diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainCoordinator.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainCoordinator.swift index 99a1fad41b32..4af7f4766a1e 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainCoordinator.swift @@ -17,7 +17,7 @@ class RegisterDomainCoordinator { private let crashLogger: CrashLogging - let analyticsSource: String? + let analyticsSource: String var site: Blog? var domainPurchasedCallback: DomainPurchasedCallback? @@ -35,7 +35,7 @@ class RegisterDomainCoordinator { /// - crashLogger: An instance of `CrashLogging` to handle crash logging. Defaults to `.main` if not provided. init(site: Blog?, domainPurchasedCallback: RegisterDomainCoordinator.DomainPurchasedCallback? = nil, - analyticsSource: String? = "domains_register", + analyticsSource: String = "domains_register", crashLogger: CrashLogging = .main) { self.site = site self.domainPurchasedCallback = domainPurchasedCallback @@ -101,29 +101,34 @@ class RegisterDomainCoordinator { /// Related to the `purchaseFromDomainManagement` Domain selection type. /// Adds the selected domain to the cart then presents a site picker view. func handleExistingSiteChoice(on viewController: UIViewController) { - let config = BlogListConfiguration(shouldShowCancelButton: false, - shouldShowNavBarButtons: false, - navigationTitle: TextContent.sitePickerNavigationTitle, - backButtonTitle: TextContent.sitePickerNavigationTitle, - shouldHideSelfHostedSites: true, - shouldHideBlogsNotSupportingDomains: true) + let config = BlogListConfiguration( + shouldShowCancelButton: false, + shouldShowNavBarButtons: false, + navigationTitle: TextContent.sitePickerNavigationTitle, + backButtonTitle: TextContent.sitePickerNavigationTitle, + shouldHideSelfHostedSites: true, + shouldHideBlogsNotSupportingDomains: true, + analyticsSource: analyticsSource + ) let blogListViewController = BlogListViewController(configuration: config, meScenePresenter: nil) blogListViewController.blogSelected = { [weak self] controller, selectedBlog in guard let self else { return } - self.site = selectedBlog controller.showLoading() self.createCart { [weak self] result in + guard let self else { + return + } switch result { case .success(let domain): - self?.domainAddedToCartAndLinkedToSiteCallback?(controller, domain.domainName, selectedBlog) - controller.hideLoading() + self.site = selectedBlog + self.domainAddedToCartAndLinkedToSiteCallback?(controller, domain.domainName, selectedBlog) case .failure: controller.displayActionableNotice(title: TextContent.errorTitle, actionTitle: TextContent.errorDismiss) - controller.hideLoading() } + controller.hideLoading() } } @@ -131,7 +136,7 @@ class RegisterDomainCoordinator { } func trackDomainPurchasingCompleted() { - WPAnalytics.track(.purchaseDomainCompleted) + self.track(.purchaseDomainCompleted) } // MARK: Helpers @@ -165,7 +170,7 @@ class RegisterDomainCoordinator { let webViewController = WebViewControllerFactory.controllerWithDefaultAccountAndSecureInteraction( url: url, - source: analyticsSource ?? "", + source: analyticsSource, title: title ) @@ -189,13 +194,14 @@ class RegisterDomainCoordinator { } } - if let site { - let properties = WPAnalytics.domainsProperties(for: site) - WPAnalytics.track(.domainsPurchaseWebviewViewed, properties: properties, blog: site) - } else { - let properties = WPAnalytics.domainsProperties(usingCredit: false, domainOnly: true) - WPAnalytics.track(.domainsPurchaseWebviewViewed, properties: properties) - } + let properties: [AnyHashable: Any] = { + if let site { + return WPAnalytics.domainsProperties(for: site, origin: nil as String?) + } else { + return WPAnalytics.domainsProperties(usingCredit: false, origin: nil, domainOnly: true) + } + }() + self.track(.domainsPurchaseWebviewViewed, properties: properties) webViewController.configureSandboxStore { viewController.navigationController?.pushViewController(webViewController, animated: true) @@ -245,6 +251,22 @@ class RegisterDomainCoordinator { } } + + // MARK: - Tracks + + private func track(_ event: WPAnalyticsEvent, properties: [AnyHashable: Any]? = nil) { + let defaultProperties: [AnyHashable: Any] = [WPAppAnalyticsKeySource: analyticsSource] + + let properties = defaultProperties.merging(properties ?? [:]) { first, second in + return first + } + + if let blog = self.site { + WPAnalytics.track(event, properties: properties, blog: blog) + } else { + WPAnalytics.track(event, properties: properties) + } + } } // MARK: - Constants diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainSuggestionsViewController.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainSuggestionsViewController.swift new file mode 100644 index 000000000000..130ec20efa35 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainSuggestionsViewController.swift @@ -0,0 +1,466 @@ +import SwiftUI +import UIKit +import WebKit +import WordPressAuthenticator +import WordPressFlux +import Combine + +enum DomainSelectionType { + case registerWithPaidPlan + case purchaseWithPaidPlan + case purchaseSeparately + case purchaseFromDomainManagement +} + +class DomainPurchaseChoicesViewModel: ObservableObject { + @Published var isGetDomainLoading: Bool = false +} + +class RegisterDomainSuggestionsViewController: UIViewController { + @IBOutlet weak var buttonContainerBottomConstraint: NSLayoutConstraint! + @IBOutlet weak var buttonContainerViewHeightConstraint: NSLayoutConstraint! + + private var constraintsInitialized = false + + private var coordinator: RegisterDomainCoordinator? + private var siteName: String? + private var domainsTableViewController: DomainSuggestionsTableViewController? + private var domainSelectionType: DomainSelectionType = .registerWithPaidPlan + private var includeSupportButton: Bool = true + private var navBarTitle: String = TextContent.title + private var analyticsSource: String? { + return coordinator?.analyticsSource + } + + @IBOutlet private var buttonViewContainer: UIView! { + didSet { + guard let view = buttonViewContainer else { + return + } + view.addTopBorder(withColor: .divider) + view.backgroundColor = UIColor(light: .systemBackground, dark: .secondarySystemBackground) + buttonViewController.move(to: self, into: view) + } + } + + private lazy var buttonViewController: NUXButtonViewController = { + let buttonViewController = NUXButtonViewController.instance() + buttonViewController.view.backgroundColor = .basicBackground + buttonViewController.delegate = self + buttonViewController.setButtonTitles( + primary: TextContent.primaryButtonTitle + ) + return buttonViewController + }() + + private lazy var transferFooterView: RegisterDomainTransferFooterView = { + let configuration = RegisterDomainTransferFooterView.Configuration { [weak self] in + guard let self else { + return + } + let destination = TransferDomainsWebViewController(source: self.analyticsSource) + self.present(UINavigationController(rootViewController: destination), animated: true) + self.track(.domainsSearchTransferDomainTapped) + } + return .init(configuration: configuration, analyticsSource: self.analyticsSource) + }() + + /// Represents the layout constraints for the transfer footer view in its visible and hidden states. + private lazy var transferFooterViewConstraints: (visible: [NSLayoutConstraint], hidden: [NSLayoutConstraint]) = { + let base = [ + transferFooterView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + transferFooterView.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ] + let visible = base + [transferFooterView.bottomAnchor.constraint(equalTo: view.bottomAnchor)] + let hidden = base + [transferFooterView.topAnchor.constraint(equalTo: view.bottomAnchor)] + return (visible: visible, hidden: hidden) + }() + + static func instance(coordinator: RegisterDomainCoordinator, + domainSelectionType: DomainSelectionType = .registerWithPaidPlan, + includeSupportButton: Bool = true, + title: String = TextContent.title) -> RegisterDomainSuggestionsViewController { + let storyboard = UIStoryboard(name: Constants.storyboardIdentifier, bundle: Bundle.main) + let controller = storyboard.instantiateViewController(withIdentifier: Constants.viewControllerIdentifier) as! RegisterDomainSuggestionsViewController + controller.coordinator = coordinator + controller.domainSelectionType = domainSelectionType + controller.includeSupportButton = includeSupportButton + controller.siteName = siteNameForSuggestions(for: coordinator.site) + controller.navBarTitle = title + + return controller + } + + private static func siteNameForSuggestions(for site: Blog?) -> String? { + guard let site else { + return nil + } + + if let siteTitle = site.settings?.name?.nonEmptyString() { + return siteTitle + } + + if let siteUrl = site.url { + let components = URLComponents(string: siteUrl) + if let firstComponent = components?.host?.split(separator: ".").first { + return String(firstComponent) + } + } + + return nil + } + + // MARK: - View Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + configure() + hideButton() + setupTransferFooterView() + track(.domainsDashboardDomainsSearchShown) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + if let domainsTableViewController { + let insets = UIEdgeInsets( + top: 0, + left: 0, + bottom: transferFooterView.isHidden ? 0 : transferFooterView.bounds.height, + right: 0 + ) + if insets != domainsTableViewController.additionalSafeAreaInsets { + domainsTableViewController.additionalSafeAreaInsets = insets + } + } + } + + // MARK: - Setup subviews + + private func setupTransferFooterView() { + guard domainSelectionType == .purchaseFromDomainManagement else { + return + } + self.view.addSubview(transferFooterView) + self.transferFooterView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate(transferFooterViewConstraints.visible) + } + + private func configure() { + title = navBarTitle + WPStyleGuide.configureColors(view: view, tableView: nil) + + /// If this is the first view controller in the navigation controller - show the cancel button + if navigationController?.children.count == 1 { + let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, + target: self, + action: #selector(handleCancelButtonTapped)) + navigationItem.leftBarButtonItem = cancelButton + } + + navigationItem.backButtonTitle = TextContent.searchBackButtonTitle + + guard includeSupportButton else { + return + } + + let supportButton = UIBarButtonItem(title: TextContent.supportButtonTitle, + style: .plain, + target: self, + action: #selector(handleSupportButtonTapped)) + navigationItem.rightBarButtonItem = supportButton + } + + // MARK: - Show / Hide Transfer Footer + + /// Updates transfer footer view constraints to either hide or show the view. + private func updateTransferFooterViewConstraints(hidden: Bool, animated: Bool = true) { + guard transferFooterView.superview != nil else { + return + } + + let constraints = transferFooterViewConstraints + let duration = animated ? WPAnimationDurationDefault : 0 + + NSLayoutConstraint.deactivate(hidden ? constraints.visible : constraints.hidden) + NSLayoutConstraint.activate(hidden ? constraints.hidden : constraints.visible) + + if !hidden { + self.transferFooterView.isHidden = false + } + UIView.animate(withDuration: duration) { + self.view.layoutIfNeeded() + } completion: { _ in + self.transferFooterView.isHidden = hidden + self.view.setNeedsLayout() + } + } + + private func showTransferFooterView(animated: Bool = true) { + self.updateTransferFooterViewConstraints(hidden: false, animated: animated) + } + + private func hideTransferFooterView(animated: Bool = true) { + self.updateTransferFooterViewConstraints(hidden: true, animated: animated) + } + + // MARK: - Bottom Hideable Button + + /// Shows the domain picking button + /// + private func showButton() { + buttonContainerBottomConstraint.constant = 0 + } + + /// Shows the domain picking button + /// + /// - Parameters: + /// - animated: whether the transition is animated. + /// + private func showButton(animated: Bool) { + guard animated else { + showButton() + return + } + + UIView.animate(withDuration: WPAnimationDurationDefault, animations: { [weak self] in + guard let self = self else { + return + } + + self.showButton() + + // Since the Button View uses auto layout, need to call this so the animation works properly. + self.view.layoutIfNeeded() + }, completion: nil) + } + + private func hideButton() { + buttonViewContainer.layoutIfNeeded() + buttonContainerBottomConstraint.constant = buttonViewContainer.frame.height + } + + /// Hides the domain picking button + /// + /// - Parameters: + /// - animated: whether the transition is animated. + /// + func hideButton(animated: Bool) { + guard animated else { + hideButton() + return + } + + UIView.animate(withDuration: WPAnimationDurationDefault, animations: { [weak self] in + guard let self = self else { + return + } + + self.hideButton() + + // Since the Button View uses auto layout, need to call this so the animation works properly. + self.view.layoutIfNeeded() + }, completion: nil) + } + + // MARK: - Navigation + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + super.prepare(for: segue, sender: sender) + + if let vc = segue.destination as? DomainSuggestionsTableViewController { + vc.delegate = self + vc.siteName = siteName + vc.blog = coordinator?.site + vc.domainSelectionType = domainSelectionType + vc.primaryDomainAddress = coordinator?.site?.primaryDomainAddress + + if coordinator?.site?.hasBloggerPlan == true { + vc.domainSuggestionType = .allowlistedTopLevelDomains(["blog"]) + } + + domainsTableViewController = vc + } + } + + // MARK: - Nav Bar Button Handling + + @objc private func handleCancelButtonTapped(sender: UIBarButtonItem) { + dismiss(animated: true) + } + + @objc private func handleSupportButtonTapped(sender: UIBarButtonItem) { + let supportVC = SupportTableViewController() + supportVC.showFromTabBar() + } + + // MARK: - Tracks + + private func track(_ event: WPAnalyticsEvent, properties: [AnyHashable: Any] = [:], blog: Blog? = nil) { + let defaultProperties = { () -> [AnyHashable: Any] in + if let blog { + return WPAnalytics.domainsProperties(for: blog, origin: self.analyticsSource) + } else { + return WPAnalytics.domainsProperties(origin: self.analyticsSource) + } + }() + + let properties = properties.merging(defaultProperties) { current, _ in + return current + } + + if let blog { + WPAnalytics.track(event, properties: properties, blog: blog) + } else { + WPAnalytics.track(event, properties: properties) + } + } +} + +// MARK: - DomainSuggestionsTableViewControllerDelegate + +extension RegisterDomainSuggestionsViewController: DomainSuggestionsTableViewControllerDelegate { + func domainSelected(_ domain: FullyQuotedDomainSuggestion?) { + self.coordinator?.domain = domain + + if domain != nil { + WPAnalytics.track(.automatedTransferCustomDomainSuggestionSelected) + showButton(animated: true) + hideTransferFooterView(animated: true) + } else { + hideButton(animated: true) + showTransferFooterView(animated: true) + } + } + + func newSearchStarted() { + WPAnalytics.track(.automatedTransferCustomDomainSuggestionQueried) + hideButton(animated: true) + showTransferFooterView(animated: true) + } +} + +// MARK: - NUXButtonViewControllerDelegate + +extension RegisterDomainSuggestionsViewController: NUXButtonViewControllerDelegate { + func primaryButtonPressed() { + guard let coordinator else { + return + } + + let properties: [AnyHashable: Any] = { + guard let domainName = self.coordinator?.domain?.domainName else { + return [:] + } + return ["domain_name": domainName] + }() + + self.track(.domainsSearchSelectDomainTapped, properties: properties, blog: coordinator.site) + + let onFailure: () -> () = { [weak self] in + self?.displayActionableNotice(title: TextContent.errorTitle, actionTitle: TextContent.errorDismiss) + self?.setPrimaryButtonLoading(false, afterDelay: 0.25) + } + + switch domainSelectionType { + case .registerWithPaidPlan: + pushRegisterDomainDetailsViewController() + case .purchaseSeparately: + setPrimaryButtonLoading(true) + coordinator.handlePurchaseDomainOnly( + on: self, + onSuccess: { [weak self] in + self?.setPrimaryButtonLoading(false, afterDelay: 0.25) + }, + onFailure: onFailure) + case .purchaseWithPaidPlan: + setPrimaryButtonLoading(true) + coordinator.addDomainToCartLinkedToCurrentSite( + on: self, + onSuccess: { [weak self] in + self?.setPrimaryButtonLoading(false, afterDelay: 0.25) + }, + onFailure: onFailure + ) + case .purchaseFromDomainManagement: + pushPurchaseDomainChoiceScreen() + } + } + + private func setPrimaryButtonLoading(_ isLoading: Bool, afterDelay delay: Double = 0.0) { + // We're dispatching here so that we can wait until after the webview has been + // fully presented before we switch the button back to its default state. + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + self.buttonViewController.setBottomButtonState(isLoading: isLoading, + isEnabled: !isLoading) + } + } + + private func pushRegisterDomainDetailsViewController() { + guard let siteID = coordinator?.site?.dotComID?.intValue else { + DDLogError("Cannot register domains for sites without a dotComID") + return + } + + guard let domain = coordinator?.domain else { + return + } + + let controller = RegisterDomainDetailsViewController() + controller.viewModel = RegisterDomainDetailsViewModel(siteID: siteID, domain: domain) { [weak self] name in + guard let self = self, let coordinator else { + return + } + coordinator.domainPurchasedCallback?(self, name) + coordinator.trackDomainPurchasingCompleted() + } + self.navigationController?.pushViewController(controller, animated: true) + } + + private func pushPurchaseDomainChoiceScreen() { + @ObservedObject var choicesViewModel = DomainPurchaseChoicesViewModel() + let view = DomainPurchaseChoicesView(viewModel: choicesViewModel, analyticsSource: analyticsSource) { [weak self] in + guard let self else { return } + choicesViewModel.isGetDomainLoading = true + self.coordinator?.handleNoSiteChoice(on: self, choicesViewModel: choicesViewModel) + } chooseSiteAction: { [weak self] in + guard let self else { return } + self.coordinator?.handleExistingSiteChoice(on: self) + } + let hostingController = UIHostingController(rootView: view) + hostingController.title = TextContent.domainChoiceTitle + self.navigationController?.pushViewController(hostingController, animated: true) + } +} + +// MARK: - Constants +extension RegisterDomainSuggestionsViewController { + + enum TextContent { + + static let title = NSLocalizedString("Search domains", + comment: "Search domain - Title for the Suggested domains screen") + static let primaryButtonTitle = NSLocalizedString("Select domain", + comment: "Register domain - Title for the Choose domain button of Suggested domains screen") + static let supportButtonTitle = NSLocalizedString("Help", comment: "Help button") + + static let errorTitle = NSLocalizedString("domains.failure.title", + value: "Sorry, the domain you are trying to add cannot be bought on the Jetpack app at this time.", + comment: "Content show when the domain selection action fails.") + static let errorDismiss = NSLocalizedString("domains.failure.dismiss", + value: "Dismiss", + comment: "Action shown in a bottom notice to dismiss it.") + static let domainChoiceTitle = NSLocalizedString("domains.purchase.choice.title", + value: "Purchase Domain", + comment: "Title for the screen where the user can choose how to use the domain they're end up purchasing.") + static let searchBackButtonTitle = NSLocalizedString("domains.search.backButton.title", + value: "Search", + comment: "Back button title that navigates back to the search domains screen.") + } + + enum Constants { + // storyboard identifiers + static let storyboardIdentifier = "RegisterDomain" + static let viewControllerIdentifier = "RegisterDomainSuggestionsViewController" + } +} diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainTransferFooterView.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainTransferFooterView.swift index fc1ce3f729eb..aaa8b6d7fc4a 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainTransferFooterView.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainTransferFooterView.swift @@ -38,6 +38,10 @@ final class RegisterDomainTransferFooterView: UIView { ) } + // MARK: - Properties + + private let analyticsSource: String? + // MARK: - Views private let titleLabel: UILabel = { @@ -58,7 +62,8 @@ final class RegisterDomainTransferFooterView: UIView { // MARK: - Init - init(configuration: Configuration) { + init(configuration: Configuration, analyticsSource: String? = nil) { + self.analyticsSource = analyticsSource super.init(frame: .zero) self.backgroundColor = UIColor(light: .systemBackground, dark: .secondarySystemBackground) self.addTopBorder(withColor: .divider) @@ -96,11 +101,9 @@ final class RegisterDomainTransferFooterView: UIView { let action = UIAction { _ in configuration.buttonAction() - WPAnalytics.track(.domainsSearchTransferDomainTapped) } self.titleLabel.text = configuration.title self.primaryButton.setTitle(configuration.buttonTitle, for: .normal) self.primaryButton.addAction(action, for: .touchUpInside) } - } diff --git a/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift b/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift index d30d45448f1a..c1423edb1389 100644 --- a/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift +++ b/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift @@ -20,8 +20,6 @@ final class DomainSelectionViewController: CollapsableHeaderViewController { private struct Metrics { static let maxLabelWidth = CGFloat(290) static let noResultsTopInset = CGFloat(64) - static let sitePromptEdgeMargin = CGFloat(50) - static let sitePromptBottomMargin = CGFloat(10) static let sitePromptTopMargin = CGFloat(4) } @@ -51,7 +49,6 @@ final class DomainSelectionViewController: CollapsableHeaderViewController { private let searchHeader: UIView private let searchTextField: SearchTextField private let searchBar = UISearchBar() - private var sitePromptView: SitePromptView! private let siteCreationEmptyTemplate = SiteCreationEmptySiteTemplate() private lazy var siteTemplateHostingController = UIHostingController(rootView: siteCreationEmptyTemplate) private let domainSelectionType: DomainSelectionType @@ -476,15 +473,6 @@ final class DomainSelectionViewController: CollapsableHeaderViewController { table.separatorInset.left = AddressTableViewCell.Appearance.contentMargins.leading } - private func query(from textField: UITextField?) -> String? { - guard let text = textField?.text, - !text.isEmpty else { - return nil - } - - return text - } - @objc private func textChanged(sender: UITextField) { search(sender.text) @@ -868,16 +856,20 @@ private extension DomainSelectionViewController { private func pushPurchaseDomainChoiceScreen() { @ObservedObject var choicesViewModel = DomainPurchaseChoicesViewModel() - let view = DomainPurchaseChoicesView(viewModel: choicesViewModel) { [weak self] in - guard let self else { return } - choicesViewModel.isGetDomainLoading = true - self.coordinator?.handleNoSiteChoice(on: self, choicesViewModel: choicesViewModel) - WPAnalytics.track(.purchaseDomainGetDomainTapped) - } chooseSiteAction: { [weak self] in - guard let self else { return } - self.coordinator?.handleExistingSiteChoice(on: self) - WPAnalytics.track(.purchaseDomainChooseSiteTapped) - } + let view = DomainPurchaseChoicesView( + viewModel: choicesViewModel, + analyticsSource: coordinator?.analyticsSource, + buyDomainAction: { [weak self] in + guard let self else { return } + choicesViewModel.isGetDomainLoading = true + self.coordinator?.handleNoSiteChoice(on: self, choicesViewModel: choicesViewModel) + WPAnalytics.track(.purchaseDomainGetDomainTapped) + }, chooseSiteAction: { [weak self] in + guard let self else { return } + self.coordinator?.handleExistingSiteChoice(on: self) + WPAnalytics.track(.purchaseDomainChooseSiteTapped) + } + ) let hostingController = UIHostingController(rootView: view) hostingController.title = Strings.domainChoiceTitle self.navigationController?.pushViewController(hostingController, animated: true) diff --git a/WordPress/Classes/ViewRelated/Domains/Utility/DomainExpiryDateFormatter.swift b/WordPress/Classes/ViewRelated/Domains/Utility/DomainExpiryDateFormatter.swift deleted file mode 100644 index 26f3967d0a26..000000000000 --- a/WordPress/Classes/ViewRelated/Domains/Utility/DomainExpiryDateFormatter.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation - -struct DomainExpiryDateFormatter { - static func expiryDate(for domain: Domain) -> String { - if domain.expiryDate.isEmpty { - return Localized.neverExpires - } else if domain.expired { - return Localized.expired - } else if domain.autoRenewing && domain.autoRenewalDate.isEmpty { - return Localized.autoRenews - } else if domain.autoRenewing { - return String(format: Localized.renewsOn, domain.autoRenewalDate) - } else { - return String(format: Localized.expiresOn, domain.expiryDate) - } - } - - enum Localized { - static let neverExpires = NSLocalizedString("Never expires", comment: "Label indicating that a domain name registration has no expiry date.") - static let autoRenews = NSLocalizedString("Auto-renew enabled", comment: "Label indicating that a domain name registration will automatically renew") - static let renewsOn = NSLocalizedString("Renews on %@", comment: "Label indicating the date on which a domain name registration will be renewed. The %@ placeholder will be replaced with a date at runtime.") - static let expiresOn = NSLocalizedString("Expires on %@", comment: "Label indicating the date on which a domain name registration will expire. The %@ placeholder will be replaced with a date at runtime.") - static let expired = NSLocalizedString("Expired", comment: "Label indicating that a domain name registration has expired.") - } -} diff --git a/WordPress/Classes/ViewRelated/Domains/View Models/DomainsStateViewModel.swift b/WordPress/Classes/ViewRelated/Domains/View Models/DomainsStateViewModel.swift new file mode 100644 index 000000000000..25f6120f36d0 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Domains/View Models/DomainsStateViewModel.swift @@ -0,0 +1,64 @@ +import Foundation + +struct DomainsStateViewModel { + let title: String + let description: String + let button: Button? + + struct Button { + let title: String + let action: () -> Void + } +} + +extension DomainsStateViewModel { + static func errorMessageViewModel(from error: Error, action: @escaping () -> Void) -> DomainsStateViewModel { + let title: String + let description: String + let button: DomainsStateViewModel.Button = .init(title: Strings.errorStateButtonTitle) { + action() + } + + let nsError = error as NSError + if nsError.domain == NSURLErrorDomain, nsError.code == NSURLErrorNotConnectedToInternet { + title = Strings.offlineEmptyStateTitle + description = Strings.offlineEmptyStateDescription + } else { + title = Strings.errorEmptyStateTitle + description = Strings.errorEmptyStateDescription + } + + return .init(title: title, description: description, button: button) + } +} + +extension DomainsStateViewModel { + enum Strings { + static let offlineEmptyStateTitle = NSLocalizedString( + "domain.management.offline.empty.state.title", + value: "No Internet Connection", + comment: "The empty state title in All Domains screen when the user is offline" + ) + static let offlineEmptyStateDescription = NSLocalizedString( + "domain.management.offline.empty.state.description", + value: "Please check your network connection and try again.", + comment: "The empty state description in All Domains screen when the user is offline" + ) + static let errorEmptyStateTitle = NSLocalizedString( + "domain.management.error.empty.state.title", + value: "Something went wrong", + comment: "The empty state title in All Domains screen when an error occurs" + ) + + static let errorEmptyStateDescription = NSLocalizedString( + "domain.management.error.empty.state.description", + value: "We encountered an error while loading your domains. Please contact support if the issue persists.", + comment: "The empty state description in All Domains screen when an error occurs" + ) + static let errorStateButtonTitle = NSLocalizedString( + "domain.management.error.state.button.title", + value: "Try again", + comment: "The empty state button title in All Domains screen when an error occurs" + ) + } +} diff --git a/WordPress/Classes/ViewRelated/Domains/View Models/SiteDomainsViewModel.swift b/WordPress/Classes/ViewRelated/Domains/View Models/SiteDomainsViewModel.swift new file mode 100644 index 000000000000..ae3a3511d122 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Domains/View Models/SiteDomainsViewModel.swift @@ -0,0 +1,184 @@ +import Foundation +import WordPressKit +import Combine + +final class SiteDomainsViewModel: ObservableObject { + private let blog: Blog + private let domainsService: DomainsServiceAllDomainsFetching? + + @Published + private(set) var state: State = .loading + private(set) var loadedDomains: [DomainsService.AllDomainsListItem] = [] + + init(blog: Blog, domainsService: DomainsServiceAllDomainsFetching?) { + self.blog = blog + self.domainsService = domainsService + } + + func refresh() { + domainsService?.fetchAllDomains(resolveStatus: true, noWPCOM: false, completion: { [weak self] result in + guard let self else { + return + } + switch result { + case .success(let domains): + self.loadedDomains = domains + let sections = Self.buildSections(from: blog, domains: domains) + self.state = .normal(sections) + case .failure(let error): + self.state = .message(self.errorMessageViewModel(from: error)) + } + }) + } + + private func errorMessageViewModel(from error: Error) -> DomainsStateViewModel { + return DomainsStateViewModel.errorMessageViewModel(from: error) { [weak self] in + self?.state = .loading + self?.refresh() + } + } + + // MARK: - Sections + + private static func buildSections(from blog: Blog, domains: [DomainsService.AllDomainsListItem]) -> [Section] { + let wpcomDomains = domains.filter { $0.wpcomDomain } + let otherDomains = domains.filter { !$0.wpcomDomain } + + return Self.buildFreeDomainSections(from: blog, wpComDomains: wpcomDomains) + Self.buildDomainsSections(from: blog, domains: otherDomains) + } + + private static func buildFreeDomainSections(from blog: Blog, wpComDomains: [DomainsService.AllDomainsListItem]) -> [Section] { + let blogWpComDomains = wpComDomains.filter { $0.blogId == blog.dotComID?.intValue } + guard let freeDomain = blogWpComDomains.count > 1 ? blogWpComDomains.first(where: { $0.isWpcomStagingDomain }) : blogWpComDomains.first else { + return [] + } + + return [ + Section( + title: Strings.freeDomainSectionTitle, + footer: blog.freeDomainIsPrimary ? Strings.primaryDomainDescription : nil, + content: .rows([.init( + viewModel: .init( + name: freeDomain.domain, + description: nil, + status: nil, + expiryDate: AllDomainsListItemViewModel.expiryDate(from: freeDomain), + isPrimary: blog.freeDomainIsPrimary + ), + navigation: nil)]) + ) + ] + } + + private static func buildDomainsSections(from blog: Blog, domains: [DomainsService.AllDomainsListItem]) -> [Section] { + var sections: [Section] = [] + + let primaryDomainName = blog.domainsList.first(where: { $0.domain.isPrimaryDomain })?.domain.domainName + let blogDomains = domains.filter({ $0.blogId == blog.dotComID?.intValue }) + let primaryDomain = blogDomains.first(where: { primaryDomainName == $0.domain }) + let otherDomains = blogDomains.filter({ primaryDomainName != $0.domain }) + + if let primaryDomain { + let section = Section( + title: String(format: Strings.domainsListSectionTitle, blog.title ?? blog.freeSiteAddress), + footer: Strings.primaryDomainDescription, + content: .rows([.init( + viewModel: .init( + name: primaryDomain.domain, + description: nil, + status: primaryDomain.status, + expiryDate: AllDomainsListItemViewModel.expiryDate(from: primaryDomain), + isPrimary: true + ), + navigation: navigation(from: primaryDomain) + )]) + ) + sections.append(section) + } + + if otherDomains.count > 0 { + let domainRows = otherDomains.map { + SiteDomainsViewModel.Section.Row( + viewModel: .init( + name: $0.domain, + description: nil, + status: $0.status, + expiryDate: AllDomainsListItemViewModel.expiryDate(from: $0), + isPrimary: false + ), + navigation: navigation(from: $0) + ) + } + + let section = Section( + title: primaryDomain == nil ? String(format: Strings.domainsListSectionTitle, blog.title ?? blog.freeSiteAddress) : nil, + footer: nil, + content: .rows(domainRows) + ) + + sections.append(section) + } + + if sections.count == 0 || blog.canRegisterDomainWithPaidPlan { + sections.append(Section(title: nil, footer: nil, content: .upgradePlan)) + } else { + sections.append(Section(title: nil, footer: nil, content: .addDomain)) + } + + return sections + } + + private static func navigation(from domain: DomainsService.AllDomainsListItem) -> SiteDomainsViewModel.Section.Row.Navigation { + return .init(domain: domain.domain, siteSlug: domain.siteSlug, type: domain.type) + } +} + +extension SiteDomainsViewModel { + enum Strings { + static let freeDomainSectionTitle = NSLocalizedString("site.domains.freeDomainSection.title", + value: "Your Free WordPress.com domain", + comment: "A section title which displays a row with a free WP.com domain") + static let primaryDomainDescription = NSLocalizedString("site.domains.primaryDomain", + value: "Your primary site address is what visitors will see in their address bar when visiting your website.", + comment: "Footer of the primary site section in the Domains Dashboard.") + static let domainsListSectionTitle: String = NSLocalizedString("site.domains.domainSection.title", + value: "Other domains for %1$@", + comment: "Header of the secondary domains list section in the Domains Dashboard. %1$@ is the name of the site.") + } +} + +// MARK: - Types + +extension SiteDomainsViewModel { + enum State { + case normal([Section]) + case loading + case message(DomainsStateViewModel) + } + + struct Section: Identifiable { + enum SectionKind { + case rows([Row]) + case addDomain + case upgradePlan + } + + struct Row: Identifiable { + struct Navigation: Hashable { + let domain: String + let siteSlug: String + let type: DomainType + let analyticsSource: String = "site_domains" + } + + let id = UUID() + let viewModel: AllDomainsListCardView.ViewModel + let navigation: Navigation? + } + + let id = UUID() + let title: String? + let footer: String? + let content: SectionKind + } +} diff --git a/WordPress/Classes/ViewRelated/Domains/Views/DomainDetailsWebViewControllerWrapper.swift b/WordPress/Classes/ViewRelated/Domains/Views/DomainDetailsWebViewControllerWrapper.swift new file mode 100644 index 000000000000..e65154a7cbd2 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Domains/Views/DomainDetailsWebViewControllerWrapper.swift @@ -0,0 +1,29 @@ +import SwiftUI +import UIKit +import WordPressKit + +/// Makes DomainDetailsWebViewController available to SwiftUI +struct DomainDetailsWebViewControllerWrapper: UIViewControllerRepresentable { + private let domain: String + private let siteSlug: String + private let type: DomainType + private let analyticsSource: String? + + init(domain: String, siteSlug: String, type: DomainType, analyticsSource: String? = nil) { + self.domain = domain + self.siteSlug = siteSlug + self.type = type + self.analyticsSource = analyticsSource + } + + func makeUIViewController(context: Context) -> DomainDetailsWebViewController { + DomainDetailsWebViewController( + domain: domain, + siteSlug: siteSlug, + type: type, + analyticsSource: analyticsSource + ) + } + + func updateUIViewController(_ uiViewController: DomainDetailsWebViewController, context: Context) { } +} diff --git a/WordPress/Classes/ViewRelated/Domains/Views/DomainResultView.swift b/WordPress/Classes/ViewRelated/Domains/Views/DomainResultView.swift index 093c07859e73..c7687ce5eabd 100644 --- a/WordPress/Classes/ViewRelated/Domains/Views/DomainResultView.swift +++ b/WordPress/Classes/ViewRelated/Domains/Views/DomainResultView.swift @@ -105,7 +105,6 @@ private extension DomainResultView { static let logoToTitleSpacing = 33.0 static let titleToSubtitleSpacing = 16.0 static let subtitleToNoticeBoxSpacing = 32.0 - static let primaryButtonCornerRadius = 7.0 } private enum Fonts { diff --git a/WordPress/Classes/ViewRelated/Domains/Views/DomainsStateView.swift b/WordPress/Classes/ViewRelated/Domains/Views/DomainsStateView.swift new file mode 100644 index 000000000000..853a2ebc1ae7 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Domains/Views/DomainsStateView.swift @@ -0,0 +1,31 @@ +import Foundation +import SwiftUI +import DesignSystem + +struct DomainsStateView: View { + private let viewModel: DomainsStateViewModel + + init(viewModel: DomainsStateViewModel) { + self.viewModel = viewModel + } + + var body: some View { + VStack(spacing: Length.Padding.single) { + Text(viewModel.title) + .font(Font.DS.heading3) + .multilineTextAlignment(.center) + .foregroundStyle(Color.DS.Foreground.secondary) + Text(viewModel.description) + .font(Font.DS.Body.medium) + .foregroundStyle(Color.DS.Foreground.secondary) + .multilineTextAlignment(.center) + if let button = viewModel.button { + Spacer() + .frame(height: Length.Padding.single) + DSButton(title: button.title, style: .init(emphasis: .primary, size: .medium)) { + button.action() + } + } + } + } +} diff --git a/WordPress/Classes/ViewRelated/Domains/Views/PrimaryDomainView.swift b/WordPress/Classes/ViewRelated/Domains/Views/PrimaryDomainView.swift new file mode 100644 index 000000000000..3517e00cab36 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Domains/Views/PrimaryDomainView.swift @@ -0,0 +1,31 @@ +import SwiftUI +import DesignSystem + +struct PrimaryDomainView: View { + var body: some View { + Group { + HStack(spacing: Length.Padding.half) { + Image(systemName: "globe") + .font(.callout) + .foregroundStyle(Color.DS.Foreground.primary) + Text(Strings.primaryDomain) + .font(.callout) + .foregroundStyle(Color.DS.Foreground.primary) + } + .padding(.vertical, Length.Padding.half) + .padding(.horizontal, Length.Padding.single) + } + .background(Color.DS.Background.secondary) + .clipShape(RoundedRectangle(cornerRadius: Length.Radius.small)) + .accessibilityElement(children: .ignore) + .accessibilityLabel(Strings.primaryDomain) + } +} + +private extension PrimaryDomainView { + enum Strings { + static let primaryDomain = NSLocalizedString("site.domains.primaryDomain.title", + value: "Primary domain", + comment: "Primary domain label, used in the site address section of the Domains Dashboard.") + } +} diff --git a/WordPress/Classes/ViewRelated/Domains/Views/SiteDomainsView.swift b/WordPress/Classes/ViewRelated/Domains/Views/SiteDomainsView.swift index 703bce9abe69..0a71b3b840ef 100644 --- a/WordPress/Classes/ViewRelated/Domains/Views/SiteDomainsView.swift +++ b/WordPress/Classes/ViewRelated/Domains/Views/SiteDomainsView.swift @@ -7,8 +7,7 @@ struct SiteDomainsView: View { @ObservedObject var blog: Blog @State var isShowingDomainSelectionWithType: DomainSelectionType? - @State var blogService = BlogService(coreDataStack: ContextManager.shared) - @State var domainsList: [Blog.DomainRepresentation] = [] + @StateObject var viewModel: SiteDomainsViewModel // Property observer private func showingDomainSelectionWithType(to value: DomainSelectionType?) { @@ -22,100 +21,111 @@ struct SiteDomainsView: View { case .none: break default: - // TODO: Analytics break } } var body: some View { - List { - if blog.supports(.domains) { - makeSiteAddressSection(blog: blog) + ZStack { + Color.DS.Background.secondary.edgesIgnoringSafeArea(.all) + + switch viewModel.state { + case .normal(let sections): + List { + makeDomainsSections(blog: blog, sections: sections) + } + .listRowSeparator(.hidden) + //.listRowSpacing(Length.Padding.double) Re-enable when we update to Xcode 15 + case .message(let messageViewModel): + VStack { + HStack(alignment: .center) { + DomainsStateView(viewModel: messageViewModel) + .padding(.horizontal, Length.Padding.double) + } + } + case .loading: + ProgressView() + .progressViewStyle(.circular) } - makeDomainsSection(blog: blog) - .listRowInsets(Metrics.insets) } - .listStyle(InsetGroupedListStyle()) - .buttonStyle(PlainButtonStyle()) - .onTapGesture(perform: { }) .onAppear { - updateDomainsList() - - blogService.refreshDomains(for: blog, success: { - updateDomainsList() - }, failure: nil) + viewModel.refresh() } .sheet(item: $isShowingDomainSelectionWithType, content: { domainSelectionType in makeDomainSearch(for: blog, domainSelectionType: domainSelectionType, onDismiss: { isShowingDomainSelectionWithType = nil - blogService.refreshDomains(for: blog, success: { - updateDomainsList() - }, failure: nil) + viewModel.refresh() }) .ignoresSafeArea() }) } + // MARK: - Domains Section + + /// Builds the domains list section with the` add a domain` button at the bottom, for the given blog @ViewBuilder - private func makeDomainsSection(blog: Blog) -> some View { - if blog.hasDomains { - makeDomainsListSection(blog: blog) - } else { - makeGetFirstDomainSection(blog: blog) + private func makeDomainsSections(blog: Blog, sections: [SiteDomainsViewModel.Section]) -> some View { + ForEach(sections, id: \.id) { section in + switch section.content { + case .rows(let rows): + makeDomainsListSection(blog: blog, section: section, rows: rows) + case .addDomain: + makeAddDomainSection(blog: blog) + case .upgradePlan: + makeGetFirstDomainSection(blog: blog) + } } } - /// Builds the site address section for the given blog - private func makeSiteAddressSection(blog: Blog) -> some View { - Section(footer: Text(TextContent.primarySiteSectionFooter(blog.hasPaidPlan))) { - VStack(alignment: .leading) { - Text(TextContent.siteAddressTitle) - Text(blog.freeSiteAddress) - .bold() - if blog.freeDomainIsPrimary { - ShapeWithTextView(title: TextContent.primaryAddressLabel) - .smallRoundedRectangle() + private func makeDomainsListSection(blog: Blog, section: SiteDomainsViewModel.Section, rows: [SiteDomainsViewModel.Section.Row]) -> some View { + Section { + ForEach(rows) { row in + if let navigation = row.navigation { + NavigationLink(destination: { + DomainDetailsWebViewControllerWrapper( + domain: navigation.domain, + siteSlug: navigation.siteSlug, + type: navigation.type, + analyticsSource: navigation.analyticsSource + ) + .navigationTitle(navigation.domain) + }, label: { + AllDomainsListCardView(viewModel: row.viewModel, padding: 0) + }) + } else { + AllDomainsListCardView(viewModel: row.viewModel, padding: 0) } } - } - } - - @ViewBuilder - private func makeDomainCell(domain: Blog.DomainRepresentation) -> some View { - VStack(alignment: .leading) { - Text(domain.domain.domainName) - if domain.domain.isPrimaryDomain { - ShapeWithTextView(title: TextContent.primaryAddressLabel) - .smallRoundedRectangle() + } header: { + if let title = section.title { + Text(title) + } + } footer: { + if let footer = section.footer { + Text(footer) } - makeExpiryRenewalLabel(domain: domain) } } - /// Builds the domains list section with the` add a domain` button at the bottom, for the given blog - private func makeDomainsListSection(blog: Blog) -> some View { + private func makeAddDomainSection(blog: Blog) -> some View { let destination: DomainSelectionType = blog.canRegisterDomainWithPaidPlan ? .registerWithPaidPlan : .purchaseSeparately - return Section(header: Text(TextContent.domainsListSectionHeader)) { - ForEach(domainsList) { - makeDomainCell(domain: $0) - } - if blog.supports(.domains) { - DSButton( - title: TextContent.additionalDomainTitle(blog.canRegisterDomainWithPaidPlan), - style: .init( - emphasis: .tertiary, - size: .small, - isJetpack: AppConfiguration.isJetpack - )) { - $isShowingDomainSelectionWithType.onChange(showingDomainSelectionWithType).wrappedValue = destination - } + + return Section { + Button { + $isShowingDomainSelectionWithType.onChange(showingDomainSelectionWithType).wrappedValue = destination + } label: { + Text(TextContent.additionalDomainTitle(blog.canRegisterDomainWithPaidPlan)) + .style(TextStyle.bodyMedium(.regular)) + .foregroundColor(Color.DS.Foreground.brand(isJetpack: AppConfiguration.isJetpack)) } } } + // MARK: - First Domain Section + /// Builds the Get New Domain section when no othert domains are present for the given blog private func makeGetFirstDomainSection(blog: Blog) -> some View { - return Section { + Section { SiteDomainsPresentationCard( title: TextContent.firstDomainTitle(blog.canRegisterDomainWithPaidPlan), description: TextContent.firstDomainDescription(blog.canRegisterDomainWithPaidPlan), @@ -151,18 +161,6 @@ struct SiteDomainsView: View { return destinations } - private var siteAddressForGetFirstDomainSection: String { - blog.canRegisterDomainWithPaidPlan ? "" : blog.freeSiteAddress - } - - private func makeExpiryRenewalLabel(domain: Blog.DomainRepresentation) -> some View { - let stringForDomain = DomainExpiryDateFormatter.expiryDate(for: domain.domain) - - return Text(stringForDomain) - .font(.subheadline) - .foregroundColor(domain.domain.expirySoon || domain.domain.expired ? Color(UIColor.error) : Color(UIColor.textSubtle)) - } - /// Instantiates the proper search depending if it's for claiming a free domain with a paid plan or purchasing a new one private func makeDomainSearch(for blog: Blog, domainSelectionType: DomainSelectionType, onDismiss: @escaping () -> Void) -> some View { return DomainSuggestionViewControllerWrapper( @@ -171,10 +169,6 @@ struct SiteDomainsView: View { onDismiss: onDismiss ) } - - private func updateDomainsList() { - domainsList = blog.domainsList - } } // MARK: - Constants @@ -184,26 +178,10 @@ private extension SiteDomainsView { enum TextContent { // Navigation bar static let navigationTitle = NSLocalizedString("Site Domains", comment: "Title of the Domains Dashboard.") - // Site address section - static func primarySiteSectionFooter(_ paidPlan: Bool) -> String { - paidPlan ? "" : NSLocalizedString("Your primary site address is what visitors will see in their address bar when visiting your website.", - comment: "Footer of the primary site section in the Domains Dashboard.") - } - - static let siteAddressTitle = NSLocalizedString("Your free WordPress.com address is", - comment: "Title of the site address section in the Domains Dashboard.") - static let primaryAddressLabel = NSLocalizedString("Primary site address", - comment: "Primary site address label, used in the site address section of the Domains Dashboard.") // Domains section - static let domainsListSectionHeader: String = NSLocalizedString("Your Site Domains", - comment: "Header of the domains list section in the Domains Dashboard.") - static let paidPlanDomainSectionFooter: String = NSLocalizedString("All WordPress.com plans include a custom domain name. Register your free premium domain now.", - comment: "Footer of the free domain registration section for a paid plan.") - static let additionalRedirectedDomainTitle: String = NSLocalizedString("Add a domain", comment: "Label of the button that starts the purchase of an additional redirected domain in the Domains Dashboard.") - static let firstFreeDomainWithPaidPlanDomainTitle: String = NSLocalizedString("site.domains.freeDomainWithPaidPlan.title", value: "Get your domain", comment: "Title of the card that starts the purchase of the first domain with a paid plan.") @@ -251,11 +229,16 @@ final class SiteDomainsViewController: UIHostingController { // MARK: - Properties private let domainManagementFeatureFlag = RemoteFeatureFlag.domainManagement + private let viewModel: SiteDomainsViewModel // MARK: - Init init(blog: Blog) { - super.init(rootView: .init(blog: blog)) + let account = try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext) + let domainsService = DomainsService(coreDataStack: ContextManager.shared, wordPressComRestApi: account?.wordPressComRestApi) + let viewModel = SiteDomainsViewModel(blog: blog, domainsService: domainsService) + self.viewModel = viewModel + super.init(rootView: .init(blog: blog, viewModel: viewModel)) } @MainActor required dynamic init?(coder aDecoder: NSCoder) { @@ -279,7 +262,10 @@ final class SiteDomainsViewController: UIHostingController { } let title = AllDomainsListViewController.Strings.title let action = UIAction { [weak self] _ in - self?.navigationController?.pushViewController(AllDomainsListViewController(), animated: true) + guard let self else { return } + let domains = self.viewModel.loadedDomains.filter { !$0.wpcomDomain } + let allDomainsViewController = AllDomainsListViewController(viewModel: .init(domains: domains)) + self.navigationController?.pushViewController(allDomainsViewController, animated: true) WPAnalytics.track(.domainsDashboardAllDomainsTapped) } self.navigationItem.rightBarButtonItem = .init(title: title, primaryAction: action) diff --git a/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopover.swift b/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopover.swift index cfc30e68cbe7..29a320a086b7 100644 --- a/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopover.swift +++ b/WordPress/Classes/ViewRelated/EEUUSCompliance/CompliancePopover.swift @@ -3,10 +3,6 @@ import JetpackStatsWidgetsCore import DesignSystem struct CompliancePopover: View { - private enum Constants { - static let verticalScrollBuffer: CGFloat = 40 - } - @StateObject var viewModel: CompliancePopoverViewModel diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift index d55335fe49f0..3786c0011a14 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift @@ -13,8 +13,6 @@ class GutenbergMediaInserterHelper: NSObject { /// fileprivate var mediaSelectionMethod: MediaSelectionMethod = .none - var didPickMediaCallback: GutenbergMediaPickerHelperCallback? - init(post: AbstractPost, gutenberg: Gutenberg) { self.post = post self.gutenberg = gutenberg @@ -88,29 +86,6 @@ class GutenbergMediaInserterHelper: NSObject { } } - func insertFromMediaEditor(assets: [AsyncImage], callback: @escaping MediaPickerDidPickMediaCallback) { - var mediaCollection: [MediaInfo] = [] - let group = DispatchGroup() - assets.forEach { asset in - group.enter() - if let image = asset.editedImage { - insertFromImage(image: image, callback: { media in - guard let media = media, - let selectedMedia = media.first else { - group.leave() - return - } - mediaCollection.append(selectedMedia) - group.leave() - }) - } - } - - group.notify(queue: .main) { - callback(mediaCollection) - } - } - func syncUploads() { for media in post.media { if media.remoteStatus == .failed { @@ -146,6 +121,10 @@ class GutenbergMediaInserterHelper: NSObject { mediaCoordinator.retryMedia(media) } + func retryFailedMediaUploads() { + _ = mediaCoordinator.uploadMedia(for: post, automatedRetry: true) + } + func hasFailedMedia() -> Bool { return mediaCoordinator.hasFailedMedia(for: post) } diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+InformativeDialog.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+InformativeDialog.swift deleted file mode 100644 index 804aac56f3bd..000000000000 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+InformativeDialog.swift +++ /dev/null @@ -1,71 +0,0 @@ -import Foundation - -/// This extension handles Alert operations. -extension GutenbergViewController { - - enum InfoDialog { - static let postMessage = NSLocalizedString( - "You’re now using the block editor for new posts — great! If you’d like to change to the classic editor, go to ‘My Site’ > ‘Site Settings’.", - comment: "Popup content about why this post is being opened in block editor" - ) - static let pageMessage = NSLocalizedString( - "You’re now using the block editor for new pages — great! If you’d like to change to the classic editor, go to ‘My Site’ > ‘Site Settings’.", - comment: "Popup content about why this post is being opened in block editor" - ) - static let phase2Message = NSLocalizedString( - "We made big improvements to the block editor and think it's worth a try!\n\nWe enabled it for new posts and pages but if you'd like to change to the classic editor, go to 'My Site' > 'Site Settings'.", - comment: "Popup content about why this post is being opened in block editor" - ) - static let title = NSLocalizedString( - "Block editor enabled", - comment: "Popup title about why this post is being opened in block editor" - ) - static let okButtonTitle = NSLocalizedString("OK", comment: "OK button to close the informative dialog on Gutenberg editor") - } - - func showInformativeDialogIfNecessary() { - if shouldPresentInformativeDialog { - showMigrationInformativeDialog() - } else if shouldPresentPhase2informativeDialog { - showPhase2InformativeDialog() - } - } - - func showMigrationInformativeDialog() { - let message = post is Page ? InfoDialog.pageMessage : InfoDialog.postMessage - GutenbergViewController.showInformativeDialog(on: self, message: message) - } - - func showPhase2InformativeDialog() { - GutenbergViewController.showInformativeDialog(on: self, message: InfoDialog.phase2Message) - GutenbergSettings().setShowPhase2Dialog(false, for: post.blog) - } - - static func showInformativeDialog( - on viewController: UIViewControllerTransitioningDelegate & UIViewController, - message: String, - animated: Bool = true - ) { - let okButton: (title: String, handler: FancyAlertViewController.FancyAlertButtonHandler?) = - ( - title: InfoDialog.okButtonTitle, - handler: { (alert, button) in - alert.dismiss(animated: animated, completion: nil) - } - ) - - let config = FancyAlertViewController.Config( - titleText: InfoDialog.title, - bodyText: message, - headerImage: nil, - dividerPosition: .top, - defaultButton: okButton, - cancelButton: nil - ) - - let alert = FancyAlertViewController.controllerWithConfiguration(configuration: config) - alert.modalPresentationStyle = .custom - alert.transitioningDelegate = viewController - viewController.present(alert, animated: animated) - } -} diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift index 232b734f5717..03098eca377a 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift @@ -6,8 +6,9 @@ import WordPressFlux /// navigation bar button of Gutenberg editor. extension GutenbergViewController { - private enum ErrorCode: Int { + enum ErrorCode: Int { case expectedSecondaryAction = 1 + case managedObjectContextMissing = 2 } func displayMoreSheet() { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index 09a1fa37ba0a..ace88febc455 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -587,17 +587,33 @@ extension GutenbergViewController { extension GutenbergViewController: GutenbergBridgeDelegate { func gutenbergDidGetRequestFetch(path: String, completion: @escaping (Result) -> Void) { - post.managedObjectContext!.perform { + guard let context = post.managedObjectContext else { + didEncounterMissingContextError() + completion(.failure(URLError(.unknown) as NSError)) + return + } + context.perform { GutenbergNetworkRequest(path: path, blog: self.post.blog, method: .get).request(completion: completion) } } func gutenbergDidPostRequestFetch(path: String, data: [String: AnyObject]?, completion: @escaping (Result) -> Void) { - post.managedObjectContext!.perform { + guard let context = post.managedObjectContext else { + didEncounterMissingContextError() + completion(.failure(URLError(.unknown) as NSError)) + return + } + context.perform { GutenbergNetworkRequest(path: path, blog: self.post.blog, method: .post, data: data).request(completion: completion) } } + private func didEncounterMissingContextError() { + DispatchQueue.main.async { + WordPressAppDelegate.crashLogging?.logError(NSError(domain: self.errorDomain, code: ErrorCode.managedObjectContextMissing.rawValue, userInfo: [NSDebugDescriptionErrorKey: "The post is missing an associated managed object context"])) + } + } + func editorDidAutosave() { autosaver.contentDidChange() } @@ -755,19 +771,6 @@ extension GutenbergViewController: GutenbergBridgeDelegate { present(alertController, animated: true, completion: nil) } - struct AnyEncodable: Encodable { - - let value: Encodable - init(value: Encodable) { - self.value = value - } - - func encode(to encoder: Encoder) throws { - try value.encode(to: encoder) - } - - } - func gutenbergDidRequestMediaFilesEditorLoad(_ mediaFiles: [[String: Any]], blockId: String) { if mediaFiles.isEmpty { @@ -850,7 +853,11 @@ extension GutenbergViewController: GutenbergBridgeDelegate { message = error.localizedDescription if media.canRetry { let retryUploadAction = UIAlertAction(title: MediaAttachmentActionSheet.retryUploadActionTitle, style: .default) { (action) in - self.mediaInserterHelper.retryUploadOf(media: media) + #if DEBUG || INTERNAL_BUILD + self.mediaInserterHelper.retryFailedMediaUploads() + #else + self.mediaInserterHelper.retryUploadOf(media: media) + #endif } alertController.addAction(retryUploadAction) } @@ -1093,6 +1100,11 @@ extension GutenbergViewController: GutenbergBridgeDelegate { extension GutenbergViewController: NetworkStatusDelegate { func networkStatusDidChange(active: Bool) { gutenberg.connectionStatusChange(isConnected: active) + #if DEBUG || INTERNAL_BUILD + if active { + mediaInserterHelper.retryFailedMediaUploads() + } + #endif } } @@ -1419,7 +1431,6 @@ private extension GutenbergViewController { ) static let stopUploadActionTitle = NSLocalizedString("Stop upload", comment: "User action to stop upload.") static let retryUploadActionTitle = NSLocalizedString("Retry", comment: "User action to retry media upload.") - static let retryAllFailedUploadsActionTitle = NSLocalizedString("Retry all", comment: "User action to retry all failed media uploads.") } } diff --git a/WordPress/Classes/ViewRelated/Jetpack/Branding/Button/JetpackButton.swift b/WordPress/Classes/ViewRelated/Jetpack/Branding/Button/JetpackButton.swift index 92171c112b6c..94edf069ca8c 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Branding/Button/JetpackButton.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Branding/Button/JetpackButton.swift @@ -91,7 +91,6 @@ class JetpackButton: CircularImageButton { static let iconInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10) static let contentInsets = UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 10) static let maximumFontPointSize: CGFloat = 22 - static let imageBackgroundViewMultiplier: CGFloat = 0.75 static var titleFont: UIFont { let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .callout) let font = UIFont(descriptor: fontDescriptor, size: min(fontDescriptor.pointSize, maximumFontPointSize)) diff --git a/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift b/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift index 29589db8777b..9075f8879504 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift @@ -278,9 +278,7 @@ private extension JetpackFullscreenOverlayViewController { static let compactStackViewSpacing: CGFloat = 10 static let closeButtonRadius: CGFloat = 30 static let mainButtonsContentInsets = NSDirectionalEdgeInsets(top: 4, leading: 12, bottom: 4, trailing: 12) - static let mainButtonsContentEdgeInsets = UIEdgeInsets(top: 4, left: 12, bottom: 4, right: 12) static let learnMoreButtonContentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 24) - static let learnMoreButtonContentEdgeInsets = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 24) static let externalIconSize = CGSize(width: 16, height: 16) static let externalIconBounds = CGRect(x: 0, y: -2, width: 16, height: 16) static let switchButtonCornerRadius: CGFloat = 6 @@ -288,7 +286,6 @@ private extension JetpackFullscreenOverlayViewController { static let titleKern: CGFloat = 0.37 static let buttonsNormalBottomSpacing: CGFloat = 30 static let singleButtonBottomSpacing: CGFloat = 60 - static let actionInfoButtonBottomSpacing: CGFloat = 24 } enum Constants { diff --git a/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift b/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift index fbbf031067bd..692ce1f0d4b9 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift @@ -349,7 +349,6 @@ private extension JetpackBrandingMenuCardCell { // Learn more button static let learnMoreButtonContentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 24) - static let learnMoreButtonContentEdgeInsets = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 24) static let learnMoreButtonTextColor: UIColor = UIColor.muriel(color: .jetpackGreen, .shade40) } diff --git a/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift b/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift index 4d7d11fd9e9a..3f73849204e2 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Login/JetpackLoginViewController.swift @@ -10,7 +10,6 @@ class JetpackLoginViewController: UIViewController { // MARK: - Constants - fileprivate let jetpackInstallRelativePath = "plugin-install.php?tab=plugin-information&plugin=jetpack" var blog: Blog // MARK: - Properties @@ -158,13 +157,6 @@ class JetpackLoginViewController: UIViewController { faqButton.isHidden = tacButton.isHidden } - // MARK: - Private Helpers - - fileprivate func managedObjectContext() -> NSManagedObjectContext { - return ContextManager.sharedInstance().mainContext - } - - // MARK: - Browser fileprivate func openInstallJetpackURL() { diff --git a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListItemViewModel.swift b/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListItemViewModel.swift index 2b08f610fd1d..aba2f3a84040 100644 --- a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListItemViewModel.swift +++ b/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListItemViewModel.swift @@ -15,6 +15,12 @@ struct AllDomainsListItemViewModel { value: "Renews", comment: "The renews label of the domain card in All Domains screen." ) + + static let neverExpires = NSLocalizedString( + "domain.management.card.neverExpires.label", + value: "Never expires", + comment: "Label indicating that a domain name registration has no expiry date." + ) } typealias Row = AllDomainsListCardView.ViewModel @@ -48,16 +54,16 @@ struct AllDomainsListItemViewModel { // MARK: - Helpers - private static func description(from domain: Domain) -> String? { + static func description(from domain: Domain) -> String? { guard !domain.isDomainOnlySite else { return nil } return !domain.blogName.isEmpty ? domain.blogName : domain.siteSlug } - private static func expiryDate(from domain: Domain) -> String? { + static func expiryDate(from domain: Domain) -> String { guard let date = domain.expiryDate, domain.hasRegistration else { - return nil + return Strings.neverExpires } let expired = date < Date() let notice = expired ? Strings.expired : Strings.renews diff --git a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListMessageStateViewModel.swift b/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListMessageStateViewModel.swift deleted file mode 100644 index 4572b56d4831..000000000000 --- a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListMessageStateViewModel.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -struct AllDomainsListMessageStateViewModel { - - let title: String - let description: String - let button: Button? - - struct Button { - let title: String - let action: () -> Void - } -} diff --git a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel+Strings.swift b/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel+Strings.swift index d728cb6229be..16ecd8f591be 100644 --- a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel+Strings.swift +++ b/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel+Strings.swift @@ -3,6 +3,11 @@ import Foundation extension AllDomainsListViewModel { enum Strings { + static let emptyStateButtonTitle = NSLocalizedString( + "domain.management.default.empty.state.button.title", + value: "Find a domain", + comment: "The empty state button title in All Domains screen when the user doesn't have any domains" + ) static let emptyStateTitle = NSLocalizedString( "domain.management.default.empty.state.title", value: "You don't have any domains", @@ -26,35 +31,5 @@ extension AllDomainsListViewModel { ) return String(format: format, searchQuery) } - static let emptyStateButtonTitle = NSLocalizedString( - "domain.management.default.empty.state.button.title", - value: "Find a domain", - comment: "The empty state button title in All Domains screen when the user doesn't have any domains" - ) - static let offlineEmptyStateTitle = NSLocalizedString( - "domain.management.offline.empty.state.title", - value: "No Internet Connection", - comment: "The empty state title in All Domains screen when the user is offline" - ) - static let offlineEmptyStateDescription = NSLocalizedString( - "domain.management.offline.empty.state.description", - value: "Please check your network connection and try again.", - comment: "The empty state description in All Domains screen when the user is offline" - ) - static let errorEmptyStateTitle = NSLocalizedString( - "domain.management.error.empty.state.title", - value: "Something went wrong", - comment: "The empty state title in All Domains screen when an error occurs" - ) - static let errorEmptyStateDescription = NSLocalizedString( - "domain.management.error.empty.state.description", - value: "We encountered an error while loading your domains. Please contact support if the issue persists.", - comment: "The empty state description in All Domains screen when an error occurs" - ) - static let errorStateButtonTitle = NSLocalizedString( - "domain.management.error.state.button.title", - value: "Try again", - comment: "The empty state button title in All Domains screen when an error occurs" - ) } } diff --git a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel.swift b/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel.swift index 38e51a965623..ddacf06ab67c 100644 --- a/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel.swift +++ b/WordPress/Classes/ViewRelated/Me/All Domains/View Models/AllDomainsListViewModel.swift @@ -13,7 +13,7 @@ class AllDomainsListViewModel { case loading /// This state is set when the list is empty or an error occurs. - case message(AllDomainsListMessageStateViewModel) + case message(DomainsStateViewModel) } private enum ViewModelError: Error { @@ -47,10 +47,14 @@ class AllDomainsListViewModel { // MARK: - Init - init(coreDataStack: CoreDataStackSwift = ContextManager.shared) { + init(coreDataStack: CoreDataStackSwift = ContextManager.shared, domains: [DomainsService.AllDomainsListItem] = []) { if let account = defaultAccount(with: coreDataStack) { self.domainsService = .init(coreDataStack: coreDataStack, wordPressComRestApi: account.wordPressComRestApi) } + self.domains = domains + if domains.count > 0 { + self.state = self.state(from: domains, searchQuery: lastSearchQuery) + } } private func defaultAccount(with contextManager: CoreDataStackSwift) -> WPAccount? { @@ -155,7 +159,7 @@ class AllDomainsListViewModel { // MARK: - Creating Message State View Models /// The message to display when the user doesn't have any domains. - private func noDomainsMessageViewModel() -> AllDomainsListMessageStateViewModel { + private func noDomainsMessageViewModel() -> DomainsStateViewModel { let action: () -> Void = { [weak self] in self?.addDomainAction?() WPAnalytics.track(.allDomainsFindDomainTapped) @@ -168,27 +172,14 @@ class AllDomainsListViewModel { } /// The message to display when an error occurs. - private func errorMessageViewModel(from error: Error) -> AllDomainsListMessageStateViewModel { - let title: String - let description: String - let button: AllDomainsListMessageStateViewModel.Button = .init(title: Strings.errorStateButtonTitle) { [weak self] in + private func errorMessageViewModel(from error: Error) -> DomainsStateViewModel { + return DomainsStateViewModel.errorMessageViewModel(from: error) { [weak self] in self?.loadData() } - - let nsError = error as NSError - if nsError.domain == NSURLErrorDomain, nsError.code == NSURLErrorNotConnectedToInternet { - title = Strings.offlineEmptyStateTitle - description = Strings.offlineEmptyStateDescription - } else { - title = Strings.errorEmptyStateTitle - description = Strings.errorEmptyStateDescription - } - - return .init(title: title, description: description, button: button) } /// The message to display when there are no domains matching the search query. - private func noSearchResultsMessageViewModel(searchQuery: String) -> AllDomainsListMessageStateViewModel { + private func noSearchResultsMessageViewModel(searchQuery: String) -> DomainsStateViewModel { return .init( title: Strings.searchEmptyStateTitle, description: Strings.searchEmptyStateDescription(searchQuery), diff --git a/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListCardView.swift b/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListCardView.swift index d8518fcf2f0d..9e6ece725437 100644 --- a/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListCardView.swift +++ b/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListCardView.swift @@ -5,38 +5,50 @@ struct AllDomainsListCardView: View { // MARK: - Types - struct ViewModel { - + struct ViewModel: Identifiable { + let id = UUID() let name: String let description: String? let status: Status? let expiryDate: String? + let isPrimary: Bool typealias Status = DomainsService.AllDomainsListItem.Status typealias StatusType = DomainsService.AllDomainsListItem.StatusType + + init(name: String, description: String?, status: Status?, expiryDate: String?, isPrimary: Bool = false) { + self.name = name + self.description = description + self.status = status + self.expiryDate = expiryDate + self.isPrimary = isPrimary + } } // MARK: - Properties private let viewModel: ViewModel + private let padding: CGFloat // MARK: - Init - init(viewModel: ViewModel) { + init(viewModel: ViewModel, padding: CGFloat = Length.Padding.double) { self.viewModel = viewModel + self.padding = padding } // MARK: - Views var body: some View { textContainerVStack - .padding(Length.Padding.double) + .padding(padding) } private var textContainerVStack: some View { VStack(alignment: .leading, spacing: Length.Padding.single) { domainText domainHeadline + primaryDomainLabel statusHStack } } @@ -59,31 +71,37 @@ struct AllDomainsListCardView: View { } } + private var primaryDomainLabel: some View { + Group { + if viewModel.isPrimary { + PrimaryDomainView() + } else { + EmptyView() + } + } + } + private var statusHStack: some View { HStack(spacing: Length.Padding.double) { - statusText - Spacer() + if let status = viewModel.status { + statusText(status: status) + Spacer() + } expirationText } } - private var statusText: some View { - Group { - if let status = viewModel.status { - HStack(spacing: Length.Padding.single) { - Circle() - .fill(status.type.indicatorColor) - .frame( - width: Length.Padding.single, - height: Length.Padding.single - ) - Text(status.value) - .foregroundColor(status.type.textColor) - .font(.subheadline.weight(status.type.fontWeight)) - } - } else { - EmptyView() - } + private func statusText(status: DomainsService.AllDomainsListItem.Status) -> some View { + HStack(spacing: Length.Padding.single) { + Circle() + .fill(status.type.indicatorColor) + .frame( + width: Length.Padding.single, + height: Length.Padding.single + ) + Text(status.value) + .foregroundColor(status.type.textColor) + .font(.subheadline.weight(status.type.fontWeight)) } } diff --git a/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListEmptyView.swift b/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListEmptyView.swift index 7b2f634c4d9d..89a63e98ec45 100644 --- a/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListEmptyView.swift +++ b/WordPress/Classes/ViewRelated/Me/All Domains/Views/AllDomainsListEmptyView.swift @@ -1,52 +1,19 @@ import UIKit import WordPressUI import DesignSystem +import SwiftUI final class AllDomainsListEmptyView: UIView { - typealias ViewModel = AllDomainsListMessageStateViewModel - - private enum Appearance { - static let labelsSpacing: CGFloat = Length.Padding.single - static let labelsButtonSpacing: CGFloat = Length.Padding.large - static let titleLabelFont: UIFont = WPStyleGuide.fontForTextStyle(.title2, fontWeight: .bold) - static let descriptionLabelFont: UIFont = WPStyleGuide.fontForTextStyle(.callout, fontWeight: .regular) - static let buttonLabelFont: UIFont = WPStyleGuide.fontForTextStyle(.body, fontWeight: .regular) - static let titleLabelColor: UIColor? = UIColor.DS.Foreground.secondary - static let descriptionLabelColor: UIColor? = UIColor.DS.Foreground.secondary - } - - // MARK: - Actions - - private var buttonAction: (() -> Void)? + typealias ViewModel = DomainsStateViewModel // MARK: - Views - private let titleLabel: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.font = Appearance.titleLabelFont - label.textColor = Appearance.titleLabelColor - label.numberOfLines = 2 - label.adjustsFontForContentSizeCategory = true - return label - }() - - private let descriptionLabel: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.font = Appearance.descriptionLabelFont - label.textColor = Appearance.descriptionLabelColor - label.numberOfLines = 0 - label.adjustsFontForContentSizeCategory = true - return label - }() - - private let button: UIButton = { - let button = FancyButton() - button.titleLabel?.font = Appearance.buttonLabelFont - button.isPrimary = true - return button + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.backgroundColor = .clear + return stackView }() // MARK: - Init @@ -63,33 +30,18 @@ final class AllDomainsListEmptyView: UIView { // MARK: - Rendering private func render(with viewModel: ViewModel?) { - let stackView = UIStackView(arrangedSubviews: [titleLabel, descriptionLabel, button]) - stackView.axis = .vertical - stackView.alignment = .fill - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.spacing = Appearance.labelsSpacing - stackView.setCustomSpacing(Appearance.labelsButtonSpacing, after: descriptionLabel) self.addSubview(stackView) self.pinSubviewToAllEdges(stackView) - self.button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) self.update(with: viewModel) } func update(with viewModel: ViewModel?) { - self.titleLabel.text = viewModel?.title - self.descriptionLabel.text = viewModel?.description - if let button = viewModel?.button { - self.button.isHidden = false - self.button.setTitle(button.title, for: .normal) - self.buttonAction = button.action - } else { - self.button.isHidden = true - } - } - - // MARK: - User Interaction + stackView.removeAllSubviews() - @objc private func didTapButton() { - self.buttonAction?() + if let viewModel = viewModel, + let stateView = UIHostingController(rootView: DomainsStateView(viewModel: viewModel)).view { + stateView.backgroundColor = .clear + stackView.addArrangedSubview(stateView) + } } } diff --git a/WordPress/Classes/ViewRelated/Me/All Domains/Views/DomainPurchaseChoicesView.swift b/WordPress/Classes/ViewRelated/Me/All Domains/Views/DomainPurchaseChoicesView.swift index 08248f3baf61..ab8b8268acce 100644 --- a/WordPress/Classes/ViewRelated/Me/All Domains/Views/DomainPurchaseChoicesView.swift +++ b/WordPress/Classes/ViewRelated/Me/All Domains/Views/DomainPurchaseChoicesView.swift @@ -10,7 +10,11 @@ struct DomainPurchaseChoicesView: View { static let imageLength: CGFloat = 36 } + @StateObject var viewModel = DomainPurchaseChoicesViewModel() + + let analyticsSource: String? + let buyDomainAction: (() -> Void) let chooseSiteAction: (() -> Void) @@ -37,28 +41,35 @@ struct DomainPurchaseChoicesView: View { .padding(.horizontal, Length.Padding.double) .background(Color.DS.Background.primary) .onAppear { - WPAnalytics.track(.purchaseDomainScreenShown) + self.track(.purchaseDomainScreenShown) } } private var getDomainCard: some View { - card(imageName: "site-menu-domains", - title: Strings.buyDomainTitle, - subtitle: Strings.buyDomainSubtitle, - buttonTitle: Strings.buyDomainButtonTitle, - isProgressViewActive: true, - action: buyDomainAction) + card( + imageName: "site-menu-domains", + title: Strings.buyDomainTitle, + subtitle: Strings.buyDomainSubtitle, + buttonTitle: Strings.buyDomainButtonTitle, + isProgressViewActive: true + ) { + self.track(.purchaseDomainGetDomainTapped) + self.buyDomainAction() + } } private var chooseSiteCard: some View { - card(imageName: "block-layout", - title: Strings.chooseSiteTitle, - subtitle: Strings.chooseSiteSubtitle, - buttonTitle: Strings.chooseSiteButtonTitle, - footer: Strings.chooseSiteFooter, - isProgressViewActive: false, - action: chooseSiteAction - ) + card( + imageName: "block-layout", + title: Strings.chooseSiteTitle, + subtitle: Strings.chooseSiteSubtitle, + buttonTitle: Strings.chooseSiteButtonTitle, + footer: Strings.chooseSiteFooter, + isProgressViewActive: false + ) { + self.track(.purchaseDomainChooseSiteTapped) + self.chooseSiteAction() + } } private func card( @@ -115,6 +126,21 @@ struct DomainPurchaseChoicesView: View { .foregroundStyle(Color.DS.Foreground.brand(isJetpack: AppConfiguration.isJetpack)) } } + + private func track(_ event: WPAnalyticsEvent, properties: [AnyHashable: Any]? = nil) { + let defaultProperties: [AnyHashable: Any] = { + guard let source = self.analyticsSource else { + return [:] + } + return [WPAppAnalyticsKeySource: source] + }() + + let properties = defaultProperties.merging(properties ?? [:]) { first, second in + return first + } + + WPAnalytics.track(event, properties: properties) + } } private extension DomainPurchaseChoicesView { @@ -182,7 +208,7 @@ private extension DomainPurchaseChoicesView { struct DomainPurchaseChoicesView_Previews: PreviewProvider { static var previews: some View { - DomainPurchaseChoicesView(viewModel: DomainPurchaseChoicesViewModel()) { + DomainPurchaseChoicesView(viewModel: DomainPurchaseChoicesViewModel(), analyticsSource: nil) { print("Buy domain tapped.") } chooseSiteAction: { print("Choose site tapped") diff --git a/WordPress/Classes/ViewRelated/Me/App Settings/AppSettingsViewController.swift b/WordPress/Classes/ViewRelated/Me/App Settings/AppSettingsViewController.swift index 140ce5362b8d..38272bd88e38 100644 --- a/WordPress/Classes/ViewRelated/Me/App Settings/AppSettingsViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/App Settings/AppSettingsViewController.swift @@ -8,11 +8,6 @@ import WordPressFlux import DesignSystem class AppSettingsViewController: UITableViewController { - enum Sections: Int { - case media - case other - } - fileprivate var handler: ImmuTableViewHandler! // MARK: - Dependencies diff --git a/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift b/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift index 2b03b09ea3e3..a6d8cba7755b 100644 --- a/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift @@ -216,7 +216,6 @@ private enum Strings { static let remoteConfigTitle = NSLocalizedString("debugMenu.remoteConfig.title", value: "Remote Config", comment: "Remote Config debug menu title") static let analyics = NSLocalizedString("debugMenu.analytics", value: "Analytics", comment: "Debug menu item title") static let featureFlags = NSLocalizedString("debugMenu.featureFlags", value: "Feature Flags", comment: "Feature flags menu item") - static let console = NSLocalizedString("debugMenu.console", value: "Console", comment: "Networking debug menu item") static let removeQuickStartRow = NSLocalizedString("debugMenu.removeQuickStart", value: "Remove Current Tour", comment: "Remove current quick start tour menu item") static let weeklyRoundup = NSLocalizedString("debugMenu.weeklyRoundup", value: "Weekly Roundup", comment: "Weekly Roundup debug menu item") } diff --git a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift index 006f06ee238e..de16534de8c1 100644 --- a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift @@ -365,6 +365,14 @@ class MeViewController: UITableViewController { navigateToTarget(for: RowTitles.accountSettings) } + /// Selects the All Domains row and pushes the All Domains view controller + /// + public func navigateToAllDomains() { + #if JETPACK + navigateToTarget(for: AllDomainsListViewController.Strings.title) + #endif + } + /// Selects the App Settings row and pushes the App Settings view controller /// @objc public func navigateToAppSettings(completion: ((AppSettingsViewController) -> Void)? = nil) { @@ -593,11 +601,6 @@ private extension MeViewController { enum HeaderTitles { static let wpAccount = NSLocalizedString("WordPress.com Account", comment: "WordPress.com sign-in/sign-out section header title") - static let products = NSLocalizedString( - "me.products.header", - value: "Products", - comment: "Products header text in Me Screen." - ) } enum LogoutAlert { diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift index 8dd57cb91311..c01f77f8b44a 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift @@ -207,7 +207,6 @@ private extension ChangeUsernameViewController { enum Constants { static let actionButtonTitle = NSLocalizedString("Save", comment: "Settings Text save button title") static let username = NSLocalizedString("Username", comment: "The header and main title") - static let cellIdentifier = "SearchTableViewCell" enum Alert { static let loading = NSLocalizedString("Loading usernames", comment: "Shown while the app waits for the username suggestions web service to return during the site creation process.") diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/View Model/ChangeUsernameViewModel.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/View Model/ChangeUsernameViewModel.swift index 0042ca4c573a..6d0d4697b122 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/View Model/ChangeUsernameViewModel.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/View Model/ChangeUsernameViewModel.swift @@ -128,10 +128,6 @@ private extension ChangeUsernameViewModel { static let highlight = NSLocalizedString("You will not be able to change your username back.", comment: "Paragraph text that needs to be highlighted") static let paragraph = NSLocalizedString("You are about to change your username, which is currently %@. %@", comment: "Paragraph displayed in the tableview header. The placholders are for the current username, highlight text and the current display name.") - enum Suggestions { - static let loading = NSLocalizedString("Loading usernames", comment: "Shown while the app waits for the username suggestions web service to return during the site creation process.") - } - enum Error { static let saveUsername = NSLocalizedString("There was an error saving the username", comment: "Text displayed when there is a failure saving the username.") } diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/AvatarMenuController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/AvatarMenuController.swift index 5b37ddc34bfe..e7c78aaef272 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/AvatarMenuController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/AvatarMenuController.swift @@ -80,10 +80,6 @@ final class AvatarMenuController: PHPickerViewControllerDelegate, ImagePickerCon SVProgressHUD.showDismissibleError(withStatus: Strings.errorTitle) } - private func dismiss() { - presentingViewController?.dismiss(animated: true) - } - private var topViewController: UIViewController? { presentingViewController?.topmostPresentedViewController } diff --git a/WordPress/Classes/ViewRelated/Media/CircularProgressView.swift b/WordPress/Classes/ViewRelated/Media/CircularProgressView.swift index a07083334314..d0c2bc161f7e 100644 --- a/WordPress/Classes/ViewRelated/Media/CircularProgressView.swift +++ b/WordPress/Classes/ViewRelated/Media/CircularProgressView.swift @@ -73,8 +73,6 @@ class CircularProgressView: UIView { } } - var errorTintColor = UIColor.white - var state: State = .stopped { didSet { refreshState() @@ -220,7 +218,6 @@ class CircularProgressView: UIView { final class AccessoryView: UIView { enum Appearance { - static let horizontalPadding: CGFloat = 4.0 static let verticalSpacing: CGFloat = 3.0 static let fontSize: CGFloat = 14.0 } diff --git a/WordPress/Classes/ViewRelated/Media/MediaItemViewController.swift b/WordPress/Classes/ViewRelated/Media/MediaItemViewController.swift index 070d6f520e9c..783d9d571920 100644 --- a/WordPress/Classes/ViewRelated/Media/MediaItemViewController.swift +++ b/WordPress/Classes/ViewRelated/Media/MediaItemViewController.swift @@ -196,8 +196,6 @@ final class MediaItemViewController: UITableViewController { } } - private var documentInteractionController: UIDocumentInteractionController? - private func presentDocumentViewControllerForMedia() { guard let remoteURL = media.remoteURL, let url = URL(string: remoteURL) else { return } @@ -362,10 +360,6 @@ final class MediaItemViewController: UITableViewController { // MARK: - Sharing Logic - private func share(media: Any, sender: UIBarButtonItem) { - share([media], sender: sender) - } - private func share(_ activityItems: [Any], sender: UIBarButtonItem) { let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) activityController.modalPresentationStyle = .popover diff --git a/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaAddMediaMenuController.swift b/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaAddMediaMenuController.swift index ddf18e531dd4..e4f5bc35f905 100644 --- a/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaAddMediaMenuController.swift +++ b/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaAddMediaMenuController.swift @@ -34,6 +34,11 @@ final class SiteMediaAddMediaMenuController: NSObject, PHPickerViewControllerDel return UIMenu(options: [.displayInline], children: children) } + func showPhotosPicker(from viewController: UIViewController) { + MediaPickerMenu(viewController: viewController, isMultipleSelectionEnabled: true) + .showPhotosPicker(delegate: self) + } + // MARK: - PHPickerViewControllerDelegate func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { diff --git a/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift b/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift index 6ccc092f5b08..b161839b365c 100644 --- a/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift +++ b/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift @@ -506,7 +506,8 @@ final class SiteMediaCollectionViewController: UIViewController, NSFetchedResult } func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { - for indexPath in indexPaths { + let count = fetchController.fetchedObjects?.count ?? 0 + for indexPath in indexPaths where indexPath.row < count { let media = fetchController.object(at: indexPath) getViewModel(for: media).cancelPrefetching() } diff --git a/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift b/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift index 0d4ae4639512..28bbafe3ab82 100644 --- a/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift +++ b/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaViewController.swift @@ -17,9 +17,12 @@ final class SiteMediaViewController: UIViewController, SiteMediaCollectionViewCo private var isPreparingToShare = false private var isFirstAppearance = true + private var showPicker = false - @objc init(blog: Blog) { + @objc init(blog: Blog, showPicker: Bool = false) { self.blog = blog + self.showPicker = showPicker + super.init(nibName: nil, bundle: nil) hidesBottomBarWhenPushed = true @@ -51,6 +54,11 @@ final class SiteMediaViewController: UIViewController, SiteMediaCollectionViewCo navigationItem.hidesSearchBarWhenScrolling = false } buttonAddMedia.shouldShowSpotlight = QuickStartTourGuide.shared.isCurrentElement(.mediaUpload) + + if showPicker && blog.userCanUploadMedia { + buttonAddMediaMenuController.showPhotosPicker(from: self) + showPicker = false + } } override func viewDidAppear(_ animated: Bool) { diff --git a/WordPress/Classes/ViewRelated/Media/StockPhotos/StockPhotosStrings.swift b/WordPress/Classes/ViewRelated/Media/StockPhotos/StockPhotosStrings.swift index 03f19443338a..d159acef3da8 100644 --- a/WordPress/Classes/ViewRelated/Media/StockPhotos/StockPhotosStrings.swift +++ b/WordPress/Classes/ViewRelated/Media/StockPhotos/StockPhotosStrings.swift @@ -9,10 +9,6 @@ extension String { return NSLocalizedString("Other Apps", comment: "Menu option used for adding media from other applications.") } - static var closePicker: String { - return NSLocalizedString("Close", comment: "Dismiss the media picker for Stock Photos") - } - static var cancelMoreOptions: String { return NSLocalizedString( "stockPhotos.strings.dismiss", diff --git a/WordPress/Classes/ViewRelated/NUX/LoginEpilogueTableViewController.swift b/WordPress/Classes/ViewRelated/NUX/LoginEpilogueTableViewController.swift index fc84ba86ea00..0106d99946e4 100644 --- a/WordPress/Classes/ViewRelated/NUX/LoginEpilogueTableViewController.swift +++ b/WordPress/Classes/ViewRelated/NUX/LoginEpilogueTableViewController.swift @@ -233,13 +233,11 @@ private extension LoginEpilogueTableViewController { } enum Settings { - static let headerReuseIdentifier = "SectionHeader" static let userCellReuseIdentifier = "userInfo" static let chooseSiteReuseIdentifier = "chooseSite" static let createNewSiteReuseIdentifier = "createNewSite" static let profileRowHeight = CGFloat(180) static let blogRowHeight = CGFloat(60) - static let headerHeight = CGFloat(50) } } diff --git a/WordPress/Classes/ViewRelated/NUX/SignupEpilogueTableViewController.swift b/WordPress/Classes/ViewRelated/NUX/SignupEpilogueTableViewController.swift index 871c2bdb12ca..1b5b0e9cf117 100644 --- a/WordPress/Classes/ViewRelated/NUX/SignupEpilogueTableViewController.swift +++ b/WordPress/Classes/ViewRelated/NUX/SignupEpilogueTableViewController.swift @@ -220,7 +220,6 @@ private extension SignupEpilogueTableViewController { static let noPasswordRows = 2 static let allAccountRows = 3 static let headerFooterHeight: CGFloat = 50 - static let footerTrailingMargin: CGFloat = 16 static let footerTopMargin: CGFloat = 8 } diff --git a/WordPress/Classes/ViewRelated/NUX/SignupEpilogueViewController.swift b/WordPress/Classes/ViewRelated/NUX/SignupEpilogueViewController.swift index 47e597637d1c..f7db1515d541 100644 --- a/WordPress/Classes/ViewRelated/NUX/SignupEpilogueViewController.swift +++ b/WordPress/Classes/ViewRelated/NUX/SignupEpilogueViewController.swift @@ -340,16 +340,3 @@ extension SignupEpilogueViewController: SignupUsernameViewControllerDelegate { } } } - -// MARK: - User Defaults - -extension UserDefaults { - var quickStartWasDismissedPermanently: Bool { - get { - return bool(forKey: #function) - } - set { - set(newValue, forKey: #function) - } - } -} diff --git a/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift b/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift index 0cdf6dc0a11c..40fff9d454e0 100644 --- a/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift +++ b/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift @@ -239,11 +239,4 @@ class UnifiedProloguePageViewController: UIViewController { return UIView() } } - - enum Metrics { - static let topInset: CGFloat = 96.0 - static let horizontalInset: CGFloat = 24.0 - static let titleToContentSpacing: CGFloat = 48.0 - static let heightRatio: CGFloat = WPDeviceIdentification.isiPad() ? 0.5 : 0.4 - } } diff --git a/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift b/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift index 57dece8291e7..cd10aafcc1e5 100644 --- a/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift +++ b/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift @@ -79,7 +79,7 @@ extension WordPressAuthenticationManager { enableSignupWithGoogle: AppConfiguration.allowSignUp, enableUnifiedAuth: true, enableUnifiedCarousel: true, - enablePasskeys: false, + enablePasskeys: true, enableSocialLogin: true) } @@ -176,7 +176,7 @@ extension WordPressAuthenticationManager { prologueButtonsBackgroundColor: prologueButtonsBackgroundColor, prologueViewBackgroundColor: prologueViewBackgroundColor, prologueBackgroundImage: authenticationHandler?.prologueBackgroundImage, - prologueButtonsBlurEffect: authenticationHandler?.prologueButtonsBlurEffect, + prologueButtonsBlurEffect: nil, navBarBackgroundColor: .appBarBackground, navButtonTextColor: .appBarTint, navTitleTextColor: .appBarText) @@ -585,26 +585,6 @@ private extension WordPressAuthenticationManager { typealias QuickStartOnDismissHandler = (Blog, Bool) -> Void - func presentQuickStartPrompt(for blog: Blog, in navigationController: UINavigationController, onDismiss: QuickStartOnDismissHandler?) { - // If the quick start prompt has already been dismissed, - // then show the My Site screen for the specified blog - guard !quickStartSettings.promptWasDismissed(for: blog) else { - - if self.windowManager.isShowingFullscreenSignIn { - self.windowManager.dismissFullscreenSignIn(blogToShow: blog) - } else { - navigationController.dismiss(animated: true) - } - - return - } - - // Otherwise, show the Quick Start prompt - let quickstartPrompt = QuickStartPromptViewController(blog: blog) - quickstartPrompt.onDismiss = onDismiss - navigationController.pushViewController(quickstartPrompt, animated: true) - } - func onDismissQuickStartPromptHandler(type: QuickStartType, onDismiss: @escaping () -> Void) -> QuickStartOnDismissHandler { return { [weak self] blog, _ in guard let self = self else { diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.swift b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.swift index 23b52cf39207..556e3a64ea23 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationDetailsViewController.swift @@ -69,10 +69,6 @@ class NotificationDetailsViewController: UIViewController, NoResultsViewHost { /// fileprivate let estimatedRowHeightsCache = NSCache() - /// A Reader Detail VC to display post content if needed - /// - private var readerDetailViewController: ReaderDetailViewController? - /// Previous NavBar Navigation Button /// var previousNavigationButton: UIButton! @@ -549,33 +545,6 @@ extension NotificationDetailsViewController { } } - - -// MARK: - Reader Helpers -// -private extension NotificationDetailsViewController { - func attachReaderViewIfNeeded() { - guard shouldAttachReaderView, - let postID = note.metaPostID, - let siteID = note.metaSiteID else { - readerDetailViewController?.remove() - return - } - - readerDetailViewController?.remove() - let readerDetailViewController = ReaderDetailViewController.controllerWithPostID(postID, siteID: siteID) - add(readerDetailViewController) - readerDetailViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.pinSubviewToSafeArea(readerDetailViewController.view) - self.readerDetailViewController = readerDetailViewController - } - - var shouldAttachReaderView: Bool { - return note.kind == .newPost - } -} - - // MARK: - Suggestions View Helpers // private extension NotificationDetailsViewController { @@ -1456,12 +1425,6 @@ private extension NotificationDetailsViewController { return NotificationActionsService(coreDataStack: ContextManager.shared) } - enum DisplayError: Error { - case missingParameter - case unsupportedFeature - case unsupportedType - } - enum ContentMedia { static let richBlockTypes = Set(arrayLiteral: FormattableContentKind.text, FormattableContentKind.comment) static let duration = TimeInterval(0.25) @@ -1477,7 +1440,6 @@ private extension NotificationDetailsViewController { enum Settings { static let numberOfSections = 1 static let estimatedRowHeight = CGFloat(44) - static let expirationFiveMinutes = TimeInterval(60 * 5) } enum Assets { diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift index bd6eb02b15d8..c333360da349 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift @@ -35,7 +35,6 @@ class NotificationSettingsViewController: UIViewController { // MARK: - Private Constants fileprivate let blogReuseIdentifier = WPBlogTableViewCell.classNameWithoutNamespaces() - fileprivate let blogRowHeight = CGFloat(54.0) fileprivate let defaultReuseIdentifier = WPTableViewCell.classNameWithoutNamespaces() fileprivate let switchReuseIdentifier = SwitchTableViewCell.classNameWithoutNamespaces() diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift index 077816b80f93..28e03dffad54 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift @@ -1821,20 +1821,6 @@ extension NotificationsViewController: WPSplitViewControllerDetailProvider { controller.view.backgroundColor = .basicBackground return controller } - - private func fetchFirstNotification() -> Notification? { - let context = managedObjectContext() - guard let fetchRequest = self.fetchRequest() else { - return nil - } - fetchRequest.fetchLimit = 1 - - if let results = try? context.fetch(fetchRequest) as? [Notification] { - return results.first - } - - return nil - } } // MARK: - Details Navigation Datasource @@ -1881,10 +1867,6 @@ private extension NotificationsViewController { return ContextManager.sharedInstance().mainContext } - var actionsService: NotificationActionsService { - return NotificationActionsService(coreDataStack: ContextManager.shared) - } - var userDefaults: UserPersistentRepository { return UserPersistentStoreFactory.instance() } @@ -2008,7 +1990,6 @@ private extension NotificationsViewController { enum Syncing { static let minimumPullToRefreshDelay = TimeInterval(1.5) static let pushMaxWait = TimeInterval(1.5) - static let syncTimeout = TimeInterval(10) static let undoTimeout = TimeInterval(4) } diff --git a/WordPress/Classes/ViewRelated/Notifications/FormattableContent/Ranges/NotificationContentRangeFactory.swift b/WordPress/Classes/ViewRelated/Notifications/FormattableContent/Ranges/NotificationContentRangeFactory.swift index 98e35c6ea947..e8239a18aa2f 100644 --- a/WordPress/Classes/ViewRelated/Notifications/FormattableContent/Ranges/NotificationContentRangeFactory.swift +++ b/WordPress/Classes/ViewRelated/Notifications/FormattableContent/Ranges/NotificationContentRangeFactory.swift @@ -74,7 +74,6 @@ struct NotificationContentRangeFactory: FormattableRangesFactory { enum RangeKeys { static let rawType = "type" static let url = "url" - static let indices = "indices" static let id = "id" static let value = "value" static let siteId = "site_id" diff --git a/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift b/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift index 942391100c5a..8541d573ab2e 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift @@ -12,26 +12,15 @@ extension WPStyleGuide { // NoteTableViewHeader public static let sectionHeaderBackgroundColor = UIColor.ungroupedListBackground - public static var sectionHeaderRegularStyle: [NSAttributedString.Key: Any] { - return [.paragraphStyle: sectionHeaderParagraph, - .font: sectionHeaderFont, - .foregroundColor: sectionHeaderTextColor] - } - // ListTableViewCell public static let unreadIndicatorColor = UIColor.primaryLight // Notification cells public static let noticonFont = UIFont(name: "Noticons", size: 16) - public static let noticonTextColor = UIColor.textInverted public static let noticonReadColor = UIColor.listSmallIcon public static let noticonUnreadColor = UIColor.primary - public static let noticonUnmoderatedColor = UIColor.warning public static let noteBackgroundReadColor = UIColor.ungroupedListBackground - public static let noteBackgroundUnreadColor = UIColor.ungroupedListUnread - - public static let noteSeparatorColor = blockSeparatorColor // Notification undo overlay public static let noteUndoBackgroundColor = UIColor.error @@ -315,7 +304,6 @@ extension WPStyleGuide { public static let headerFontSize = CGFloat(12) public static let headerLineSize = CGFloat(16) - public static let subjectFontSize = UIDevice.isPad() ? CGFloat(16) : CGFloat(14) public static let subjectNoticonSize = UIDevice.isPad() ? CGFloat(15) : CGFloat(14) public static let subjectLineSize = UIDevice.isPad() ? CGFloat(24) : CGFloat(18) public static let snippetLineSize = subjectLineSize @@ -329,9 +317,6 @@ extension WPStyleGuide { // // ParagraphStyle's - fileprivate static let sectionHeaderParagraph = NSMutableParagraphStyle( - minLineHeight: headerLineSize, lineBreakMode: .byWordWrapping, alignment: .natural - ) fileprivate static let subjectParagraph = NSMutableParagraphStyle( minLineHeight: subjectLineSize, lineBreakMode: .byWordWrapping, alignment: .natural ) @@ -352,7 +337,6 @@ extension WPStyleGuide { ) // Colors - fileprivate static let sectionHeaderTextColor = UIColor.textSubtle fileprivate static let subjectTextColor = UIColor.text fileprivate static let subjectNoticonColor = noticonReadColor fileprivate static let footerTextColor = UIColor.textSubtle @@ -361,9 +345,6 @@ extension WPStyleGuide { fileprivate static let headerTitleContextColor = UIColor.primary // Fonts - fileprivate static var sectionHeaderFont: UIFont { - return WPStyleGuide.fontForTextStyle(.caption1, fontWeight: .semibold) - } fileprivate static var subjectRegularFont: UIFont { return WPStyleGuide.fontForTextStyle(.subheadline) } diff --git a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockActionsTableViewCell.swift b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockActionsTableViewCell.swift index 95878a73e2ef..57dfbd092d48 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockActionsTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Views/NoteBlockActionsTableViewCell.swift @@ -366,11 +366,6 @@ private extension NoteBlockActionsTableViewCell { // MARK: - Private Constants // private extension NoteBlockActionsTableViewCell { - struct Edit { - static let normalTitle = NSLocalizedString("Edit", comment: "Verb, edit a comment") - static let normalHint = NSLocalizedString("Edits a comment", comment: "Edit Action Spoken hint.") - } - struct Constants { static let buttonSpacing = CGFloat(20) static let buttonSpacingCompact = CGFloat(2) diff --git a/WordPress/Classes/ViewRelated/Pages/PageListCell.swift b/WordPress/Classes/ViewRelated/Pages/PageListCell.swift index a0b989a52704..27f7c3af8678 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageListCell.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageListCell.swift @@ -13,7 +13,6 @@ final class PageListCell: UITableViewCell, PostSearchResultCell, Reusable { private let ellipsisButton = UIButton(type: .custom) private let contentStackView = UIStackView() private var indentationIconView = UIImageView() - private var cancellables: [AnyCancellable] = [] // MARK: - Properties diff --git a/WordPress/Classes/ViewRelated/People/PersonViewController.swift b/WordPress/Classes/ViewRelated/People/PersonViewController.swift index b8a4ad68436a..16a7a73c274a 100644 --- a/WordPress/Classes/ViewRelated/People/PersonViewController.swift +++ b/WordPress/Classes/ViewRelated/People/PersonViewController.swift @@ -165,7 +165,6 @@ final class PersonViewController: UITableViewController { // MARK: - Constants private let sectionHeaderHeight = CGFloat(20) - private let gravatarPlaceholderImage = UIImage(named: "gravatar.png") private let roleSegueIdentifier = "editRole" private let userInfoCellIdentifier = "userInfoCellIdentifier" private let actionCellIdentifier = "actionCellIdentifier" diff --git a/WordPress/Classes/ViewRelated/Plans/PlanDetailViewController.swift b/WordPress/Classes/ViewRelated/Plans/PlanDetailViewController.swift index ed90c521b07c..394fd7b739ea 100644 --- a/WordPress/Classes/ViewRelated/Plans/PlanDetailViewController.swift +++ b/WordPress/Classes/ViewRelated/Plans/PlanDetailViewController.swift @@ -3,9 +3,6 @@ import CocoaLumberjack import WordPressShared class PlanDetailViewController: UIViewController { - fileprivate let cellIdentifier = "PlanFeatureListItem" - - fileprivate let tableViewHorizontalMargin: CGFloat = 24.0 fileprivate let planImageDropshadowRadius: CGFloat = 3.0 private var noResultsViewController: NoResultsViewController? diff --git a/WordPress/Classes/ViewRelated/Plugins/PluginViewController.swift b/WordPress/Classes/ViewRelated/Plugins/PluginViewController.swift index d77aa47c9d62..d10ce10f00b8 100644 --- a/WordPress/Classes/ViewRelated/Plugins/PluginViewController.swift +++ b/WordPress/Classes/ViewRelated/Plugins/PluginViewController.swift @@ -153,10 +153,6 @@ private extension PluginViewController { } } - private func addChildController(_ controller: UIViewController) { - - } - private func getNoResultsViewController() -> NoResultsViewController { if let noResultsViewController = self.noResultsViewController { return noResultsViewController diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift index 63523783be00..cab1cc536093 100644 --- a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift @@ -15,7 +15,6 @@ class AbstractPostListViewController: UIViewController, { typealias SyncPostResult = (posts: [AbstractPost], hasMore: Bool) - private static let postsControllerRefreshInterval = TimeInterval(300) private static let httpErrorCodeForbidden = 403 private static let postsFetchRequestBatchSize = 10 private static let pagesNumberOfLoadedElement = 100 diff --git a/WordPress/Classes/ViewRelated/Post/Categories/PostCategoriesViewController.swift b/WordPress/Classes/ViewRelated/Post/Categories/PostCategoriesViewController.swift index d8965f0ca5f5..947d6edf4bad 100644 --- a/WordPress/Classes/ViewRelated/Post/Categories/PostCategoriesViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Categories/PostCategoriesViewController.swift @@ -25,8 +25,6 @@ import Foundation private var categoryIndentationDict = [Int: Int]() private var selectedCategories = [PostCategory]() - private var saveButtonItem: UIBarButtonItem? - private var hasSyncedCategories = false @objc init(blog: Blog, currentSelection: [PostCategory]?, selectionMode: CategoriesSelectionMode) { diff --git a/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift b/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift index f81aeb95d7d7..0d7975411d61 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCompactCell.swift @@ -198,9 +198,7 @@ class PostCompactCell: UITableViewCell { private enum Constants { static let separator = " · " - static let contentSpacing: CGFloat = 8 static let imageRadius: CGFloat = 2 - static let labelsVerticalAlignment: CGFloat = -1 static let opacity: Float = 1 static let margin: CGFloat = 16 } @@ -247,10 +245,6 @@ extension PostCompactCell { } } - func hideSeparator() { - separator.isHidden = true - } - func disableiPadReadableMargin() { iPadReadableLeadingAnchor?.isActive = false iPadReadableTrailingAnchor?.isActive = false diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift index 53f914b7f661..74244b582fab 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift @@ -212,17 +212,6 @@ extension PublishingEditor { present(alertController, animated: true, completion: nil) } - fileprivate func displayHasFailedMediaAlert(then: @escaping () -> ()) { - let alertController = UIAlertController(title: FailedMediaRemovalAlert.title, message: FailedMediaRemovalAlert.message, preferredStyle: .alert) - alertController.addDefaultActionWithTitle(FailedMediaRemovalAlert.acceptTitle) { [weak self] alertAction in - self?.removeFailedMedia() - then() - } - - alertController.addCancelActionWithTitle(FailedMediaRemovalAlert.cancelTitle) - present(alertController, animated: true, completion: nil) - } - /// If the user is publishing a post, displays the Prepublishing Nudges /// Otherwise, shows a confirmation Action Sheet. /// @@ -663,10 +652,3 @@ private struct PostUploadingAlert { static let message = NSLocalizedString("Your post is currently being uploaded. Please wait until this completes.", comment: "This is a notification the user receives if they are trying to preview a post before the upload process is complete.") static let acceptTitle = NSLocalizedString("OK", comment: "Accept Action") } - -private struct FailedMediaRemovalAlert { - static let title = NSLocalizedString("Uploads failed", comment: "Title for alert when trying to save post with failed media items") - static let message = NSLocalizedString("Some media uploads failed. This action will remove all failed media from the post.\nSave anyway?", comment: "Confirms with the user if they save the post all media that failed to upload will be removed from it.") - static let acceptTitle = NSLocalizedString("Yes", comment: "Accept Action") - static let cancelTitle = NSLocalizedString("Not Now", comment: "Nicer dialog answer for \"No\".") -} diff --git a/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift b/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift index 3c75ff1d7edc..1c9af4ceb0d6 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift @@ -254,19 +254,9 @@ class PostEditorNavigationBarManager { } extension PostEditorNavigationBarManager { - private enum Constants { - static let closeButtonInsets = NSDirectionalEdgeInsets(top: 3, leading: 3, bottom: 3, trailing: 3) - static let closeButtonEdgeInsets = UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3) - } - private enum Fonts { - static let semiBold = WPFontManager.systemSemiBoldFont(ofSize: 16) static var blogTitle: UIFont { WPStyleGuide.navigationBarStandardFont } } - - private enum Assets { - static let closeButtonModalImage = UIImage.gridicon(.cross) - } } diff --git a/WordPress/Classes/ViewRelated/Post/PostEditorState.swift b/WordPress/Classes/ViewRelated/Post/PostEditorState.swift index 4765eac86ac0..982b526b0f90 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditorState.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditorState.swift @@ -115,10 +115,6 @@ public enum PostEditorAction { } } - fileprivate var isPostPostShown: Bool { - return false - } - fileprivate var secondaryPublishAction: PostEditorAction? { switch self { case .publish: @@ -359,29 +355,12 @@ public class PostEditorStateContext { return action.publishActionLabel } - var publishQuestionTitleText: String { - return action.publishingActionQuestionLabel - } - /// Returns the WPAnalyticsStat enum to be tracked when this post is published /// var publishActionAnalyticsStat: WPAnalyticsStat { return action.publishActionAnalyticsStat } - // TODO: Remove as dead code? - /// Indicates if the editor should be dismissed when the publish button is tapped - /// - var publishActionDismissesEditor: Bool { - return action != .update - } - - /// Should post-post be shown for the current editor when publishing has happened - /// - var isPostPostShown: Bool { - return action.isPostPostShown - } - /// Returns whether the secondary publish button should be displayed, or not /// var isSecondaryPublishButtonShown: Bool { @@ -451,11 +430,3 @@ fileprivate func isFutureDated(_ date: Date?) -> Bool { return comparison == .orderedAscending } - -fileprivate func isPastDated(_ date: Date?) -> Bool { - guard let date = date else { - return false - } - - return date < Date() -} diff --git a/WordPress/Classes/ViewRelated/Post/PostListFilterSettings.swift b/WordPress/Classes/ViewRelated/Post/PostListFilterSettings.swift index f6f3ae70917f..8cd5b8e3e041 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListFilterSettings.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListFilterSettings.swift @@ -6,7 +6,6 @@ import WordPressShared class PostListFilterSettings: NSObject { fileprivate static let currentPostAuthorFilterKey = "CurrentPostAuthorFilterKey" fileprivate static let currentPageListStatusFilterKey = "CurrentPageListStatusFilterKey" - fileprivate static let currentPostListStatusFilterKey = "CurrentPostListStatusFilterKey" @objc let blog: Blog @objc let postType: PostServiceType @@ -33,11 +32,6 @@ class PostListFilterSettings: NSObject { return allPostListFilters! } - func filterThatDisplaysPostsWithStatus(_ postStatus: BasePost.Status) -> PostListFilter { - let index = indexOfFilterThatDisplaysPostsWithStatus(postStatus) - return availablePostListFilters()[index] - } - func indexOfFilterThatDisplaysPostsWithStatus(_ postStatus: BasePost.Status) -> Int { var index = 0 var found = false diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index 74fbd571e849..6466912def9f 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -7,10 +7,6 @@ import UIKit final class PostListViewController: AbstractPostListViewController, UIViewControllerRestoration, InteractivePostViewDelegate { static private let postsViewControllerRestorationKey = "PostsViewControllerRestorationKey" - private var showingJustMyPosts: Bool { - filterSettings.currentPostAuthorFilter() == .mine - } - /// If set, when the post list appear it will show the tab for this status private var initialFilterWithPostStatus: BasePost.Status? diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.swift index 708d15c43344..e5969af5db3e 100644 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.swift @@ -91,7 +91,6 @@ class PrepublishingHeaderView: UITableViewHeaderFooterView, NibLoadable { static let backButtonSize = CGSize(width: 28, height: 28) static let imageRadius: CGFloat = 4 static let leftRightInset: CGFloat = 16 - static let title = NSLocalizedString("Publishing To", comment: "Label that describes in which blog the user is publishing to") static let close = NSLocalizedString("Close", comment: "Voiceover accessibility label informing the user that this button dismiss the current view") static let doubleTapToDismiss = NSLocalizedString("Double tap to dismiss", comment: "Voiceover accessibility hint informing the user they can double tap a modal alert to dismiss it") } diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift index d4a2107e2122..4e0924d82e7d 100644 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift @@ -86,10 +86,6 @@ class PrepublishingNavigationController: LightNavigationController { navigationBar.scrollEdgeAppearance = appearance navigationBar.compactAppearance = appearance } - - private enum Constants { - static let iPadPreferredContentSize = CGSize(width: 300.0, height: 300.0) - } } diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController+JetpackSocial.swift index 2287ac3ea503..007546ac9d71 100644 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController+JetpackSocial.swift @@ -131,12 +131,6 @@ private extension PrepublishingViewController { WPAnalytics.track(.jetpackSocialNoConnectionCardDisplayed, properties: ["source": Constants.trackingSource]) } - func makeNoConnectionView() -> UIView { - let viewModel = makeNoConnectionViewModel() - let controller = JetpackSocialNoConnectionView.createHostController(with: viewModel) - return controller.view - } - func makeNoConnectionViewModel() -> JetpackSocialNoConnectionViewModel { let context = post.managedObjectContext ?? coreDataStack.mainContext guard let services = try? PublicizeService.allSupportedServices(in: context) else { diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift index 6493129af2e3..56a87acdf351 100644 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift @@ -47,9 +47,9 @@ class PrepublishingViewController: UITableViewController { return PublishSettingsViewModel(post: post) }() - private lazy var presentedVC: DrawerPresentationController? = { + private var presentedVC: DrawerPresentationController? { return (navigationController as? PrepublishingNavigationController)?.presentedVC - }() + } enum CompletionResult { case completed(AbstractPost) @@ -133,13 +133,11 @@ class PrepublishingViewController: UITableViewController { /// Toggles `keyboardShown` as the keyboard notifications come in private func configureKeyboardToggle() { NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification) - .map { _ in return true } - .assign(to: \.keyboardShown, on: self) + .sink { [weak self] _ in self?.keyboardShown = true } .store(in: &cancellables) NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification) - .map { _ in return false } - .assign(to: \.keyboardShown, on: self) + .sink { [weak self] _ in self?.keyboardShown = false } .store(in: &cancellables) } diff --git a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift index 9b53f309e66f..6326b5928aef 100644 --- a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift +++ b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewTextViewManager.swift @@ -12,7 +12,6 @@ class RevisionPreviewTextViewManager: NSObject { static let mediaPlaceholderImageSize = CGSize(width: 128, height: 128) static let placeholderMediaLink = URL(string: "placeholder://") - static let placeholderDocumentLink = URL(string: "documentUploading://") } } diff --git a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewViewController.swift b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewViewController.swift index fa19665386e0..64fd61e057fd 100644 --- a/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Revisions/Browser/Preview/RevisionPreviewViewController.swift @@ -15,7 +15,6 @@ class RevisionPreviewViewController: UIViewController, StoryboardLoadable { private let mainContext = ContextManager.sharedInstance().mainContext private let textViewManager = RevisionPreviewTextViewManager() private var titleInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0) - private var textViewInsets = UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 6.0) private lazy var textView: TextView = { let aztext = TextView(defaultFont: WPFontManager.notoRegularFont(ofSize: 16), diff --git a/WordPress/Classes/ViewRelated/Post/Search/PostSearchSuggestionsService.swift b/WordPress/Classes/ViewRelated/Post/Search/PostSearchSuggestionsService.swift index f605ca98d0ca..ca50302c9160 100644 --- a/WordPress/Classes/ViewRelated/Post/Search/PostSearchSuggestionsService.swift +++ b/WordPress/Classes/ViewRelated/Post/Search/PostSearchSuggestionsService.swift @@ -28,12 +28,16 @@ actor PostSearchSuggestionsService { let tokens = await [authors, tags] let selectedTokenIDs = Set(selectedTokens.map(\.id)) - return Array(tokens + let output = Array(tokens .flatMap { $0 } .filter { !selectedTokenIDs.contains($0.token.id) } .sorted { ($0.score, $0.token.value) > ($1.score, $1.token.value) } .map { $0.token } .prefix(3)) + + // Remove duplicates + var encounteredIDs = Set() + return output.filter { encounteredIDs.insert($0.id).inserted } } private struct RankedToken { @@ -79,7 +83,7 @@ actor PostSearchSuggestionsService { private func getTagTokens(for searchTerm: String, selectedTokens: [any PostSearchToken]) async -> [RankedToken] { guard !selectedTokens.contains(where: { $0 is PostSearchTagToken }) else { - return [] // Don't suggest authors anymore + return [] // Don't suggest tags anymore } let tokens = await getAllTagTokens() let search = StringRankedSearch(searchTerm: searchTerm) diff --git a/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewModel.swift b/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewModel.swift index 72c806f85290..f17c6fa3dd5f 100644 --- a/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewModel.swift @@ -27,7 +27,6 @@ final class PostSearchViewModel: NSObject, PostSearchServiceDelegate { private let entityName: String private var searchService: PostSearchService? - private var localSearchTask: Task? private let suggestionsService: PostSearchSuggestionsService private var suggestionsTask: Task? private var isRefreshing = false diff --git a/WordPress/Classes/ViewRelated/Ratings/AppFeedbackPromptView.swift b/WordPress/Classes/ViewRelated/Ratings/AppFeedbackPromptView.swift index 7789c0ad5c6c..9ffe11e56a30 100644 --- a/WordPress/Classes/ViewRelated/Ratings/AppFeedbackPromptView.swift +++ b/WordPress/Classes/ViewRelated/Ratings/AppFeedbackPromptView.swift @@ -9,7 +9,6 @@ class AppFeedbackPromptView: UIView { private let leftButton = RoundedButton() private let rightButton = RoundedButton() private let buttonStack = UIStackView() - private var onRequestingFeedback = false // MARK: - UIView's Methods diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/ReaderDetailViewController.swift b/WordPress/Classes/ViewRelated/Reader/Detail/ReaderDetailViewController.swift index bbc52c8975ae..adcd80195d85 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/ReaderDetailViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/ReaderDetailViewController.swift @@ -816,9 +816,6 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView { private enum Constants { static let margin: CGFloat = UIDevice.isPad() ? 0 : 8 - static let bottomMargin: CGFloat = 16 - static let toolbarHeight: CGFloat = 50 - static let delay: Double = 50 static let preferredToolbarHeight: CGFloat = 58.0 } diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift index ac5d93e60ea9..3f2602ad629a 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift @@ -37,12 +37,6 @@ class ReaderDetailHeaderView: UIStackView, NibLoadable, ReaderDetailHeader { /// private var post: ReaderPost? - /// The user interface direction for the view's semantic content attribute. - /// - private var layoutDirection: UIUserInterfaceLayoutDirection { - return UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) - } - /// Any interaction with the header is sent to the delegate /// weak var delegate: ReaderDetailHeaderViewDelegate? diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift index 4bdaa00f48d9..fb7015570538 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift @@ -247,6 +247,7 @@ class ReaderDetailToolbar: UIView, NibLoadable { WPStyleGuide.applyReaderReblogActionButtonStyle(reblogButton, showTitle: false) configureActionButtonStyle(reblogButton) + prepareReblogForVoiceOver() } private func playLikeButtonAnimation() { @@ -371,11 +372,6 @@ class ReaderDetailToolbar: UIView, NibLoadable { } fileprivate func configureButtonTitles() { - guard let post = post else { - return - } - - let commentCount = post.commentCount()?.intValue ?? 0 let commentTitle = Constants.commentButtonTitle likeButton.setTitle(likeButtonTitle, for: .normal) @@ -388,14 +384,6 @@ class ReaderDetailToolbar: UIView, NibLoadable { WPStyleGuide.applyReaderReblogActionButtonTitle(reblogButton, showTitle: true) } - private func commentLabel(count: Int) -> String { - if traitCollection.horizontalSizeClass == .compact { - return count > 0 ? String(count) : "" - } else { - return WPStyleGuide.commentCountForDisplay(count) - } - } - private func likeLabel(count: Int) -> String { if traitCollection.horizontalSizeClass == .compact { return count > 0 ? String(count) : "" diff --git a/WordPress/Classes/ViewRelated/Reader/OldReaderPostCardCell.swift b/WordPress/Classes/ViewRelated/Reader/OldReaderPostCardCell.swift index 303479218c4b..4cb5e7b90011 100644 --- a/WordPress/Classes/ViewRelated/Reader/OldReaderPostCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/OldReaderPostCardCell.swift @@ -137,10 +137,6 @@ protocol ReaderTopicsChipsDelegate: AnyObject { return WPStyleGuide.readerCardSummaryAttributes() }() - private lazy var readerCardReadingTimeAttributes: [NSAttributedString.Key: Any] = { - return WPStyleGuide.readerCardReadingTimeAttributes() - }() - // MARK: - Lifecycle Methods open override func awakeFromNib() { @@ -912,30 +908,6 @@ private extension OldReaderPostCardCell { reblogActionButton.accessibilityTraits = UIAccessibilityTraits.button } - func followLabel() -> String { - return followButtonIsSelected() ? followingLabel() : notFollowingLabel() - } - - func followingLabel() -> String { - return NSLocalizedString("Following", comment: "Accessibility label for following buttons.") - } - - func notFollowingLabel() -> String { - return NSLocalizedString("Not following", comment: "Accessibility label for unselected following buttons.") - } - - func followHint() -> String { - return followButtonIsSelected() ? unfollow(): follow() - } - - func unfollow() -> String { - return NSLocalizedString("Unfollows blog", comment: "Spoken hint describing action for selected following buttons.") - } - - func follow() -> String { - return NSLocalizedString("Follows blog", comment: "Spoken hint describing action for unselected following buttons.") - } - func followButtonIsSelected() -> Bool { return contentProvider?.isFollowing() ?? false } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift index a758f282ff4f..80af6e31900e 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift @@ -464,7 +464,6 @@ private extension ReaderPostCardCell { func configureLabel(_ label: UILabel, text: String?) { guard let text else { - label.removeFromSuperview() return } label.setText(text) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift index 3cba3af58a29..d67c66da3eb1 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift @@ -159,13 +159,6 @@ class ReaderPostCellActions: NSObject, ReaderPostCellDelegate { saveForLaterAction = saveAction } - fileprivate func visitSiteForPost(_ post: ReaderPost) { - guard let origin = origin else { - return - } - ReaderVisitSiteAction().execute(with: post, context: ContextManager.sharedInstance().mainContext, origin: origin) - } - fileprivate func showAttributionForPost(_ post: ReaderPost) { guard let origin = origin else { return diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSearchViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSearchViewController.swift index 150cada27c25..c55495510042 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSearchViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSearchViewController.swift @@ -48,7 +48,6 @@ import Gridicons fileprivate var siteSearchController: ReaderSiteSearchViewController? { return jpSiteSearchController.childVC as? ReaderSiteSearchViewController } - fileprivate let searchBarSearchIconSize = CGFloat(13.0) fileprivate var suggestionsController: ReaderSearchSuggestionsViewController? fileprivate var restoredSearchTopic: ReaderSearchTopic? fileprivate var didBumpStats = false diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSiteHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSiteHeaderView.swift index 2e2f10296ef4..e5a4f8a93d5c 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSiteHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSiteHeaderView.swift @@ -116,10 +116,6 @@ struct ReaderSiteHeader: View { struct Constants { static let defaultSiteImage = "blavatar-default" - static let iconSide = WPStyleGuide.fontSizeForTextStyle(.callout) - static let followIconSize = CGSize(width: iconSide, height: iconSide) - static let followIcon = UIImage.gridicon(.readerFollow, size: followIconSize).imageWithTintColor(.white) - static let followingIcon = UIImage.gridicon(.readerFollowing, size: followIconSize).imageWithTintColor(.buttonIcon) static let countsFormat = NSLocalizedString("reader.site.header.counts", value: "%1$@ posts • %2$@ followers", comment: "The formatted number of posts and followers for a site. " + diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift index b4437e4e2a05..953cda3fb558 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift @@ -15,18 +15,6 @@ fileprivate func < (lhs: T?, rhs: T?) -> Bool { } } -// FIXME: comparison operators with optionals were removed from the Swift Standard Libary. -// Consider refactoring the code to use the non-optional operators. -fileprivate func > (lhs: T?, rhs: T?) -> Bool { - switch (lhs, rhs) { - case let (l?, r?): - return l > r - default: - return rhs < lhs - } -} - - @objc open class ReaderSiteStreamHeader: UIView, ReaderStreamHeader { @IBOutlet fileprivate weak var avatarImageView: UIImageView! @IBOutlet fileprivate weak var titleLabel: UILabel! diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift index d6c580c3dde0..686e71d5969a 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift @@ -150,7 +150,6 @@ extension ReaderStreamViewController { private enum UndoCell { static let nibName = "ReaderSavedPostUndoCell" static let reuseIdentifier = "ReaderUndoCellReuseIdentifier" - static let height: CGFloat = 44 } func setupUndoCell(_ tableView: UITableView) { @@ -175,11 +174,3 @@ extension ReaderStreamViewController { WPAnalytics.trackReader(.readerSavedListShown, properties: ["source": ReaderSaveForLaterOrigin.readerMenu.viewAllPostsValue]) } } - -private extension ReaderStreamViewController { - - struct Constants { - // The number of characters allowed for the horizontal layout. - static let tagStreamHeaderHorizontalLengthLimit = 10 - } -} diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift index c79cc02d8665..105bf6305c1b 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift @@ -32,9 +32,6 @@ import Combine /// view controller is very large, with over 2000 lines of code! private let siteBlockingController = ReaderPostBlockingController() - /// Facilitates sharing of a blog via `ReaderStreamViewController+Sharing.swift`. - private let sharingController = PostSharingController() - // MARK: - Services private lazy var readerPostService = ReaderPostService(coreDataStack: coreDataStack) @@ -106,7 +103,6 @@ import Combine private let refreshInterval = 300 private var cleanupAndRefreshAfterScrolling = false private let recentlyBlockedSitePostObjectIDs = NSMutableArray() - private let frameForEmptyHeaderView = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 30.0) private let heightForFooterView = CGFloat(34.0) private let estimatedHeightsCache = NSCache() private var isLoggedIn = false @@ -2136,7 +2132,6 @@ private extension ReaderStreamViewController { } enum NoTopicConstants { - static let retryButtonTitle = NSLocalizedString("Retry", comment: "title for action that tries to connect to the reader after a loading error.") static let contentErrorTitle = NSLocalizedString("Unable to load this content right now.", comment: "Default title shown for no-results when the device is offline.") static let contentErrorSubtitle = NSLocalizedString("Check your network connection and try again.", comment: "Default subtitle for no-results when there is no connection") static let contentErrorImage = "cloud" diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTableConfiguration.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTableConfiguration.swift index 74df272d060a..b4f8b89cc78d 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTableConfiguration.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTableConfiguration.swift @@ -73,10 +73,6 @@ final class ReaderTableConfiguration { return tableView.dequeueReusableCell(withIdentifier: readerCardCellReuseIdentifier) as! ReaderPostCardCell } - func oldPostCardCell(_ tableView: UITableView) -> OldReaderPostCardCell { - return tableView.dequeueReusableCell(withIdentifier: oldReaderCardCellReuseIdentifier) as! OldReaderPostCardCell - } - func gapMarkerCell(_ tableView: UITableView) -> ReaderGapMarkerCell { return tableView.dequeueReusableCell(withIdentifier: readerGapMarkerCellReuseIdentifier) as! ReaderGapMarkerCell } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTopicsCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTopicsCardCell.swift index 013ec13e7667..18e4fd4a96d0 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTopicsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTopicsCardCell.swift @@ -82,8 +82,6 @@ class ReaderTopicsCardCell: UITableViewCell, NibLoadable { static let title = NSLocalizedString("You might like", comment: "A suggestion of topics the user might like") static let reuseIdentifier = ReaderInterestsCollectionViewCell.defaultReuseID - - static let collectionViewMinHeight: CGFloat = 40.0 } } diff --git a/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderInterestsStyleGuide.swift b/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderInterestsStyleGuide.swift index df518a4c3010..36316b6be3d4 100644 --- a/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderInterestsStyleGuide.swift +++ b/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderInterestsStyleGuide.swift @@ -12,17 +12,6 @@ class ReaderInterestsStyleGuide { let borderWidth: CGFloat let borderColor: UIColor - /// The legacy metrics for the cell style before `readerImprovements` feature - static let legacy = Metrics( - interestsLabelMargin: 8.0, - cellCornerRadius: 4.0, - cellSpacing: 6.0, - cellHeight: 26.0, - maxCellWidthMultiplier: 0.8, - borderWidth: 0, - borderColor: .clear - ) - static let latest = Metrics( interestsLabelMargin: 16.0, cellCornerRadius: 5.0, diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift index 6cc9f6ddc96c..1b4807b593e3 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift @@ -47,7 +47,6 @@ class ReaderTabView: UIView { self?.tabBar.items = tabItems self?.tabBar.setSelectedIndex(index) self?.configureTabBarElements() - self?.hideGhost() self?.addContentToContainerView() } @@ -291,50 +290,6 @@ private extension ReaderTabView { } - -// MARK: - Ghost - -private extension ReaderTabView { - - /// Build the ghost tab bar - func makeGhostTabBar() -> FilterTabBar { - let ghostTabBar = FilterTabBar() - - ghostTabBar.items = Appearance.ghostTabItems - ghostTabBar.isUserInteractionEnabled = false - ghostTabBar.tabBarHeight = Appearance.barHeight - ghostTabBar.dividerColor = .clear - - return ghostTabBar - } - - /// Show the ghost tab bar - func showGhost() { - let ghostTabBar = makeGhostTabBar() - tabBar.addSubview(ghostTabBar) - tabBar.pinSubviewToAllEdges(ghostTabBar) - - loadingView = ghostTabBar - - ghostTabBar.startGhostAnimation(style: GhostStyle(beatDuration: GhostStyle.Defaults.beatDuration, - beatStartColor: .placeholderElement, - beatEndColor: .placeholderElementFaded)) - - } - - /// Hide the ghost tab bar - func hideGhost() { - loadingView?.stopGhostAnimation() - loadingView?.removeFromSuperview() - loadingView = nil - } - - struct GhostTabItem: FilterTabBarItem { - var title: String - let accessibilityIdentifier = "" - } -} - // MARK: - Appearance private extension ReaderTabView { @@ -342,10 +297,7 @@ private extension ReaderTabView { enum Appearance { static let barHeight: CGFloat = 48 - static let tabBarAnimationsDuration = 0.2 - static let defaultFilterButtonTitle = NSLocalizedString("Filter", comment: "Title of the filter button in the Reader") - static let filterButtonMaxFontSize: CGFloat = 28.0 static let filterButtonFont = WPStyleGuide.fontForTextStyle(.headline, fontWeight: .regular) static let filterButtonInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0) static let filterButtonimageInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0) @@ -356,8 +308,6 @@ private extension ReaderTabView { static let dividerWidth: CGFloat = .hairlineBorderWidth static let dividerColor: UIColor = .divider - // "ghost" titles are set to the default english titles, as they won't be visible anyway - static let ghostTabItems = [GhostTabItem(title: "Following"), GhostTabItem(title: "Discover"), GhostTabItem(title: "Likes"), GhostTabItem(title: "Saved")] } } diff --git a/WordPress/Classes/ViewRelated/Reader/WPStyleGuide+Reader.swift b/WordPress/Classes/ViewRelated/Reader/WPStyleGuide+Reader.swift index d32a7ee0d8da..99f2eccd93d2 100644 --- a/WordPress/Classes/ViewRelated/Reader/WPStyleGuide+Reader.swift +++ b/WordPress/Classes/ViewRelated/Reader/WPStyleGuide+Reader.swift @@ -102,12 +102,6 @@ extension WPStyleGuide { ] } - @objc public class func readerCardReadingTimeAttributes() -> [NSAttributedString.Key: Any] { - let font = WPStyleGuide.fontForTextStyle(Cards.subtextTextStyle) - - return [.font: font] - } - // MARK: - Detail styles @objc public class func readerDetailTitleAttributes() -> [NSAttributedString.Key: Any] { @@ -438,13 +432,6 @@ extension WPStyleGuide { button.setImage(icon, for: .normal) applyReaderActionButtonStyle(button, imageColor: UIColor(light: .black, dark: .white)) } - /// Applies the settings button style to the button passed as an argument - class func applyReaderSettingsButtonStyle(_ button: UIButton) { - let icon = UIImage.gridicon(.cog) - - button.setImage(icon, for: .normal) - applyReaderActionButtonStyle(button) - } // MARK: - Gap Marker Styles @@ -479,18 +466,9 @@ extension WPStyleGuide { public static let subtextTextStyle: UIFont.TextStyle = .caption1 public static let loadMoreButtonTextStyle: UIFont.TextStyle = .subheadline - public static let crossPostTitleTextStyle: UIFont.TextStyle = .body - public static let crossPostSubtitleTextStyle: UIFont.TextStyle = .caption1 - public static let crossPostLineSpacing: CGFloat = 2.0 - public static let actionButtonSize: CGSize = CGSize(width: 20, height: 20) } - public struct Detail { - public static let titleTextStyle: UIFont.TextStyle = .title2 - public static let contentTextStyle: UIFont.TextStyle = .callout - } - public struct ReaderDetail { public static var reblogToolbarIcon: UIImage? { return UIImage(named: "icon-reader-reblog")?.withRenderingMode(.alwaysTemplate) @@ -525,30 +503,10 @@ extension WPStyleGuide { } public struct FollowButton { - struct Style { - static let followBackgroundColor: UIColor = .primaryButtonBackground - static let followTextColor: UIColor = .white - static let followingBackgroundColor: UIColor = .clear - static let followingIconColor: UIColor = .buttonIcon - static let followingTextColor: UIColor = .textSubtle - - static let imageTitleSpace: CGFloat = 2.0 - static let imageEdgeInsets = UIEdgeInsets(top: 0, left: -imageTitleSpace, bottom: 0, right: imageTitleSpace) - static let titleEdgeInsets = UIEdgeInsets(top: 0, left: imageTitleSpace, bottom: 0, right: -imageTitleSpace) - static let contentEdgeInsets = UIEdgeInsets(top: 6.0, left: 12.0, bottom: 6.0, right: 12.0) - } - struct Text { static let accessibilityHint = NSLocalizedString("Follows the tag.", comment: "VoiceOver accessibility hint, informing the user the button can be used to follow a tag.") static let followStringForDisplay = NSLocalizedString("Follow", comment: "Verb. Button title. Follow a new blog.") static let followingStringForDisplay = NSLocalizedString("Following", comment: "Verb. Button title. The user is following a blog.") } } - - public struct FollowConversationButton { - struct Style { - static let imageEdgeInsets = UIEdgeInsets(top: 1.0, left: -4.0, bottom: 0.0, right: -4.0) - static let contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 0.0) - } - } } diff --git a/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter+TableView.swift b/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter+TableView.swift index 9ec22c1eb556..320a9d0ca279 100644 --- a/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter+TableView.swift +++ b/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter+TableView.swift @@ -4,6 +4,5 @@ extension ShareAppContentPresenter { struct RowConstants { static let buttonTitle = AppConstants.Settings.shareButtonTitle static let buttonIconImage: UIImage? = .init(systemName: "square.and.arrow.up") - static let buttonTintColor: UIColor = .primary } } diff --git a/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift b/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift index 174da4386d9a..431cae36c141 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift @@ -37,13 +37,6 @@ class TemplatePreviewViewController: UIViewController, NoResultsViewHost, UIPopo } private var onDismissWithDeviceSelected: ((PreviewDevice) -> ())? - lazy var ghostView: GutenGhostView = { - let ghost = GutenGhostView() - ghost.hidesToolbar = true - ghost.translatesAutoresizingMaskIntoConstraints = false - return ghost - }() - private var accentColor: UIColor { return UIColor { (traitCollection: UITraitCollection) -> UIColor in if traitCollection.userInterfaceStyle == .dark { diff --git a/WordPress/Classes/ViewRelated/Site Creation/Shared/KeyboardInfo.swift b/WordPress/Classes/ViewRelated/Site Creation/Shared/KeyboardInfo.swift deleted file mode 100644 index 1c55b9f13a8c..000000000000 --- a/WordPress/Classes/ViewRelated/Site Creation/Shared/KeyboardInfo.swift +++ /dev/null @@ -1,31 +0,0 @@ -struct KeyboardInfo { - let animationCurve: UIView.AnimationCurve - let animationDuration: Double - let isLocal: Bool - let frameBegin: CGRect - let frameEnd: CGRect -} - -extension KeyboardInfo { - init?(_ notification: Foundation.Notification) { - guard notification.name == UIResponder.keyboardWillShowNotification || notification.name == UIResponder.keyboardWillHideNotification else { - return nil - } - - guard let u = notification.userInfo, - let curve = u[UIWindow.keyboardAnimationCurveUserInfoKey] as? Int, - let aCurve = UIView.AnimationCurve(rawValue: curve), - let duration = u[UIWindow.keyboardAnimationDurationUserInfoKey] as? Double, - let local = u[UIWindow.keyboardIsLocalUserInfoKey] as? Bool, - let begin = u[UIWindow.keyboardFrameBeginUserInfoKey] as? CGRect, - let end = u[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect else { - return nil - } - - animationCurve = aCurve - animationDuration = duration - isLocal = local - frameBegin = begin - frameEnd = end - } -} diff --git a/WordPress/Classes/ViewRelated/Site Creation/Shared/ShadowView.swift b/WordPress/Classes/ViewRelated/Site Creation/Shared/ShadowView.swift index a216439d5972..89778d84d519 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Shared/ShadowView.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Shared/ShadowView.swift @@ -33,8 +33,4 @@ final class ShadowView: UIView { maskPath.addPath(shadowPath) shadowMaskLayer.path = maskPath } - - func clearShadow() { - shadowLayer.removeFromSuperlayer() - } } diff --git a/WordPress/Classes/ViewRelated/Site Creation/Shared/SiteCreationAnalyticsHelper.swift b/WordPress/Classes/ViewRelated/Site Creation/Shared/SiteCreationAnalyticsHelper.swift index 2938b938b943..dbd3dbda0155 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Shared/SiteCreationAnalyticsHelper.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Shared/SiteCreationAnalyticsHelper.swift @@ -21,11 +21,9 @@ class SiteCreationAnalyticsHelper { private static let siteDesignKey = "template" private static let previewModeKey = "preview_mode" private static let verticalSlugKey = "vertical_slug" - private static let verticalSearchTerm = "search_term" private static let variationKey = "variation" private static let siteNameKey = "site_name" private static let recommendedKey = "recommended" - private static let customTreatmentNameKey = "custom_treatment_variation_name" // MARK: - Lifecycle static func trackSiteCreationAccessed(source: String) { @@ -58,24 +56,6 @@ class SiteCreationAnalyticsHelper { WPAnalytics.track(.enhancedSiteCreationIntentQuestionCanceled) } - // MARK: - Site Name - static func trackSiteNameViewed() { - WPAnalytics.track(.enhancedSiteCreationSiteNameViewed) - } - - static func trackSiteNameEntered(_ name: String) { - let properties = [siteNameKey: name] - WPAnalytics.track(.enhancedSiteCreationSiteNameEntered, properties: properties) - } - - static func trackSiteNameSkipped() { - WPAnalytics.track(.enhancedSiteCreationSiteNameSkipped) - } - - static func trackSiteNameCanceled() { - WPAnalytics.track(.enhancedSiteCreationSiteNameCanceled) - } - // MARK: - Site Design static func trackSiteDesignViewed(previewMode: PreviewDevice) { WPAnalytics.track(.enhancedSiteCreationSiteDesignViewed, withProperties: commonProperties(previewMode)) diff --git a/WordPress/Classes/ViewRelated/Site Creation/Shared/TableDataCoordinator.swift b/WordPress/Classes/ViewRelated/Site Creation/Shared/TableDataCoordinator.swift index cffcf7980d84..c6c28144ab41 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Shared/TableDataCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Shared/TableDataCoordinator.swift @@ -1,7 +1,5 @@ import UIKit -typealias TableViewProvider = UITableViewDataSource & UITableViewDelegate - /// Generic-based implementation of the UITableViewDataSource and UITableViewDelegate protocol. /// final class TableDataCoordinator: NSObject, UITableViewDataSource, UITableViewDelegate where Cell: ModelSettableCell, Cell: UITableViewCell, Model == Cell.DataType { diff --git a/WordPress/Classes/ViewRelated/Site Creation/Shared/TableViewOffsetCoordinator.swift b/WordPress/Classes/ViewRelated/Site Creation/Shared/TableViewOffsetCoordinator.swift deleted file mode 100644 index 043d447d4b8f..000000000000 --- a/WordPress/Classes/ViewRelated/Site Creation/Shared/TableViewOffsetCoordinator.swift +++ /dev/null @@ -1,202 +0,0 @@ - -import UIKit - -/// In Site Creation, both Verticals & Domains coordinate table view header appearance, keyboard behavior, & offsets. -/// This class manages that shared behavior. -/// -final class TableViewOffsetCoordinator { - - // MARK: Properties - private struct Constants { - static let headerAnimationDuration = Double(0.25) // matches current system keyboard transition duration - static let topMargin = CGFloat(36) - static let domainHeaderSection = 0 - } - - /// The table view to coordinate - private weak var tableView: UITableView? - - //// The view containing the toolbar - private weak var footerControlContainer: UIView? - - //// The toolbar - private weak var footerControl: UIView? - - //// The constraint linking the bottom of the footerControl to its container - private weak var toolbarBottomConstraint: NSLayoutConstraint? - - /// Tracks the content offset introduced by the keyboard being presented - private var keyboardContentOffset = CGFloat(0) - - /// To avoid wasted animations, we track whether or not we have already adjusted the table view - private var tableViewHasBeenAdjusted = false - - /// Track the status of the toolbar, wether we have adjusted its position or remains at its initial location - private var toolbarHasBeenAdjusted = false - - // MARK: TableViewOffsetCoordinator - - /// Initializes a table view offset coordinator with the specified table view. - /// - /// - Parameter tableView: the table view to manage - /// - Parameter footerControlContainer: the view containing the toolbar - /// - Parameter toolbar: a view that needs to be offset in coordination with the table view - /// - Parameter toolbarBottomConstraint: the constraint linking the bottom if footerControlContainer and toolbar - /// - init(coordinated tableView: UITableView, footerControlContainer: UIView? = nil, footerControl: UIView? = nil, toolbarBottomConstraint: NSLayoutConstraint? = nil) { - self.tableView = tableView - self.footerControlContainer = footerControlContainer - self.footerControl = footerControl - self.toolbarBottomConstraint = toolbarBottomConstraint - } - - // MARK: Internal behavior - - /// This method hides the table view header and adjusts the content offset so that the input text field is visible. - /// - func adjustTableOffsetIfNeeded() { - guard let tableView = tableView, keyboardContentOffset > 0, tableViewHasBeenAdjusted == false else { - return - } - - let topInset: CGFloat - if WPDeviceIdentification.isiPhone(), let header = tableView.tableHeaderView as? TitleSubtitleTextfieldHeader { - let textfieldFrame = header.textField.frame - topInset = textfieldFrame.origin.y - Constants.topMargin - } else { - topInset = 0 - } - - let bottomInset: CGFloat - if WPDeviceIdentification.isiPad() && UIDevice.current.orientation.isPortrait { - bottomInset = 0 - } else { - bottomInset = keyboardContentOffset - } - - let targetInsets = UIEdgeInsets(top: -topInset, left: 0, bottom: bottomInset, right: 0) - - UIView.animate(withDuration: Constants.headerAnimationDuration, delay: 0, options: .beginFromCurrentState, animations: { [weak self] in - guard let self = self, let tableView = self.tableView else { - return - } - - tableView.contentInset = targetInsets - tableView.scrollIndicatorInsets = targetInsets - if WPDeviceIdentification.isiPhone(), let header = tableView.tableHeaderView as? TitleSubtitleTextfieldHeader { - header.titleSubtitle.alpha = 0.0 - tableView.headerView(forSection: Constants.domainHeaderSection)?.isHidden = true - } - }, completion: { [weak self] _ in - self?.tableViewHasBeenAdjusted = true - }) - } - - /// This method resets the table view header and the content offset to the default state. - /// - func resetTableOffsetIfNeeded() { - guard WPDeviceIdentification.isiPhone(), tableViewHasBeenAdjusted == true else { - return - } - - UIView.animate(withDuration: Constants.headerAnimationDuration, delay: 0, options: .beginFromCurrentState, animations: { [weak self] in - guard let self = self, let tableView = self.tableView else { - return - } - - let finalOffset: UIEdgeInsets - if let footerControl = self.footerControl, self.toolbarHasBeenAdjusted == true { - let toolbarHeight = footerControl.frame.size.height - finalOffset = UIEdgeInsets(top: -1 * toolbarHeight, - left: 0, bottom: toolbarHeight, right: 0) - } else { - finalOffset = .zero - } - tableView.contentInset = finalOffset - tableView.scrollIndicatorInsets = finalOffset - if WPDeviceIdentification.isiPhone(), let header = tableView.tableHeaderView as? TitleSubtitleTextfieldHeader { - header.titleSubtitle.alpha = 1.0 - } - }, completion: { [weak self] _ in - self?.tableViewHasBeenAdjusted = false - }) - } - - @objc - func keyboardWillShow(_ notification: Foundation.Notification) { - guard let payload = KeyboardInfo(notification) else { - return - } - - let keyboardScreenFrame = payload.frameEnd - keyboardContentOffset = keyboardScreenFrame.height - - adjustToolbarOffsetIfNeeded() - } - - @objc - private func keyboardWillHide(_ notification: Foundation.Notification) { - keyboardContentOffset = 0 - toolbarHasBeenAdjusted = false - toolbarBottomConstraint?.constant = 0 - } - - private func adjustToolbarOffsetIfNeeded() { - guard let footerControl = footerControl, let footerControlContainer = footerControlContainer else { - return - } - - var constraintConstant = keyboardContentOffset - - let bottomInset = footerControlContainer.safeAreaInsets.bottom - constraintConstant -= bottomInset - - if let header = tableView?.tableHeaderView as? TitleSubtitleTextfieldHeader { - let textFieldFrame = header.textField.frame - - let newToolbarFrame = footerControl.frame.offsetBy(dx: 0.0, dy: -1 * constraintConstant) - - toolbarBottomConstraint?.constant = constraintConstant - footerControlContainer.setNeedsUpdateConstraints() - - UIView.animate(withDuration: Constants.headerAnimationDuration, delay: 0, options: .beginFromCurrentState, animations: { [weak self] in - guard let self = self, let tableView = self.tableView else { - return - } - - if textFieldFrame.intersects(newToolbarFrame) { - let contentInsets = UIEdgeInsets(top: -1 * footerControl.frame.height, left: 0.0, bottom: constraintConstant + footerControl.frame.height, right: 0.0) - self.toolbarHasBeenAdjusted = true - tableView.contentInset = contentInsets - tableView.scrollIndicatorInsets = contentInsets - } - footerControlContainer.layoutIfNeeded() - }, completion: { [weak self] _ in - self?.tableViewHasBeenAdjusted = false - }) - } - } - - func startListeningToKeyboardNotifications() { - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillShow), - name: UIResponder.keyboardWillShowNotification, - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillHide), - name: UIResponder.keyboardWillHideNotification, - object: nil) - } - - func stopListeningToKeyboardNotifications() { - NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) - } - - func showBottomToolbar() { - footerControl?.isHidden = false - } - - func hideBottomToolbar() { - footerControl?.isHidden = true - } -} diff --git a/WordPress/Classes/ViewRelated/Site Creation/Shared/TitleSubtitleTextfieldHeader.swift b/WordPress/Classes/ViewRelated/Site Creation/Shared/TitleSubtitleTextfieldHeader.swift index c55287847eb0..363113425eec 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Shared/TitleSubtitleTextfieldHeader.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Shared/TitleSubtitleTextfieldHeader.swift @@ -112,89 +112,3 @@ final class SearchTextField: UITextField { leftViewMode = .always } } - -// MARK: - TitleSubtitleTextfieldHeader - -final class TitleSubtitleTextfieldHeader: UIView { - - // MARK: Properties - - private struct Constants { - static let spacing = CGFloat(10) - static let bottomMargin = CGFloat(16) - } - - private(set) lazy var titleSubtitle: TitleSubtitleHeader = { - let returnValue = TitleSubtitleHeader(frame: .zero) - returnValue.translatesAutoresizingMaskIntoConstraints = false - - return returnValue - }() - - private(set) var textField = SearchTextField() - - private lazy var stackView: UIStackView = { - - let returnValue = UIStackView(arrangedSubviews: [self.titleSubtitle, self.textField]) - returnValue.translatesAutoresizingMaskIntoConstraints = false - returnValue.axis = .vertical - returnValue.spacing = Constants.spacing - NSLayoutConstraint.activate([ - textField.leadingAnchor.constraint(equalTo: returnValue.leadingAnchor), - textField.trailingAnchor.constraint(equalTo: returnValue.trailingAnchor) - ]) - - return returnValue - }() - - // MARK: UIView - - override init(frame: CGRect) { - super.init(frame: frame) - setupView() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupView() - } - - // MARK: Private behavior - - private func setupView() { - translatesAutoresizingMaskIntoConstraints = false - addSubview(stackView) - NSLayoutConstraint.activate([ - stackView.leadingAnchor.constraint(equalTo: leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor), - stackView.topAnchor.constraint(equalTo: topAnchor), - stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.bottomMargin) - ]) - - setStyles() - - prepareForVoiceOver() - } - - private func setStyles() { - backgroundColor = .clear - } - - func setTitle(_ text: String) { - titleSubtitle.setTitle(text) - } - - func setSubtitle(_ text: String) { - titleSubtitle.setSubtitle(text) - } -} - -extension TitleSubtitleTextfieldHeader: Accessible { - func prepareForVoiceOver() { - prepareSearchFieldForVoiceOver() - } - - private func prepareSearchFieldForVoiceOver() { - textField.accessibilityTraits = .searchField - } -} diff --git a/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift b/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift index 8c367ee609fe..8f55c0937952 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift @@ -138,9 +138,6 @@ extension SiteIntentViewController { private enum Metrics { static let largeTitleLines = 2 - static let continueButtonPadding: CGFloat = 16 - static let continueButtonBottomOffset: CGFloat = 12 - static let continueButtonHeight: CGFloat = 44 } } diff --git a/WordPress/Classes/ViewRelated/Site Creation/Site Segments/SiteSegmentsCell.swift b/WordPress/Classes/ViewRelated/Site Creation/Site Segments/SiteSegmentsCell.swift index e1a0f5dee260..6eb627ff20f7 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Site Segments/SiteSegmentsCell.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Site Segments/SiteSegmentsCell.swift @@ -10,12 +10,6 @@ private extension String { } } -private extension SiteSegment { - var iconTintColor: UIColor? { - return self.iconColor?.hexAsColor() - } -} - final class SiteSegmentsCell: UITableViewCell, ModelSettableCell { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var title: UILabel! diff --git a/WordPress/Classes/ViewRelated/Site Creation/Web Address/SitePromptView.swift b/WordPress/Classes/ViewRelated/Site Creation/Web Address/SitePromptView.swift deleted file mode 100644 index c244bcdd401f..000000000000 --- a/WordPress/Classes/ViewRelated/Site Creation/Web Address/SitePromptView.swift +++ /dev/null @@ -1,61 +0,0 @@ -import UIKit -import Gridicons - -class SitePromptView: UIView { - - private struct Parameters { - static let cornerRadius = CGFloat(8) - static let borderWidth = CGFloat(1) - static let borderColor = UIColor.primaryButtonBorder - } - - @IBOutlet weak var sitePrompt: UILabel! { - didSet { - sitePrompt.text = NSLocalizedString("example.com", comment: "Provides a sample of what a domain name looks like.") - } - } - - @IBOutlet weak var lockIcon: UIImageView! { - didSet { - lockIcon.image = UIImage.gridicon(.lock) - } - } - var contentView: UIView! - - override init(frame: CGRect) { - super.init(frame: frame) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { - contentView.layer.borderColor = Parameters.borderColor.cgColor - } - } - - private func commonInit() { - let bundle = Bundle(for: SitePromptView.self) - guard - let nibViews = bundle.loadNibNamed("SitePromptView", owner: self, options: nil), - let loadedView = nibViews.first as? UIView - else { - return - } - - contentView = loadedView - addSubview(contentView) - contentView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate( - contentView.constrainToSuperViewEdges() - ) - contentView.layer.cornerRadius = Parameters.cornerRadius - contentView.layer.borderColor = Parameters.borderColor.cgColor - contentView.layer.borderWidth = Parameters.borderWidth - } -} diff --git a/WordPress/Classes/ViewRelated/Site Creation/Web Address/SitePromptView.xib b/WordPress/Classes/ViewRelated/Site Creation/Web Address/SitePromptView.xib deleted file mode 100644 index 58b072a131f5..000000000000 --- a/WordPress/Classes/ViewRelated/Site Creation/Web Address/SitePromptView.xib +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizard.swift b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizard.swift index 158b60e8a2b0..002e7070a1d6 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizard.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizard.swift @@ -1,12 +1,5 @@ /// Coordinates the UI flow for creating a new site final class SiteCreationWizard: Wizard { - private lazy var firstContentViewController: UIViewController? = { - guard let firstStep = self.steps.first else { - return nil - } - return firstStep.content - }() - private lazy var navigation: WizardNavigation? = { return WizardNavigation(steps: self.steps) }() diff --git a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift index 5e5c224cf4ae..7bbd21a85008 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift @@ -2,13 +2,6 @@ import Foundation import WordPressKit -// MARK: - SiteCreationRequestAssemblyError - -enum SiteCreationRequestAssemblyError: Error { - case invalidSegmentIdentifier - case invalidVerticalIdentifier -} - // MARK: - SiteCreator // Tracks data state shared between Site Creation Wizard Steps. I am not too fond of the name, but it kind of works for now. diff --git a/WordPress/Classes/ViewRelated/Site Creation/Wizard/WizardNavigation.swift b/WordPress/Classes/ViewRelated/Site Creation/Wizard/WizardNavigation.swift index ab39f2898ccc..772f566ad404 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Wizard/WizardNavigation.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Wizard/WizardNavigation.swift @@ -5,13 +5,6 @@ final class WizardNavigation: UINavigationController { private let steps: [WizardStep] private let pointer: WizardNavigationPointer - private lazy var firstContentViewController: UIViewController? = { - guard let firstStep = self.steps.first else { - return nil - } - return firstStep.content - }() - init(steps: [WizardStep]) { self.steps = steps self.pointer = WizardNavigationPointer(capacity: steps.count) diff --git a/WordPress/Classes/ViewRelated/Stats/Charts/Charts+LargeValueFormatter.swift b/WordPress/Classes/ViewRelated/Stats/Charts/Charts+LargeValueFormatter.swift index 711ecf558aae..9a22564f6f35 100644 --- a/WordPress/Classes/ViewRelated/Stats/Charts/Charts+LargeValueFormatter.swift +++ b/WordPress/Classes/ViewRelated/Stats/Charts/Charts+LargeValueFormatter.swift @@ -3,8 +3,6 @@ import Foundation import DGCharts -private let MAX_LENGTH = 5 - @objc protocol Testing123 { } public class LargeValueFormatter: NSObject, ValueFormatter, AxisValueFormatter { diff --git a/WordPress/Classes/ViewRelated/Stats/Charts/InsightsLineChart.swift b/WordPress/Classes/ViewRelated/Stats/Charts/InsightsLineChart.swift index 16f4e3fbe8a8..4d8aea514d3c 100644 --- a/WordPress/Classes/ViewRelated/Stats/Charts/InsightsLineChart.swift +++ b/WordPress/Classes/ViewRelated/Stats/Charts/InsightsLineChart.swift @@ -129,15 +129,6 @@ class InsightsLineChart { return (thisWeekEntries: thisWeekEntries, prevWeekEntries: prevWeekEntries) } - - func primaryLineColor(forFilterDimension filterDimension: StatsInsightsFilterDimension) -> UIColor { - switch filterDimension { - case .views: - return UIColor(light: .muriel(name: .blue, .shade50), dark: .muriel(name: .blue, .shade50)) - case .visitors: - return UIColor(light: .muriel(name: .purple, .shade50), dark: .muriel(name: .purple, .shade50)) - } - } } private extension InsightsLineChart { diff --git a/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift b/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift index 878f5705053d..1138bdda561d 100644 --- a/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift @@ -18,14 +18,10 @@ class StatsLineChartView: LineChartView { private struct Constants { static let intrinsicHeight = CGFloat(190) - static let highlightAlpha = CGFloat(1) static let highlightLineWidth = 1.0 static let highlightLineDashLengths = 4.4 static let horizontalAxisLabelCount = 3 - static let presentationDelay = TimeInterval(0.5) - static let primaryDataSetIndex = 0 static let rotationDelay = TimeInterval(0.35) - static let secondaryDataSetIndex = 1 static let topOffset = CGFloat(16) static let trailingOffset = CGFloat(8) static let verticalAxisLabelCount = 5 @@ -57,13 +53,6 @@ class StatsLineChartView: LineChartView { private var statsInsightsFilterDimension: StatsInsightsFilterDimension - private var isHighlightNeeded: Bool { - guard let primaryDataSet = primaryDataSet, primaryDataSet.isHighlightEnabled else { - return false - } - return styling.primaryHighlightColor != nil - } - private var primaryDataSet: ChartDataSetProtocol? { return data?.dataSets.first } @@ -308,8 +297,6 @@ private extension StatsLineChartView { // MARK: - ChartViewDelegate -private typealias StatsLineChartMarker = MarkerView - extension StatsLineChartView: ChartViewDelegate { func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) { captureAnalyticsEvent() diff --git a/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift b/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift index 0481c0315762..50cbfb96b9cb 100644 --- a/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift +++ b/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift @@ -245,8 +245,6 @@ extension WPStyleGuide { static let customizeInsightsDismissButtonFont = WPStyleGuide.fontForTextStyle(.body, fontWeight: .regular) static let customizeInsightsTryButtonFont = UIFont.systemFont(ofSize: UIFont.preferredFont(forTextStyle: .body).pointSize, weight: .medium) - static let manageInsightsButtonTintColor = UIColor.textSubtle - static let positiveColor = UIColor.success static let negativeColor = UIColor.error static let neutralColor = UIColor.muriel(color: MurielColor(name: .blue)) diff --git a/WordPress/Classes/ViewRelated/Stats/Helpers/StatsPeriodHelper.swift b/WordPress/Classes/ViewRelated/Stats/Helpers/StatsPeriodHelper.swift index 090a4590177b..554b4a51b408 100644 --- a/WordPress/Classes/ViewRelated/Stats/Helpers/StatsPeriodHelper.swift +++ b/WordPress/Classes/ViewRelated/Stats/Helpers/StatsPeriodHelper.swift @@ -168,14 +168,6 @@ class StatsPeriodHelper { } private extension Date { - func adjusted(for period: StatsPeriodUnit, in calendar: Calendar, value: Int) -> Date { - guard let adjustedDate = calendar.date(byAdding: period.calendarComponent, value: value, to: self) else { - DDLogError("[Stats] Couldn't do basic math on Calendars in Stats. Returning original value.") - return self - } - return adjustedDate - } - func lastDayOfTheWeek(in calendar: Calendar, with offset: Int) -> Date? { let components = DateComponents(day: 7 * offset) diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsViewModel.swift index f6b44a0c49ba..90f4341b4a25 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsViewModel.swift @@ -383,10 +383,6 @@ class SiteStatsInsightsViewModel: Observable { ]) } - func isFetchingOverview() -> Bool { - return insightsStore.isFetchingOverview - } - func fetchingFailed() -> Bool { return insightsStore.fetchingFailed(for: .insights) } @@ -395,10 +391,6 @@ class SiteStatsInsightsViewModel: Observable { return insightsStore.containsCachedData(for: insightsToShow) } - func yearlyPostingActivity(from date: Date = Date()) -> [[PostingStreakEvent]] { - return insightsStore.getYearlyPostingActivity(from: date) - } - func annualInsightsYear() -> Int? { return insightsStore.getAnnualAndMostPopularTime()?.annualInsightsYear } @@ -830,13 +822,6 @@ private extension SiteStatsInsightsViewModel { dataRows: followersData ?? []) } - func createAddInsightRow() -> StatsTotalRowData { - return StatsTotalRowData(name: StatSection.insightsAddInsight.title, - data: "", - icon: Style.imageForGridiconType(.plus, withTint: .darkGrey), - statSection: .insightsAddInsight) - } - func updateMostRecentChartData(_ periodSummary: StatsSummaryTimeIntervalData?) { if mostRecentChartData == nil, let periodSummary = periodSummary, diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/StatsLatestPostSummaryInsightsCell.swift b/WordPress/Classes/ViewRelated/Stats/Insights/StatsLatestPostSummaryInsightsCell.swift index c7f0e08a0a74..886bdb5cb297 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/StatsLatestPostSummaryInsightsCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/StatsLatestPostSummaryInsightsCell.swift @@ -7,7 +7,6 @@ class StatsLatestPostSummaryInsightsCell: StatsBaseCell, LatestPostSummaryConfig private typealias Style = WPStyleGuide.Stats private var lastPostInsight: StatsLastPostInsight? private var lastPostDetails: StatsPostDetails? - private var postTitle = StatSection.noPostTitle private let outerStackView = UIStackView() private let postStackView = UIStackView() diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsDetailTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsDetailTableViewController.swift index c73afd119f67..267a576da1ab 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsDetailTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsDetailTableViewController.swift @@ -29,9 +29,7 @@ class SiteStatsDetailTableViewController: UITableViewController, StoryboardLoada private var receipt: Receipt? private let insightsStore = StoreContainer.shared.statsInsights - private var insightsChangeReceipt: Receipt? private let periodStore = StoreContainer.shared.statsPeriod - private var periodChangeReceipt: Receipt? private lazy var tableHandler: ImmuTableViewHandler = { return ImmuTableViewHandler(takeOver: self) @@ -228,12 +226,6 @@ private extension SiteStatsDetailTableViewController { } } - func applyTableUpdates() { - tableView.performBatchUpdates({ - updateStatSectionForFilterChange() - }) - } - func clearExpandedRows() { StatsDataHelper.clearExpandedDetails() } diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsTableViewController.swift index de42e4c968cb..0a672b7a893b 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsTableViewController.swift @@ -17,9 +17,7 @@ class SiteStatsInsightsDetailsTableViewController: SiteStatsBaseTableViewControl private var receipt: Receipt? private let insightsStore = StoreContainer.shared.statsInsights - private var insightsChangeReceipt: Receipt? private let periodStore = StoreContainer.shared.statsPeriod - private var periodChangeReceipt: Receipt? private lazy var tableHandler: ImmuTableViewHandler = { return ImmuTableViewHandler(takeOver: self) @@ -246,28 +244,6 @@ private extension SiteStatsInsightsDetailsTableViewController { func clearExpandedRows() { StatsDataHelper.clearExpandedDetails() } - - func updateStatSectionForFilterChange() { - guard let oldStatSection = statSection else { - return - } - - switch oldStatSection { - case .insightsFollowersWordPress: - statSection = .insightsFollowersEmail - case .insightsFollowersEmail: - statSection = .insightsFollowersWordPress - case .insightsCommentsAuthors: - statSection = .insightsCommentsPosts - case .insightsCommentsPosts: - statSection = .insightsCommentsAuthors - default: - // Return here as `initViewModel` is only needed for filtered cards. - return - } - - initViewModel() - } } // MARK: - SiteStatsDetailsDelegate Methods diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsViewModel.swift index 38b6816955f8..1f32b412b1fd 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/SiteStatsInsightsDetailsViewModel.swift @@ -704,10 +704,6 @@ private extension SiteStatsInsightsDetailsViewModel { // MARK: - Tabbed Cards - func tabbedRowsFrom(_ commentsRowData: [StatsTotalRowData]) -> [DetailDataRow] { - return dataRowsFor(commentsRowData) - } - func tabDataForFollowerType(_ followerType: StatSection) -> TabData { let tabTitle = followerType.tabTitle var followers: [StatsFollower] = [] diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/StatsFollowersChartViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/StatsFollowersChartViewModel.swift index eb7d42acf9c8..636054d415d4 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/StatsFollowersChartViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/StatsFollowersChartViewModel.swift @@ -44,8 +44,6 @@ struct StatsFollowersChartViewModel { static let emailGroupTitle = NSLocalizedString("Email", comment: "Title of Stats section that shows email followers.") static let socialGroupTitle = NSLocalizedString("Social", comment: "Title of Stats section that shows social followers.") - static let followersMaxGroupCount = 3 - static let wpComColor: UIColor = .muriel(name: .blue, .shade50) static let emailColor: UIColor = .muriel(name: .blue, .shade5) static let socialColor: UIColor = .muriel(name: .orange, .shade30) diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift index 6332a498df9f..300f32c49c73 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift @@ -104,7 +104,6 @@ class StatsTotalRow: UIView, NibLoadable, Accessible { @IBOutlet weak var disclosureButton: UIButton! private(set) var rowData: StatsTotalRowData? - private var dataBarMaxTrailing: Float = 0.0 private typealias Style = WPStyleGuide.Stats private weak var delegate: StatsTotalRowDelegate? private weak var referrerDelegate: StatsTotalRowReferrerDelegate? diff --git a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift index d1ae45dde5f8..1291b51bdc1f 100644 --- a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift @@ -158,13 +158,6 @@ extension SiteStatsDashboardViewController: StatsForegroundObservable { // MARK: - Private Extension private extension SiteStatsDashboardViewController { - - struct Constants { - static let progressViewInitialProgress = Float(0.03) - static let progressViewHideDelay = 1 - static let progressViewHideDuration = 0.15 - } - var currentSelectedPeriod: StatsPeriodType { get { let selectedIndex = filterTabBar?.selectedIndex ?? StatsPeriodType.insights.rawValue diff --git a/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift b/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift index 132d155080e8..290527fb88bf 100644 --- a/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift @@ -495,7 +495,6 @@ private extension SupportTableViewController { static let closeButton = NSLocalizedString("support.button.close.title", value: "Done", comment: "Dismiss the current view") static let wpHelpCenter = NSLocalizedString("support.row.helpCenter.title", value: "WordPress Help Center", comment: "Option in Support view to launch the Help Center.") static let contactUs = NSLocalizedString("support.row.contactUs.title", value: "Contact support", comment: "Option in Support view to contact the support team.") - static let wpForums = NSLocalizedString("support.row.forums.title", value: "WordPress Forums", comment: "Option in Support view to view the Forums.") static let prioritySupportSectionHeader = NSLocalizedString("support.sectionHeader.prioritySupport.title", value: "Priority Support", comment: "Section header in Support view for priority support.") static let wpForumsSectionHeader = NSLocalizedString("support.sectionHeader.forum.title", value: "Community Forums", comment: "Section header in Support view for the Forums.") static let advancedSectionHeader = NSLocalizedString("support.sectionHeader.advanced.title", value: "Advanced", comment: "Section header in Support view for advanced information.") diff --git a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift index 9d5f67b6b083..66d668f0e523 100644 --- a/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift +++ b/WordPress/Classes/ViewRelated/System/Coordinators/MySitesCoordinator.swift @@ -69,9 +69,14 @@ class MySitesCoordinator: NSObject { navigationController.restorationIdentifier = MySitesCoordinator.navigationControllerRestorationID navigationController.navigationBar.isTranslucent = false - let tabBarImage = AppStyleGuide.mySiteTabIcon - navigationController.tabBarItem.image = tabBarImage - navigationController.tabBarItem.selectedImage = tabBarImage + if FeatureFlag.newTabIcons.enabled { + navigationController.tabBarItem.image = UIImage(named: "tab-bar-home-unselected")?.withRenderingMode(.alwaysOriginal) + navigationController.tabBarItem.selectedImage = UIImage(named: "tab-bar-home-selected") + } else { + let tabBarImage = AppStyleGuide.mySiteTabIcon + navigationController.tabBarItem.image = tabBarImage + navigationController.tabBarItem.selectedImage = tabBarImage + } navigationController.tabBarItem.accessibilityLabel = NSLocalizedString("My Site", comment: "The accessibility value of the my site tab.") navigationController.tabBarItem.accessibilityIdentifier = "mySitesTabButton" navigationController.tabBarItem.title = NSLocalizedString("My Site", comment: "The accessibility value of the my site tab.") @@ -101,16 +106,6 @@ class MySitesCoordinator: NSObject { navigationController.viewControllers = [rootContentViewController] } - // MARK: - Sites List - - private func showSitesList() { - showRootViewController() - - let navigationController = UINavigationController(rootViewController: blogListViewController) - navigationController.modalPresentationStyle = .formSheet - mySiteViewController.present(navigationController, animated: true) - } - // MARK: - Blog Details @objc @@ -125,11 +120,11 @@ class MySitesCoordinator: NSObject { } } - func showBlogDetails(for blog: Blog, then subsection: BlogDetailsSubsection) { + func showBlogDetails(for blog: Blog, then subsection: BlogDetailsSubsection, userInfo: [AnyHashable: Any] = [:]) { showBlogDetails(for: blog) if let mySiteViewController = navigationController.topViewController as? MySiteViewController { - mySiteViewController.showBlogDetailsSubsection(subsection) + mySiteViewController.showBlogDetailsSubsection(subsection, userInfo: userInfo) } } @@ -219,6 +214,10 @@ class MySitesCoordinator: NSObject { showBlogDetails(for: blog, then: .media) } + func showMediaPicker(for blog: Blog) { + showBlogDetails(for: blog, then: .media, userInfo: [BlogDetailsViewController.userInfoShowPickerKey(): true]) + } + func showComments(for blog: Blog) { showBlogDetails(for: blog, then: .comments) } diff --git a/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift b/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift index 5b4bed599cb6..ca8d879a1c03 100644 --- a/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift +++ b/WordPress/Classes/ViewRelated/System/Floating Create Button/CreateButtonCoordinator.swift @@ -153,10 +153,6 @@ import WordPressFlux } } - private func isShowingStoryOption() -> Bool { - actions.contains(where: { $0 is StoryAction }) - } - private func actionSheetController(with traitCollection: UITraitCollection) -> UIViewController { let actionSheetVC = CreateButtonActionSheet(headerView: createPromptHeaderView(), actions: actions) setupPresentation(on: actionSheetVC, for: traitCollection) diff --git a/WordPress/Classes/ViewRelated/System/Floating Create Button/FloatingActionButton.swift b/WordPress/Classes/ViewRelated/System/Floating Create Button/FloatingActionButton.swift index e8d1181720bb..1dd08a8949ea 100644 --- a/WordPress/Classes/ViewRelated/System/Floating Create Button/FloatingActionButton.swift +++ b/WordPress/Classes/ViewRelated/System/Floating Create Button/FloatingActionButton.swift @@ -1,8 +1,6 @@ /// A rounded button with a shadow intended for use as a "Floating Action Button" class FloatingActionButton: UIButton { - private var shadowLayer: CALayer? - private enum Constants { static let shadowColor: UIColor = UIColor.gray(.shade20) static let shadowRadius: CGFloat = 3 diff --git a/WordPress/Classes/ViewRelated/System/Floating Create Button/SheetActions.swift b/WordPress/Classes/ViewRelated/System/Floating Create Button/SheetActions.swift index ba7c16a2cd88..e45852afde2a 100644 --- a/WordPress/Classes/ViewRelated/System/Floating Create Button/SheetActions.swift +++ b/WordPress/Classes/ViewRelated/System/Floating Create Button/SheetActions.swift @@ -39,16 +39,6 @@ struct PageAction: ActionSheetItem { } struct StoryAction: ActionSheetItem { - - private enum Constants { - enum Badge { - static let font = UIFont.preferredFont(forTextStyle: .caption1) - static let insets = UIEdgeInsets(top: 2, left: 8, bottom: 2, right: 8) - static let cornerRadius: CGFloat = 2 - static let backgroundColor = UIColor.muriel(color: MurielColor(name: .red, shade: .shade50)) - } - } - let handler: () -> Void let source: String diff --git a/WordPress/Classes/ViewRelated/System/WPTabBarController+MeTab.swift b/WordPress/Classes/ViewRelated/System/WPTabBarController+MeTab.swift index e803229578bd..82fdacb20be1 100644 --- a/WordPress/Classes/ViewRelated/System/WPTabBarController+MeTab.swift +++ b/WordPress/Classes/ViewRelated/System/WPTabBarController+MeTab.swift @@ -14,9 +14,13 @@ extension WPTabBarController { NotificationCenter.default.addObserver(self, selector: #selector(accountDidChange), name: .WPAccountEmailAndDefaultBlogUpdated, object: nil) } - @objc func configureMeTabImage(placeholderImage: UIImage) { - meNavigationController?.tabBarItem.image = placeholderImage - meNavigationController?.tabBarItem.selectedImage = placeholderImage + @objc func configureMeTabImage(placeholderImage: UIImage?) { + configureMeTabImage(unselectedPlaceholderImage: placeholderImage, selectedPlaceholderImage: placeholderImage) + } + + @objc func configureMeTabImage(unselectedPlaceholderImage: UIImage?, selectedPlaceholderImage: UIImage?) { + meNavigationController?.tabBarItem.image = unselectedPlaceholderImage + meNavigationController?.tabBarItem.selectedImage = selectedPlaceholderImage guard let account = defaultAccount(), let email = account.email else { @@ -45,14 +49,20 @@ extension WPTabBarController { } @objc private func accountDidChange() { - configureMeTabImage(placeholderImage: UIImage(named: "icon-tab-me") ?? UIImage()) + guard FeatureFlag.newTabIcons.enabled else { + configureMeTabImage(placeholderImage: UIImage(named: "icon-tab-me")) + return + } + + configureMeTabImage(unselectedPlaceholderImage: UIImage(named: "tab-bar-me-unselected"), + selectedPlaceholderImage: UIImage(named: "tab-bar-me-selected")) } } extension UITabBarItem { func configureGravatarImage(_ image: UIImage) { - let gravatarIcon = image.gravatarIcon(size: 28.0) + let gravatarIcon = image.gravatarIcon(size: 26.0) self.image = gravatarIcon?.blackAndWhite?.withAlpha(0.36) self.selectedImage = gravatarIcon } diff --git a/WordPress/Classes/ViewRelated/System/WPTabBarController+Swift.swift b/WordPress/Classes/ViewRelated/System/WPTabBarController+Swift.swift index 0ab38e7acbff..a6becc8df5a2 100644 --- a/WordPress/Classes/ViewRelated/System/WPTabBarController+Swift.swift +++ b/WordPress/Classes/ViewRelated/System/WPTabBarController+Swift.swift @@ -106,4 +106,18 @@ extension WPTabBarController { return selectedViewController.supportedInterfaceOrientations } + + @objc func animateSelectedItem(_ item: UITabBarItem, for tabBar: UITabBar) { + + guard let index = tabBar.items?.firstIndex(of: item), tabBar.subviews.count > index + 1, + let imageView = tabBar.subviews[index + 1].subviews.last as? UIImageView else { + return + } + + let bounceAnimation = CAKeyframeAnimation(keyPath: "transform.scale") + bounceAnimation.values = [0.8, 1.02, 1.0] + bounceAnimation.duration = TimeInterval(0.2) + bounceAnimation.calculationMode = CAAnimationCalculationMode.cubic + imageView.layer.add(bounceAnimation, forKey: nil) + } } diff --git a/WordPress/Classes/ViewRelated/System/WPTabBarController.m b/WordPress/Classes/ViewRelated/System/WPTabBarController.m index 390af0d56201..cf722e609c93 100644 --- a/WordPress/Classes/ViewRelated/System/WPTabBarController.m +++ b/WordPress/Classes/ViewRelated/System/WPTabBarController.m @@ -181,9 +181,14 @@ - (UINavigationController *)readerNavigationController _readerNavigationController.navigationBar.translucent = NO; _readerNavigationController.view.backgroundColor = [UIColor murielBasicBackground]; - UIImage *readerTabBarImage = [UIImage imageNamed:@"icon-tab-reader"]; - _readerNavigationController.tabBarItem.image = readerTabBarImage; - _readerNavigationController.tabBarItem.selectedImage = readerTabBarImage; + if ([Feature enabled:FeatureFlagNewTabIcons]) { + _readerNavigationController.tabBarItem.image = [[UIImage imageNamed:@"tab-bar-reader-unselected"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; + _readerNavigationController.tabBarItem.selectedImage = [UIImage imageNamed:@"tab-bar-reader-selected"]; + } else { + UIImage *readerTabBarImage = [UIImage imageNamed:@"icon-tab-reader"]; + _readerNavigationController.tabBarItem.image = readerTabBarImage; + _readerNavigationController.tabBarItem.selectedImage = readerTabBarImage; + } _readerNavigationController.restorationIdentifier = WPReaderNavigationRestorationID; _readerNavigationController.tabBarItem.accessibilityIdentifier = @"readerTabButton"; _readerNavigationController.tabBarItem.title = NSLocalizedString(@"Reader", @"The accessibility value of the Reader tab."); @@ -207,11 +212,19 @@ - (UINavigationController *)notificationsNavigationController } _notificationsNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController]; _notificationsNavigationController.navigationBar.translucent = NO; - self.notificationsTabBarImage = [UIImage imageNamed:@"icon-tab-notifications"]; - NSString *unreadImageName = [AppConfiguration isJetpack] ? @"icon-tab-notifications-unread-jetpack" : @"icon-tab-notifications-unread"; - self.notificationsTabBarImageUnread = [[UIImage imageNamed:unreadImageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; - _notificationsNavigationController.tabBarItem.image = self.notificationsTabBarImage; - _notificationsNavigationController.tabBarItem.selectedImage = self.notificationsTabBarImage; + if ([Feature enabled:FeatureFlagNewTabIcons]) { + self.notificationsTabBarImage = [[UIImage imageNamed:@"tab-bar-notifications-unselected"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; + NSString *unreadImageName = [AppConfiguration isJetpack] ? @"tab-bar-notifications-unread-jp" : @"tab-bar-notifications-unread-wp"; + self.notificationsTabBarImageUnread = [[UIImage imageNamed:unreadImageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; + _notificationsNavigationController.tabBarItem.image = self.notificationsTabBarImage; + _notificationsNavigationController.tabBarItem.selectedImage = [UIImage imageNamed:@"tab-bar-notifications-selected"]; + } else { + self.notificationsTabBarImage = [UIImage imageNamed:@"icon-tab-notifications"]; + NSString *unreadImageName = [AppConfiguration isJetpack] ? @"icon-tab-notifications-unread-jetpack" : @"icon-tab-notifications-unread"; + self.notificationsTabBarImageUnread = [[UIImage imageNamed:unreadImageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; + _notificationsNavigationController.tabBarItem.image = self.notificationsTabBarImage; + _notificationsNavigationController.tabBarItem.selectedImage = self.notificationsTabBarImage; + } _notificationsNavigationController.restorationIdentifier = WPNotificationsNavigationRestorationID; _notificationsNavigationController.tabBarItem.accessibilityIdentifier = @"notificationsTabButton"; _notificationsNavigationController.tabBarItem.accessibilityLabel = NSLocalizedString(@"Notifications", @"Notifications tab bar item accessibility label"); @@ -224,7 +237,12 @@ - (UINavigationController *)meNavigationController { if (!_meNavigationController) { _meNavigationController = [[UINavigationController alloc] initWithRootViewController:self.meViewController]; - [self configureMeTabImageWithPlaceholderImage:[UIImage imageNamed:@"icon-tab-me"]]; + if ([Feature enabled:FeatureFlagNewTabIcons]) { + [self configureMeTabImageWithUnselectedPlaceholderImage:[[UIImage imageNamed:@"tab-bar-me-unselected"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] + selectedPlaceholderImage:[UIImage imageNamed:@"tab-bar-me-selected"]]; + } else { + [self configureMeTabImageWithPlaceholderImage:[UIImage imageNamed:@"icon-tab-me"]]; + } _meNavigationController.restorationIdentifier = WPMeNavigationRestorationID; _meNavigationController.tabBarItem.accessibilityLabel = NSLocalizedString(@"Me", @"The accessibility value of the me tab."); _meNavigationController.tabBarItem.accessibilityIdentifier = @"meTabButton"; @@ -472,6 +490,16 @@ - (void)showNotificationsTabForNoteWithID:(NSString *)notificationID [self.notificationsViewController showDetailsForNotificationWithID:notificationID]; } +#pragma mark - UITabBarDelegate + +- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item +{ + UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; + [generator impactOccurred]; + + [self animateSelectedItem:item for:tabBar]; +} + #pragma mark - Zendesk Notifications - (void)updateIconIndicators:(NSNotification *)notification diff --git a/WordPress/Classes/ViewRelated/Themes/WPStyleGuide+Themes.swift b/WordPress/Classes/ViewRelated/Themes/WPStyleGuide+Themes.swift index 18617f6635ca..660c676c763d 100644 --- a/WordPress/Classes/ViewRelated/Themes/WPStyleGuide+Themes.swift +++ b/WordPress/Classes/ViewRelated/Themes/WPStyleGuide+Themes.swift @@ -98,19 +98,18 @@ extension WPStyleGuide { let columnWidth = trunc(columnsWidth / numberOfColumns) return columnWidth } + public static func cellHeightForCellWidth(_ width: CGFloat) -> CGFloat { let imageHeight = (width - cellImageInset) * cellImageRatio return imageHeight + cellInfoBarHeight } - public static func cellHeightForFrameWidth(_ width: CGFloat) -> CGFloat { - let cellWidth = cellWidthForFrameWidth(width) - return cellHeightForCellWidth(cellWidth) - } + public static func cellSizeForFrameWidth(_ width: CGFloat) -> CGSize { let cellWidth = cellWidthForFrameWidth(width) let cellHeight = cellHeightForCellWidth(cellWidth) return CGSize(width: cellWidth.zeroIfNaN(), height: cellHeight.zeroIfNaN()) } + public static func imageWidthForFrameWidth(_ width: CGFloat) -> CGFloat { let cellWidth = cellWidthForFrameWidth(width) return cellWidth - cellImageInset @@ -121,7 +120,6 @@ extension WPStyleGuide { public static let themeMargins = UIEdgeInsets(top: rowMargin, left: columnMargin, bottom: rowMargin, right: columnMargin) public static let infoMargins = UIEdgeInsets() - public static let minimumSearchHeight: CGFloat = 44 public static let searchAnimationDuration: TimeInterval = 0.2 } diff --git a/WordPress/Classes/ViewRelated/Tools/Confirmable.h b/WordPress/Classes/ViewRelated/Tools/Confirmable.h deleted file mode 100644 index cfb6fd153807..000000000000 --- a/WordPress/Classes/ViewRelated/Tools/Confirmable.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@protocol Confirmable - -- (void)cancel; -- (void)confirm; - -@end - diff --git a/WordPress/Classes/ViewRelated/Tools/PromptViewController.swift b/WordPress/Classes/ViewRelated/Tools/PromptViewController.swift deleted file mode 100644 index 50229548d20d..000000000000 --- a/WordPress/Classes/ViewRelated/Tools/PromptViewController.swift +++ /dev/null @@ -1,125 +0,0 @@ -import Foundation -import UIKit - - -/// Wraps a given UIViewController, that conforms to the Confirmable Protocol, into: -/// - A PromptViewController instance, which deals with the NavigationItem buttons -/// - (And verything) inside a UINavigationController instance. -/// -public func PromptViewController(_ viewController: T) -> UINavigationController where T: Confirmable { - let viewController = PromptContainerViewController(viewController: viewController) - return UINavigationController(rootViewController: viewController) -} - - -/// ViewController container, that presents a Done / Cancel button, and forwards their events to -/// the childrenViewController (which *must* implement the Confirmable protocol). -/// -private class PromptContainerViewController: UIViewController { - // MARK: - Initializers / Deinitializers - - deinit { - stopListeningToProperties(childViewController) - } - - @objc init(viewController: UIViewController) { - // You stay with us, sir - childViewController = viewController - - super.init(nibName: nil, bundle: nil) - precondition(viewController.conforms(to: Confirmable.self)) - - setupNavigationButtons() - attachChildViewController(viewController) - setupChildViewConstraints(viewController.view) - startListeningToProperties(viewController) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("Not meant for Nib usage!") - } - - - - // MARK: - Private Helpers - - fileprivate func attachChildViewController(_ viewController: UIViewController) { - // Attach! - viewController.willMove(toParent: self) - view.addSubview(viewController.view) - addChild(viewController) - viewController.didMove(toParent: self) - } - - fileprivate func setupChildViewConstraints(_ childrenView: UIView) { - // We grow, you grow. We shrink, you shrink. Capicci? - childrenView.translatesAutoresizingMaskIntoConstraints = false - childrenView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true - childrenView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true - childrenView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - childrenView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true - } - - - // MARK: - KVO Rocks! - - fileprivate func startListeningToProperties(_ viewController: UIViewController) { - for key in Properties.all where viewController.responds(to: NSSelectorFromString(key.rawValue)) { - viewController.addObserver(self, forKeyPath: key.rawValue, options: [.initial, .new], context: nil) - } - } - - fileprivate func stopListeningToProperties(_ viewController: UIViewController) { - for key in Properties.all where viewController.responds(to: NSSelectorFromString(key.rawValue)) { - viewController.removeObserver(self, forKeyPath: key.rawValue) - } - } - - override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - guard let unwrappedKeyPath = keyPath, let property = Properties(rawValue: unwrappedKeyPath) else { - return - } - - switch property { - case .title: - title = change?[NSKeyValueChangeKey.newKey] as? String ?? String() - case .doneButtonEnabled: - navigationItem.rightBarButtonItem?.isEnabled = change?[NSKeyValueChangeKey.newKey] as? Bool ?? true - } - } - - - // MARK: - Navigation Buttons - - fileprivate func setupNavigationButtons() { - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, - target: self, - action: #selector(cancelButtonWasPressed)) - - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, - target: self, - action: #selector(doneButtonWasPressed)) - } - - @objc - @IBAction func cancelButtonWasPressed(_ sender: AnyObject) { - (childViewController as? Confirmable)?.cancel() - } - - @objc - @IBAction func doneButtonWasPressed(_ sender: AnyObject) { - (childViewController as? Confirmable)?.confirm() - } - - - - // MARK: - Private Constants - fileprivate enum Properties: String { - case title = "title" - case doneButtonEnabled = "doneButtonEnabled" - static let all = [title, doneButtonEnabled] - } - - // MARK: - Private Properties - fileprivate let childViewController: UIViewController -} diff --git a/WordPress/Classes/ViewRelated/Tools/SettingsTextViewController.h b/WordPress/Classes/ViewRelated/Tools/SettingsTextViewController.h index 5f8f0e8dcc27..df27e0e18b50 100644 --- a/WordPress/Classes/ViewRelated/Tools/SettingsTextViewController.h +++ b/WordPress/Classes/ViewRelated/Tools/SettingsTextViewController.h @@ -1,5 +1,4 @@ #import -#import "Confirmable.h" // Typedef's typedef NS_ENUM(NSInteger, SettingsTextModes) { @@ -18,7 +17,7 @@ typedef void (^SettingsTextOnDismiss)(void); /// Reusable component that renders a UITextField + Hint onscreen. Useful for Text / Password / Email data entry. /// -@interface SettingsTextViewController : UITableViewController +@interface SettingsTextViewController : UITableViewController /// Block to be executed on dismiss, if the value was effectively updated. /// diff --git a/WordPress/Classes/ViewRelated/Views/LinearGradientView.swift b/WordPress/Classes/ViewRelated/Views/LinearGradientView.swift index 975c5da3e3fb..fa9eee077c17 100644 --- a/WordPress/Classes/ViewRelated/Views/LinearGradientView.swift +++ b/WordPress/Classes/ViewRelated/Views/LinearGradientView.swift @@ -19,10 +19,6 @@ class LinearGradientView: UIView { contentMode = .redraw } - private func configure() { - contentMode = .redraw - } - override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext(), diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift index c30a8d902a4a..b6cf9c9c16e6 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift @@ -443,19 +443,9 @@ private extension WPRichContentView { struct Constants { static let textContainerInset = UIEdgeInsets.init(top: 0.0, left: 0.0, bottom: 16.0, right: 0.0) static let defaultAttachmentHeight = CGFloat(50.0) - static let photonQuality = 65 } } -// MARK: - Rich Media Struct - -/// A simple struct used to keep references to a rich text image and its associated attachment. -/// -struct RichMedia { - let image: WPRichTextImage - let attachment: WPTextAttachment -} - // This is very much based on Aztec.LayoutManager — most of this code is pretty much copy-pasted // from there and trimmed to only contain the relevant parts. @objc fileprivate class BlockquoteBackgroundLayoutManager: NSLayoutManager { diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextFormatter.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextFormatter.swift index accbb0321511..61210129d08e 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextFormatter.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextFormatter.swift @@ -30,15 +30,6 @@ class WPRichTextFormatter { ] }() - /// An array of tag names that the formatter can process. - /// - lazy var tagNames: [String] = { - return self.tags.map { tag -> String in - return tag.tagName - } - }() - - /// Converts the specified HTML formatted string to an NSAttributedString. /// /// - Parameters: @@ -272,21 +263,6 @@ class WPRichTextFormatter { } return (processedString, attachments) } - - - /// Returns the html processor that handles the specified tag. - /// - /// - Parameters: - /// - tagName: The name of an HTML tag. - /// - /// - Returns: An HtmlTagProcessor optional. - /// - func processorForTagName(_ tagName: String) -> HtmlTagProcessor? { - return tags.filter({ (item) -> Bool in - item.tagName == tagName - }).first - } - } diff --git a/WordPress/Classes/ViewRelated/WhatsNew/Views/FindOutMoreCell.swift b/WordPress/Classes/ViewRelated/WhatsNew/Views/FindOutMoreCell.swift index 2e8ba5699da4..56ae59925add 100644 --- a/WordPress/Classes/ViewRelated/WhatsNew/Views/FindOutMoreCell.swift +++ b/WordPress/Classes/ViewRelated/WhatsNew/Views/FindOutMoreCell.swift @@ -41,7 +41,6 @@ class FindOutMoreCell: UITableViewCell, Reusable { private extension FindOutMoreCell { enum Appearance { static let buttonTitle = NSLocalizedString("Find out more", comment: "Title for the find out more button in the What's New page.") - static let buttonFont = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .callout), size: 16) static let topMargin: CGFloat = -8 } diff --git a/WordPress/Classes/ViewRelated/WhatsNew/Views/GridCell.swift b/WordPress/Classes/ViewRelated/WhatsNew/Views/GridCell.swift index 96481f9ee480..1eaa7c3b9a87 100644 --- a/WordPress/Classes/ViewRelated/WhatsNew/Views/GridCell.swift +++ b/WordPress/Classes/ViewRelated/WhatsNew/Views/GridCell.swift @@ -139,9 +139,6 @@ private extension GridCell { // heading static let headingFont = UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline), size: 17) - // sub-heading - static let subHeadingFont = UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline), size: 15) - // main stack view static let mainStackViewInsets = UIEdgeInsets(top: 0, left: 0, bottom: 24, right: 0) diff --git a/WordPress/Jetpack/AppImages.xcassets/wp-jp-circular-lockup.imageset/Contents.json b/WordPress/Jetpack/AppImages.xcassets/wp-jp-circular-lockup.imageset/Contents.json new file mode 100644 index 000000000000..228e98858de7 --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/wp-jp-circular-lockup.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "wp-jp-circular-lockup.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Jetpack/AppImages.xcassets/wp-jp-circular-lockup.imageset/wp-jp-circular-lockup.pdf b/WordPress/Jetpack/AppImages.xcassets/wp-jp-circular-lockup.imageset/wp-jp-circular-lockup.pdf new file mode 100644 index 000000000000..52552a1b7745 Binary files /dev/null and b/WordPress/Jetpack/AppImages.xcassets/wp-jp-circular-lockup.imageset/wp-jp-circular-lockup.pdf differ diff --git a/WordPress/Jetpack/AppStyleGuide.swift b/WordPress/Jetpack/AppStyleGuide.swift index cfb471116e9e..3fd7f60ef1b2 100644 --- a/WordPress/Jetpack/AppStyleGuide.swift +++ b/WordPress/Jetpack/AppStyleGuide.swift @@ -30,7 +30,6 @@ extension AppStyleGuide { // MARK: - Images extension AppStyleGuide { static let mySiteTabIcon = UIImage(named: "jetpack-icon-tab-mysites") - static let aboutAppIcon = UIImage(named: "jetpack-install-logo") static let quickStartExistingSite = UIImage(named: "wp-illustration-quickstart-existing-site-jetpack") } diff --git a/WordPress/Jetpack/Classes/JetpackAuthenticationManager.swift b/WordPress/Jetpack/Classes/JetpackAuthenticationManager.swift index 2598d4c0edb8..39f2bd0ea05f 100644 --- a/WordPress/Jetpack/Classes/JetpackAuthenticationManager.swift +++ b/WordPress/Jetpack/Classes/JetpackAuthenticationManager.swift @@ -5,7 +5,7 @@ struct JetpackAuthenticationManager: AuthenticationHandler { let statusBarStyle: UIStatusBarStyle = .default let prologueViewController: UIViewController? = JetpackPrologueViewController() let buttonViewTopShadowImage: UIImage? = UIImage() - let prologueButtonsBackgroundColor: UIColor? = JetpackPrologueStyleGuide.backgroundColor + let prologueButtonsBackgroundColor: UIColor? = JetpackPrologueStyleGuide.gradientColor let prologueButtonsBlurEffect: UIBlurEffect? = JetpackPrologueStyleGuide.prologueButtonsBlurEffect let prologuePrimaryButtonStyle: NUXButtonStyle? = JetpackPrologueStyleGuide.continueButtonStyle let prologueSecondaryButtonStyle: NUXButtonStyle? = JetpackPrologueStyleGuide.siteAddressButtonStyle diff --git a/WordPress/Jetpack/Classes/NUX/JetpackPrologueStyleGuide.swift b/WordPress/Jetpack/Classes/NUX/JetpackPrologueStyleGuide.swift index e71de459117b..3b6d10b5daa8 100644 --- a/WordPress/Jetpack/Classes/NUX/JetpackPrologueStyleGuide.swift +++ b/WordPress/Jetpack/Classes/NUX/JetpackPrologueStyleGuide.swift @@ -2,19 +2,20 @@ import UIKit import WordPressAuthenticator -/// The colors in here intentionally do not support light or dark modes since they're the same on both. -/// struct JetpackPrologueStyleGuide { // Background color static let backgroundColor = UIColor.clear // Gradient overlay color - static let gradientColor = UIColor(light: .muriel(color: .jetpackGreen, .shade0), dark: .muriel(color: .jetpackGreen, .shade100)) + static let gradientColor = UIColor( + light: .white, + dark: UIColor(hexString: "050A21") + ) // Continue with WordPress button colors - static let continueFillColor = UIColor(light: .muriel(color: .jetpackGreen, .shade50), dark: .white) - static let continueHighlightedFillColor = UIColor(light: .muriel(color: .jetpackGreen, .shade90), dark: whiteWithAlpha07) - static let continueTextColor = UIColor(light: .white, dark: .muriel(color: .jetpackGreen, .shade80)) + static let continueFillColor = JetpackPromptsConfiguration.Constants.evenColor ?? .systemBlue // This is just to satisfy the compiler + static let continueHighlightedFillColor = continueFillColor.withAlphaComponent(0.9) + static let continueTextColor = UIColor.white static let continueHighlightedTextColor = whiteWithAlpha07 @@ -22,8 +23,8 @@ struct JetpackPrologueStyleGuide { static let siteFillColor = UIColor.clear static let siteBorderColor = UIColor.clear static let siteTextColor = UIColor(light: .muriel(color: .jetpackGreen, .shade90), dark: .white) - static let siteHighlightedFillColor = whiteWithAlpha07 - static let siteHighlightedBorderColor = whiteWithAlpha07 + static let siteHighlightedFillColor = UIColor.clear + static let siteHighlightedBorderColor = UIColor.clear static let siteHighlightedTextColor = UIColor(light: .muriel(color: .jetpackGreen, .shade50), dark: whiteWithAlpha07) // Color used in both old and versions @@ -34,9 +35,6 @@ struct JetpackPrologueStyleGuide { // Blur effect for the prologue buttons static let prologueButtonsBlurEffect: UIBlurEffect? = UIBlurEffect(style: .regular) - - - struct Title { static let font: UIFont = WPStyleGuide.fontForTextStyle(.title3, fontWeight: .semibold) static let textColor: UIColor = .white diff --git a/WordPress/Jetpack/Classes/NUX/JetpackPrologueViewController.swift b/WordPress/Jetpack/Classes/NUX/JetpackPrologueViewController.swift index 9a6d2ef857b6..0c239f463fd7 100644 --- a/WordPress/Jetpack/Classes/NUX/JetpackPrologueViewController.swift +++ b/WordPress/Jetpack/Classes/NUX/JetpackPrologueViewController.swift @@ -5,14 +5,6 @@ class JetpackPrologueViewController: UIViewController { @IBOutlet weak var stackView: UIStackView! @IBOutlet weak var titleLabel: UILabel! - var starFieldView: StarFieldView = { - let config = StarFieldViewConfig(particleImage: JetpackPrologueStyleGuide.Stars.particleImage, - starColors: JetpackPrologueStyleGuide.Stars.colors) - let view = StarFieldView(with: config) - view.layer.masksToBounds = true - return view - }() - private let motion: CMMotionManager? = { let motion = CMMotionManager() motion.deviceMotionUpdateInterval = Constants.deviceMotionUpdateInterval @@ -27,7 +19,7 @@ class JetpackPrologueViewController: UIViewController { }() private lazy var logoImageView: UIImageView = { - let imageView = UIImageView(image: UIImage(named: "jetpack-logo")) + let imageView = UIImageView(image: UIImage(named: "wp-jp-circular-lockup")) imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }() @@ -36,11 +28,6 @@ class JetpackPrologueViewController: UIViewController { makeGradientLayer() }() - private lazy var logoWidthConstraint: NSLayoutConstraint = { - let width = Constants.logoWidth(for: traitCollection.horizontalSizeClass) - return logoImageView.widthAnchor.constraint(equalToConstant: width) - }() - private func makeGradientLayer() -> CAGradientLayer { let gradientLayer = CAGradientLayer() @@ -94,26 +81,13 @@ class JetpackPrologueViewController: UIViewController { view.layer.insertSublayer(gradientLayer, above: jetpackAnimatedView.layer) // constraints NSLayoutConstraint.activate([ - logoWidthConstraint, - logoImageView.heightAnchor.constraint(equalTo: logoImageView.widthAnchor), + logoImageView.widthAnchor.constraint(equalToConstant: 132.35), + logoImageView.heightAnchor.constraint(equalToConstant: 80), logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), - logoImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 68) + logoImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 135) ]) } - private func loadOldPrologueView() { - view.addSubview(starFieldView) - view.layer.addSublayer(gradientLayer) - titleLabel.text = NSLocalizedString("Site security and performance\nfrom your pocket", comment: "Prologue title label, the \n force splits it into 2 lines.") - titleLabel.textColor = JetpackPrologueStyleGuide.Title.textColor - titleLabel.font = JetpackPrologueStyleGuide.Title.font - // Move the layers to appear below everything else - starFieldView.layer.zPosition = Constants.starLayerPosition - gradientLayer.zPosition = Constants.gradientLayerPosition - addParallax(to: stackView) - updateLabel(for: traitCollection) - } - func updateLabel(for traitCollection: UITraitCollection) { let contentSize = traitCollection.preferredContentSizeCategory @@ -125,8 +99,6 @@ class JetpackPrologueViewController: UIViewController { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - logoWidthConstraint.constant = Constants.logoWidth(for: traitCollection.horizontalSizeClass) - guard previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle else { updateLabel(for: traitCollection) return @@ -161,8 +133,6 @@ class JetpackPrologueViewController: UIViewController { private struct Constants { static let parallaxAmount: CGFloat = 30 - static let starLayerPosition: CGFloat = -100 - static let gradientLayerPosition: CGFloat = -99 /// New landing screen @@ -172,10 +142,6 @@ class JetpackPrologueViewController: UIViewController { static let defaultAngleDegrees: Double = 30.0 /// Uniform multiplier used to tweak the rate generated from an angle static let angleRateMultiplier: CGFloat = 1.3 - /// Returns the Jetpack logo width depending on the given size class - static func logoWidth(for sizeClass: UIUserInterfaceSizeClass) -> CGFloat { - return sizeClass == .compact ? 68 : 78 - } } } diff --git a/WordPress/Jetpack/Classes/NUX/New Landing Screen/Model/JetpackPrompt.swift b/WordPress/Jetpack/Classes/NUX/New Landing Screen/Model/JetpackPrompt.swift deleted file mode 100644 index f41675bf6502..000000000000 --- a/WordPress/Jetpack/Classes/NUX/New Landing Screen/Model/JetpackPrompt.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI - -struct JetpackPrompt: Identifiable { - var id = UUID() - let index: Int - let text: String - let color: Color - var frameHeight: CGFloat - var initialOffset: CGFloat -} diff --git a/WordPress/Jetpack/Classes/NUX/New Landing Screen/ViewModel/JetpackPromptsConfiguration.swift b/WordPress/Jetpack/Classes/NUX/New Landing Screen/ViewModel/JetpackPromptsConfiguration.swift index c8d22eae7ba4..bfba55814066 100644 --- a/WordPress/Jetpack/Classes/NUX/New Landing Screen/ViewModel/JetpackPromptsConfiguration.swift +++ b/WordPress/Jetpack/Classes/NUX/New Landing Screen/ViewModel/JetpackPromptsConfiguration.swift @@ -1,13 +1,13 @@ import SwiftUI +import DesignSystem /// Text and constants for animated prompts in the Jetpack prologue screen struct JetpackPromptsConfiguration { enum Constants { // alternate colors in rows - static let evenColor = UIColor.muriel(color: .jetpackGreen, .shade50) - static let oddColor = UIColor(light: .muriel(color: .jetpackGreen, .shade50).withAlphaComponent(0.5), - dark: .muriel(color: .jetpackGreen, .shade20)) + static let evenColor = UIColor.DS.Background.brand(isJetpack: false) + static let oddColor = UIColor.muriel(color: .jetpackGreen, .shade40) static let basePrompts = [ NSLocalizedString("jetpack.prologue.prompt.updatePlugin", diff --git a/WordPress/Jetpack/Classes/NUX/StarFieldView.swift b/WordPress/Jetpack/Classes/NUX/StarFieldView.swift deleted file mode 100644 index dd9f246c1e10..000000000000 --- a/WordPress/Jetpack/Classes/NUX/StarFieldView.swift +++ /dev/null @@ -1,194 +0,0 @@ -import UIKit - -struct StarFieldViewConfig { - var particleImage: UIImage? - var starColors: [UIColor] -} - -class StarFieldView: UIView { - struct Particle { - let image: UIImage - let tintColor: UIColor - } - - let config: StarFieldViewConfig - - /// The base emitter layer that fills the background - var emitterLayer: StarFieldEmitterLayer? - - /// A special layer that moves when the user touches - var interactiveEmitterLayer: InteractiveStarFieldEmitterLayer? - - // MARK: - Config - init(with config: StarFieldViewConfig) { - self.config = config - super.init(frame: .zero) - - configure() - } - - required init?(coder: NSCoder) { - self.config = StarFieldViewConfig(particleImage: nil, starColors: []) - super.init(coder: coder) - - configure() - } - - private func configure() { - backgroundColor = .clear - - makeEmitterLayer() - } - - private func makeEmitterLayer() { - guard emitterLayer == nil, let image = config.particleImage else { - return - } - - let particles = config.starColors.map { Particle(image: image, tintColor: $0) } - - // Background layer - self.emitterLayer = { - let layer = StarFieldEmitterLayer(with: particles) - self.layer.addSublayer(layer) - return layer - }() - } - - override func layoutSubviews() { - super.layoutSubviews() - - emitterLayer?.frame = bounds - interactiveEmitterLayer?.frame = bounds - } - - // MARK: - Touches - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - if interactiveEmitterLayer == nil { - interactiveEmitterLayer = { - let particles = config.starColors.map { Particle(image: config.particleImage!, tintColor: $0) } - let layer = InteractiveStarFieldEmitterLayer(with: particles) - self.layer.addSublayer(layer) - - return layer - }() - } - - touchesMoved(touches, with: event) - } - - override func touchesMoved(_ touches: Set, with event: UIEvent?) { - guard let firstTouch = touches.first else { - return - } - - let location = firstTouch.location(in: self) - let radius = firstTouch.majorRadius - - interactiveEmitterLayer?.touchesMoved(to: location, with: radius) - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - interactiveEmitterLayer?.touchesEnded() - - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent?) { - touchesEnded(touches, with: event) - } -} - -class StarFieldEmitterLayer: CAEmitterLayer { - init(with particles: [StarFieldView.Particle]) { - super.init() - - needsDisplayOnBoundsChange = true - emitterCells = particles.map { ParticleCell(with: $0) } - } - - override func layoutSublayers() { - super.layoutSublayers() - - emitterMode = .outline - emitterShape = .sphere - emitterSize = bounds.insetBy(dx: -50, dy: -50).size - emitterPosition = CGPoint(x: bounds.midX, y: bounds.maxY) - speed = 0.5 - } - - override init(layer: Any) { - super.init(layer: layer) - } - - private class ParticleCell: CAEmitterCell { - init(with particle: StarFieldView.Particle) { - super.init() - - let randomAlpha = CGFloat.random(in: 0.3...0.5) - color = particle.tintColor.withAlphaComponent(randomAlpha).cgColor - contents = particle.image.cgImage - - birthRate = 5 - lifetime = Float.infinity - lifetimeRange = 0 - velocity = 5 - velocityRange = velocity * 0.5 - yAcceleration = -0.01 - - scale = WPDeviceIdentification.isiPad() ? 0.07 : 0.04 - scaleRange = 0.05 - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class InteractiveStarFieldEmitterLayer: StarFieldEmitterLayer { - override init(with particles: [StarFieldView.Particle]) { - super.init(with: particles) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override init(layer: Any) { - super.init(layer: layer) - } - - override func layoutSublayers() { - super.layoutSublayers() - - emitterShape = .circle - emitterSize = .zero - beginTime = CACurrentMediaTime() - } - - /// Moves the emitter point to the touch location - /// - Parameters: - /// - location: The location to move the emitter point to - /// - radius: The size of the emitter - public func touchesMoved(to location: CGPoint, with radius: CGFloat = 10) { - lifetime = 1 - birthRate = 1 - speed = 10 - - emitterPosition = location - emitterSize = CGSize(width: radius, height: radius) - } - - public func touchesBegan() { - beginTime = CACurrentMediaTime() - } - - public func touchesEnded() { - lifetime = 0 - emitterSize = .zero - } -} diff --git a/WordPress/Jetpack/Classes/Utility/DataMigrator.swift b/WordPress/Jetpack/Classes/Utility/DataMigrator.swift index 4b76fa8f6b9e..6c65c92c6dec 100644 --- a/WordPress/Jetpack/Classes/Utility/DataMigrator.swift +++ b/WordPress/Jetpack/Classes/Utility/DataMigrator.swift @@ -116,7 +116,6 @@ private extension DataMigrator { /// This way we can delete just the value for its key and leave the rest of shared defaults untouched. struct DefaultsWrapper { static let dictKey = "defaults_staging_dictionary" - let defaultsDict: [String: Any] } /// Convenience wrapper to check whether the export data is ready to be imported. diff --git a/WordPress/Jetpack/Classes/ViewRelated/Login Error/JetpackLoginErrorViewController.swift b/WordPress/Jetpack/Classes/ViewRelated/Login Error/JetpackLoginErrorViewController.swift deleted file mode 100644 index a8f23105b6c0..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/Login Error/JetpackLoginErrorViewController.swift +++ /dev/null @@ -1,104 +0,0 @@ -import UIKit - -class JetpackLoginErrorViewController: UIViewController { - private let viewModel: JetpackErrorViewModel - - // IBOutlets - @IBOutlet weak var imageView: UIImageView! - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var descriptionLabel: UILabel! - @IBOutlet weak var primaryButton: UIButton! - @IBOutlet weak var secondaryButton: UIButton! - - init(viewModel: JetpackErrorViewModel) { - self.viewModel = viewModel - - super.init(nibName: nil, bundle: nil) - } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - UIDevice.isPad() ? .all : .portrait - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - configureImageView() - configureTitleLabel() - configureDescriptionLabel() - configurePrimaryButton() - configureSecondaryButton() - } - -} - -// MARK: - View Configuration -extension JetpackLoginErrorViewController { - private func configureImageView() { - guard let image = viewModel.image else { - imageView.isHidden = true - imageView.image = nil - - return - } - - imageView.image = image - } - - private func configureTitleLabel() { - guard let title = viewModel.title else { - titleLabel.isHidden = true - return - } - - titleLabel.isHidden = false - titleLabel.text = title - } - - private func configureDescriptionLabel() { - descriptionLabel.font = Self.descriptionFont - descriptionLabel.textColor = Self.descriptionTextColor - descriptionLabel.adjustsFontForContentSizeCategory = true - - guard let attributedString = viewModel.description.attributedStringValue else { - descriptionLabel.text = viewModel.description.stringValue - return - } - - descriptionLabel.attributedText = attributedString - } - - private func configurePrimaryButton() { - guard let title = viewModel.primaryButtonTitle else { - primaryButton.isHidden = true - return - } - - primaryButton.setTitle(title, for: .normal) - primaryButton.on(.touchUpInside) { [weak self] _ in - self?.viewModel.didTapPrimaryButton(in: self) - } - } - - private func configureSecondaryButton() { - guard let title = viewModel.secondaryButtonTitle else { - secondaryButton.isHidden = true - return - } - - secondaryButton.setTitle(title, for: .normal) - secondaryButton.on(.touchUpInside) { [weak self] _ in - self?.viewModel.didTapSecondaryButton(in: self) - } - } -} - -// MARK: - Styles -extension JetpackLoginErrorViewController { - static let descriptionFont: UIFont = WPStyleGuide.fontForTextStyle(.body) - static let descriptionTextColor: UIColor = .text -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/Login Error/JetpackLoginErrorViewController.xib b/WordPress/Jetpack/Classes/ViewRelated/Login Error/JetpackLoginErrorViewController.xib deleted file mode 100644 index d94e68dc65e4..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/Login Error/JetpackLoginErrorViewController.xib +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackErrorViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackErrorViewModel.swift deleted file mode 100644 index 0fc48005c4dc..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackErrorViewModel.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation - -protocol JetpackErrorViewModel { - /// The primary icon image - /// If this is nil the image will be hidden - var image: UIImage? { get } - - /// The title for the error description - /// If nil, title will be hidden - var title: String? { get } - - /// The error description text - var description: FormattedStringProvider { get } - - /// The title for the first button - /// If this is nil the button will be hidden - var primaryButtonTitle: String? { get } - - /// The title for the second button - /// If this is nil the button will be hidden - var secondaryButtonTitle: String? { get } - - /// Executes action associated to a tap in the view controller primary button - /// - Parameter viewController: usually the view controller sending the tap - func didTapPrimaryButton(in viewController: UIViewController?) - - /// Executes action associated to a tap in the view controller secondary button - /// - Parameter viewController: usually the view controller sending the tap - func didTapSecondaryButton(in viewController: UIViewController?) -} - -/// Helper struct to define a type as both a regular string and an attributed one -struct FormattedStringProvider { - let stringValue: String - let attributedStringValue: NSAttributedString? - - init(string: String) { - stringValue = string - attributedStringValue = nil - } - - init(attributedString: NSAttributedString) { - attributedStringValue = attributedString - stringValue = attributedString.string - } -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNoSitesErrorViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNoSitesErrorViewModel.swift deleted file mode 100644 index da3485dd61be..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNoSitesErrorViewModel.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation - -struct JetpackNoSitesErrorViewModel: JetpackErrorViewModel { - let image: UIImage? = UIImage(named: "jetpack-empty-state-illustration") - var title: String? = Constants.title - var description: FormattedStringProvider = FormattedStringProvider(string: Constants.description) - var primaryButtonTitle: String? = Constants.primaryButtonTitle - var secondaryButtonTitle: String? = Constants.secondaryButtonTitle - - func didTapPrimaryButton(in viewController: UIViewController?) { - guard let url = URL(string: Constants.helpURLString) else { - return - } - - let controller = WebViewControllerFactory.controller(url: url, source: "jetpack_no_sites") - let navController = UINavigationController(rootViewController: controller) - - viewController?.present(navController, animated: true) - } - - func didTapSecondaryButton(in viewController: UIViewController?) { - AccountHelper.logOutDefaultWordPressComAccount() - } - - private struct Constants { - static let title = NSLocalizedString("No Jetpack sites found", - comment: "Title when users have no Jetpack sites.") - - static let description = NSLocalizedString("If you already have a site, you’ll need to install the free Jetpack plugin and connect it to your WordPress.com account.", - comment: "Message explaining that they will need to install Jetpack on one of their sites.") - - - static let primaryButtonTitle = NSLocalizedString("See Instructions", - comment: "Action button linking to instructions for installing Jetpack." - + "Presented when logging in with a site address that does not have a valid Jetpack installation") - - static let secondaryButtonTitle = NSLocalizedString("Try With Another Account", - comment: "Action button that will restart the login flow." - + "Presented when logging in with a site address that does not have a valid Jetpack installation") - - static let helpURLString = "https://jetpack.com/support/getting-started-with-jetpack/" - } -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNotFoundErrorViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNotFoundErrorViewModel.swift deleted file mode 100644 index 5aac59c8d6c4..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNotFoundErrorViewModel.swift +++ /dev/null @@ -1,73 +0,0 @@ -import UIKit - -struct JetpackNotFoundErrorViewModel: JetpackErrorViewModel { - let title: String? = nil - let image: UIImage? = UIImage(named: "jetpack-empty-state-illustration") - var description: FormattedStringProvider { - let siteName = siteURL - let description = String(format: Constants.description, siteName) - let font: UIFont = JetpackLoginErrorViewController.descriptionFont.semibold() - - let attributedString = NSMutableAttributedString(string: description) - attributedString.applyStylesToMatchesWithPattern(siteName, styles: [.font: font]) - - return FormattedStringProvider(attributedString: attributedString) - } - - var primaryButtonTitle: String? = Constants.primaryButtonTitle - var secondaryButtonTitle: String? = Constants.secondaryButtonTitle - - private let siteURL: String - - init(with siteURL: String?) { - self.siteURL = siteURL?.trimURLScheme() ?? Constants.yourSite - } - - func didTapPrimaryButton(in viewController: UIViewController?) { - guard let url = URL(string: Constants.helpURLString) else { - return - } - - let controller = WebViewControllerFactory.controller(url: url, source: "jetpack_not_found") - let navController = UINavigationController(rootViewController: controller) - - viewController?.present(navController, animated: true) - } - - func didTapSecondaryButton(in viewController: UIViewController?) { - viewController?.navigationController?.popToRootViewController(animated: true) - } - - private struct Constants { - static let yourSite = NSLocalizedString("your site", - comment: "Placeholder for site url, if the url is unknown." - + "Presented when logging in with a site address that does not have a valid Jetpack installation." - + "The error would read: to use this app for your site you'll need...") - - static let description = NSLocalizedString("To use this app for %@ you'll need to have the Jetpack plugin installed and activated.", - comment: "Message explaining that Jetpack needs to be installed for a particular site. " - + "Reads like 'To use this app for example.com you'll need to have...") - - static let primaryButtonTitle = NSLocalizedString("See Instructions", - comment: "Action button linking to instructions for installing Jetpack." - + "Presented when logging in with a site address that does not have a valid Jetpack installation") - - static let secondaryButtonTitle = NSLocalizedString("Try With Another Account", - comment: "Action button that will restart the login flow." - + "Presented when logging in with a site address that does not have a valid Jetpack installation") - - static let helpURLString = "https://jetpack.com/support/getting-started-with-jetpack/" - } -} - -private extension String { - // Removes http:// or https:// - func trimURLScheme() -> String? { - guard let urlComponents = URLComponents(string: self), - let host = urlComponents.host else { - return self - } - - return host + urlComponents.path - } -} diff --git a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNotWPErrorViewModel.swift b/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNotWPErrorViewModel.swift deleted file mode 100644 index 9f1128a788c6..000000000000 --- a/WordPress/Jetpack/Classes/ViewRelated/Login Error/View Models/JetpackNotWPErrorViewModel.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -struct JetpackNotWPErrorViewModel: JetpackErrorViewModel { - let title: String? = nil - let image: UIImage? = UIImage(named: "jetpack-empty-state-illustration") - var description: FormattedStringProvider = FormattedStringProvider(string: Constants.description) - var primaryButtonTitle: String? = Constants.primaryButtonTitle - var secondaryButtonTitle: String? = nil - - func didTapPrimaryButton(in viewController: UIViewController?) { - viewController?.navigationController?.popToRootViewController(animated: true) - } - - func didTapSecondaryButton(in viewController: UIViewController?) { } - - private struct Constants { - static let description = NSLocalizedString("We were not able to detect a WordPress site at the address you entered." - + " Please make sure WordPress is installed and that you are running" - + " the latest available version.", - comment: "Message explaining that WordPress was not detected.") - - - static let primaryButtonTitle = NSLocalizedString("Try With Another Account", - comment: "Action button that will restart the login flow." - + "Presented when logging in with a site address that does not have a valid Jetpack installation") - } -} diff --git a/WordPress/Jetpack/JetpackDebug.entitlements b/WordPress/Jetpack/JetpackDebug.entitlements index 301d465cda3d..7300f805fe0e 100644 --- a/WordPress/Jetpack/JetpackDebug.entitlements +++ b/WordPress/Jetpack/JetpackDebug.entitlements @@ -12,6 +12,7 @@ applinks:jetpack.com webcredentials:wordpress.com + webcredentials:*.wordpress.com applinks:apps.wordpress.com applinks:wordpress.com applinks:*.wordpress.com diff --git a/WordPress/Jetpack/JetpackRelease-Alpha.entitlements b/WordPress/Jetpack/JetpackRelease-Alpha.entitlements index 13b1fd842aaa..efdce8fda2ba 100644 --- a/WordPress/Jetpack/JetpackRelease-Alpha.entitlements +++ b/WordPress/Jetpack/JetpackRelease-Alpha.entitlements @@ -8,6 +8,7 @@ applinks:jetpack.com webcredentials:wordpress.com + webcredentials:*.wordpress.com applinks:apps.wordpress.com applinks:wordpress.com applinks:*.wordpress.com diff --git a/WordPress/Jetpack/JetpackRelease-Internal.entitlements b/WordPress/Jetpack/JetpackRelease-Internal.entitlements index ff1223201637..2df878e17e81 100644 --- a/WordPress/Jetpack/JetpackRelease-Internal.entitlements +++ b/WordPress/Jetpack/JetpackRelease-Internal.entitlements @@ -8,6 +8,7 @@ applinks:jetpack.com webcredentials:wordpress.com + webcredentials:*.wordpress.com applinks:apps.wordpress.com applinks:wordpress.com applinks:*.wordpress.com diff --git a/WordPress/Jetpack/JetpackRelease.entitlements b/WordPress/Jetpack/JetpackRelease.entitlements index 301d465cda3d..7300f805fe0e 100644 --- a/WordPress/Jetpack/JetpackRelease.entitlements +++ b/WordPress/Jetpack/JetpackRelease.entitlements @@ -12,6 +12,7 @@ applinks:jetpack.com webcredentials:wordpress.com + webcredentials:*.wordpress.com applinks:apps.wordpress.com applinks:wordpress.com applinks:*.wordpress.com diff --git a/WordPress/Jetpack/Launch Screen.storyboard b/WordPress/Jetpack/Launch Screen.storyboard index 0b15765afd02..07c45d7eb96d 100644 --- a/WordPress/Jetpack/Launch Screen.storyboard +++ b/WordPress/Jetpack/Launch Screen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -17,10 +17,10 @@ - - + + - + @@ -39,7 +39,7 @@ - + diff --git a/WordPress/Jetpack/Resources/release_notes.txt b/WordPress/Jetpack/Resources/release_notes.txt index 93a123fb2d20..c2c9251550cf 100644 --- a/WordPress/Jetpack/Resources/release_notes.txt +++ b/WordPress/Jetpack/Resources/release_notes.txt @@ -1,5 +1,18 @@ -We updated the classic editor with new media pickers for Photos and Site Media. Don’t worry, you can still upload images, videos, and more to your site. +* [**] [internal] A minor refactor in authentication flow, including but not limited to social sign-in and two factor authentication. [#22086] +* [***] Plans: Upgrade to a WPCOM plan from domains dashboard in Jetpack app. [#22261] +* [**] [internal] Refactor domain selection flows to use the same domain selection UI. [22254] +* [**] Re-enable the support for using Security Keys as a second factor during login [#22258] +* [*] Fix crash in editor that sometimes happens after modifying tags or categories [#22265] +* [**] Updated login screen's colors to highlight WordPress - Jetpack brand relationship +* [*] Add defensive code to make sure the retain cycles in the editor don't lead to crashes [#22252] +* [*] Updated Site Domains screen to make domains management more convenient [#22294, #22311] +* [**] [internal] Adds support for dynamic dashboard cards driven by the backend [#22326] +* [**] [internal] Add support for the Phase One Fast Media Uploads banner [#22330] +* [*] [internal] Remove personalizeHomeTab feature flag [#22280] +* [*] Fix a rare crash in post search related to tags [#22275] +* [*] Fix a rare crash when deleting posts [#22277] +* [*] Fix a rare crash in Site Media prefetching cancellation [#22278] +* [*] Fix an issue with BlogDashboardPersonalizationService being used on the background thread [#22335] +* [***] Block Editor: Avoid keyboard dismiss when interacting with text blocks [https://github.com/WordPress/gutenberg/pull/57070] +* [**] Block Editor: Auto-scroll upon block insertion [https://github.com/WordPress/gutenberg/pull/57273] -Speaking of media types—you can now add media filters to the Site Media screen. If you’re using an iPhone, you’ll notice the new aspect ratio mode, too. Both options are available when you tap the title menu. - -Finally, we fixed the broken compliance pop-up that appears while you’re checking stats during the onboarding process. We also fixed a rare crash that happened while logging out. Sweet. diff --git a/WordPress/JetpackStatsWidgets/LockScreenWidgets/LockScreenStatsWidget.swift b/WordPress/JetpackStatsWidgets/LockScreenWidgets/LockScreenStatsWidget.swift index 5d7e582259c6..be5246d1dfe2 100644 --- a/WordPress/JetpackStatsWidgets/LockScreenWidgets/LockScreenStatsWidget.swift +++ b/WordPress/JetpackStatsWidgets/LockScreenWidgets/LockScreenStatsWidget.swift @@ -33,5 +33,6 @@ struct LockScreenStatsWidget: Widget { .configurationDisplayName(config.displayName) .description(config.description) .supportedFamilies(config.supportFamilies) + .iOS17ContentMarginsDisabled() /// Temporarily disable additional iOS17 margins for widgets } } diff --git a/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenMultiStatView.swift b/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenMultiStatView.swift index cdb3f6869efb..2ddcc195588b 100644 --- a/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenMultiStatView.swift +++ b/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenMultiStatView.swift @@ -31,9 +31,11 @@ struct LockScreenMultiStatView: View { Spacer() } } + .removableWidgetBackground() .accessibilityElement(children: .combine) } else { Text("Not implemented for widget family \(family.debugDescription)") + .removableWidgetBackground() } } diff --git a/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenSingleStatView.swift b/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenSingleStatView.swift index d78d8f74339c..c9b82721c27d 100644 --- a/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenSingleStatView.swift +++ b/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenSingleStatView.swift @@ -17,9 +17,11 @@ struct LockScreenSingleStatView: View { Spacer() } } + .removableWidgetBackground() .accessibilityElement(children: .combine) } else { Text("Not implemented for widget family \(family.debugDescription)") + .removableWidgetBackground() } } } diff --git a/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenUnconfiguredView.swift b/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenUnconfiguredView.swift index dd651b5cd16a..89f9310e5522 100644 --- a/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenUnconfiguredView.swift +++ b/WordPress/JetpackStatsWidgets/LockScreenWidgets/Views/LockScreenUnconfiguredView.swift @@ -14,8 +14,10 @@ struct LockScreenUnconfiguredView: View { .minimumScaleFactor(0.8) .multilineTextAlignment(.center) } + .removableWidgetBackground() } else { Text("Not implemented for widget family \(family.debugDescription)") + .removableWidgetBackground() } } } diff --git a/WordPress/JetpackStatsWidgets/Model/ListViewData.swift b/WordPress/JetpackStatsWidgets/Model/ListViewData.swift index 8f484f19b16b..91b03cea78b4 100644 --- a/WordPress/JetpackStatsWidgets/Model/ListViewData.swift +++ b/WordPress/JetpackStatsWidgets/Model/ListViewData.swift @@ -1,4 +1,5 @@ import SwiftUI +import JetpackStatsWidgetsCore struct ListViewData { diff --git a/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift b/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift index 0908fda35ced..37c39152aa10 100644 --- a/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift +++ b/WordPress/JetpackStatsWidgets/Remote service/StatsWidgetsService.swift @@ -156,7 +156,7 @@ class StatsWidgetsService { url: widgetData.url, timeZone: widgetData.timeZone, date: Date(), - stats: ThisWeekWidgetStats(days: ThisWeekWidgetStats.daysFrom(summaryData: summaryData))) + stats: ThisWeekWidgetStats(days: ThisWeekWidgetStats.daysFrom(summaryData: summaryData.map { ThisWeekWidgetStats.Input(periodStartDate: $0.periodStartDate, viewsCount: $0.viewsCount) }))) completion(.success(newWidgetData)) DispatchQueue.global().async { diff --git a/WordPress/JetpackStatsWidgets/Views/Cards/LockScreenFlexibleCard.swift b/WordPress/JetpackStatsWidgets/Views/Cards/LockScreenFlexibleCard.swift new file mode 100644 index 000000000000..47a48ec05dcc --- /dev/null +++ b/WordPress/JetpackStatsWidgets/Views/Cards/LockScreenFlexibleCard.swift @@ -0,0 +1,44 @@ +import SwiftUI + +/// A card with a title and a string value that is shown on LockScreen without background +struct LockScreenFlexibleCard: View { + let title: LocalizedString + let description: LocalizedString + let lineLimit: Int + + init(title: LocalizedString, description: LocalizedString, lineLimit: Int = 1) { + self.title = title + self.description = description + self.lineLimit = lineLimit + } + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline, spacing: 4) { + Image("icon-jetpack") + .resizable() + .frame(width: 14, height: 14) + Text(description) + .font(Appearance.textFont) + .fontWeight(Appearance.textFontWeight) + .foregroundColor(Appearance.textColor) + .lineLimit(lineLimit) + } + Text(title) + .font(Appearance.titleFont) + .foregroundColor(Appearance.titleColor) + } + } +} + +// MARK: - Appearance +extension LockScreenFlexibleCard { + private enum Appearance { + static let textFont = Font.headline + static let textFontWeight = Font.Weight.semibold + static let textColor = Color(.label) + + static let titleFont = Font.subheadline + static let titleColor = Color(.secondaryLabel) + } +} diff --git a/WordPress/JetpackStatsWidgets/Views/Cards/LockScreenVerticalCard.swift b/WordPress/JetpackStatsWidgets/Views/Cards/LockScreenVerticalCard.swift new file mode 100644 index 000000000000..752c31c24d84 --- /dev/null +++ b/WordPress/JetpackStatsWidgets/Views/Cards/LockScreenVerticalCard.swift @@ -0,0 +1,39 @@ +import SwiftUI + +/// A card with a title and a value stacked vertically shown on LockScreen without background +struct LockScreenVerticalCard: View { + let title: LocalizedString + let value: Int + + private var accessibilityLabel: Text { + // The colon makes VoiceOver pause between elements + Text(title) + Text(": ") + Text(value.abbreviatedString()) + } + + var body: some View { + VStack(alignment: .leading) { + StatsValueView(value: value, + font: Appearance.extraLargeTextFont, + fontWeight: .heavy, + foregroundColor: Appearance.textColor, + lineLimit: nil) + .accessibility(label: accessibilityLabel) + Text(title) + .font(Appearance.titleFont) + .fontWeight(Appearance.titleFontWeight) + .foregroundColor(Appearance.titleColor) + .accessibility(hidden: true) + } + } +} + +// MARK: - Appearance +extension LockScreenVerticalCard { + private enum Appearance { + static let titleFont = Font.headline + static let titleFontWeight = Font.Weight.semibold + static let titleColor = Color(UIColor.label) + static let extraLargeTextFont = Font.system(size: 48, weight: .bold) + static let textColor = Color(UIColor.label) + } +} diff --git a/WordPress/JetpackStatsWidgets/Views/Cards/StatsValueView.swift b/WordPress/JetpackStatsWidgets/Views/Cards/StatsValueView.swift index 3436559bd445..324ced823334 100644 --- a/WordPress/JetpackStatsWidgets/Views/Cards/StatsValueView.swift +++ b/WordPress/JetpackStatsWidgets/Views/Cards/StatsValueView.swift @@ -30,5 +30,6 @@ struct StatsValueView: View { .fontWeight(fontWeight) .foregroundColor(foregroundColor) .lineLimit(lineLimit) + .minimumScaleFactor(0.5) } } diff --git a/WordPress/JetpackStatsWidgets/Views/Cards/VerticalCard.swift b/WordPress/JetpackStatsWidgets/Views/Cards/VerticalCard.swift index afef33c3743a..b9fb75193e2f 100644 --- a/WordPress/JetpackStatsWidgets/Views/Cards/VerticalCard.swift +++ b/WordPress/JetpackStatsWidgets/Views/Cards/VerticalCard.swift @@ -6,6 +6,8 @@ struct VerticalCard: View { let value: Int let largeText: Bool + @Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground: Bool + private var titleFont: Font { largeText ? Appearance.largeTextFont : Appearance.textFont } @@ -17,18 +19,31 @@ struct VerticalCard: View { var body: some View { VStack(alignment: .leading) { - Text(title) - .font(Appearance.titleFont) - .fontWeight(Appearance.titleFontWeight) - .foregroundColor(Appearance.titleColor) - .accessibility(hidden: true) - StatsValueView(value: value, - font: titleFont, - fontWeight: .regular, - foregroundColor: Appearance.textColor, - lineLimit: nil) - .accessibility(label: accessibilityLabel) - + if showsWidgetContainerBackground { + Text(title) + .font(Appearance.titleFont) + .fontWeight(Appearance.titleFontWeight) + .foregroundColor(Appearance.titleColor) + .accessibility(hidden: true) + StatsValueView(value: value, + font: titleFont, + fontWeight: .regular, + foregroundColor: Appearance.textColor, + lineLimit: nil) + .accessibility(label: accessibilityLabel) + } else { + StatsValueView(value: value, + font: Appearance.extraLargeTextFont, + fontWeight: .heavy, + foregroundColor: Appearance.textColor, + lineLimit: nil) + .accessibility(label: accessibilityLabel) + Text(title) + .font(Appearance.titleFont) + .fontWeight(Appearance.titleFontWeight) + .foregroundColor(Appearance.titleColor) + .accessibility(hidden: true) + } } } } @@ -43,6 +58,7 @@ extension VerticalCard { static let titleColor = Color(UIColor.primary) static let largeTextFont = Font.largeTitle + static let extraLargeTextFont = Font.system(size: 48, weight: .bold) static let textFont = Font.title static let textColor = Color(.label) } diff --git a/WordPress/JetpackStatsWidgets/Views/Helpers/iOS17WidgetAPIs.swift b/WordPress/JetpackStatsWidgets/Views/Helpers/iOS17WidgetAPIs.swift new file mode 100644 index 000000000000..01e4ea037c92 --- /dev/null +++ b/WordPress/JetpackStatsWidgets/Views/Helpers/iOS17WidgetAPIs.swift @@ -0,0 +1,24 @@ +import SwiftUI +import WidgetKit + +extension View { + func removableWidgetBackground(_ backgroundView: some View = EmptyView()) -> some View { + if #available(iOSApplicationExtension 17.0, *) { + return containerBackground(for: .widget) { + backgroundView + } + } else { + return background(backgroundView) + } + } +} + +extension WidgetConfiguration { + func iOS17ContentMarginsDisabled() -> some WidgetConfiguration { + if #available(iOSApplicationExtension 17.0, *) { + return contentMarginsDisabled() + } else { + return self + } + } +} diff --git a/WordPress/JetpackStatsWidgets/Views/ListStatsView.swift b/WordPress/JetpackStatsWidgets/Views/ListStatsView.swift index d96ecbde2e7c..6dcc94a67657 100644 --- a/WordPress/JetpackStatsWidgets/Views/ListStatsView.swift +++ b/WordPress/JetpackStatsWidgets/Views/ListStatsView.swift @@ -1,5 +1,6 @@ import SwiftUI import WidgetKit +import JetpackStatsWidgetsCore struct ListStatsView: View { @Environment(\.widgetFamily) var family: WidgetFamily @@ -33,6 +34,7 @@ struct ListStatsView: View { } } } + .removableWidgetBackground() } } diff --git a/WordPress/JetpackStatsWidgets/Views/MultiStatsView.swift b/WordPress/JetpackStatsWidgets/Views/MultiStatsView.swift index 86b86be89bdc..07dcb61ad517 100644 --- a/WordPress/JetpackStatsWidgets/Views/MultiStatsView.swift +++ b/WordPress/JetpackStatsWidgets/Views/MultiStatsView.swift @@ -23,6 +23,7 @@ Spacer() } } + .removableWidgetBackground() } /// Constructs a two-card column for the medium size Today widget diff --git a/WordPress/JetpackStatsWidgets/Views/SingleStatView.swift b/WordPress/JetpackStatsWidgets/Views/SingleStatView.swift index 580d755823b2..f606543d2121 100644 --- a/WordPress/JetpackStatsWidgets/Views/SingleStatView.swift +++ b/WordPress/JetpackStatsWidgets/Views/SingleStatView.swift @@ -1,19 +1,59 @@ import SwiftUI +import WidgetKit struct SingleStatView: View { + let title: String + let description: String + let valueTitle: String + let value: Int - let viewData: GroupedViewData + @Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground: Bool + + init(viewData: GroupedViewData) { + self.title = viewData.widgetTitle + self.description = viewData.siteName + self.valueTitle = viewData.upperLeftTitle + self.value = viewData.upperLeftValue + } + + init(title: String, description: String, valueTitle: String, value: Int) { + self.title = title + self.description = description + self.valueTitle = valueTitle + self.value = value + } var body: some View { HStack { - VStack(alignment: .leading) { - FlexibleCard(axis: .vertical, title: viewData.widgetTitle, value: .description(viewData.siteName), lineLimit: 2) - - Spacer() - VerticalCard(title: viewData.upperLeftTitle, value: viewData.upperLeftValue, largeText: true) + if showsWidgetContainerBackground { + VStack(alignment: .leading) { + FlexibleCard(axis: .vertical, title: title, value: .description(description), lineLimit: 2) + Spacer() + VerticalCard(title: valueTitle, value: value, largeText: true) + } + .padding() + } else { + VStack(alignment: .leading) { + Spacer() + LockScreenFlexibleCard(title: title, description: description, lineLimit: 2) + Spacer().frame(height: 4) + LockScreenVerticalCard(title: valueTitle, value: value) + Spacer() + } } Spacer() } + .removableWidgetBackground() + } +} + +@available(iOS 16.0, *) +struct SingleStatView_Previews: PreviewProvider { + static var previews: some View { + SingleStatView(title: "Today", description: "My WordPress Site", valueTitle: "Views", value: 124909) + .previewContext( + WidgetPreviewContext(family: .systemSmall) + ) } } diff --git a/WordPress/JetpackStatsWidgets/Views/StatsWidgetsView.swift b/WordPress/JetpackStatsWidgets/Views/StatsWidgetsView.swift index 56c3d720865f..4428961b9b08 100644 --- a/WordPress/JetpackStatsWidgets/Views/StatsWidgetsView.swift +++ b/WordPress/JetpackStatsWidgets/Views/StatsWidgetsView.swift @@ -27,7 +27,6 @@ struct StatsWidgetsView: View { case .systemSmall: SingleStatView(viewData: viewData) .widgetURL(viewData.statsURL?.appendingSource(.homeScreenWidget)) - .padding() case .systemMedium: MultiStatsView(viewData: viewData) diff --git a/WordPress/JetpackStatsWidgets/Views/UnconfiguredView.swift b/WordPress/JetpackStatsWidgets/Views/UnconfiguredView.swift index f515edd8008b..9145b392d1be 100644 --- a/WordPress/JetpackStatsWidgets/Views/UnconfiguredView.swift +++ b/WordPress/JetpackStatsWidgets/Views/UnconfiguredView.swift @@ -3,13 +3,15 @@ import SwiftUI struct UnconfiguredView: View { var timelineEntry: StatsWidgetEntry + @Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground: Bool var body: some View { Text(unconfiguredMessage) .font(.footnote) - .foregroundColor(Color(.secondaryLabel)) + .foregroundColor(showsWidgetContainerBackground ? Color(.secondaryLabel) : Color(.label)) .multilineTextAlignment(.center) .padding() + .removableWidgetBackground() } var unconfiguredMessage: LocalizedString { diff --git a/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetAllTime.swift b/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetAllTime.swift index 6c0fdd588372..0bbe43f5e0f6 100644 --- a/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetAllTime.swift +++ b/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetAllTime.swift @@ -27,5 +27,6 @@ struct HomeWidgetAllTime: Widget { .configurationDisplayName(LocalizableStrings.allTimeWidgetTitle) .description(LocalizableStrings.allTimePreviewDescription) .supportedFamilies([.systemSmall, .systemMedium]) + .iOS17ContentMarginsDisabled() /// Temporarily disable additional iOS17 margins for widgets } } diff --git a/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetThisWeek.swift b/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetThisWeek.swift index 38c2cb596544..f48f4ebb6479 100644 --- a/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetThisWeek.swift +++ b/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetThisWeek.swift @@ -1,5 +1,6 @@ import WidgetKit import SwiftUI +import JetpackStatsWidgetsCore struct HomeWidgetThisWeek: Widget { @@ -47,5 +48,6 @@ struct HomeWidgetThisWeek: Widget { .configurationDisplayName(LocalizableStrings.thisWeekWidgetTitle) .description(LocalizableStrings.thisWeekPreviewDescription) .supportedFamilies([.systemMedium, .systemLarge]) + .iOS17ContentMarginsDisabled() /// Temporarily disable additional iOS17 margins for widgets } } diff --git a/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetToday.swift b/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetToday.swift index be7dfb66ad36..93225f77cc33 100644 --- a/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetToday.swift +++ b/WordPress/JetpackStatsWidgets/Widgets/HomeWidgetToday.swift @@ -28,5 +28,6 @@ struct HomeWidgetToday: Widget { .configurationDisplayName(LocalizableStrings.todayWidgetTitle) .description(LocalizableStrings.todayPreviewDescription) .supportedFamilies([.systemSmall, .systemMedium]) + .iOS17ContentMarginsDisabled() /// Temporarily disable additional iOS17 margins for widgets for StandBy } } diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-selected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-selected.imageset/Contents.json new file mode 100644 index 000000000000..389937d2e482 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-selected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tab-bar-home-selected.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-selected.imageset/tab-bar-home-selected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-selected.imageset/tab-bar-home-selected.pdf new file mode 100644 index 000000000000..cf3214b74428 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-selected.imageset/tab-bar-home-selected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/Contents.json new file mode 100644 index 000000000000..406a1bf929c8 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "tab-bar-home-unselected.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tab-bar-home-unselected-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/tab-bar-home-unselected-dark.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/tab-bar-home-unselected-dark.pdf new file mode 100644 index 000000000000..7982e2fb7e8b Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/tab-bar-home-unselected-dark.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/tab-bar-home-unselected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/tab-bar-home-unselected.pdf new file mode 100644 index 000000000000..a04417928e26 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-home-unselected.imageset/tab-bar-home-unselected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-selected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-selected.imageset/Contents.json new file mode 100644 index 000000000000..fd311e91eeb0 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-selected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tab-bar-me-selected.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-selected.imageset/tab-bar-me-selected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-selected.imageset/tab-bar-me-selected.pdf new file mode 100644 index 000000000000..a8f6dcf410a0 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-selected.imageset/tab-bar-me-selected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/Contents.json new file mode 100644 index 000000000000..506d3351507e --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "tab-bar-me-unselected.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tab-bar-me-unselected-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/tab-bar-me-unselected-dark.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/tab-bar-me-unselected-dark.pdf new file mode 100644 index 000000000000..03957cbba0e4 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/tab-bar-me-unselected-dark.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/tab-bar-me-unselected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/tab-bar-me-unselected.pdf new file mode 100644 index 000000000000..7f6272ef5bf8 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-me-unselected.imageset/tab-bar-me-unselected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-selected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-selected.imageset/Contents.json new file mode 100644 index 000000000000..a62a87b53879 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-selected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tab-bar-notifications-selected.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-selected.imageset/tab-bar-notifications-selected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-selected.imageset/tab-bar-notifications-selected.pdf new file mode 100644 index 000000000000..222f8140415c Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-selected.imageset/tab-bar-notifications-selected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/Contents.json new file mode 100644 index 000000000000..f9f8ed8b37b1 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "tab-bar-notifications-unread-jp.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tab-bar-notifications-unread-jp-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/tab-bar-notifications-unread-jp-dark.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/tab-bar-notifications-unread-jp-dark.pdf new file mode 100644 index 000000000000..21cd3e937b19 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/tab-bar-notifications-unread-jp-dark.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/tab-bar-notifications-unread-jp.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/tab-bar-notifications-unread-jp.pdf new file mode 100644 index 000000000000..2c0cdbc01368 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-jp.imageset/tab-bar-notifications-unread-jp.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/Contents.json new file mode 100644 index 000000000000..28b70218b344 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "tab-bar-notifications-unread-wp.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tab-bar-notifications-unread-wp-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/tab-bar-notifications-unread-wp-dark.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/tab-bar-notifications-unread-wp-dark.pdf new file mode 100644 index 000000000000..f830b4afc8bd Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/tab-bar-notifications-unread-wp-dark.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/tab-bar-notifications-unread-wp.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/tab-bar-notifications-unread-wp.pdf new file mode 100644 index 000000000000..3d4ff59f51af Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unread-wp.imageset/tab-bar-notifications-unread-wp.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/Contents.json new file mode 100644 index 000000000000..98462678c016 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "tab-bar-notifications-unselected.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tab-bar-notifications-unselected-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/tab-bar-notifications-unselected-dark.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/tab-bar-notifications-unselected-dark.pdf new file mode 100644 index 000000000000..64520deb2378 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/tab-bar-notifications-unselected-dark.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/tab-bar-notifications-unselected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/tab-bar-notifications-unselected.pdf new file mode 100644 index 000000000000..3663a2fa0a02 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-notifications-unselected.imageset/tab-bar-notifications-unselected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-selected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-selected.imageset/Contents.json new file mode 100644 index 000000000000..aedb797c23ed --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-selected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tab-bar-reader-selected.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-selected.imageset/tab-bar-reader-selected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-selected.imageset/tab-bar-reader-selected.pdf new file mode 100644 index 000000000000..339c6d26aa43 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-selected.imageset/tab-bar-reader-selected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/Contents.json new file mode 100644 index 000000000000..44332fb41316 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "tab-bar-reader-unselected.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tab-bar-reader-unselected-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/tab-bar-reader-unselected-dark.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/tab-bar-reader-unselected-dark.pdf new file mode 100644 index 000000000000..ad4e40803f65 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/tab-bar-reader-unselected-dark.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/tab-bar-reader-unselected.pdf b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/tab-bar-reader-unselected.pdf new file mode 100644 index 000000000000..f8f89ece67cf Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/Bottom Tabs/tab-bar-reader-unselected.imageset/tab-bar-reader-unselected.pdf differ diff --git a/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/Contents.json index 0ce038741595..f997bc669365 100644 --- a/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/Contents.json +++ b/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "JPBackground.pdf", + "filename" : "background-light.pdf", "idiom" : "universal" } ], diff --git a/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/JPBackground.pdf b/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/JPBackground.pdf deleted file mode 100644 index 1538f6f7c14c..000000000000 Binary files a/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/JPBackground.pdf and /dev/null differ diff --git a/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/background-light.pdf b/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/background-light.pdf new file mode 100644 index 000000000000..663767edb6ad Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/New Jetpack prologue/JPBackground.imageset/background-light.pdf differ diff --git a/WordPress/Resources/ar.lproj/Localizable.strings b/WordPress/Resources/ar.lproj/Localizable.strings index 21e4f46b21e3..9e84947c5d14 100644 --- a/WordPress/Resources/ar.lproj/Localizable.strings +++ b/WordPress/Resources/ar.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-05 16:54:08+0000 */ +/* Translation-Revision-Date: 2024-01-03 15:54:27+0000 */ /* Plural-Forms: nplurals=6; plural=(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n % 100 >= 3 && n % 100 <= 10) ? 3 : ((n % 100 >= 11 && n % 100 <= 99) ? 4 : 5)))); */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: ar */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "الكل"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "تتضمن جميع خطط ووردبريس.كوم السنوية اسم نطاق مخصصًا. سجِّل نطاقك المجاني الآن."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "تتضمن جميع خطط WordPress.com اسم نطاق مخصصًا. قم بتسجيل نطاقك المتميز المجاني الآن."; @@ -9448,11 +9451,17 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "نسخ عنوان URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "زيارة الموقع"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "معرفة المزيد"; /* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "بالنسبة إلى شهر يناير، ستأتي موجّهات التدوين من Bloganuary - تحدٍّ مجتمعي لدينا لإنشاء عادة التدوين الخاصة بالعام الجديد."; +"bloganuary.dashboard.card.description" = "في شهر يناير، ستأتي موجّهات التدوين من Bloganuary - تحدٍّ مجتمعي لدينا لإنشاء عادة التدوين الخاصة بالعام الجديد."; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "أصبحت Bloganuary هنا!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary قادمة!"; @@ -9805,6 +9814,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "إضافة"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "تحديد صور"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "عرض العناصر المحدَّدة (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "تفاصيل الحملة"; @@ -10258,6 +10276,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "محذوف!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "الكل"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "الصوت"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "الوثائق"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "الصور"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "الفيديوهات"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "حذف"; @@ -10336,6 +10369,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "تسجيل فيديو"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ من %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × ⁦%2$d⁩ بكسل"; @@ -10489,6 +10525,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "ليس لديك أي مواقع"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "إضافة موقع"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "إجراءات الموقع"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "النقر لإظهار مزيد من إجراءات الموقع"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "تخصيص الصفحة الرئيسية"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "تغيير أيقونة الموقع"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "تغيير عنوان الموقع"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "تغيير الموقع"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "زيارة الموقع"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "تجاهل"; @@ -11012,6 +11072,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "معاينة الوسائط"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "إضافة"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "إلغاء التحديد"; @@ -11186,6 +11249,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "تجاهُل"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "الصور مقدمة من Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "ابحث للعثور على صور مجانية لإضافتها إلى مكتبة الوسائط الخاصة بك!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "في هذه المحادثة"; @@ -11333,6 +11402,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "مساعدة"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "ابحث للعثور على صور بتنسيق GIF لإضافتها إلى مكتبة الوسائط الخاصة بك!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "سيتم حذف هذه العناصر:"; diff --git a/WordPress/Resources/de.lproj/Localizable.strings b/WordPress/Resources/de.lproj/Localizable.strings index bd30dd56f7fb..a6e78cdad650 100644 --- a/WordPress/Resources/de.lproj/Localizable.strings +++ b/WordPress/Resources/de.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-05 12:54:09+0000 */ +/* Translation-Revision-Date: 2024-01-04 00:31:04+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: de */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Alle"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Alle WordPress.com-Jahrestarife enthalten einen individuellen Domainnamen. Registriere jetzt deine kostenlose Domain."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Alle WordPress.com-Tarife beinhalten einen individuellen Domain-Namen. Registriere jetzt deine kostenlose Premium-Domain."; @@ -9454,12 +9457,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "URL kopieren"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Zur Website"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Mehr erfahren"; /* Short description for the Bloganuary event, shown right below the title. */ "bloganuary.dashboard.card.description" = "Im Januar erhältst du Blog-Schreibanregungen vom Bloganuary, unserer Community-Challenge. Diese soll dir helfen, Bloggen im neuen Jahr zur Gewohnheit zu machen."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Der Bloganuary ist da!"; + /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Der Bloganuary kommt!"; @@ -9814,6 +9823,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Hinzufügen"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Bilder auswählen"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Auswahl anzeigen (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Kampagnendetails"; @@ -10267,6 +10285,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "Gelöscht!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "Alle"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Audio"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Dokumente"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Bilder"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Videos"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "Löschen"; @@ -10345,6 +10378,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "Video aufnehmen"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ von %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d×%2$d px"; @@ -10498,6 +10534,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Du hast keine Websites"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Website hinzufügen"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Website-Aktionen"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Tippe, um weitere Website-Aktionen anzuzeigen"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Startseite personalisieren"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Website-Icon ändern"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Website-Titel ändern"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Website wechseln"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Zur Website"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Schließen"; @@ -10836,10 +10896,20 @@ Tapping on this row allows the user to edit the sharing message. */ Note: Since the display space is limited, a short or concise translation is preferred. */ "reader.detail.toolbar.comment.button" = "Kommentar"; +/* Title for the Like button in the Reader Detail toolbar. +This is shown when the user has not liked the post yet. +Note: Since the display space is limited, a short or concise translation is preferred. */ +"reader.detail.toolbar.like.button" = "Mit einem „Like“ markieren"; + /* Accessibility hint for the Like button state. The button shows that the user has not liked the post, but tapping on this button will add a Like to the post. */ "reader.detail.toolbar.like.button.a11y.hint" = "Markiert diesen Beitrag mit einem „Like“."; +/* Title for the Like button in the Reader Detail toolbar. +This is shown when the user has already liked the post. +Note: Since the display space is limited, a short or concise translation is preferred. */ +"reader.detail.toolbar.liked.button" = "Mit einem „Like“ markiert"; + /* Accessibility hint for the Liked button state. The button shows that the user has liked the post, but tapping on this button will remove their like from the post. */ "reader.detail.toolbar.liked.button.a11y.hint" = "Entfernt das „Like“ für diesen Beitrag."; @@ -10875,9 +10945,15 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility hint for the comment button on the reader post card cell */ "reader.post.button.comment.accessibility.hint" = "Öffnet die Kommentare für den Beitrag."; +/* Text for the 'Like' button on the reader post card cell. */ +"reader.post.button.like" = "Mit einem „Like“ markieren"; + /* Accessibility hint for the like button on the reader post card cell */ "reader.post.button.like.accessibility.hint" = "Markiert den Beitrag mit einem „Like“."; +/* Text for the 'Liked' button on the reader post card cell. */ +"reader.post.button.liked" = "Mit einem „Like“ markiert"; + /* Accessibility hint for the liked button on the reader post card cell */ "reader.post.button.liked.accessibility.hint" = "Entfernt das „Like“ für den Beitrag."; @@ -11055,6 +11131,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "Medienvorschau anzeigen"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Hinzufügen"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "Abwählen"; @@ -11229,6 +11308,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Verwerfen"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Fotos bereitgestellt von Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "Suche nach kostenlosen Fotos, die du deiner Mediathek hinzufügen kannst!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "In dieser Konversation"; @@ -11376,6 +11461,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Hilfe"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Suche nach kostenlosen GIFs, die du deiner Mediathek hinzufügen kannst!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "Diese Auswahl wird gelöscht:"; diff --git a/WordPress/Resources/en-AU.lproj/Localizable.strings b/WordPress/Resources/en-AU.lproj/Localizable.strings index f2fa2d2ec7da..e7ac13e96635 100644 --- a/WordPress/Resources/en-AU.lproj/Localizable.strings +++ b/WordPress/Resources/en-AU.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-09-18 15:19:37+0000 */ +/* Translation-Revision-Date: 2024-01-04 02:58:06+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: en_AU */ @@ -244,12 +244,30 @@ translators: Block name. %s: The localized block name */ /* translators: accessibility text for blocks with invalid content. %d: localized block title */ "%s block. This block has invalid content" = "%s block. This block has invalid content"; +/* translators: %s: name of the synced block */ +"%s detached" = "%s detached"; + /* translators: %s: embed block variant's label e.g: \"Twitter\". */ "%s embed block previews are coming soon" = "%s embed block previews are coming soon"; +/* translators: %s: social link name e.g: \"Instagram\". */ +"%s has URL set" = "%s has URL set"; + +/* translators: %s: social link name e.g: \"Instagram\". */ +"%s has no URL set" = "%s has no URL set"; + +/* translators: %s: embed block variant's label e.g: \"Twitter\". */ +"%s link" = "%s link"; + /* translators: %s: embed block variant's label e.g: \"Twitter\". */ "%s previews not yet available" = "%s previews not yet available"; +/* translators: %s: social link name e.g: \"Instagram\". */ +"%s social icon" = "%s social icon"; + +/* translators: displayed right after the classic block is converted to blocks. %s: The localized classic block name */ +"'%s' block converted to blocks" = "'%s' block converted to blocks"; + /* translators: Missing block alert title. %s: The localized block name */ "'%s' is not fully-supported" = "'%s' is not fully supported"; @@ -400,6 +418,9 @@ translators: Block name. %s: The localized block name */ /* Label for selecting the Accelerated Mobile Pages (AMP) Blog Traffic Setting */ "Accelerated Mobile Pages (AMP)" = "Accelerated Mobile Pages (AMP)"; +/* No comment provided by engineer. */ +"Access this Paywall block on your web browser for advanced settings." = "Access this Paywall block on your web browser for advanced settings."; + /* Title for the account section in site settings screen */ "Account" = "Account"; @@ -514,15 +535,33 @@ translators: Block name. %s: The localized block name */ /* Placeholder text. A call to action for the user to type any topic to which they would like to subscribe. */ "Add any topic" = "Add any topic"; +/* No comment provided by engineer. */ +"Add audio" = "Add audio"; + /* No comment provided by engineer. */ "Add blocks" = "Add blocks"; +/* No comment provided by engineer. */ +"Add button text" = "Add button text"; + +/* No comment provided by engineer. */ +"Add description" = "Add description"; + +/* No comment provided by engineer. */ +"Add image" = "Add image"; + +/* No comment provided by engineer. */ +"Add image or video" = "Add image or video"; + /* Accessibility hint text for adding an image to a new user account. */ "Add image, or avatar, to represent this new account." = "Adds image, or avatar, to represent this new account."; /* No comment provided by engineer. */ "Add link text" = "Add link text"; +/* translators: %s: social link name e.g: \"Instagram\". */ +"Add link to %s" = "Add link to %s"; + /* No comment provided by engineer. */ "Add menu item above" = "Add menu item above"; @@ -569,6 +608,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Add title" = "Add title"; +/* No comment provided by engineer. */ +"Add video" = "Add video"; + /* User-facing string, presented to reflect that site assembly is underway. */ "Adding site features" = "Adding site features"; @@ -607,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "All"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "All WordPress.com annual plans include a custom domain name. Register your free domain now."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "All WordPress.com plans include a custom domain name. Register your free premium domain now."; @@ -671,6 +716,15 @@ translators: Block name. %s: The localized block name */ Label for the alt for a media asset (image) */ "Alt Text" = "Alt Text"; +/* No comment provided by engineer. */ +"Alternatively, you can convert the content to blocks." = "Alternatively, you can convert the content to blocks."; + +/* No comment provided by engineer. */ +"Alternatively, you can detach and edit this block separately by tapping “Detach”." = "Alternatively, you can detach and edit this block separately by tapping “Detach”."; + +/* translators: Alternative option included in a warning related to having blocks deeply nested. */ +"Alternatively, you can flatten the content by ungrouping the block." = "Alternatively, you can flatten the content by ungrouping the block."; + /* Instruction text to explain to help users type their password instead of using magic link login option. */ "Alternatively, you may enter the password for this account." = "Alternatively, you may enter the password for this account."; @@ -1039,10 +1093,16 @@ translators: Block name. %s: The localized block name */ /* Notice that a page without content has been created */ "Blank page created" = "Blank page created"; +/* Noun. Links to a blog's Blaze screen. */ +"Blaze" = "Blaze"; + /* Accessibility label for block quote button on formatting toolbar. Discoverability title for block quote keyboard shortcut. */ "Block Quote" = "Block Quote"; +/* No comment provided by engineer. */ +"Block cannot be rendered because it is deeply nested. Tap here for more details." = "Block cannot be rendered because it is deeply nested. Tap here for more details."; + /* translators: displayed right after the block is copied. */ "Block copied" = "Block copied"; @@ -1055,6 +1115,9 @@ translators: Block name. %s: The localized block name */ /* Popup title about why this post is being opened in block editor */ "Block editor enabled" = "Block editor enabled"; +/* translators: displayed right after the block is grouped */ +"Block grouped" = "Block grouped"; + /* Jetpack Settings: Block malicious login attempts */ "Block malicious login attempts" = "Block malicious login attempts"; @@ -1070,9 +1133,15 @@ translators: Block name. %s: The localized block name */ /* The title of a button that triggers blocking a site from the user's reader. */ "Block this site" = "Block this site"; +/* translators: displayed right after the block is ungrouped. */ +"Block ungrouped" = "Block ungrouped"; + /* Notice title when blocking a site succeeds. */ "Blocked site" = "Blocked site"; +/* Notice title when blocking a user succeeds. */ +"Blocked user" = "Blocked user"; + /* Blocklist Title Settings: Comments Blocklist */ "Blocklist" = "Blocklist"; @@ -1083,6 +1152,12 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Blocks are pieces of content that you can insert, rearrange, and style without needing to know how to code. Blocks are an easy and modern way for you to create beautiful layouts." = "Blocks are pieces of content that you can insert, rearrange, and style without needing to know how to code. Blocks are an easy and modern way for you to create beautiful layouts."; +/* No comment provided by engineer. */ +"Blocks menu" = "Blocks menu"; + +/* translators: Warning related to having blocks deeply nested. %d: The deepest nesting level. */ +"Blocks nested deeper than %d levels may not render properly in the mobile editor." = "Blocks nested deeper than %d levels may not render properly in the mobile editor."; + /* Title of a button that displays the WordPress.com blog */ "Blog" = "Blog"; @@ -1135,6 +1210,9 @@ translators: Block name. %s: The localized block name */ /* Jetpack Settings: Brute Force Attack Protection Section */ "Brute Force Attack Protection" = "Brute Force Attack Protection"; +/* No comment provided by engineer. */ +"Build layouts" = "Build layouts"; + /* Discoverability title for bullet list keyboard shortcut. */ "Bullet List" = "Bullet List"; @@ -1150,6 +1228,9 @@ translators: Block name. %s: The localized block name */ /* Title for a list of different button styles. */ "Button Style" = "Button Style"; +/* No comment provided by engineer. */ +"Button position" = "Button position"; + /* Label for the post author in the post detail. */ "By " = "By "; @@ -1248,6 +1329,9 @@ translators: Block name. %s: The localized block name */ /* Dialog box title for when the user is canceling an upload. */ "Cancel media uploads" = "Cancel media uploads"; +/* No comment provided by engineer. */ +"Cancel search" = "Cancel search"; + /* Share extension dialog dismiss button label - displayed when user is missing a login token. */ "Cancel sharing" = "Cancel sharing"; @@ -1388,6 +1472,9 @@ translators: Block name. %s: The localized block name */ Title for the button to progress with the selected site homepage design. */ "Choose" = "Choose"; +/* Title for selecting a new homepage */ +"Choose Homepage" = "Choose Homepage"; + /* Title for settings section which allows user to select their home page and posts page */ "Choose Pages" = "Choose Pages"; @@ -1403,6 +1490,9 @@ translators: Block name. %s: The localized block name */ /* Select domain name. Title */ "Choose a domain" = "Choose a domain"; +/* No comment provided by engineer. */ +"Choose a file" = "Choose a file"; + /* OK Button title shown in alert informing users about the Reader Save for Later feature. */ "Choose a new app icon" = "Choose a new app icon"; @@ -1482,6 +1572,9 @@ translators: Block name. %s: The localized block name */ /* Message of the trash confirmation alert. */ "Clear all old activity logs?" = "Clear all old activity logs?"; +/* No comment provided by engineer. */ +"Clear search" = "Clear search"; + /* Title of a button. */ "Clear search history" = "Clear search history"; @@ -1534,6 +1627,12 @@ translators: Block name. %s: The localized block name */ /* Title displayed for selection of custom app icons that have colorful backgrounds. */ "Colorful backgrounds" = "Colourful backgrounds"; +/* translators: %d: column index. */ +"Column %d" = "Column %d"; + +/* No comment provided by engineer. */ +"Column Settings" = "Column Settings"; + /* No comment provided by engineer. */ "Columns Settings" = "Columns Settings"; @@ -1997,6 +2096,12 @@ translators: Block name. %s: The localized block name */ /* Button title for download backup action */ "Create downloadable file" = "Create downloadable file"; +/* No comment provided by engineer. */ +"Create embed" = "Create embed"; + +/* No comment provided by engineer. */ +"Create link" = "Create link"; + /* Title of a Quick Start Tour */ "Create your site" = "Create your site"; @@ -2015,6 +2120,9 @@ translators: Block name. %s: The localized block name */ /* Description of the cell displaying status of a backup in progress */ "Creating downloadable backup" = "Creating downloadable backup"; +/* No comment provided by engineer. */ +"Crosspost" = "Crosspost"; + /* No comment provided by engineer. */ "Current" = "Current"; @@ -2033,6 +2141,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Current placeholder text is" = "Current placeholder text is"; +/* translators: accessibility text. Inform about current unit value. %s: Current unit value. */ +"Current unit is %s" = "Current unit is %s"; + /* translators: %s: current cell value. */ "Current value is %s" = "Current value is %s"; @@ -2048,6 +2159,9 @@ translators: Block name. %s: The localized block name */ /* Description of the current entry being restored. %1$@ is a placeholder for the specific entry being restored. */ "Currently restoring: %1$@" = "Currently restoring: %1$@"; +/* No comment provided by engineer. */ +"Custom URL" = "Custom URL"; + /* Placeholder for Invite People message field. */ "Custom message…" = "Custom message…"; @@ -2120,6 +2234,9 @@ translators: Block name. %s: The localized block name */ /* Only December needs to be translated */ "December 17, 2017" = "December 17, 2017"; +/* No comment provided by engineer. */ +"Deeply nested block" = "Deeply nested block"; + /* Description of the default paragraph formatting style in the editor. Placeholder text displayed in the share extension's summary view. It lets the user know the default category will be used on their post. */ "Default" = "Default"; @@ -2184,6 +2301,9 @@ translators: Block name. %s: The localized block name */ /* Verb. Denies a 2fa authentication challenge. */ "Deny" = "Deny"; +/* No comment provided by engineer. */ +"Describe the purpose of the image. Leave empty if decorative." = "Describe the purpose of the image. Leave empty if decorative."; + /* Label for the description for a media asset (image / video) Section header for tag name in Tag Details View. Title of section that contains plugins' description */ @@ -2192,6 +2312,9 @@ translators: Block name. %s: The localized block name */ /* Shortened version of the main title to be used in back navigation. */ "Design" = "Design"; +/* Navigates to design system gallery only available in development builds */ +"Design System" = "Design System"; + /* Title for the desktop web preview */ "Desktop" = "Desktop"; @@ -2460,6 +2583,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Duplicate block" = "Duplicate block"; +/* No comment provided by engineer. */ +"Dynamic" = "Dynamic"; + /* Placeholder text for the search field int the Site Intent screen. */ "E.g. Fashion, Poetry, Politics" = "Eg. Fashion, Poetry, Politics"; @@ -2502,6 +2628,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Edit file" = "Edit file"; +/* No comment provided by engineer. */ +"Edit focal point" = "Edit focal point"; + /* No comment provided by engineer. */ "Edit media" = "Edit media"; @@ -2520,6 +2649,12 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Edit video" = "Edit video"; +/* translators: %s: name of the host app (e.g. WordPress) */ +"Editing synced patterns is not yet supported on %s for Android" = "Editing synced patterns is not yet supported on %s for Android"; + +/* translators: %s: name of the host app (e.g. WordPress) */ +"Editing synced patterns is not yet supported on %s for iOS" = "Editing synced patterns is not yet supported on %s for iOS"; + /* Editing GIF alert message. */ "Editing this GIF will remove its animation." = "Editing this GIF will remove its animation."; @@ -2581,6 +2716,12 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Embed block previews are coming soon" = "Embed block previews are coming soon"; +/* translators: accessibility text. %s: Embed caption. */ +"Embed caption. %s" = "Embed caption. %s"; + +/* translators: accessibility text. Empty Embed caption. */ +"Embed caption. Empty" = "Embed caption. Empty"; + /* No comment provided by engineer. */ "Embed media" = "Embed media"; @@ -3077,6 +3218,12 @@ translators: Block name. %s: The localized block name */ /* An example tag used in the login prologue screens. */ "Football" = "Football"; +/* translators: Recommendation included in a warning related to having blocks deeply nested. */ +"For this reason, we recommend editing the block using the web editor." = "For this reason, we recommend editing the block using the web editor."; + +/* translators: Recommendation included in a warning related to having blocks deeply nested. */ +"For this reason, we recommend editing the block using your web browser." = "For this reason, we recommend editing the block using your web browser."; + /* Register Domain - Domain contact information section header description */ "For your convenience, we have pre-filled your WordPress.com contact information. Please review to be sure it’s the correct information you want to use for this domain." = "For your convenience, we have pre-filled your WordPress.com contact information. Please review to be sure it’s the correct information you want to use for this domain."; @@ -3110,6 +3257,9 @@ translators: Block name. %s: The localized block name */ /* Button title displayed in popup indicating date of change on another device */ "From another device" = "From another device"; +/* No comment provided by engineer. */ +"From clipboard" = "From clipboard"; + /* Button title displayed in popup indicating date of change on device */ "From this device" = "From this device"; @@ -3367,6 +3517,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Hide keyboard" = "Hide keyboard"; +/* No comment provided by engineer. */ +"Hide search heading" = "Hide search heading"; + /* Displays the History screen from the editor's alert sheet Title of a navigation button that opens the scan history view Title of the post history screen */ @@ -3407,6 +3560,12 @@ translators: Block name. %s: The localized block name */ /* How to create story description */ "How to create a story post" = "How to create a story post"; +/* No comment provided by engineer. */ +"How to edit your page" = "How to edit your page"; + +/* No comment provided by engineer. */ +"How to edit your post" = "How to edit your post"; + /* Title for the fix section in Threat Details */ "How will we fix it?" = "How will we fix it?"; @@ -3506,6 +3665,9 @@ translators: Block name. %s: The localized block name */ /* The plugin is not active on the site and has enabled automatic updates */ "Inactive, Autoupdates on" = "Inactive, Autoupdates on"; +/* Title of the switch to turn on or off the blogging prompts feature. */ +"Include a Blogging Prompt" = "Include a Blogging Prompt"; + /* Describes a standard *.wordpress.com site domain */ "Included with Site" = "Included with Site"; @@ -3535,9 +3697,18 @@ translators: Block name. %s: The localized block name */ Button title used in media picker to insert media (photos / videos) into a post. Placeholder will be the number of items that will be inserted. */ "Insert %@" = "Insert %@"; +/* No comment provided by engineer. */ +"Insert Audio Block" = "Insert Audio Block"; + +/* No comment provided by engineer. */ +"Insert Gallery Block" = "Insert Gallery Block"; + /* Accessibility label for insert horizontal ruler button on formatting toolbar. */ "Insert Horizontal Ruler" = "Insert Horizontal Ruler"; +/* No comment provided by engineer. */ +"Insert Image Block" = "Insert Image Block"; + /* Accessibility label for insert link button on formatting toolbar. Discoverability title for insert link keyboard shortcut. Label action for inserting a link on the editor */ @@ -3546,6 +3717,12 @@ translators: Block name. %s: The localized block name */ /* Discoverability title for insert media keyboard shortcut. */ "Insert Media" = "Insert Media"; +/* No comment provided by engineer. */ +"Insert Video Block" = "Insert Video Block"; + +/* No comment provided by engineer. */ +"Insert crosspost" = "Insert crosspost"; + /* Accessibility label for insert media button on formatting toolbar. */ "Insert media" = "Insert media"; @@ -3555,6 +3732,9 @@ translators: Block name. %s: The localized block name */ /* Default accessibility label for the media picker insert button. */ "Insert selected" = "Insert selected"; +/* No comment provided by engineer. */ +"Inside" = "Inside"; + /* Title of Insights stats filter. */ "Insights" = "Insights"; @@ -3594,6 +3774,9 @@ translators: Block name. %s: The localized block name */ /* Interior Design site intent topic */ "Interior Design" = "Interior Design"; +/* Title displayed on the feature introduction view. */ +"Introducing Blogging Prompts" = "Introducing Blogging Prompts"; + /* Stories intro header title */ "Introducing Story Posts" = "Introducing Story Posts"; @@ -3914,6 +4097,9 @@ translators: Block name. %s: The localized block name */ /* Link name field placeholder */ "Link Name" = "Link Name"; +/* No comment provided by engineer. */ +"Link Rel" = "Link Rel"; + /* Noun. Title for screen in editor that allows to configure link options */ "Link Settings" = "Link Settings"; @@ -3943,6 +4129,9 @@ translators: Block name. %s: The localized block name */ /* A short label. A call to action to load more posts. */ "Load more posts" = "Load more posts"; +/* No comment provided by engineer. */ +"Loading" = "Loading"; + /* Text displayed while loading the activity feed for a site */ "Loading Activities..." = "Loading Activities..."; @@ -4118,6 +4307,9 @@ translators: Block name. %s: The localized block name */ /* Return to blog screen action when theme activation succeeds */ "Manage site" = "Manage site"; +/* No comment provided by engineer. */ +"Manual" = "Manual"; + /* Section name for manual offsets in time zone selector */ "Manual Offsets" = "Manual Offsets"; @@ -4214,6 +4406,9 @@ translators: Block name. %s: The localized block name */ /* Error message to show to users when trying to upload a media object with file size is larger than the max file size allowed in the site */ "Media filesize (%@) is too large to upload. Maximum allowed is %@" = "Media filesize (%1$@) is too large to upload. Maximum allowed is %2$@"; +/* translators: %s: block title e.g: \"Paragraph\". */ +"Media options" = "Media options"; + /* Alert displayed to the user when multiple media items have uploaded successfully. */ "Media uploaded (%ld files)" = "Media uploaded (%ld files)"; @@ -4223,6 +4418,9 @@ translators: Block name. %s: The localized block name */ /* Medium image size. Should be the same as in core WP. */ "Medium" = "Medium"; +/* No comment provided by engineer. */ +"Mention" = "Mention"; + /* The default text used for filling the name of a menu when creating it. Title for the site menu view on the My Site screen */ "Menu" = "Menu"; @@ -4281,6 +4479,9 @@ translators: Block name. %s: The localized block name */ /* Label for the posting activity legend. */ "More Posts" = "More Posts"; +/* No comment provided by engineer. */ +"More support options" = "More support options"; + /* Insights 'Most Popular Time' header */ "Most Popular Time" = "Most Popular Time"; @@ -4317,6 +4518,9 @@ translators: Block name. %s: The localized block name */ /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ "Move block up from row %1$s to row %2$s" = "Move block up from row %1$s to row %2$s"; +/* No comment provided by engineer. */ +"Move blocks" = "Move blocks"; + /* Option to move Insight down in the view. */ "Move down" = "Move down"; @@ -4392,6 +4596,10 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Navigates to layout selection screen" = "Navigates to layout selection screen"; +/* translators: %s: Select control button label e.g. Small +translators: %s: Select control button label e.g. \"Button width\" */ +"Navigates to select %s" = "Navigates to select %s"; + /* No comment provided by engineer. */ "Navigates to the previous content sheet" = "Navigates to the previous content sheet"; @@ -4559,6 +4767,9 @@ translators: Block name. %s: The localized block name */ /* Accessibility label for no currently selected range. */ "No date range selected" = "No date range selected"; +/* No comment provided by engineer. */ +"No description" = "No description"; + /* Title for the view when there aren't any fixed threats to display */ "No fixed threats" = "No fixed threats"; @@ -4642,6 +4853,9 @@ translators: Block name. %s: The localized block name */ /* Accessibility value for a Stats' Posting Activity Month if there are no posts. */ "No posts." = "No posts."; +/* No comment provided by engineer. */ +"No preview available" = "No preview available"; + /* String to display in place of the site address, in case it was not retrieved from the backend. */ "No primary site address found" = "No primary site address found"; @@ -4676,6 +4890,9 @@ translators: Block name. %s: The localized block name */ /* Message for a notice informing the user their scan completed and no threats were found */ "No threats found" = "No threats found"; +/* No comment provided by engineer. */ +"No title" = "No title"; + /* Disabled No alignment for an image (default). Should be the same as in core WP. No comment will be autoapproved @@ -4867,6 +5084,9 @@ translators: Block name. %s: The localized block name */ Title for the warning shown to the user when the app realizes there should be an auth token but there isn't one. */ "Oops!" = "Oops!"; +/* No comment provided by engineer. */ +"Opacity" = "Opacity"; + /* No comment provided by engineer. */ "Open Block Actions Menu" = "Open Block Actions Menu"; @@ -4923,6 +5143,9 @@ translators: Block name. %s: The localized block name */ /* Divider on initial auth view separating auth options. */ "Or" = "Or"; +/* Instruction text for other forms of two-factor auth methods. */ +"Or choose another form of authentication." = "Or choose another form of authentication."; + /* Label for button to log in using site address. Underscores _..._ denote underline. */ "Or log in by _entering your site address_." = "Or log in by _entering your site address_."; @@ -4964,6 +5187,9 @@ translators: Block name. %s: The localized block name */ Other Sites Notification Settings Title */ "Other Sites" = "Other Sites"; +/* No comment provided by engineer. */ +"Outside" = "Outside"; + /* Register Domain - Phone number section header title */ "PHONE" = "Phone"; @@ -5107,6 +5333,12 @@ translators: Block name. %s: The localized block name */ /* User action to play a video on the editor. */ "Play video" = "Play video"; +/* No comment provided by engineer. */ +"Playback Bar Color" = "Playback Bar Colour"; + +/* No comment provided by engineer. */ +"Playback Settings" = "Playback Settings"; + /* Suggestion to add content before trying to publish post or page */ "Please add some content before trying to publish." = "Please add some content before trying to publish."; @@ -5179,6 +5411,9 @@ translators: Block name. %s: The localized block name */ /* Instructional text shown when requesting the user's password for a login initiated via Sign In with Apple */ "Please enter the password for your WordPress.com account to log in with your Apple ID." = "Please enter the password for your WordPress.com account to log in with your Apple ID."; +/* Instruction text on the two-factor screen. */ +"Please enter the verification code from your authenticator app." = "Please enter the verification code from your authenticator app."; + /* Popup message to ask for user credentials (fields shown below). */ "Please enter your credentials" = "Please enter your credentials"; @@ -5424,6 +5659,9 @@ translators: Block name. %s: The localized block name */ Privacy Settings Title */ "Privacy Settings" = "Privacy Settings"; +/* No comment provided by engineer. */ +"Privacy and Rating" = "Privacy and Rating"; + /* Link to the CCPA privacy notice for residents of California. */ "Privacy notice for California users" = "Privacy notice for California users"; @@ -5599,6 +5837,9 @@ translators: Block name. %s: The localized block name */ /* Message shwon to confirm a publicize connection has been successfully reconnected. */ "Reconnected" = "Reconnected"; +/* Action button to redo last change */ +"Redo" = "Redo"; + /* Label for link title in Referrers stat. */ "Referrer" = "Referrer"; @@ -5680,6 +5921,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Remove block" = "Remove block"; +/* No comment provided by engineer. */ +"Remove blocks" = "Remove blocks"; + /* Option to remove Insight from view. */ "Remove from insights" = "Remove from insights"; @@ -6032,6 +6276,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Search block label. Current text is" = "Search block label. Current text is"; +/* No comment provided by engineer. */ +"Search blocks" = "Search blocks"; + /* No comment provided by engineer. */ "Search button. Current button text is" = "Search button. Current button text is"; @@ -6041,9 +6288,18 @@ translators: Block name. %s: The localized block name */ /* title of the button that searches the first domain. */ "Search for a domain" = "Search for a domain"; +/* Select domain name. Subtitle */ +"Search for a short and memorable keyword to help people find and visit your website." = "Search for a short and memorable keyword to help people find and visit your website."; + +/* No comment provided by engineer. */ +"Search input field." = "Search input field."; + /* No comment provided by engineer. */ "Search or type URL" = "Search or type URL"; +/* No comment provided by engineer. */ +"Search settings" = "Search settings"; + /* Menus search bar placeholder text. */ "Search..." = "Search..."; @@ -6123,6 +6379,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Select a color" = "Select a colour"; +/* No comment provided by engineer. */ +"Select a color above" = "Select a colour above"; + /* Reader select interests next button disabled title text */ "Select a few to continue" = "Select a few to continue"; @@ -6180,6 +6439,13 @@ translators: Block name. %s: The localized block name */ /* Accessibility label for summary of currently selected range. %1$@ is the start date, %2$@ is the end date. */ "Selected range: %1$@ to %2$@" = "Selected range: %1$@ to %2$@"; +/* translators: %s: Select font size option value e.g: \"Selected: Large\". +translators: %s: Select control option value e.g: \"Auto, 25%\". */ +"Selected: %s" = "Selected: %s"; + +/* No comment provided by engineer. */ +"Selected: Default" = "Selected: Default"; + /* Menus alert message for alerting the user to unsaved changes while trying to select a different menu location. */ "Selecting a different menu location will discard changes you've made to the current menu. Are you sure you want to continue?" = "Selecting a different menu location will discard changes you've made to the current menu. Are you sure you want to continue?"; @@ -6509,6 +6775,9 @@ translators: Block name. %s: The localized block name */ /* Menu title to skip today's prompt. */ "Skip for today" = "Skip for today"; +/* translators: Slash inserter autocomplete results */ +"Slash inserter results" = "Slash inserter results"; + /* Label for the slug field. Should be the same as WP core. */ "Slug" = "Slug"; @@ -6791,6 +7060,9 @@ translators: Block name. %s: The localized block name */ /* Accessibility Identifier for the Default Font Aztec Style. */ "Switches to the default Font Size" = "Switches to the default Font Size"; +/* No comment provided by engineer. */ +"Synced patterns" = "Synced patterns"; + /* Title for the app appearance setting (light / dark mode) that uses the system default value */ "System default" = "System default"; @@ -6849,6 +7121,9 @@ translators: Block name. %s: The localized block name */ /* No comment provided by engineer. */ "Tap here to show help" = "Tap here to show help"; +/* No comment provided by engineer. */ +"Tap here to show more details." = "Tap here to show more details."; + /* Accessibility hint for a button that opens a view that allows to add new stats cards. */ "Tap to add new stats cards." = "Tap to add new stats cards."; @@ -6937,12 +7212,18 @@ translators: Block name. %s: The localized block name */ /* Title of a button style */ "Text Only" = "Text Only"; +/* No comment provided by engineer. */ +"Text color" = "Text colour"; + /* No comment provided by engineer. */ "Text formatting controls are located within the toolbar positioned above the keyboard while editing a text block" = "Text formatting controls are located within the toolbar positioned above the keyboard while editing a text block"; /* Button title */ "Text me a code instead" = "Text me a code instead"; +/* The button's title text to send a 2FA code via SMS text message. */ +"Text me a code via SMS" = "Text me a code via SMS"; + /* Message of alert when theme activation succeeds */ "Thanks for choosing %@ by %@" = "Thanks for choosing %1$@ by %2$@"; @@ -7333,6 +7614,9 @@ translators: Block name. %s: The localized block name */ /* Message displayed when a threat is ignored successfully. */ "Threat ignored." = "Threat ignored."; +/* No comment provided by engineer. */ +"Three" = "Three"; + /* Thumbnail image size. Should be the same as in core WP. */ "Thumbnail" = "Thumbnail"; @@ -7353,6 +7637,9 @@ translators: Block name. %s: The localized block name */ /* Description of a Quick Start Tour */ "Time to finish setting up your site! Our checklist walks you through the next steps." = "Time to finish setting up your site! Our checklist walks you through the next steps."; +/* Error when the uses takes more than 1 minute to submit a security key. */ +"Time's up, but don't worry, your security is our priority. Please try again!" = "Time's up, but don't worry, your security is our priority. Please try again!"; + /* WordPress.com Marketing Footer Text */ "Tips for getting the most out of WordPress.com." = "Tips for getting the most out of WordPress.com."; @@ -7476,6 +7763,15 @@ translators: Block name. %s: The localized block name */ /* Title for the traffic section in site settings screen */ "Traffic" = "Traffic"; +/* Describes a domain that was transferred from elsewhere to wordpress.com */ +"Transferred Domain" = "Transferred Domain"; + +/* translators: %s: block title e.g: \"Paragraph\". */ +"Transform %s to" = "Transform %s to"; + +/* No comment provided by engineer. */ +"Transform block…" = "Transform block…"; + /* Accessibility label for trash buttons in nav bars Trashes a comment Trashes the comment */ @@ -7520,6 +7816,9 @@ translators: Block name. %s: The localized block name */ Re-load the history again. It appears if the loading call fails. */ "Try again" = "Try again"; +/* No comment provided by engineer. */ +"Try another search term" = "Try another search term"; + /* Button title on the blogging prompt's feature introduction view to answer a prompt. */ "Try it now" = "Try it now"; @@ -7556,6 +7855,9 @@ translators: Block name. %s: The localized block name */ /* Notice title when turning site notifications on succeeds. */ "Turned on site notifications" = "Turned on site notifications"; +/* Notification Settings switch for the app. */ +"Turning the switch off will disable all notifications from this app, regardless of type." = "Turning the switch off will disable all notifications from this app, regardless of type."; + /* Title of button that displays the app's Twitter profile */ "Twitter" = "Twitter"; @@ -7568,6 +7870,9 @@ translators: Block name. %s: The localized block name */ /* Type menu item in share extension. */ "Type" = "Type"; +/* No comment provided by engineer. */ +"Type a URL" = "Type a URL"; + /* Placeholder text for domain search during site creation. */ "Type a keyword for more ideas" = "Type a keyword for more ideas"; @@ -7613,6 +7918,9 @@ translators: Block name. %s: The localized block name */ /* Message displayed when opening the link to the downloadable backup fails. */ "Unable to download file" = "Unable to download file"; +/* No comment provided by engineer. */ +"Unable to embed media" = "Unable to embed media"; + /* The app failed to subscribe to the comments for the post */ "Unable to follow conversation" = "Unable to follow conversation"; @@ -7765,6 +8073,9 @@ translators: Block name. %s: The localized block name */ /* VoiceOver accessibility hint, informing the user the button can be used to unfollow a blog. */ "Unfollows the blog." = "Unfollows the blog."; +/* No comment provided by engineer. */ +"Ungroup block" = "Ungroup block"; + /* Unhides a site from the site picker list */ "Unhide" = "Unhide"; @@ -7784,6 +8095,9 @@ translators: Block name. %s: The localized block name */ /* Search Terms label for 'unknown search terms'. */ "Unknown search terms" = "Unknown search terms"; +/* translators: %s: the hex color value */ +"Unlabeled color. %s" = "Unlabeled colour. %s"; + /* VoiceOver accessibility hint, informing the user the button can be used to stop liking a comment */ "Unlike the Comment." = "Unlike the Comment."; @@ -7922,6 +8236,9 @@ translators: Block name. %s: The localized block name */ /* Label to show while uploading media to server */ "Uploading..." = "Uploading..."; +/* No comment provided by engineer. */ +"Uploading…" = "Uploading…"; + /* Title for alert when trying to save post with failed media items */ "Uploads failed" = "Uploads failed"; @@ -7934,9 +8251,15 @@ translators: Block name. %s: The localized block name */ /* Title of a row displayed on the debug screen used to configure the sandbox store use in the App. */ "Use Sandbox Store" = "Use Sandbox Store"; +/* The button's title text to use a security key. */ +"Use a security key" = "Use a security key"; + /* Option to enable the block editor for new posts */ "Use block editor" = "Use block editor"; +/* No comment provided by engineer. */ +"Use icon button" = "Use icon button"; + /* The button title text for logging in with WP.com password instead of magic link. */ "Use password to sign in" = "Use password to sign in"; @@ -8124,6 +8447,10 @@ translators: Block name. %s: The localized block name */ /* Message shown on screen while waiting for Google to finish its signup process. */ "Waiting for Google to complete…" = "Waiting for Google to complete…"; +/* Text while the webauthn signature is being verified + Text while waiting for a security key challenge */ +"Waiting for security key" = "Waiting for security key"; + /* View title during the Google auth process. */ "Waiting..." = "Waiting..."; @@ -8135,6 +8462,9 @@ translators: Block name. %s: The localized block name */ Title for Jetpack Restore Warning screen */ "Warning" = "Warning"; +/* No comment provided by engineer. */ +"Warning message" = "Warning Message"; + /* Caption displayed in promotional screens shown during the login flow. */ "Watch your audience grow with in-depth analytics." = "Watch your audience grow with in-depth analytics."; @@ -8444,6 +8774,12 @@ translators: Block name. %s: The localized block name */ /* Navigates to page with details about What is WordPress.com. */ "What is WordPress.com?" = "What is WordPress.com?"; +/* No comment provided by engineer. */ +"What is a block?" = "What is a block?"; + +/* No comment provided by engineer. */ +"What is alt text?" = "What is alt text?"; + /* Title for the problem section in the Threat Details */ "What was the problem?" = "What was the problem?"; @@ -8489,9 +8825,18 @@ translators: Block name. %s: The localized block name */ /* An error message shown when a wpcom user provides the wrong password. */ "Whoops, something went wrong and we couldn't log you in. Please try again!" = "Whoops, something went wrong and we couldn't log you in. Please try again!"; +/* Generic error on the 2FA screen */ +"Whoops, something went wrong. Please try again!" = "Whoops, something went wrong. Please try again!"; + +/* Error when the uses chooses an invalid security key on the 2FA screen. */ +"Whoops, that security key does not seem valid. Please try again with another one" = "Whoops, that security key does not seem valid. Please try again with another one"; + /* Error message shown when an incorrect two factor code is provided. */ "Whoops, that's not a valid two-factor verification code. Double-check your code and try again!" = "Whoops, that's not a valid two-factor verification code. Double-check your code and try again!"; +/* No comment provided by engineer. */ +"Width Settings" = "Width Settings"; + /* Help text when editing email address */ "Will not be publicly displayed." = "Will not be publicly displayed."; @@ -8610,6 +8955,12 @@ translators: Block name. %s: The localized block name */ /* Title of button that asks the users if they'd like to focus on checking their sites stats */ "Writing blog posts" = "Writing blog posts"; +/* No comment provided by engineer. */ +"X-Axis Position" = "X-Axis Position"; + +/* No comment provided by engineer. */ +"Y-Axis Position" = "Y-Axis Position"; + /* Option to select the Yahoo Mail app when logging in with magic links */ "Yahoo Mail" = "Yahoo Mail"; @@ -8659,6 +9010,12 @@ translators: Block name. %s: The localized block name */ /* Information shown below the optional password field after new account creation. */ "You can always log in with a link like the one you just used, but you can also set up a password if you prefer." = "You can always log in with a link like the one you just used, but you can also set up a password if you prefer."; +/* Note displayed in the Feature Introduction view. */ +"You can control Blogging Prompts and Reminders at any time in My Site > Settings > Blogging" = "You can control Blogging Prompts and Reminders at any time in My Site > Settings > Blogging"; + +/* Accessibility hint for Note displayed in the Feature Introduction view. */ +"You can control Blogging Prompts and Reminders at any time in My Site, Settings, Blogging" = "You can control Blogging Prompts and Reminders at any time in My Site, Settings, Blogging"; + /* No comment provided by engineer. */ "You can edit this block using the web version of the editor." = "You can edit this block using the web version of the editor."; @@ -8950,93 +9307,595 @@ translators: Block name. %s: The localized block name */ /* Age between dates equaling one hour. */ "an hour" = "an hour"; -/* The title of a button to close the classic editor deprecation notice alert dialog. */ -"aztecPost.deprecationNotice.dismiss" = "Dismiss"; +/* This is the string we display when prompting the user to review the Jetpack app */ +"appRatings.jetpack.prompt" = "What do you think about Jetpack?"; -/* User action to dismiss media options. */ -"aztecPost.mediaAttachmentActionSheet.dismiss" = "Dismiss"; +/* This is the string we display when prompting the user to review the WordPress app */ +"appRatings.wordpress.prompt" = "What do you think about WordPress?"; -/* Action shown in a bottom notice to dismiss it. */ -"blogDashboard.dismiss" = "Dismiss"; +/* Option to enable the optimization of images when uploading. */ +"appSettings.media.imageOptimizationRow" = "Optimise Images"; -/* Verb. Dismisses the blogging prompt notification. */ -"bloggingPrompt.pushNotification.customActionDescription.dismiss" = "Dismiss"; +/* Indicates an image will use high quality when uploaded. */ +"appSettings.media.imageQuality.high" = "High"; -/* Used when displaying author of a plugin. */ -"by %@" = "by %@"; +/* Indicates an image will use low quality when uploaded. */ +"appSettings.media.imageQuality.low" = "Low"; -/* Displayed in the confirmation alert when marking comment notifications as read. */ -"comment" = "comment"; +/* Indicates an image will use maximum quality when uploaded. */ +"appSettings.media.imageQuality.maximum" = "Maximum"; -/* The menu item to select during a guided tour. */ -"connections" = "connections"; +/* Indicates an image will use medium quality when uploaded. */ +"appSettings.media.imageQuality.medium" = "Medium"; -/* Customize Insights description */ -"customizeInsightsCell.content" = "Create your own customised dashboard and choose what reports to see. Focus on the data you care most about."; +/* The quality of image used when uploading */ +"appSettings.media.imageQuality.title" = "Quality"; -/* Accessibility hint */ -"customizeInsightsCell.dismissButton.accessibilityHint" = "Tap to dismiss this card"; +/* Title for the image quality settings option. */ +"appSettings.media.imageQualityRow" = "Image Quality"; -/* Customize Insights button title */ -"customizeInsightsCell.dismissButton.title" = "Dismiss"; +/* Message of an alert informing users to enable image optimization in uploads. */ +"appSettings.optimizeImagesPopup.message" = "Image optimisation shrinks images for faster uploading.\n\nThis option is enabled by default, but you can change it in the app settings at any time."; -/* Customize Insights title */ -"customizeInsightsCell.title" = "Customise your insights"; +/* Title of an alert informing users to enable image optimization in uploads. */ +"appSettings.optimizeImagesPopup.title" = "Keep optimising images?"; -/* Accessibility hint */ -"customizeInsightsCell.tryItButton.accessibilityHint" = "Tap to customise insights"; +/* Title of button for turning off image optimization, displayed in the alert informing users to enable image optimization in uploads. */ +"appSettings.optimizeImagesPopup.turnOff" = "No, turn off"; -/* Customize Insights button title */ -"customizeInsightsCell.tryItButton.title" = "Try it now"; +/* Title of button for leaving on image optimization, displayed in the alert informing users to enable image optimization in uploads. */ +"appSettings.optimizeImagesPopup.turnOn" = "Yes, leave on"; -/* No comment provided by engineer. */ -"double-tap to change unit" = "double-tap to change unit"; +/* translators: displays audio file extension. e.g. MP3 audio file */ +"audio file" = "audio file"; -/* Register Domain - Address information field Number placeholder */ -"eg. 1122334455" = "eg. 1122334455"; +/* Alert message when something goes wrong with the selected image. */ +"avatarMenu.failedToSetAvatarAlertMessage" = "Unable to load the image. Please choose a different one or try again later."; -/* Register Domain - Address information field Country Code placeholder */ -"eg. 44" = "eg. 44"; +/* Title for menu that is shown when you tap your gravatar */ +"avatarMenu.title" = "Update Gravatar"; -/* Accessibility label for more button in dashboard quick start card. */ -"ellipsisButton.AccessibilityLabel" = "More"; +/* The title of a button to close the classic editor deprecation notice alert dialog. */ +"aztecPost.deprecationNotice.dismiss" = "Dismiss"; -/* Placeholder for the site url textfield. - Provides a sample of what a domain name looks like. - Site Address placeholder */ -"example.com" = "example.com"; +/* User action to dismiss media options. */ +"aztecPost.mediaAttachmentActionSheet.dismiss" = "Dismiss"; -/* Displayed in the confirmation alert when marking follow notifications as read. */ -"follow" = "follow"; +/* Button title for the button that shows the Blaze flow when tapped. */ +"blaze.campaigns.create.button.title" = "Create"; -/* Title for button that will open up the blogging reminders screen. */ -"growAudienceCell.bloggingReminders.actionButton" = "Set up blogging reminders"; +/* Text displayed when there are no Blaze campaigns to display. */ +"blaze.campaigns.empty.subtitle" = "You have not created any campaigns yet. Click create to get started."; -/* Title for button that will open up the blogging reminders screen. */ -"growAudienceCell.bloggingReminders.completed.actionButton" = "Edit reminders"; +/* Title displayed when there are no Blaze campaigns to display. */ +"blaze.campaigns.empty.title" = "You have no campaigns"; -/* A detailed message to users indicating that they've set up blogging reminders. */ -"growAudienceCell.bloggingReminders.completed.details" = "Keep blogging and check back to see visitors arriving at your site."; +/* Text displayed when there is a failure loading Blaze campaigns. */ +"blaze.campaigns.errorMessage" = "There was an error loading campaigns."; -/* A hint to users that they've set up blogging reminders. */ -"growAudienceCell.bloggingReminders.completed.tip" = "You set up blogging reminders"; +/* Title for the view when there's an error loading Blaze campiagns. */ +"blaze.campaigns.errorTitle" = "Oops"; -/* A detailed message to users about growing the audience for their site through blogging reminders. */ -"growAudienceCell.bloggingReminders.details" = "Posting regularly can help build an audience. Reminders help keep you on track."; +/* Displayed while Blaze campaigns are being loaded. */ +"blaze.campaigns.loading.title" = "Loading campaigns..."; -/* Title for button that will dismiss the Grow Your Audience card. */ -"growAudienceCell.dismiss" = "Dismiss"; +/* Title for the screen that allows users to manage their Blaze campaigns. */ +"blaze.campaigns.title" = "Blaze Campaigns"; -/* Title for button that will open up the follow topics screen. */ -"growAudienceCell.readerDiscover.actionButton" = "Discover blogs to follow"; +/* Description for the Blaze dashboard card. */ +"blaze.dashboard.card.description" = "Display your work across millions of sites."; -/* Title for button that will open up the follow topics screen. */ -"growAudienceCell.readerDiscover.completed.action" = "Do it again"; +/* Title for a menu action in the context menu on the Blaze card. */ +"blaze.dashboard.card.menu.hide" = "Hide this"; -/* A detailed message to users indicating that they've set up reader discover. */ -"growAudienceCell.readerDiscover.completed.details" = "Keep going! Liking and commenting is a good way to build a network. Go to Reader to find more posts."; +/* Title for a menu action in the context menu on the Blaze card. */ +"blaze.dashboard.card.menu.learnMore" = "Learn more"; -/* A hint to users that they've set up reader discover. */ +/* Title for the Blaze dashboard card. */ +"blaze.dashboard.card.title" = "Promote your content with Blaze"; + +/* Button title for a Blaze overlay prompting users to select a post to blaze. */ +"blaze.overlay.buttonTitle" = "Blaze a post now"; + +/* Description for the Blaze overlay. */ +"blaze.overlay.descriptionOne" = "Promote any post or page in only a few minutes for just a few dollars a day."; + +/* Description for the Blaze overlay. */ +"blaze.overlay.descriptionThree" = "Track your campaign's performance and cancel at anytime."; + +/* Description for the Blaze overlay. */ +"blaze.overlay.descriptionTwo" = "Your content will appear on millions of WordPress and Tumblr sites."; + +/* Title for the Blaze overlay. */ +"blaze.overlay.title" = "Drive more traffic to your site with Blaze"; + +/* Button title for the Blaze overlay prompting users to blaze the selected page. */ +"blaze.overlay.withPage.buttonTitle" = "Blaze this page"; + +/* Button title for the Blaze overlay prompting users to blaze the selected post. */ +"blaze.overlay.withPost.buttonTitle" = "Blaze this post"; + +/* Short status description */ +"blazeCampaign.status.active" = "Active"; + +/* Short status description */ +"blazeCampaign.status.approved" = "Approved"; + +/* Short status description */ +"blazeCampaign.status.canceled" = "Cancelled"; + +/* Short status description */ +"blazeCampaign.status.completed" = "Completed"; + +/* Short status description */ +"blazeCampaign.status.inmoderation" = "In Moderation"; + +/* Short status description */ +"blazeCampaign.status.processing" = "Processing"; + +/* Short status description */ +"blazeCampaign.status.rejected" = "Rejected"; + +/* Short status description */ +"blazeCampaign.status.scheduled" = "Scheduled"; + +/* Title for budget stats view */ +"blazeCampaigns.budget" = "Budget"; + +/* Title for impressions stats view */ +"blazeCampaigns.clicks" = "Clicks"; + +/* Title for impressions stats view */ +"blazeCampaigns.impressions" = "Impressions"; + +/* Title for the context menu action that hides the dashboard card. */ +"blogDashboard.contextMenu.hideThis" = "Hide this"; + +/* Action shown in a bottom notice to dismiss it. */ +"blogDashboard.dismiss" = "Dismiss"; + +/* Context menu button title */ +"blogHeader.actionCopyURL" = "Copy URL"; + +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Visit site"; + +/* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ +"bloganuary.dashboard.card.button.learnMore" = "Learn more"; + +/* Short description for the Bloganuary event, shown right below the title. */ +"bloganuary.dashboard.card.description" = "For the month of January, blogging prompts will come from Bloganuary — our community challenge to build a blogging habit for the new year."; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary is here!"; + +/* Title for the Bloganuary dashboard card. */ +"bloganuary.dashboard.card.title" = "Bloganuary is coming!"; + +/* Title of a button that calls the user to enable the Blogging Prompts feature. */ +"bloganuary.learnMore.modal.button.promptsDisabled" = "Turn on blogging prompts"; + +/* Title of a button that will dismiss the Bloganuary modal when tapped. +Note that the word 'go' here should have a closer meaning to 'start' rather than 'move forward'. */ +"bloganuary.learnMore.modal.button.promptsEnabled" = "Let’s go!"; + +/* The second line of the description shown in the Bloganuary modal sheet. */ +"bloganuary.learnMore.modal.description.second" = "Publish your response."; + +/* The third line of the description shown in the Bloganuary modal sheet. */ +"bloganuary.learnMore.modal.description.third" = "Read other bloggers’ responses to get inspiration and make new connections."; + +/* The first line of the description shown in the Bloganuary modal sheet. */ +"bloganuary.learnMore.modal.descriptions.first" = "Receive a new prompt to inspire you each day."; + +/* An additional piece of information shown in case the user has the Blogging Prompts feature disabled. */ +"bloganuary.learnMore.modal.footer.addition" = "To join Bloganuary you need to enable Blogging Prompts."; + +/* An informative excerpt shown in a subtler tone. */ +"bloganuary.learnMore.modal.footer.text" = "Bloganuary will use Daily Blogging Prompts to send you topics for the month of January."; + +/* The headline text of the Bloganuary modal sheet. */ +"bloganuary.learnMore.modal.headline" = "Join our month-long writing challenge"; + +/* Verb. Dismisses the blogging prompt notification. */ +"bloggingPrompt.pushNotification.customActionDescription.dismiss" = "Dismiss"; + +/* Used when displaying author of a plugin. */ +"by %@" = "by %@"; + +/* Option for users to rate a chat bot answer as helpful. */ +"chat.rateHelpful" = "Rate as helpful"; + +/* Displayed in the confirmation alert when marking comment notifications as read. */ +"comment" = "comment"; + +/* Sentence fragment. +The full phrase is 'Comments on' followed by the title of the post on a separate line. */ +"comment.header.subText.commentThread" = "Comments on"; + +/* Provides a hint that the current screen displays a comment on a post. +The title of the post will be displayed below this text. +Example: Comment on + My First Post */ +"comment.header.subText.post" = "Comment on"; + +/* Provides a hint that the current screen displays a reply to a comment. +%1$@ is a placeholder for the comment author's name that's been replied to. +Example: Reply to Pamela Nguyen */ +"comment.header.subText.reply" = "Reply to %1$@"; + +/* Error message informing a user about an invalid password. */ +"common.reEnterPasswordMessage" = "The username or password stored in the app may be out of date. Please re-enter your password in the settings and try again."; + +/* An error message. */ +"common.unableToConnect" = "Unable to Connect"; + +/* Footnote for the privacy compliance popover. */ +"compliance.analytics.popover.footnote" = "These cookies allow us to optimise performance by collecting information on how users interact with our websites."; + +/* Save Button Title for the privacy compliance popover. */ +"compliance.analytics.popover.save.button" = "Save"; + +/* Settings Button Title for the privacy compliance popover. */ +"compliance.analytics.popover.settings.button" = "Go to Settings"; + +/* Subtitle for the privacy compliance popover. */ +"compliance.analytics.popover.subtitle" = "We process your personal data to optimise our website and marketing activities based on your consent and our legitimate interest."; + +/* Title for the privacy compliance popover. */ +"compliance.analytics.popover.title" = "Manage privacy"; + +/* Toggle Title for the privacy compliance popover. */ +"compliance.analytics.popover.toggle" = "Analytics"; + +/* The menu item to select during a guided tour. */ +"connections" = "connections"; + +/* Customize Insights description */ +"customizeInsightsCell.content" = "Create your own customised dashboard and choose what reports to see. Focus on the data you care most about."; + +/* Accessibility hint */ +"customizeInsightsCell.dismissButton.accessibilityHint" = "Tap to dismiss this card"; + +/* Customize Insights button title */ +"customizeInsightsCell.dismissButton.title" = "Dismiss"; + +/* Customize Insights title */ +"customizeInsightsCell.title" = "Customise your insights"; + +/* Accessibility hint */ +"customizeInsightsCell.tryItButton.accessibilityHint" = "Tap to customise insights"; + +/* Customize Insights button title */ +"customizeInsightsCell.tryItButton.title" = "Try it now"; + +/* Title for an empty state view when no cards are displayed */ +"dasboard.emptyView.subtitle" = "Add cards that fit your needs to see information about your site."; + +/* Title for an empty state view when no cards are displayed */ +"dasboard.emptyView.title" = "No cards to display"; + +/* Personialize home tab button title */ +"dasboard.personalizeHomeButtonTitle" = "Personalise your home tab"; + +/* Title for a menu action in the context menu on the Jetpack Social dashboard card. */ +"dashboard.card.social.menu.hide" = "Hide this"; + +/* Title for the Jetpack Social dashboard card when the user has no social connections. */ +"dashboard.card.social.noconnections.title" = "Share across your social networks"; + +/* Title for the Jetpack Social dashboard card when the user has no social shares left. */ +"dashboard.card.social.noshares.title" = "You’re out of shares!"; + +/* Title for comments button on dashboard. */ +"dashboard.menu.comments" = "Comments"; + +/* Title for media button on dashboard. */ +"dashboard.menu.media" = "Media"; + +/* Title for more button on dashboard. */ +"dashboard.menu.more" = "More"; + +/* Title for pages button on dashboard. */ +"dashboard.menu.pages" = "Pages"; + +/* Title for posts button on dashboard. */ +"dashboard.menu.posts" = "Posts"; + +/* Title for stats button on dashboard. */ +"dashboard.menu.stats" = "Stats"; + +/* Title for the Activity Log dashboard card context menu item that navigates the user to the full Activity Logs screen. */ +"dashboardCard.ActivityLog.contextMenu.allActivity" = "All activity"; + +/* Title for the Activity Log dashboard card. */ +"dashboardCard.ActivityLog.title" = "Recent activity"; + +/* Title for an action that opens the full pages list. */ +"dashboardCard.Pages.contextMenu.allPages" = "All pages"; + +/* Title for the Pages dashboard card. */ +"dashboardCard.Pages.title" = "Pages"; + +/* Title for impressions stats view */ +"dashboardCard.blazeCampaigns.clicks" = "Clicks"; + +/* Title of a button that starts the campaign creation flow. */ +"dashboardCard.blazeCampaigns.createCampaignButton" = "Create campaign"; + +/* Title for impressions stats view */ +"dashboardCard.blazeCampaigns.impressions" = "Impressions"; + +/* Title for the Learn more button in the More menu. */ +"dashboardCard.blazeCampaigns.learnMore" = "Learn more"; + +/* Title for the card displaying blaze campaigns. */ +"dashboardCard.blazeCampaigns.title" = "Blaze campaign"; + +/* Title for the View All Campaigns button in the More menu */ +"dashboardCard.blazeCampaigns.viewAllCampaigns" = "View all campaigns"; + +/* Title of a button that starts the page creation flow. */ +"dashboardCard.pages.add.button.title" = "Add pages to your site"; + +/* Title of label marking a draft page */ +"dashboardCard.pages.cell.status.draft" = "Draft"; + +/* Title of label marking a published page */ +"dashboardCard.pages.cell.status.publish" = "Published"; + +/* Title of label marking a scheduled page */ +"dashboardCard.pages.cell.status.schedule" = "Scheduled"; + +/* Title of a button that starts the page creation flow. */ +"dashboardCard.pages.create.button.title" = "Create another page"; + +/* Title of a label that encourages the user to create a new page. */ +"dashboardCard.pages.create.description" = "Start with bespoke, mobile friendly layouts."; + +/* Title for the View stats button in the More menu */ +"dashboardCard.stats.viewStats" = "View stats"; + +/* Feature flags menu item */ +"debugMenu.featureFlags" = "Feature Flags"; + +/* General section title */ +"debugMenu.generalSectionTitle" = "General"; + +/* Remote config params debug menu footer explaining the meaning of a cell with a checkmark. */ +"debugMenu.remoteConfig.footer" = "Overridden parameters are denoted by a checkmark."; + +/* Hint for overriding remote config params */ +"debugMenu.remoteConfig.hint" = "Override the chosen param by defining a new value here."; + +/* Placeholder for overriding remote config params */ +"debugMenu.remoteConfig.placeholder" = "No remote or default value"; + +/* Remote Config debug menu title */ +"debugMenu.remoteConfig.title" = "Remote Config"; + +/* Remove current quick start tour menu item */ +"debugMenu.removeQuickStart" = "Remove Current Tour"; + +/* Title for a menu action in the context menu on the Jetpack install card. */ +"domain.dashboard.card.menu.hide" = "Hide this"; + +/* Search domain - Title for the Suggested domains screen */ +"domain.management.addDomain.search.title" = "Search for a domain"; + +/* Domain management buy domain card button title */ +"domain.management.buy.card.button.title" = "Get Domain"; + +/* Domain management buy domain card subtitle */ +"domain.management.buy.card.subtitle" = "Add a site later."; + +/* Domain management buy domain card title */ +"domain.management.buy.card.title" = "Just buy a domain"; + +/* The expired label of the domain card in All Domains screen. */ +"domain.management.card.expired.label" = "Expired"; + +/* The renews label of the domain card in All Domains screen. */ +"domain.management.card.renews.label" = "Renews"; + +/* The empty state button title in All Domains screen when the user doesn't have any domains */ +"domain.management.default.empty.state.button.title" = "Find a domain"; + +/* The empty state description in All Domains screen when the user doesn't have any domains */ +"domain.management.default.empty.state.description" = "Tap below to find your perfect domain."; + +/* The empty state title in All Domains screen when the user doesn't have any domains */ +"domain.management.default.empty.state.title" = "You don't have any domains"; + +/* The empty state description in All Domains screen when an error occurs */ +"domain.management.error.empty.state.description" = "We encountered an error while loading your domains. Please contact support if the issue persists."; + +/* The empty state title in All Domains screen when an error occurs */ +"domain.management.error.empty.state.title" = "Something went wrong"; + +/* The empty state button title in All Domains screen when an error occurs */ +"domain.management.error.state.button.title" = "Try again"; + +/* The empty state description in All Domains screen when the user is offline */ +"domain.management.offline.empty.state.description" = "Please check your network connection and try again."; + +/* The empty state title in All Domains screen when the user is offline */ +"domain.management.offline.empty.state.title" = "No Internet Connection"; + +/* Domain management choose site card button title */ +"domain.management.purchase.footer" = "*A free domain for one year is included with all paid annual plans"; + +/* Domain management purchase domain screen title */ +"domain.management.purchase.subtitle" = "Don't worry, you can easily add a site later."; + +/* Domain management purchase domain screen title. */ +"domain.management.purchase.title" = "Choose how to use your domain"; + +/* The search bar title in All Domains screen. */ +"domain.management.search-bar.title" = "Search domains"; + +/* The empty state description in All Domains screen when the are no domains matching the search criteria */ +"domain.management.search.empty.state.description" = "We couldn't find any domains that match your search for '%@'"; + +/* The empty state title in All Domains screen when the are no domains matching the search criteria */ +"domain.management.search.empty.state.title" = "No Matching Domains Found"; + +/* Domain management choose site card button title */ +"domain.management.site.card.button.title" = "Choose Site"; + +/* Domain management choose site card subtitle */ +"domain.management.site.card.footer" = "Free domain for the first year*"; + +/* Domain management choose site card subtitle */ +"domain.management.site.card.subtitle" = "Use with a site you already started."; + +/* Domain management choose site card title */ +"domain.management.site.card.title" = "Existing WordPress.com site"; + +/* Domain Management Screen Title */ +"domain.management.title" = "All Domains"; + +/* Domain Purchase Completion footer */ +"domain.purchase.preview.footer" = "It may take up to 30 minutes for your custom domain to start working."; + +/* Domain Purchase Completion description (only for FREE domains). */ +"domain.purchase.preview.free.description" = "Next, we'll help you get it ready to be browsed."; + +/* Domain Purchase Completion description (only for PAID domains). */ +"domain.purchase.preview.paid.description" = "We’ve emailed your receipt. Next, we'll help you get it ready for everyone."; + +/* Reflects that site is live when domain purchase feature flag is ON. */ +"domain.purchase.preview.title" = "Kudos, your site is live!"; + +/* The 'Best Alternative' label under the domain name in 'Choose a domain' screen */ +"domain.suggestions.row.best-alternative" = "Best Alternative"; + +/* The text to display for paid domains on sale in 'Site Creation > Choose a domain' screen */ +"domain.suggestions.row.first-year" = "for the first year"; + +/* The text to display for free domains in 'Site Creation > Choose a domain' screen */ +"domain.suggestions.row.free" = "Free"; + +/* The text to display for paid domains that are free for the first year with the paid plan in 'Site Creation > Choose a domain' screen */ +"domain.suggestions.row.free-with-plan" = "Free for the first year with annual paid plans"; + +/* The 'Recommended' label under the domain name in 'Choose a domain' screen */ +"domain.suggestions.row.recommended" = "Recommended"; + +/* The 'Sale' label under the domain name in 'Choose a domain' screen */ +"domain.suggestions.row.sale" = "Sale"; + +/* The text to display for paid domains in 'Site Creation > Choose a domain' screen */ +"domain.suggestions.row.yearly" = "per year"; + +/* Title for the checkout screen. */ +"domains.checkout.title" = "Checkout"; + +/* Action shown in a bottom notice to dismiss it. */ +"domains.failure.dismiss" = "Dismiss"; + +/* Content show when the domain selection action fails. */ +"domains.failure.title" = "Sorry, the domain you are trying to add cannot be bought on the Jetpack app at this time."; + +/* Title for the screen where the user can choose how to use the domain they're end up purchasing. */ +"domains.purchase.choice.title" = "Purchase Domain"; + +/* Back button title that navigates back to the search domains screen. */ +"domains.search.backButton.title" = "Search"; + +/* Title of screen where user chooses a site to connect to their selected domain */ +"domains.sitePicker.title" = "Choose Site"; + +/* No comment provided by engineer. */ +"double-tap to change unit" = "double-tap to change unit"; + +/* Register Domain - Address information field Number placeholder */ +"eg. 1122334455" = "eg. 1122334455"; + +/* Register Domain - Address information field Country Code placeholder */ +"eg. 44" = "eg. 44"; + +/* Accessibility label for more button in dashboard quick start card. */ +"ellipsisButton.AccessibilityLabel" = "More"; + +/* Placeholder for the site url textfield. + Provides a sample of what a domain name looks like. + Site Address placeholder */ +"example.com" = "example.com"; + +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Add"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Select Images"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "View Selected (%@)"; + +/* Title of screen the displays the details of an advertisement campaign. */ +"feature.blaze.campaignDetails.title" = "Campaign Details"; + +/* Name of a feature that allows the user to promote their posts. */ +"feature.blaze.title" = "Blaze"; + +/* Displayed in the confirmation alert when marking follow notifications as read. */ +"follow" = "follow"; + +/* Description for the Free to Paid plans dashboard card. */ +"freeToPaidPlans.dashboard.card.description" = "Get a free domain for the first year, remove ads on your site, and increase your storage."; + +/* Title for a menu action in the context menu on the Free to Paid plans dashboard card. */ +"freeToPaidPlans.dashboard.card.menu.hide" = "Hide this"; + +/* Title for the Free to Paid plans dashboard card. */ +"freeToPaidPlans.dashboard.card.shortTitle" = "Free domain with an annual plan"; + +/* Done button title on the domain purchase result screen. Closes the screen. */ +"freeToPaidPlans.resultView.done" = "Done"; + +/* Notice on the domain purchase result screen. Tells user how long it might take for their domain to be ready. */ +"freeToPaidPlans.resultView.notice" = "It may take up to 30 minutes for your domain to start working properly"; + +/* Sub-title for the domain purchase result screen. Tells user their domain is being set up. */ +"freeToPaidPlans.resultView.subtitle" = "Your new domain %@ is being set up."; + +/* Title for the domain purchase result screen. Tells user their domain was obtained. */ +"freeToPaidPlans.resultView.title" = "All ready to go!"; + +/* A generic error message for a footer view in a list with pagination */ +"general.pagingFooterView.errorMessage" = "An error occurred"; + +/* A footer retry button */ +"general.pagingFooterView.retry" = "Retry"; + +/* Title for button that will open up the blogging reminders screen. */ +"growAudienceCell.bloggingReminders.actionButton" = "Set up blogging reminders"; + +/* Title for button that will open up the blogging reminders screen. */ +"growAudienceCell.bloggingReminders.completed.actionButton" = "Edit reminders"; + +/* A detailed message to users indicating that they've set up blogging reminders. */ +"growAudienceCell.bloggingReminders.completed.details" = "Keep blogging and check back to see visitors arriving at your site."; + +/* A hint to users that they've set up blogging reminders. */ +"growAudienceCell.bloggingReminders.completed.tip" = "You set up blogging reminders"; + +/* A detailed message to users about growing the audience for their site through blogging reminders. */ +"growAudienceCell.bloggingReminders.details" = "Posting regularly can help build an audience. Reminders help keep you on track."; + +/* Title for button that will dismiss the Grow Your Audience card. */ +"growAudienceCell.dismiss" = "Dismiss"; + +/* Title for button that will open up the follow topics screen. */ +"growAudienceCell.readerDiscover.actionButton" = "Discover blogs to follow"; + +/* Title for button that will open up the follow topics screen. */ +"growAudienceCell.readerDiscover.completed.action" = "Do it again"; + +/* A detailed message to users indicating that they've set up reader discover. */ +"growAudienceCell.readerDiscover.completed.details" = "Keep going! Liking and commenting is a good way to build a network. Go to Reader to find more posts."; + +/* A hint to users that they've set up reader discover. */ "growAudienceCell.readerDiscover.completed.tip" = "You've connected with other blogs"; /* A detailed message to users about growing the audience for their site through reader discover. */ @@ -9132,6 +9991,9 @@ translators: Block name. %s: The localized block name */ /* Title of a badge indicating when a feature in singular form will be removed. First argument is the feature name. Second argument is the number of days/weeks it will be removed in. Ex: Reader is moving in 2 weeks */ "jetpack.branding.badge_banner.moving_in.singular" = "%1$@ is moving in %2$@"; +/* Title of a badge or banner indicating that this feature will be moved in a few days. */ +"jetpack.branding.badge_banner.moving_in_days.plural" = "Moving to the Jetpack app in a few days."; + /* Title of a badge indicating that a feature in plural form will be removed soon. First argument is the feature name. Ex: Notifications are moving soon */ "jetpack.branding.badge_banner.moving_soon.plural" = "%@ are moving soon"; @@ -9156,18 +10018,42 @@ translators: Block name. %s: The localized block name */ /* Title of a button that navigates the user to the Jetpack app if installed, or to the app store. */ "jetpack.fullscreen.overlay.early.switch.title" = "Switch to the new Jetpack app"; +/* Title of a button that navigates the user to the Jetpack app if installed, or to the app store. */ +"jetpack.fullscreen.overlay.late.switch.title" = "Switch to the Jetpack app"; + /* Title of a button that displays a blog post in a web view. */ "jetpack.fullscreen.overlay.learnMore" = "Learn more at jetpack.com"; +/* Description of the Notifications feature. */ +"jetpack.fullscreen.overlay.newUsers.notifications.subtitle" = "Get notifications for new comments, likes, views, and more."; + +/* Description of the Reader feature. */ +"jetpack.fullscreen.overlay.newUsers.reader.subtitle" = "Find and follow your favourite sites and communities, and share you content."; + +/* Description of the Statistics feature. */ +"jetpack.fullscreen.overlay.newUsers.stats.subtitle" = "Watch your traffic grow with helpful insights and comprehensive stats."; + /* Name of the Statistics feature. */ "jetpack.fullscreen.overlay.newUsers.stats.title" = "Stats & Insights"; +/* Title of a screen that prompts the user to switch the Jetpack app. */ +"jetpack.fullscreen.overlay.newUsers.subtitle" = "Jetpack lets you do more with your WordPress site. Switching is free and only takes a minute."; + +/* Title of a screen that prompts the user to switch the Jetpack app. */ +"jetpack.fullscreen.overlay.newUsers.title" = "Give WordPress a boost with Jetpack"; + /* Title of a button that dismisses an overlay and displays the Notifications screen. */ "jetpack.fullscreen.overlay.notifications.continue.title" = "Continue to Notifications"; /* Title of a button that dismisses an overlay that prompts the user to switch the Jetpack app. */ "jetpack.fullscreen.overlay.phaseFour.general.continue.title" = "Do this later"; +/* Title of a screen that prompts the user to switch the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseFour.subtitle" = "Stats, Reader, Notifications and other Jetpack powered features have been removed from the WordPress app."; + +/* Title of a screen that prompts the user to switch the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseFour.title" = "Jetpack features have moved."; + /* Subtitle of a screen displayed when the user accesses the Notifications screen from the WordPress app. The screen showcases the Jetpack app. */ "jetpack.fullscreen.overlay.phaseOne.notifications.subtitle" = "Switch to the Jetpack app to keep receiving real-time notifications on your device."; @@ -9177,158 +10063,929 @@ translators: Block name. %s: The localized block name */ /* Subtitle of a screen displayed when the user accesses the Reader screen from the WordPress app. The screen showcases the Jetpack app. */ "jetpack.fullscreen.overlay.phaseOne.reader.subtitle" = "Switch to the Jetpack app to find, follow, and like all your favourite sites and posts with Reader."; -/* Title of a button that dismisses an overlay and displays the Reader screen. */ -"jetpack.fullscreen.overlay.reader.continue.title" = "Continue to Reader"; +/* Title of a screen displayed when the user accesses the Reader screen from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseOne.reader.title" = "Follow any site with the Jetpack app"; + +/* Subtitle of a screen displayed when the user trys creating a new site from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseOne.siteCreation.subtitle" = "Jetpack provides stats, notifications and more to help you build and grow the WordPress site of your dreams."; + +/* Subtitle of a screen displayed when the user accesses the Stats screen from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseOne.stats.subtitle" = "Switch to the Jetpack app to watch your site’s traffic grow with stats and insights."; + +/* Title of a screen displayed when the user accesses the Stats screen from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseOne.stats.title" = "Get your stats using the new Jetpack app"; + +/* A footnote in a screen displayed when the user accesses a Jetpack powered feature from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseThree.footnote" = "Switching is free and only takes a minute."; + +/* Title of a button that dismisses an overlay that showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseThree.general.continue.title" = "Continue without Jetpack"; + +/* Title of a screen that showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseThree.general.title" = "Jetpack features are moving soon."; + +/* Subtitle of a screen displayed when the user trys creating a new site from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseTwo.siteCreation.subtitle" = "Jetpack provides stats, notifications and more to help you build and grow the WordPress site of your dreams.\n\nThe WordPress app no longer supports creating a new site."; + +/* Subtitle of a screen displayed when the user accesses a Jetpack-powered feature from the WordPress app. */ +"jetpack.fullscreen.overlay.phaseTwoAndThree.fallbackSubtitle" = "Stats, Reader, Notifications and other Jetpack powered features will be removed from the WordPress app soon."; + +/* Title of a screen displayed when the user accesses the Notifications screen from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseTwoAndThree.notifications.title" = "Notifications are moving to Jetpack"; + +/* Title of a screen displayed when the user accesses the Reader screen from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseTwoAndThree.reader.title" = "Reader is moving to the Jetpack app"; + +/* Title of a screen displayed when the user accesses the Stats screen from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.phaseTwoAndThree.stats.title" = "Stats are moving to the Jetpack app"; + +/* Subtitle of a screen displayed when the user accesses a Jetpack-powered feature from the WordPress app. The '%@' characters are a placeholder for the date the features will be removed. */ +"jetpack.fullscreen.overlay.phaseTwoAndThree.subtitle" = "Stats, Reader, Notifications and other Jetpack powered features will be removed from the WordPress app on %@."; + +/* Title of a button that dismisses an overlay and displays the Reader screen. */ +"jetpack.fullscreen.overlay.reader.continue.title" = "Continue to Reader"; + +/* Title of a screen that prompts the user to switch the Jetpack app. */ +"jetpack.fullscreen.overlay.selfHosted.subtitle" = "The Jetpack mobile app is designed to work in companion with the Jetpack plugin. Switch now to get access to Stats, Reader, Notifications and more."; + +/* Title of a screen that prompts the user to switch the Jetpack app. */ +"jetpack.fullscreen.overlay.selfHosted.title" = "Your site has the Jetpack plugin"; + +/* Title of a button that navigates the user to the Jetpack app if installed, or to the app store. */ +"jetpack.fullscreen.overlay.siteCreation.continue.title" = "Continue without Jetpack"; + +/* Title of a button that navigates the user to the Jetpack app if installed, or to the app store. */ +"jetpack.fullscreen.overlay.siteCreation.switch.title" = "Try the new Jetpack app"; + +/* Title of a screen displayed when the user trys creating a new site from the WordPress app. The screen showcases the Jetpack app. */ +"jetpack.fullscreen.overlay.siteCreation.title" = "Create a new WordPress site with the Jetpack app"; + +/* Title of a button that dismisses an overlay and displays the Stats screen. */ +"jetpack.fullscreen.overlay.stats.continue.title" = "Continue to Stats"; + +/* The description text shown after the user has successfully installed the Jetpack plugin. */ +"jetpack.install-flow.success.description" = "Ready to use this site with the app."; + +/* Title of the primary button shown after the Jetpack plugin has been installed. Tapping on the button dismisses the installation screen. */ +"jetpack.install-flow.success.primaryButtonText" = "Done"; + +/* Title of a button for connecting user account to Jetpack. */ +"jetpack.install.connectUser.button.title" = "Connect your user account"; + +/* Message asking the user if they want to set up Jetpack from notifications */ +"jetpack.install.connectUser.notifications.description" = "To get helpful notifications on your phone from your WordPress site, you'll need to connect to your user account."; + +/* Message asking the user if they want to set up Jetpack from stats by connecting their user account */ +"jetpack.install.connectUser.stats.description" = "To use stats on your site, you'll need to connect the Jetpack plugin to your user account."; + +/* Description inside a menu card communicating that features are moving to the Jetpack app. */ +"jetpack.menuCard.description" = "Stats, Reader, Notifications and other features will move to the Jetpack mobile app soon."; + +/* Menu item title to hide the card. */ +"jetpack.menuCard.hide" = "Hide this"; + +/* Title of a button that displays a blog post in a web view. */ +"jetpack.menuCard.learnMore" = "Learn more"; + +/* Description inside a menu card prompting users to switch to the Jetpack app. */ +"jetpack.menuCard.newUsers.title" = "Unlock your site’s full potential. Get Stats, Reader, Notifications and more with Jetpack."; + +/* Title of a button prompting users to switch to the Jetpack app. */ +"jetpack.menuCard.phaseFour.title" = "Switch to Jetpack"; + +/* Menu item title to hide the card for now and show it later. */ +"jetpack.menuCard.remindLater" = "Remind me later"; + +/* Jetpack Plugin Modal footnote */ +"jetpack.plugin.modal.footnote" = "By setting up Jetpack you agree to our %@"; + +/* Jetpack Plugin Modal primary button title */ +"jetpack.plugin.modal.primary.button.title" = "Install the full plugin"; + +/* Jetpack Plugin Modal secondary button title */ +"jetpack.plugin.modal.secondary.button.title" = "Contact Support"; + +/* The 'full Jetpack plugin' string in the subtitle */ +"jetpack.plugin.modal.subtitle.jetpack.plugin" = "full Jetpack plugin"; + +/* Jetpack Plugin Modal footnote terms and conditions */ +"jetpack.plugin.modal.terms" = "Terms and Conditions"; + +/* Jetpack Plugin Modal title */ +"jetpack.plugin.modal.title" = "Please install the full Jetpack plugin"; + +/* Add an author prompt for the jetpack prologue */ +"jetpack.prologue.prompt.addAuthor" = "Add an author"; + +/* Build a site prompt for the jetpack prologue */ +"jetpack.prologue.prompt.buildSite" = "Build a site"; + +/* Check notifications prompt for the jetpack prologue */ +"jetpack.prologue.prompt.checkNotifications" = "Check notifications"; + +/* Share on Facebook prompt for the jetpack prologue */ +"jetpack.prologue.prompt.fbShare" = "Share on Facebook"; + +/* Fix a security issue prompt for the jetpack prologue */ +"jetpack.prologue.prompt.fixSecurity" = "Fix a security issue"; + +/* Post a photo prompt for the jetpack prologue */ +"jetpack.prologue.prompt.postPhoto" = "Post a photo"; + +/* Respond to comments prompt for the jetpack prologue */ +"jetpack.prologue.prompt.respondComments" = "Respond to comments"; + +/* Restore a backup prompt for the jetpack prologue */ +"jetpack.prologue.prompt.restoreBackup" = "Restore a backup"; + +/* Search for plugins prompt for the jetpack prologue */ +"jetpack.prologue.prompt.searchPlugins" = "Search for plugins"; + +/* Update a plugin prompt for the jetpack prologue */ +"jetpack.prologue.prompt.updatePlugin" = "Update a plugin"; + +/* Watch your stats prompt for the jetpack prologue */ +"jetpack.prologue.prompt.watchStats" = "Watch your stats"; + +/* Write a blog prompt for the jetpack prologue */ +"jetpack.prologue.prompt.writeBlog" = "Write a blog"; + +/* Title for a call-to-action button on the Jetpack install card. */ +"jetpackinstallcard.button.learn" = "Learn more"; + +/* Title for a menu action in the context menu on the Jetpack install card. */ +"jetpackinstallcard.menu.hide" = "Hide this"; + +/* Text displayed in the Jetpack install card on the Home screen and Menu screen when a user has an individual Jetpack plugin installed but not the full plugin. %1$@ is a placeholder for the plugin the user has installed. %1$@ is bold. */ +"jetpackinstallcard.notice.individual" = "This site is using the %1$@ plugin, which doesn't support all features of the app yet. Please install the full Jetpack plugin."; + +/* Text displayed in the Jetpack install card on the Home screen and Menu screen when a user has multiple installed individual Jetpack plugins but not the full plugin. */ +"jetpackinstallcard.notice.multiple" = "This site is using individual Jetpack plugins, which don’t support all features of the app yet. Please install the full Jetpack plugin."; + +/* Later today */ +"later today" = "later today"; + +/* Displayed in the confirmation alert when marking like notifications as read. */ +"like" = "like"; + +/* Indicating that referrer was marked as spam */ +"marked as spam" = "marked as spam"; + +/* Products header text in Me Screen. */ +"me.products.header" = "Products"; + +/* Title of error prompt shown when a sync fails. */ +"media.syncFailed" = "Unable to sync media"; + +/* An error message the app shows if media import fails */ +"mediaExporter.error.unknown" = "The item could not be added to the Media library"; + +/* An error message the app shows if media import fails */ +"mediaExporter.error.unsupportedContentType" = "Unsupported content type"; + +/* Message of an alert informing users that the video they are trying to select is not allowed. */ +"mediaExporter.videoLimitExceededError" = "Uploading videos longer than 5 minutes requires a paid plan."; + +/* Accessibility hint for add button to add items to the user's media library */ +"mediaLibrary.addButtonAccessibilityHint" = "Add new media"; + +/* Accessibility label for add button to add items to the user's media library */ +"mediaLibrary.addButtonAccessibilityLabel" = "Add"; + +/* Button name in the more menu */ +"mediaLibrary.aspectRatioGrid" = "Aspect Ratio Grid"; + +/* Context menu button */ +"mediaLibrary.buttonDelete" = "Delete"; + +/* Media screen navigation bar button Select title */ +"mediaLibrary.buttonSelect" = "Select"; + +/* Context menu button */ +"mediaLibrary.buttonShare" = "Share"; + +/* Verb. Button title. Tapping cancels an action. */ +"mediaLibrary.deleteConfirmationCancel" = "Cancel"; + +/* Title for button that permanently deletes one or more media items (photos / videos) */ +"mediaLibrary.deleteConfirmationConfirm" = "Delete"; + +/* Message prompting the user to confirm that they want to permanently delete a group of media items. */ +"mediaLibrary.deleteConfirmationMessageMany" = "Are you sure you want to permanently delete these items?"; + +/* Message prompting the user to confirm that they want to permanently delete a media item. Should match Calypso. */ +"mediaLibrary.deleteConfirmationMessageOne" = "Are you sure you want to permanently delete this item?"; + +/* Text displayed in HUD if there was an error attempting to delete a group of media items. */ +"mediaLibrary.deletionFailureMessage" = "Unable to delete all media items."; + +/* Text displayed in HUD while a media item is being deleted. */ +"mediaLibrary.deletionProgressViewTitle" = "Deleting..."; + +/* Text displayed in HUD after successfully deleting a media item */ +"mediaLibrary.deletionSuccessMessage" = "Deleted!"; + +/* The name of the media filter */ +"mediaLibrary.filterAll" = "All"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Audio"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Documents"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Images"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Videos"; + +/* User action to delete un-uploaded media. */ +"mediaLibrary.retryOptionsAlert.delete" = "Delete"; + +/* Verb. Button title. Tapping dismisses a prompt. */ +"mediaLibrary.retryOptionsAlert.dismissButton" = "Dismiss"; + +/* User action to retry media upload. */ +"mediaLibrary.retryOptionsAlert.retry" = "Retry Upload"; + +/* Message displayed when no results are returned from a media library search. Should match Calypso. */ +"mediaLibrary.searchResultsEmptyTitle" = "No media matching your search"; + +/* Text displayed in HUD if there was an error attempting to share a group of media items. */ +"mediaLibrary.sharingFailureMessage" = "Unable to share the selected items."; + +/* Button name in the more menu */ +"mediaLibrary.squareGrid" = "Square Grid"; + +/* Media screen navigation title */ +"mediaLibrary.title" = "Media"; + +/* Bottom toolbar title in the selection mode */ +"mediaLibrary.toolbarSelectImagesMany" = "%d Images Selected"; + +/* Bottom toolbar title in the selection mode */ +"mediaLibrary.toolbarSelectImagesOne" = "1 Image Selected"; + +/* Bottom toolbar title in the selection mode */ +"mediaLibrary.toolbarSelectItemsMany" = "%d Items Selected"; + +/* Bottom toolbar title in the selection mode */ +"mediaLibrary.toolbarSelectItemsOne" = "1 Item Selected"; + +/* Bottom toolbar title in the selection mode */ +"mediaLibrary.toolbarSelectItemsPrompt" = "Select Items"; + +/* The title of the button to dismiss the alert shown when the picked media cannot be imported into stories. */ +"mediaPicker.failedMediaExportAlert.dismissButton" = "Dismiss"; + +/* Error message when picked media cannot be imported into stories. */ +"mediaPicker.failedMediaExportAlert.message" = "Your media could not be exported. If the problem persists you can contact us via the Me > Help & Support screen."; + +/* Error title when picked media cannot be imported into stories. */ +"mediaPicker.failedMediaExportAlert.title" = "Failed Media Export"; + +/* Message for alert when access to camera is not granted */ +"mediaPicker.noCameraAccessMessage" = "This app needs permission to access the Camera to capture new media, please change the privacy settings if you wish to allow this."; + +/* Title for alert when access to camera is not granted */ +"mediaPicker.noCameraAccessTitle" = "Media Capture"; + +/* Button that opens the Settings app */ +"mediaPicker.openSettings" = "Open Settings"; + +/* The name of the action in the context menu for selecting photos from Tenor (free GIF library) */ +"mediaPicker.pickFromFreeGIFLibrary" = "Free GIF Library"; + +/* The name of the action in the context menu (user's WordPress Media Library */ +"mediaPicker.pickFromMediaLibrary" = "Choose from Media"; + +/* The name of the action in the context menu for selecting photos from other apps (Files app) */ +"mediaPicker.pickFromOtherApps" = "Other Files"; + +/* The name of the action in the context menu */ +"mediaPicker.pickFromPhotosLibrary" = "Choose from Device"; + +/* The name of the action in the context menu for selecting photos from free stock photos */ +"mediaPicker.pickFromStockPhotos" = "Free Photo Library"; + +/* The name of the action in the context menu */ +"mediaPicker.takePhoto" = "Take Photo"; + +/* The name of the action in the context menu */ +"mediaPicker.takePhotoOrVideo" = "Take Photo or Video"; + +/* The name of the action in the context menu */ +"mediaPicker.takeVideo" = "Take Video"; + +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ of %2$@"; + +/* Max image size in pixels (e.g. 300x300px) */ +"mediaSizeSlider.valueFormat" = "%1$d × %2$d px"; + +/* The description in the Delete WordPress screen */ +"migration.deleteWordpress.description" = "It looks like you still have the WordPress app installed."; + +/* The primary button title in the Delete WordPress screen */ +"migration.deleteWordpress.primaryButton" = "Got it"; + +/* The secondary button title in the Delete WordPress screen */ +"migration.deleteWordpress.secondaryButton" = "Need help?"; + +/* The title in the Delete WordPress screen */ +"migration.deleteWordpress.title" = "You no longer need the WordPress app on your device"; + +/* Footer for the migration done screen. */ +"migration.done.footer" = "We recommend uninstalling the WordPress app on your device to avoid data conflicts."; + +/* Highlighted text in the footer of the migration done screen. */ +"migration.done.footer.highlighted" = "uninstalling the WordPress app"; + +/* Primary description in the migration done screen. */ +"migration.done.primaryDescription" = "We’ve transferred all your data and settings. Everything is right where you left it."; + +/* Secondary description (second paragraph) in the migration done screen. */ +"migration.done.secondaryDescription" = "It's time to continue your WordPress journey on the Jetpack app!"; + +/* Title of the migration done screen. */ +"migration.done.title" = "Thanks for switching to Jetpack!"; + +/* The description in the Load WordPress screen */ +"migration.loadWordpress.description" = "It looks like you have the WordPress app installed."; + +/* The primary button title in the Load WordPress screen */ +"migration.loadWordpress.primaryButton" = "Open WordPress"; + +/* The secondary button title in the Load WordPress screen */ +"migration.loadWordpress.secondaryButton" = "No thanks"; + +/* The secondary description in the Load WordPress screen */ +"migration.loadWordpress.secondaryDescription" = "Would you like to transfer your data from the WordPress app and sign in automatically?"; + +/* The title in the Load WordPress screen */ +"migration.loadWordpress.title" = "Welcome to Jetpack!"; + +/* Secondary button title in the migration notifications screen. */ +"migration.notifications.actions.secondary.title" = "Decide later"; + +/* Footer for the migration notifications screen. */ +"migration.notifications.footer" = "When the alert appears tap Allow to continue receiving all your WordPress notifications."; + +/* Highlighted text in the footer of the migration notifications screen. */ +"migration.notifications.footer.highlighted" = "Allow"; + +/* Primary description in the migration notifications screen. */ +"migration.notifications.primaryDescription" = "You’ll get all the same notifications but now they’ll come from the Jetpack app."; + +/* Secondary description in the migration notifications screen */ +"migration.notifications.secondaryDescription" = "We’ll disable notifications for the WordPress app."; + +/* Title of the migration notifications screen. */ +"migration.notifications.title" = "Allow notifications to keep up with your site"; + +/* The primary description in the migration welcome screen */ +"migration.welcome.primaryDescription" = "It looks like you’re switching from the WordPress app."; + +/* The plural form of the secondary description in the migration welcome screen */ +"migration.welcome.secondaryDescription.plural" = "We found your sites. Continue to transfer all your data and sign in to Jetpack automatically."; + +/* The singular form of the secondary description in the migration welcome screen */ +"migration.welcome.secondaryDescription.singular" = "We found your site. Continue to transfer all your data and sign in to Jetpack automatically."; + +/* The title in the migration welcome screen */ +"migration.welcome.title" = "Welcome to Jetpack!"; + +/* Primary button title in the migration done screen. */ +"migrationDone.actions.primaryTitle" = "Let's go"; + +/* Description for the static screen displayed prompting users to switch the Jetpack app. */ +"movedToJetpack.description" = "The Jetpack app has all the WordPress app’s functionality, and now exclusive access to Stats, Reader, Notifications and more."; + +/* Hint for the static screen displayed prompting users to switch the Jetpack app. */ +"movedToJetpack.hint" = "Switching is free and only takes a minute."; + +/* Title for a button that prompts users to switch to the Jetpack app. */ +"movedToJetpack.jetpackButtonTitle" = "Switch to the Jetpack app"; + +/* Title for a button that displays a blog post in a web view. */ +"movedToJetpack.learnMoreButtonTitle" = "Learn more at jetpack.com"; + +/* Title for the static screen displayed in the Stats screen prompting users to switch to the Jetpack app. */ +"movedToJetpack.notifications.title" = "Use WordPress with Notifications in the Jetpack app."; + +/* Title for the static screen displayed in the Reader screen prompting users to switch to the Jetpack app. */ +"movedToJetpack.reader.title" = "Use WordPress with Reader in the Jetpack app."; + +/* Title for the static screen displayed in the Stats screen prompting users to switch to the Jetpack app. */ +"movedToJetpack.stats.title" = "Use WordPress with Stats in the Jetpack app."; + +/* Section title for the content table section in the blog details screen */ +"my-site.menu.content.section.title" = "Content"; + +/* Section title for the maintenance table section in the blog details screen */ +"my-site.menu.maintenance.section.title" = "Maintenance"; + +/* Title for the social row in the blog details screen */ +"my-site.menu.social.row.title" = "Social"; + +/* Section title for the traffic table section in the blog details screen */ +"my-site.menu.traffic.section.title" = "Traffic"; + +/* Title for the card displaying draft posts. */ +"my-sites.drafts.card.title" = "Work on a draft post"; + +/* Title for the View all drafts button in the More menu */ +"my-sites.drafts.card.viewAllDrafts" = "View all drafts"; + +/* Title for the View all scheduled drafts button in the More menu */ +"my-sites.scheduled.card.viewAllScheduledPosts" = "View all scheduled posts"; + +/* Title for the card displaying today's stats. */ +"my-sites.stats.card.title" = "Today's Stats"; + +/* Title for the domain focus card on My Site */ +"mySite.domain.focus.cardCell.title" = "News"; + +/* Button title of the domain focus card on My Site */ +"mySite.domain.focus.cardView.button.title" = "Transfer your domains"; + +/* Description of the domain focus card on My Site */ +"mySite.domain.focus.cardView.description" = "As you may know, Google Domains has been sold to Squarespace. Transfer your domains to WordPress.com now, and we'll pay all transfer fees plus an extra year of your domain registration."; + +/* Title of the domain focus card on My Site */ +"mySite.domain.focus.cardView.title" = "Reclaim your Google Domains"; + +/* Action sheet button title. Launches the flow to a add self-hosted site. */ +"mySite.noSites.actionSheet.addSelfHostedSite" = "Add self-hosted site"; + +/* Action sheet button title. Launches the flow to create a WordPress.com site. */ +"mySite.noSites.actionSheet.createWPComSite" = "Create WordPress.com site"; + +/* Button title. Displays the account and setting screen. */ +"mySite.noSites.button.accountAndSettings" = "Account and settings"; + +/* Button title. Displays a screen to add a new site when tapped. */ +"mySite.noSites.button.addNewSite" = "Add new site"; + +/* Message description for when a user has no sites. */ +"mySite.noSites.description" = "Create a new site for your business, magazine, or personal blog; or connect an existing WordPress installation."; + +/* Message title for when a user has no sites. */ +"mySite.noSites.title" = "You don't have any sites"; + +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Add site"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Site Actions"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Tap to show more site actions"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Personalise home"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Change site icon"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Change site title"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Switch site"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Visit site"; + +/* Dismiss button title. */ +"noResultsViewController.dismissButton" = "Dismiss"; + +/* This is one of the buttons we display inside of the prompt to review the app */ +"notifications.appRatings.prompt.no.buttonTitle" = "Could improve"; + +/* This is one of the buttons we display inside of the prompt to review the app */ +"notifications.appRatings.prompt.yes.buttonTitle" = "I like it"; + +/* This is one of the buttons we display when prompting the user for a review */ +"notifications.appRatings.sendFeedback.no.buttonTitle" = "No thanks"; + +/* This is one of the buttons we display when prompting the user for a review */ +"notifications.appRatings.sendFeedback.yes.buttonTitle" = "Send feedback"; + +/* Badge for page cells */ +"pageList.badgeHomepage" = "Homepage"; + +/* Badge for page cells */ +"pageList.badgeLocalChanges" = "Local changes"; + +/* Badge for page cells */ +"pageList.badgePendingReview" = "Pending review"; + +/* Badge for page cells */ +"pageList.badgePosts" = "Posts page"; + +/* Badge for page cells */ +"pageList.badgePrivate" = "Private"; + +/* Subtitle of the theme template homepage cell */ +"pages.template.subtitle" = "Your homepage is using a Theme template and will open in the web editor."; + +/* Title of the theme template homepage cell */ +"pages.template.title" = "Homepage"; + +/* Message informing the user that their static homepage page was set successfully */ +"pages.updatePage.successTitle" = "Page successfully updated"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"pagesList.deletePermanently.actionTitle" = "Delete Permanently"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"pagesList.deletePermanently.alertMessage" = "Are you sure you want to permanently delete this page?"; + +/* Title of the confirmation alert when deleting a page from the trash. */ +"pagesList.deletePermanently.alertTitle" = "Delete Permanently?"; + +/* Menu option for filtering posts by everyone */ +"pagesList.pagesByEveryone" = "Pages by everyone"; + +/* Menu option for filtering posts by me */ +"pagesList.pagesByMe" = "Pages by me"; + +/* Trash option in the trash page confirmation alert. */ +"pagesList.trash.actionTitle" = "Move to Trash"; + +/* Message of the trash page confirmation alert. */ +"pagesList.trash.alertMessage" = "Are you sure you want to trash this page?"; + +/* Title of the trash page confirmation alert. */ +"pagesList.trash.alertTitle" = "Trash this page?"; + +/* Cancels an Action */ +"pagesList.trash.cancel" = "Cancel"; + +/* No comment provided by engineer. */ +"password" = "password"; + +/* Section footer displayed below the list of toggles */ +"personalizeHome.cardsSectionFooter" = "Cards may show different content depending on what's happening on your site. We're working on more cards and controls."; + +/* Section header */ +"personalizeHome.cardsSectionHeader" = "Show or hide cards"; + +/* Card title for the pesonalization menu */ +"personalizeHome.dashboardCard.activityLog" = "Recent activity"; + +/* Card title for the pesonalization menu */ +"personalizeHome.dashboardCard.blaze" = "Blaze"; + +/* Card title for the pesonalization menu */ +"personalizeHome.dashboardCard.draftPosts" = "Draft posts"; + +/* Card title for the pesonalization menu */ +"personalizeHome.dashboardCard.pages" = "Pages"; + +/* Card title for the pesonalization menu */ +"personalizeHome.dashboardCard.prompts" = "Blogging prompts"; + +/* Card title for the pesonalization menu */ +"personalizeHome.dashboardCard.scheduledPosts" = "Scheduled posts"; + +/* Card title for the pesonalization menu */ +"personalizeHome.dashboardCard.todaysStats" = "Today's stats"; + +/* Section header for shortcuts */ +"personalizeHome.shortcutsSectionHeader" = "Show or hide shortcuts"; + +/* Page title */ +"personalizeHome.title" = "Personalise Home Tab"; + +/* Register Domain - Domain contact information field Phone */ +"phone number" = "phone number"; + +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.createdTimeAgo" = "Created %@"; + +/* Status mesasge for post cells */ +"post.deletingPostPermanentlyStatusMessage" = "Deleting post..."; + +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.editedTimeAgo" = "Edited %@"; + +/* Status mesasge for post cells */ +"post.movingToTrashStatusMessage" = "Moving post to trash..."; + +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.publishedTimeAgo" = "Published %@"; + +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.scheduledForDate" = "Scheduled %@"; + +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.trashedTimeAgo" = "Trashed %@"; + +/* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ +"postList.a11y.authorChunkFormat" = "By %@."; + +/* Accessibility label for a post's excerpt in the post list. The parameter is the post excerpt. For example, \"Excerpt. This is the first paragraph.\" */ +"postList.a11y.exerptChunkFormat" = "Excerpt. %@."; + +/* Accessibility label for a sticky post in the post list. */ +"postList.a11y.sticky" = "Sticky."; + +/* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ +"postList.a11y.titleAndDateChunkFormat" = "%1$@, %2$@."; + +/* Title for the 'Trash' post list row swipe action */ +"postList.swipeActionDelete" = "Trash"; + +/* Title for the 'Delete' post list row swipe action */ +"postList.swipeActionDeletePermanently" = "Delete"; + +/* Title for the 'Share' post list row swipe action */ +"postList.swipeActionShare" = "Share"; + +/* Title for the 'View' post list row swipe action */ +"postList.swipeActionView" = "View"; + +/* User action to dismiss featured media options. */ +"postSettings.featuredImageUploadActionSheet.dismiss" = "Dismiss"; + +/* User action to remove featured media. */ +"postSettings.featuredImageUploadActionSheet.remove" = "Remove"; + +/* User action to retry featured media upload. */ +"postSettings.featuredImageUploadActionSheet.retryUpload" = "Retry"; + +/* Title for action sheet with featured media options. */ +"postSettings.featuredImageUploadActionSheet.title" = "Featured Image Options"; + +/* Section title for the disabled Twitter service in the Post Settings screen */ +"postSettings.section.disabledTwitter.header" = "Twitter Auto-Sharing Is No Longer Available"; + +/* Button in Post Settings */ +"postSettings.setFeaturedImageButton" = "Set Featured Image"; + +/* Error message on post/page settings screen */ +"postSettings.updateFailedMessage" = "Failed to update the post settings"; + +/* Promote the post with Blaze. */ +"posts.blaze.actionTitle" = "Promote with Blaze"; + +/* Label for the Post List option that cancels automatic uploading of a post. */ +"posts.cancelUpload.actionTitle" = "Cancel upload"; + +/* Label for post comments option. Tapping displays comments for a post. */ +"posts.comments.actionTitle" = "Comments"; + +/* Label for the delete post option. Tapping permanently deletes a post. */ +"posts.delete.actionTitle" = "Delete permanently"; + +/* Label for an option that moves a post to the draft folder */ +"posts.draft.actionTitle" = "Move to draft"; + +/* Label for post duplicate option. Tapping creates a copy of the post. */ +"posts.duplicate.actionTitle" = "Duplicate"; + +/* Opens a submenu for page attributes. */ +"posts.pageAttributes.actionTitle" = "Page attributes"; + +/* Label for the preview post button. Tapping displays the post as it appears on the web. */ +"posts.preview.actionTitle" = "Preview"; + +/* Label for an option that moves a publishes a post immediately */ +"posts.publish.actionTitle" = "Publish now"; + +/* Retry uploading the post. */ +"posts.retry.actionTitle" = "Retry"; + +/* Set the selected page as the homepage. */ +"posts.setHomepage.actionTitle" = "Set as homepage"; + +/* Set the parent page for the selected page. */ +"posts.setParent.actionTitle" = "Set parent"; + +/* Set the selected page as a posts page. */ +"posts.setPostsPage.actionTitle" = "Set as posts page"; + +/* Set the selected page as a regular page. */ +"posts.setRegularPage.actionTitle" = "Set as regular page"; + +/* Label for post settings option. Tapping displays settings for a post. */ +"posts.settings.actionTitle" = "Settings"; + +/* Share the post. */ +"posts.share.actionTitle" = "Share"; + +/* Label for post stats option. Tapping displays statistics for a post. */ +"posts.stats.actionTitle" = "Stats"; + +/* Label for a option that moves a post to the trash folder */ +"posts.trash.actionTitle" = "Move to trash"; + +/* Label for the view post button. Tapping displays the post as it appears on the web. */ +"posts.view.actionTitle" = "View"; + +/* A short message explaining that a page was deleted permanently. */ +"postsList.deletePage.message" = "Page deleted permanently"; + +/* A short message explaining that a post was deleted permanently. */ +"postsList.deletePost.message" = "Post deleted permanently"; + +/* A short message explaining that a page was moved to the trash bin. */ +"postsList.movePageToTrash.message" = "Page moved to trash"; + +/* A short message explaining that a post was moved to the trash bin. */ +"postsList.movePostToTrash.message" = "Post moved to trash"; + +/* Menu option for filtering posts by everyone */ +"postsList.postsByEveryone" = "Posts by everyone"; + +/* Menu option for filtering posts by me */ +"postsList.postsByMe" = "Posts by me"; + +/* Title for the button to subscribe to Jetpack Social on the remaining shares view */ +"postsettings.social.remainingshares.subscribe" = "Subscribe now to share more"; + +/* The second half of the remaining social shares a user has. This is only displayed when there is no social limit warning. */ +"postsettings.social.remainingshares.text.part" = " in the next 30 days"; -/* Title of a button that navigates the user to the Jetpack app if installed, or to the app store. */ -"jetpack.fullscreen.overlay.siteCreation.continue.title" = "Continue without Jetpack"; +/* Beginning text of the remaining social shares a user has left. %1$d is their current remaining shares. This text is combined with ' in the next 30 days' if there is no warning displayed. */ +"postsettings.social.shares.text.format" = "%1$d social shares remaining"; -/* Title of a button that navigates the user to the Jetpack app if installed, or to the app store. */ -"jetpack.fullscreen.overlay.siteCreation.switch.title" = "Try the new Jetpack app"; +/* The primary label for the auto-sharing row on the pre-publishing sheet. +Indicates the number of social accounts that will be sharing the blog post. +%1$d is a placeholder for the number of social network accounts that will be auto-shared. +Example: Sharing to 3 accounts */ +"prepublishing.social.label.multipleConnections" = "Sharing to %1$d accounts"; -/* Title of a button that dismisses an overlay and displays the Stats screen. */ -"jetpack.fullscreen.overlay.stats.continue.title" = "Continue to Stats"; +/* The primary label for the auto-sharing row on the pre-publishing sheet. +Indicates the blog post will not be shared to any social accounts. */ +"prepublishing.social.label.notSharing" = "Not sharing to social"; -/* Menu item title to hide the card. */ -"jetpack.menuCard.hide" = "Hide this"; +/* The primary label for the auto-sharing row on the pre-publishing sheet. +Indicates the number of social accounts that will be sharing the blog post. +This string is displayed when some of the social accounts are turned off for auto-sharing. +%1$d is a placeholder for the number of social media accounts that will be sharing the blog post. +%2$d is a placeholder for the total number of social media accounts connected to the user's blog. +Example: Sharing to 2 of 3 accounts */ +"prepublishing.social.label.partialConnections" = "Sharing to %1$d of %2$d accounts"; -/* Title of a button that displays a blog post in a web view. */ -"jetpack.menuCard.learnMore" = "Learn more"; +/* The primary label for the auto-sharing row on the pre-publishing sheet. +Indicates the blog post will be shared to a social media account. +%1$@ is a placeholder for the account name. +Example: Sharing to @wordpress */ +"prepublishing.social.label.singleConnection" = "Sharing to %1$@"; -/* Title of a button prompting users to switch to the Jetpack app. */ -"jetpack.menuCard.phaseFour.title" = "Switch to Jetpack"; +/* A subtext that's shown below the primary label in the auto-sharing row on the pre-publishing sheet. +Informs the remaining limit for post auto-sharing. +%1$d is a placeholder for the remaining shares. +Example: 27 social shares remaining */ +"prepublishing.social.remainingShares.format" = "%1$d social shares remaining"; -/* Menu item title to hide the card for now and show it later. */ -"jetpack.menuCard.remindLater" = "Remind me later"; +/* a VoiceOver description for the warning icon to hint that the remaining shares are low. */ +"prepublishing.social.warningIcon.accessibilityHint" = "Warning"; -/* Add an author prompt for the jetpack prologue */ -"jetpack.prologue.prompt.addAuthor" = "Add an author"; +/* The navigation title for a screen that edits the sharing message for the post. */ +"prepublishing.socialAccounts.editMessage.navigationTitle" = "Customise message"; -/* Build a site prompt for the jetpack prologue */ -"jetpack.prologue.prompt.buildSite" = "Build a site"; +/* The label for a call-to-action button in the social accounts' footer section. */ +"prepublishing.socialAccounts.footer.button.text" = "Subscribe to share more"; -/* Check notifications prompt for the jetpack prologue */ -"jetpack.prologue.prompt.checkNotifications" = "Check notifications"; +/* Text shown below the list of social accounts to indicate how many social shares available for the site. +Note that the '30 days' part is intended to be a static value. +%1$d is a placeholder for the amount of remaining shares. +Example: 27 social shares remaining in the next 30 days */ +"prepublishing.socialAccounts.footer.remainingShares.text" = "%1$d social shares remaining in the next 30 days"; -/* Share on Facebook prompt for the jetpack prologue */ -"jetpack.prologue.prompt.fbShare" = "Share on Facebook"; +/* a VoiceOver description for the warning icon to hint that the remaining shares are low. */ +"prepublishing.socialAccounts.footer.warningIcon.accessibilityHint" = "Warning"; -/* Fix a security issue prompt for the jetpack prologue */ -"jetpack.prologue.prompt.fixSecurity" = "Fix a security issue"; +/* The label displayed for a table row that displays the sharing message for the post. +Tapping on this row allows the user to edit the sharing message. */ +"prepublishing.socialAccounts.message.label" = "Message"; -/* Post a photo prompt for the jetpack prologue */ -"jetpack.prologue.prompt.postPhoto" = "Post a photo"; +/* The navigation title for the pre-publishing social accounts screen. */ +"prepublishing.socialAccounts.navigationTitle" = "Social"; -/* Respond to comments prompt for the jetpack prologue */ -"jetpack.prologue.prompt.respondComments" = "Respond to comments"; +/* Title for a tappable string that opens the reader with a prompts tag */ +"prompts.card.viewprompts.title" = "View all responses"; -/* Restore a backup prompt for the jetpack prologue */ -"jetpack.prologue.prompt.restoreBackup" = "Restore a backup"; +/* Subtitle of the notification when prompts are hidden from the dashboard card */ +"prompts.notification.removed.subtitle" = "Visit Site Settings to turn back on"; -/* Search for plugins prompt for the jetpack prologue */ -"jetpack.prologue.prompt.searchPlugins" = "Search for plugins"; +/* Title of the notification when prompts are hidden from the dashboard card */ +"prompts.notification.removed.title" = "Blogging Prompts hidden"; -/* Update a plugin prompt for the jetpack prologue */ -"jetpack.prologue.prompt.updatePlugin" = "Update a plugin"; +/* Button label that dismisses the qr log in flow and returns the user back to the previous screen */ +"qrLoginVerifyAuthorization.completedInstructions.dismiss" = "Dismiss"; -/* Watch your stats prompt for the jetpack prologue */ -"jetpack.prologue.prompt.watchStats" = "Watch your stats"; +/* Subtitle instructing the user to tap the dismiss button to leave the log in flow. %@ is a placeholder for the dismiss button name. */ +"qrLoginVerifyAuthorization.completedInstructions.subtitle" = "Tap '%@' and head back to your web browser to continue."; -/* Write a blog prompt for the jetpack prologue */ -"jetpack.prologue.prompt.writeBlog" = "Write a blog"; +/* Title for the success view when the user has successfully logged in */ +"qrLoginVerifyAuthorization.completedInstructions.title" = "You're logged in!"; -/* Later today */ -"later today" = "later today"; +/* The quick tour actions item to select during a guided tour. */ +"quickStart.moreMenu" = "More"; -/* Displayed in the confirmation alert when marking like notifications as read. */ -"like" = "like"; +/* Accessibility hint to inform that the author section can be tapped to see posts from the site. */ +"reader.detail.header.authorInfo.a11y.hint" = "Views posts from the site"; -/* Indicating that referrer was marked as spam */ -"marked as spam" = "marked as spam"; +/* Title for the Comment button on the Reader Detail toolbar. +Note: Since the display space is limited, a short or concise translation is preferred. */ +"reader.detail.toolbar.comment.button" = "Comment"; -/* Verb. Button title. Tapping dismisses a prompt. */ -"mediaLibrary.retryOptionsAlert.dismissButton" = "Dismiss"; +/* Title for the Like button in the Reader Detail toolbar. +This is shown when the user has not liked the post yet. +Note: Since the display space is limited, a short or concise translation is preferred. */ +"reader.detail.toolbar.like.button" = "Like"; -/* The title of the button to dismiss the alert shown when the picked media cannot be imported into stories. */ -"mediaPicker.failedMediaExportAlert.dismissButton" = "Dismiss"; +/* Accessibility hint for the Like button state. The button shows that the user has not liked the post, +but tapping on this button will add a Like to the post. */ +"reader.detail.toolbar.like.button.a11y.hint" = "Likes this post."; -/* The primary button title in the Delete WordPress screen */ -"migration.deleteWordpress.primaryButton" = "Got it"; +/* Title for the Like button in the Reader Detail toolbar. +This is shown when the user has already liked the post. +Note: Since the display space is limited, a short or concise translation is preferred. */ +"reader.detail.toolbar.liked.button" = "Liked"; -/* The secondary button title in the Delete WordPress screen */ -"migration.deleteWordpress.secondaryButton" = "Need help?"; +/* Accessibility hint for the Liked button state. The button shows that the user has liked the post, +but tapping on this button will remove their like from the post. */ +"reader.detail.toolbar.liked.button.a11y.hint" = "Unlikes this post."; -/* Primary description in the migration done screen. */ -"migration.done.primaryDescription" = "We’ve transferred all your data and settings. Everything is right where you left it."; +/* Accessibility hint for the 'Save Post' button. */ +"reader.detail.toolbar.save.button.a11y.hint" = "Saves this post for later."; -/* Title of the migration done screen. */ -"migration.done.title" = "Thanks for switching to Jetpack!"; +/* Accessibility label for the 'Save Post' button. */ +"reader.detail.toolbar.save.button.a11y.label" = "Save post"; -/* The primary button title in the Load WordPress screen */ -"migration.loadWordpress.primaryButton" = "Open WordPress"; +/* Accessibility hint for the 'Save Post' button when a post is already saved. */ +"reader.detail.toolbar.saved.button.a11y.hint" = "Unsaves this post."; -/* The secondary button title in the Load WordPress screen */ -"migration.loadWordpress.secondaryButton" = "No thanks"; +/* Accessibility label for the 'Save Post' button when a post has been saved. */ +"reader.detail.toolbar.saved.button.a11y.label" = "Saved Post"; -/* The title in the Load WordPress screen */ -"migration.loadWordpress.title" = "Welcome to Jetpack!"; +/* Reader search button accessibility label. */ +"reader.navigation.search.button.label" = "Search"; -/* Secondary button title in the migration notifications screen. */ -"migration.notifications.actions.secondary.title" = "Decide later"; +/* Reader settings button accessibility label. */ +"reader.navigation.settings.button.label" = "Reader Settings"; -/* Highlighted text in the footer of the migration notifications screen. */ -"migration.notifications.footer.highlighted" = "Allow"; +/* A default value used to fill in the site name when the followed site somehow has missing site name or URL. +Example: given a notice format "Following %@" and empty site name, this will be "Following this site". */ +"reader.notice.follow.site.unknown" = "this site"; -/* Primary description in the migration notifications screen. */ -"migration.notifications.primaryDescription" = "You’ll get all the same notifications but now they’ll come from the Jetpack app."; +/* Notice title when blocking a user fails. */ +"reader.notice.user.blocked" = "reader.notice.user.block.failed"; -/* Secondary description in the migration notifications screen */ -"migration.notifications.secondaryDescription" = "We’ll disable notifications for the WordPress app."; +/* Text for the 'Comment' button on the reader post card cell. */ +"reader.post.button.comment" = "Comment"; -/* Title of the migration notifications screen. */ -"migration.notifications.title" = "Allow notifications to keep up with your site"; +/* Accessibility hint for the comment button on the reader post card cell */ +"reader.post.button.comment.accessibility.hint" = "Opens the comments for the post."; -/* The primary description in the migration welcome screen */ -"migration.welcome.primaryDescription" = "It looks like you’re switching from the WordPress app."; +/* Text for the 'Like' button on the reader post card cell. */ +"reader.post.button.like" = "Like"; -/* The plural form of the secondary description in the migration welcome screen */ -"migration.welcome.secondaryDescription.plural" = "We found your sites. Continue to transfer all your data and sign in to Jetpack automatically."; +/* Accessibility hint for the like button on the reader post card cell */ +"reader.post.button.like.accessibility.hint" = "Likes the post."; -/* The singular form of the secondary description in the migration welcome screen */ -"migration.welcome.secondaryDescription.singular" = "We found your site. Continue to transfer all your data and sign in to Jetpack automatically."; +/* Text for the 'Liked' button on the reader post card cell. */ +"reader.post.button.liked" = "Liked"; -/* The title in the migration welcome screen */ -"migration.welcome.title" = "Welcome to Jetpack!"; +/* Accessibility hint for the liked button on the reader post card cell */ +"reader.post.button.liked.accessibility.hint" = "Unlikes the post."; -/* Dismiss button title. */ -"noResultsViewController.dismissButton" = "Dismiss"; +/* Accessibility hint for the site header on the reader post card cell */ +"reader.post.button.menu.accessibility.hint" = "Opens a menu with more actions."; -/* No comment provided by engineer. */ -"password" = "password"; +/* Accessibility label for the more menu button on the reader post card cell */ +"reader.post.button.menu.accessibility.label" = "More"; -/* Register Domain - Domain contact information field Phone */ -"phone number" = "phone number"; +/* Text for the 'Reblog' button on the reader post card cell. */ +"reader.post.button.reblog" = "Reblog"; -/* User action to dismiss featured media options. */ -"postSettings.featuredImageUploadActionSheet.dismiss" = "Dismiss"; +/* Accessibility hint for the reblog button on the reader post card cell */ +"reader.post.button.reblog.accessibility.hint" = "Reblogs the post."; -/* User action to remove featured media. */ -"postSettings.featuredImageUploadActionSheet.remove" = "Remove"; +/* Accessibility hint for the site header on the reader post card cell */ +"reader.post.header.accessibility.hint" = "Opens the site details for the post."; -/* User action to retry featured media upload. */ -"postSettings.featuredImageUploadActionSheet.retryUpload" = "Retry"; +/* The title of a button that triggers blocking a user from the user's reader. */ +"reader.post.menu.block.user" = "Block this user"; -/* Title for action sheet with featured media options. */ -"postSettings.featuredImageUploadActionSheet.title" = "Featured Image Options"; +/* The title of a button that removes a saved post. */ +"reader.post.menu.remove.post" = "Remove Saved Post"; -/* Title of the notification when prompts are hidden from the dashboard card */ -"prompts.notification.removed.title" = "Blogging Prompts hidden"; +/* The title of a button that triggers the reporting of a post's author. */ +"reader.post.menu.report.user" = "Report this user"; -/* Button label that dismisses the qr log in flow and returns the user back to the previous screen */ -"qrLoginVerifyAuthorization.completedInstructions.dismiss" = "Dismiss"; +/* The title of a button that saves a post. */ +"reader.post.menu.save.post" = "Save"; -/* Title for the success view when the user has successfully logged in */ -"qrLoginVerifyAuthorization.completedInstructions.title" = "You're logged in!"; +/* The formatted number of posts and followers for a site. '%1$@' is a placeholder for the site post count. '%2$@' is a placeholder for the site follower count. Example: `5,000 posts • 10M followers` */ +"reader.site.header.counts" = "%1$@ posts • %2$@ followers"; /* Spoken accessibility label */ "readerDetail.backButton.accessibilityLabel" = "Back"; @@ -9339,6 +10996,9 @@ translators: Block name. %s: The localized block name */ /* Button title for the follow conversations tooltip. */ "readerDetail.followConversationTooltipButton.accessibilityLabel" = "Got it"; +/* Message for the follow conversations tooltip. */ +"readerDetail.followConversationTooltipMessage.accessibilityLabel" = "Get notified when new comments are added to this post."; + /* Title of follow conversations tooltip. */ "readerDetail.followConversationTooltipTitle.accessibilityLabel" = "Follow the conversation"; @@ -9360,6 +11020,54 @@ translators: Block name. %s: The localized block name */ /* Title for the tooltip anchor. */ "readerDetail.tooltipAnchorTitle.accessibilityLabel" = "New"; +/* The button title for the transfer footer view in Register Domain screen */ +"register.domain.transfer.button.title" = "Transfer domain"; + +/* The title for the transfer footer view in Register Domain screen */ +"register.domain.transfer.title" = "Looking to transfer a domain you already own?"; + +/* Information of what related post are and how they are presented */ +"relatedPostsSettings.optionsFooter" = "Related Posts displays relevant content from your site below your posts."; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview1.details" = "in \"Mobile\""; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview1.title" = "Big iPhone\/iPad Update Now Available"; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview2.details" = "in \"Apps\""; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview2.title" = "The WordPress for Android App Gets a Big Facelift"; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview3.details" = "in \"Upgrade\""; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview3.title" = "Upgrade Focus: VideoPress For Weddings"; + +/* Section title for related posts section preview */ +"relatedPostsSettings.previewsHeaders" = "Preview"; + +/* Label for Related Post header preview */ +"relatedPostsSettings.relatedPostsHeader" = "Related Posts"; + +/* Message to show when setting save failed */ +"relatedPostsSettings.settingsUpdateFailed" = "Settings update failed"; + +/* Label for configuration switch to show/hide the header for the related posts section */ +"relatedPostsSettings.showHeader" = "Show Header"; + +/* Label for configuration switch to enable/disable related posts */ +"relatedPostsSettings.showRelatedPosts" = "Show Related Posts"; + +/* Label for configuration switch to show/hide images thumbnail for the related posts */ +"relatedPostsSettings.showThumbnail" = "Show Images"; + +/* Title for screen that allows configuration of your blog/site related posts settings. */ +"relatedPostsSettings.title" = "Related Posts"; + /* User action to dismiss media options. */ "shareExtension.editor.attachmentActions.dismiss" = "Dismiss"; @@ -9375,24 +11083,168 @@ translators: Block name. %s: The localized block name */ /* Share extension error dialog cancel button label. */ "shareModularViewController.retryAlert.dismiss" = "Dismiss"; +/* Share extension error dialog text. */ +"shareModularViewController.retryAlert.message" = "Whoops, something went wrong while sharing. You can try again, maybe it was a glitch."; + /* Share extension error dialog title. */ "shareModularViewController.retryAlert.title" = "Sharing Error"; +/* Template site address for the search bar. */ +"site.cration.domain.site.address" = "https:\/\/yoursitename.com"; + +/* The error message to show in the 'Site Creation > Assembly Step' when the domain checkout fails for unknown reasons. */ +"site.creation.assembly.step.domain.checkout.error.subtitle" = "Your website has been created successfully, but we encountered an issue while preparing your custom domain for checkout. Please try again or contact support for assistance."; + +/* Site name description that sits in the template website view. */ +"site.creation.domain.tooltip.description" = "Like the example above, a domain allows people to find and visit your site from their web browser."; + +/* Site name that is placed in the tooltip view. */ +"site.creation.domain.tooltip.site.name" = "YourSiteName.com"; + +/* Back button title shown in Site Creation flow to come back from Plan selection to Domain selection */ +"siteCreation.domain.backButton.title" = "Domains"; + +/* Button to progress to the next step after selecting domain in Site Creation */ +"siteCreation.domains.buttons.selectDomain" = "Select domain"; + +/* Accessibility label for audio items in the media collection view. The parameter is the creation date of the audio. */ +"siteMedia.accessibilityLabelAudio" = "Audio, %@"; + +/* Accessibility label for other media items in the media collection view. The parameter is the filename file. */ +"siteMedia.accessibilityLabelDocument" = "Document, %@"; + +/* Accessibility label for image thumbnails in the media collection view. The parameter is the creation date of the image. */ +"siteMedia.accessibilityLabelImage" = "Image, %@"; + +/* Accessibility label for video thumbnails in the media collection view. The parameter is the creation date of the video. */ +"siteMedia.accessibilityLabelVideo" = "Video, %@"; + +/* Accessibility label to use when creation date from media asset is not know. */ +"siteMedia.accessibilityUnknownCreationDate" = "Unknown creation date"; + +/* Accessibility hint for actions when displaying media items. */ +"siteMedia.cellAccessibilityHint" = "Select media."; + +/* Accessibility hint for media item preview for user's viewing an item in their media library */ +"siteMediaItem.contentViewAccessibilityHint" = "Tap to view media in full screen"; + +/* Accessibility label for media item preview for user's viewing an item in their media library */ +"siteMediaItem.contentViewAccessibilityLabel" = "Preview media"; + +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Add"; + +/* Button selection media in media picker */ +"siteMediaPicker.deselect" = "Deselect"; + +/* Button selection media in media picker */ +"siteMediaPicker.select" = "Select"; + +/* Media screen navigation title */ +"siteMediaPicker.title" = "Media"; + +/* Title for screen to select the privacy options for a blog */ +"siteSettings.privacy.title" = "Privacy"; + +/* Hint for users when hidden privacy setting is set */ +"siteVisibility.hidden.hint" = "Your site is hidden from visitors behind a \"Coming Soon\" notice until it is ready for viewing."; + +/* Text for privacy settings: Hidden */ +"siteVisibility.hidden.title" = "Hidden"; + +/* Hint for users when private privacy setting is set */ +"siteVisibility.private.hint" = "Your site is only visible to you and users you approve."; + +/* Text for privacy settings: Private */ +"siteVisibility.private.title" = "Private"; + +/* Hint for users when public privacy setting is set */ +"siteVisibility.public.hint" = "Your site is visible to everyone, and it may be indexed by search engines."; + +/* Text for privacy settings: Public */ +"siteVisibility.public.title" = "Public"; + +/* Text for unknown privacy setting */ +"siteVisibility.unknown.hint" = "Unknown"; + +/* Text for unknown privacy setting */ +"siteVisibility.unknown.title" = "Unknown"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "Reminders"; +/* Body text for the Jetpack Social no connection view */ +"social.noconnection.body" = "Increase your traffic by auto-sharing your posts with your friends on social media."; + +/* Title for the connect button to add social sharing for the Jetpack Social no connection view */ +"social.noconnection.connectAccounts" = "Connect accounts"; + +/* Accessibility label for the social media icons in the Jetpack Social no connection view */ +"social.noconnection.icons.accessibility.label" = "Social media icons"; + +/* Title for the not now button to hide the Jetpack Social no connection view */ +"social.noconnection.notnow" = "Not now"; + +/* Plural body text for the Jetpack Social no shares dashboard card. %1$d is the number of social accounts the user has. */ +"social.noshares.body.plural" = "Your posts won’t be shared to your %1$d social accounts."; + +/* Singular body text for the Jetpack Social no shares dashboard card. */ +"social.noshares.body.singular" = "Your posts won’t be shared to your social account."; + +/* Accessibility label for the social media icons in the Jetpack Social no shares dashboard card */ +"social.noshares.icons.accessibility.label" = "Social media icons"; + +/* Title for the button to subscribe to Jetpack Social on the no shares dashboard card */ +"social.noshares.subscribe" = "Subscribe to share more"; + +/* Section title for the disabled Twitter service in the Social screen */ +"social.section.disabledTwitter.header" = "Twitter Auto-Sharing Is No Longer Available"; + +/* Text for a hyperlink that allows the user to learn more about the Twitter deprecation. */ +"social.twitterDeprecation.link" = "Find out more"; + +/* A smallprint that hints the reason behind why Twitter is deprecated. */ +"social.twitterDeprecation.text" = "Twitter auto-sharing is no longer available due to Twitter's changes in terms and pricing."; + /* Accessibility label used for distinguishing Views and Visitors in the Stats → Insights Views Visitors Line chart. */ "stats.insights.accessibility.label.viewsVisitorsLastDays" = "Last 7-days"; /* Accessibility label used for distinguishing Views and Visitors in the Stats → Insights Views Visitors Line chart. */ "stats.insights.accessibility.label.viewsVisitorsPreviousDays" = "Previous 7-days"; +/* Label shown on some metrics in the Stats Insights section, such as Comments count. The placeholders will be populated with a change and a percentage – e.g. '+17 (40%) higher than the previous 7-days'. The *s mark the numerical values, which will be highlighted differently from the rest of the text. */ +"stats.insights.label.totalLikes.higher" = "*%1$@%2$@ (%3$@%%)* higher than the previous 7-days"; + +/* Label shown on some metrics in the Stats Insights section, such as Comments count. The placeholders will be populated with a change and a percentage – e.g. '-17 (40%) lower than the previous 7-days'. The *s mark the numerical values, which will be highlighted differently from the rest of the text. */ +"stats.insights.label.totalLikes.lower" = "*%1$@%2$@ (%3$@%%)* lower than the previous 7-days"; + +/* Label shown in Stats Insights when a metric is showing the same level as the previous 7 days */ +"stats.insights.label.totalLikes.same" = "The same as the previous 7-days"; + +/* Stats insights views higher than previous 7 days */ +"stats.insights.label.views.sevenDays.higher" = "Your views in the last 7-days are %@ higher than the previous 7-days.\n"; + +/* Stats insights views lower than previous 7 days */ +"stats.insights.label.views.sevenDays.lower" = "Your views in the last 7-days are %@ lower than the previous 7-days.\n"; + +/* Stats insights label shown when the user's view count is the same as the previous 7 days. */ +"stats.insights.label.views.sevenDays.same" = "Your views in the last 7-days are the same as the previous 7-days.\n"; + /* Last 7-days legend label */ "stats.insights.label.viewsVisitorsLastDays" = "Last 7-days"; /* Previous 7-days legend label */ "stats.insights.label.viewsVisitorsPreviousDays" = "Previous 7-days"; +/* Stats insights visitors higher than previous 7 days */ +"stats.insights.label.visitors.sevenDays.higher" = "Your visitors in the last 7-days are %@ higher than the previous 7-days.\n"; + +/* Stats insights visitors lower than previous 7 days */ +"stats.insights.label.visitors.sevenDays.lower" = "Your visitors in the last 7-days are %@ lower than the previous 7-days.\n"; + +/* Stats insights label shown when the user's visitor count is the same as the previous 7 days. */ +"stats.insights.label.visitors.sevenDays.same" = "Your visitors in the last 7-days are the same as the previous 7-days.\n"; + /* Title for Comments count in Latest Post Summary stats card. */ "stats.insights.latestPostSummary.comments" = "Comments"; @@ -9447,15 +11299,171 @@ translators: Block name. %s: The localized block name */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "Not enough activity. Check back later when your site's had more visitors!"; +/* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ +"stats.insights.totalLikes.guideText.plural" = "Your latest post %1$@ has received %2$@ likes."; + +/* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ +"stats.insights.totalLikes.guideText.singular" = "Your latest post %1$@ has received %2$@ like."; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Dismiss"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Photos provided by Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "Search to find free photos to add to your Media Library!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "In this conversation"; /* Section title for regular suggestions */ "suggestions.section.regular" = "Site members"; +/* Dismiss the current view */ +"support.button.close.title" = "Done"; + +/* Accessibility hint, informing user the button can be used to visit the Jetpack migration FAQ website. */ +"support.button.jetpackMigation.accessibilityHint" = "Tap to visit the Jetpack app FAQ in an external browser"; + +/* Option in Support view to visit the Jetpack migration FAQ website. */ +"support.button.jetpackMigration.title" = "Visit our FAQ"; + +/* Button for confirming logging out from WordPress.com account */ +"support.button.logOut.title" = "Log Out"; + +/* Accessibility hint, informing user the button can be used to visit the support forums website. */ +"support.button.visitForum.accessibilityHint" = "Tap to visit the community forum website in an external browser"; + +/* Option in Support view to visit the WordPress.org support forums. */ +"support.button.visitForum.title" = "Visit WordPress.org"; + +/* Indicator that the chat bot is processing user's input. */ +"support.chatBot.botThinkingIndicator" = "Thinking..."; + +/* Dismiss the current view */ +"support.chatBot.close.title" = "Close"; + +/* Button for users to contact the support team directly. */ +"support.chatBot.contactSupport" = "Contact support"; + +/* Initial message shown to the user when the chat starts. */ +"support.chatBot.firstMessage" = "Hi there, I'm the Jetpack AI Assistant.\\n\\nWhat can we help you with?\\n\\nIf I can't answer your question, I'll help you open a support ticket with our team!"; + +/* Placeholder text for the chat input field. */ +"support.chatBot.inputPlaceholder" = "Send a message..."; + +/* An example question shown to a user seeking support */ +"support.chatBot.questionFive" = "I forgot my login information"; + +/* An example question shown to a user seeking support */ +"support.chatBot.questionFour" = "Why can't I log in?"; + +/* An example question shown to a user seeking support */ +"support.chatBot.questionOne" = "What is my site address?"; + +/* An example question shown to a user seeking support */ +"support.chatBot.questionSix" = "How can I use my custom domain in the app?"; + +/* An example question shown to a user seeking support */ +"support.chatBot.questionThree" = "I can't upload photos\/videos"; + +/* An example question shown to a user seeking support */ +"support.chatBot.questionTwo" = "Help, my site is down!"; + +/* Option for users to report a chat bot answer as inaccurate. */ +"support.chatBot.reportInaccuracy" = "Report as inaccurate"; + +/* Button title referring to the sources of information. */ +"support.chatBot.sources" = "Sources"; + +/* Prompt for users suggesting to select a default question from the list to start a support chat. */ +"support.chatBot.suggestionsPrompt" = "Not sure what to ask?"; + +/* Notice informing user that there was an error submitting their support ticket. */ +"support.chatBot.ticketCreationFailure" = "Error submitting support ticket"; + +/* Notice informing user that their support ticket is being created. */ +"support.chatBot.ticketCreationLoading" = "Creating support ticket..."; + +/* Notice informing user that their support ticket has been created. */ +"support.chatBot.ticketCreationSuccess" = "Ticket created"; + +/* Title of the view that shows support chat bot. */ +"support.chatBot.title" = "Contact Support"; + +/* A title for a text that displays a transcript of an answer in a support chat */ +"support.chatBot.zendesk.answer" = "Answer"; + +/* A title for a text that displays a transcript of user's question in a support chat */ +"support.chatBot.zendesk.question" = "Question"; + +/* A title for a text that displays a transcript from a conversation between Jetpack Mobile Bot (chat bot) and a user */ +"support.chatBot.zendesk.transcript" = "Jetpack Mobile Bot transcript"; + +/* Suggestion in Support view to visit the Forums. */ +"support.row.communityForum.title" = "Ask a question in the community forum and get help from our group of volunteers."; + +/* Accessibility hint describing what happens if the Contact Email button is tapped. */ +"support.row.contactEmail.accessibilityHint" = "Shows a dialog for changing the Contact Email."; + +/* Display value for Support email field if there is no user email address. */ +"support.row.contactEmail.emailNoteSet.detail" = "Not Set"; + +/* Support email label. */ +"support.row.contactEmail.title" = "Email"; + +/* Option in Support view to contact the support team. */ +"support.row.contactUs.title" = "Contact support"; + +/* Option in Support view to enable/disable adding debug information to support ticket. */ +"support.row.debug.title" = "Debug"; + +/* Support email label. */ +"support.row.email.title" = "Email"; + +/* Option in Support view to view the Forums. */ +"support.row.forums.title" = "WordPress Forums"; + +/* Option in Support view to launch the Help Center. */ +"support.row.helpCenter.title" = "WordPress Help Centre"; + +/* An informational card description in Support view explaining what tapping the link on card does */ +"support.row.jetpackMigration.description" = "Our FAQ provides answers to common questions you may have."; + +/* An informational card title in Support view */ +"support.row.jetpackMigration.title" = "Thank you for switching to the Jetpack app!"; + +/* Option in Support view to see activity logs. */ +"support.row.logs.title" = "Logs"; + +/* Option in Support view to access previous help tickets. */ +"support.row.tickets.title" = "Tickets"; + +/* Label in Support view displaying the app version. */ +"support.row.version.title" = "Version"; + +/* Support screen footer text explaining the benefits of enabling the Debug feature. */ +"support.sectionFooter.advanced.title" = "Enable Debugging to include additional information in your logs that can help troubleshoot issues with the app."; + +/* WordPress.com sign-out section header title */ +"support.sectionHeader.account.title" = "WordPress.com Account"; + +/* Section header in Support view for advanced information. */ +"support.sectionHeader.advanced.title" = "Advanced"; + +/* Section header in Support view for the Forums. */ +"support.sectionHeader.forum.title" = "Community Forums"; + +/* Section header in Support view for priority support. */ +"support.sectionHeader.prioritySupport.title" = "Priority Support"; + +/* View title for Help & Support page. */ +"support.title" = "Help"; + +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Search to find GIFs to add to your Media Library!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "these items will be deleted:"; @@ -9477,9 +11485,27 @@ translators: Block name. %s: The localized block name */ /* Verb. Dismiss the web view screen. */ "webKit.button.dismiss" = "Dismiss"; +/* Preview title of all-time posts and most views widget */ +"widget.allTimePostViews.previewTitle" = "All-Time Posts & Most Views"; + +/* Preview title of all-time views widget */ +"widget.allTimeViews.previewTitle" = "All-Time Views"; + +/* Preview title of all-time views and visitors widget */ +"widget.allTimeViewsVisitors.previewTitle" = "All-Time Views & Visitors"; + /* Title of best views ever label in all time widget */ "widget.alltime.bestviews.label" = "Best views ever"; +/* Title of the label which displays the number of the most daily views the site has ever had. Keep the translation as short as possible. */ +"widget.alltime.bestviewsshort.label" = "Most Views"; + +/* Title of the no data view in all time widget */ +"widget.alltime.nodata.view.title" = "Unable to load all time stats."; + +/* Title of the no site view in all time widget */ +"widget.alltime.nosite.view.title" = "Create or add a site to see all time stats."; + /* Title of posts label in all time widget */ "widget.alltime.posts.label" = "Posts"; @@ -9501,6 +11527,18 @@ translators: Block name. %s: The localized block name */ /* Title of the unconfigured view in today widget */ "widget.jetpack.today.unconfigured.view.title" = "Log in to Jetpack to see today's stats."; +/* Title of the one-liner information consist of views field and all time date range in lock screen all time views widget */ +"widget.lockscreen.alltimeview.label" = "All-Time Views"; + +/* Title of the one-liner information consist of views field and today date range in lock screen today views widget */ +"widget.lockscreen.todayview.label" = "Views Today"; + +/* Title of the no data view in this week widget */ +"widget.thisweek.nodata.view.title" = "Unable to load this week's stats."; + +/* Title of the no site view in this week widget */ +"widget.thisweek.nosite.view.title" = "Create or add a site to see this week's stats."; + /* Description of all time widget in the preview */ "widget.thisweek.preview.description" = "Stay up to date with this week activity on your WordPress site."; @@ -9513,12 +11551,21 @@ translators: Block name. %s: The localized block name */ /* Title of comments label in today widget */ "widget.today.comments.label" = "Comments"; +/* Title of the disabled view in today widget */ +"widget.today.disabled.view.title" = "Stats have moved to the Jetpack app. Switching is free and only takes a minute."; + /* Title of likes label in today widget */ "widget.today.likes.label" = "Likes"; +/* Fallback title of the no data view in the stats widget */ +"widget.today.nodata.view.fallbackTitle" = "Unable to load site stats."; + /* Title of the no data view in today widget */ "widget.today.nodata.view.title" = "Unable to load today's stats."; +/* Title of the no site view in today widget */ +"widget.today.nosite.view.title" = "Create or add a site to see today's stats."; + /* Description of today widget in the preview */ "widget.today.preview.description" = "Stay up to date with today's activity on your WordPress site."; @@ -9537,6 +11584,15 @@ translators: Block name. %s: The localized block name */ /* Title of visitors label in today widget */ "widget.today.visitors.label" = "Visitors"; +/* Preview title of today's likes and commnets widget */ +"widget.todayLikesComments.previewTitle" = "Today's Likes & Comments"; + +/* Preview title of today's views widget */ +"widget.todayViews.previewTitle" = "Today's Views"; + +/* Preview title of today's views and visitors widget */ +"widget.todayViewsVisitors.previewTitle" = "Today's Views & Visitors"; + /* Second part of delete screen title stating [the site] will be unavailable in the future. */ "will be unavailable in the future." = "will be unavailable in the future."; @@ -9561,6 +11617,30 @@ translators: Block name. %s: The localized block name */ /* This is a comma separated list of keywords used for spotlight indexing of the 'My Sites' tab. */ "wordpress, sites, site, blogs, blog" = "wordpress, sites, site, blogs, blog"; +/* Jetpack Plugin Modal on WordPress primary button title */ +"wordpress.jetpack.plugin.modal.primary.button.title" = "Switch to the Jetpack app"; + +/* Jetpack Plugin Modal on WordPress secondary button title */ +"wordpress.jetpack.plugin.modal.secondary.button.title" = "Continue without Jetpack"; + +/* Jetpack Plugin Modal (multiple plugins) on WordPress subtitle with formatted texts. %1$@ is for the site name. */ +"wordpress.jetpack.plugin.modal.subtitle.plural" = "%1$@ is using individual Jetpack plugins, which isn't supported by the WordPress App."; + +/* Jetpack Plugin Modal on WordPress (single plugin) subtitle with formatted texts. %1$@ is for the site name and %2$@ is for the specific plugin name. */ +"wordpress.jetpack.plugin.modal.subtitle.singular" = "%1$@ is using the %2$@ plugin, which isn't supported by the WordPress App."; + +/* Second paragraph of the Jetpack Plugin Modal on WordPress asking the user to switch to Jetpack. */ +"wordpress.jetpack.plugin.modal.subtitle.switch" = "Please switch to the Jetpack app where we'll guide you through connecting the full Jetpack plugin so that you can use all the apps features for this site."; + +/* Jetpack Plugin Modal title in WordPress */ +"wordpress.jetpack.plugin.modal.title" = "Sorry, this site isn't supported by the WordPress app"; + +/* Description of the jetpack migration success card, used in My site. */ +"wp.migration.successCard.description" = "Welcome to the Jetpack app. You can uninstall the WordPress app."; + +/* Title of a button that displays a blog post in a web view. */ +"wp.migration.successCard.learnMore" = "Learn more"; + /* Placeholder for site url, if the url is unknown.Presented when logging in with a site address that does not have a valid Jetpack installation.The error would read: to use this app for your site you'll need... */ "your site" = "your site"; diff --git a/WordPress/Resources/en-GB.lproj/Localizable.strings b/WordPress/Resources/en-GB.lproj/Localizable.strings index 7ffa497033f2..749d6cdd5e9b 100644 --- a/WordPress/Resources/en-GB.lproj/Localizable.strings +++ b/WordPress/Resources/en-GB.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-12 09:37:53+0000 */ +/* Translation-Revision-Date: 2024-01-03 09:18:47+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: en_GB */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "All"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "All WordPress.com annual plans include a custom domain name. Register your free domain now."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "All WordPress.com plans include a custom domain name. Register your free premium domain now."; @@ -9463,6 +9466,9 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Short description for the Bloganuary event, shown right below the title. */ "bloganuary.dashboard.card.description" = "For the month of January, blogging prompts will come from Bloganuary – our community challenge to build a blogging habit for the new year."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary is here!"; + /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary is coming!"; @@ -10528,6 +10534,9 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "You don't have any sites"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Add site"; + /* Button that reveals more site actions */ "mySite.siteActions.button" = "Site Actions"; diff --git a/WordPress/Resources/en.lproj/Localizable.strings b/WordPress/Resources/en.lproj/Localizable.strings index 4f670fc9b27c..0108161cb77e 100644 --- a/WordPress/Resources/en.lproj/Localizable.strings +++ b/WordPress/Resources/en.lproj/Localizable.strings @@ -2,9 +2,6 @@ /* MARK: - Localizable.strings */ -/* Per-year postfix shown after a domain's cost. */ -" / year" = " / year"; - /* Description for the restore action. $1$@ is a placeholder for the selected date. */ "%1$@ is the selected point for your restore." = "%1$@ is the selected point for your restore."; @@ -209,9 +206,6 @@ /* Displays the number of words and characters in text */ "%li words, %li characters" = "%1$li words, %2$li characters"; -/* translators: %s: Block name e.g. \"Image block\"\ntranslators: Block name. %s: The localized block name */ -"%s block" = "%s block"; - /* translators: %s: block title e.g: \"Paragraph\". */ "%s block options" = "%s block options"; @@ -500,9 +494,6 @@ /* The title on the add category screen */ "Add a Category" = "Add a Category"; -/* Hint for the reader CSS URL field */ -"Add a custom CSS URL here to be loaded in Reader. If you're running Calypso locally this can be something like: http://192.168.15.23:3000/calypso/reader-mobile.css" = "Add a custom CSS URL here to be loaded in Reader. If you're running Calypso locally this can be something like: http://192.168.15.23:3000/calypso/reader-mobile.css"; - /* Label of the button that starts the purchase of an additional redirected domain in the Domains Dashboard. */ "Add a domain" = "Add a domain"; @@ -695,9 +686,8 @@ /* Message shown when all Quick Start tasks are complete. */ "All tasks complete!" = "All tasks complete!"; -/* Footer of the free domain registration section for a paid plan. - Information about redeeming domain credit on site dashboard. */ -"All WordPress.com plans include a custom domain name. Register your free premium domain now." = "All WordPress.com plans include a custom domain name. Register your free premium domain now."; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "All WordPress.com annual plans include a custom domain name. Register your free domain now."; /* Insights 'All-Time' header */ "All-Time" = "All-Time"; @@ -1051,9 +1041,6 @@ /* The plugin can not be manually updated or deactivated */ "Auto-managed on this site" = "Auto-managed on this site"; -/* Label indicating that a domain name registration will automatically renew */ -"Auto-renew enabled" = "Auto-renew enabled"; - /* Discussion Settings Title Settings: Comments Approval settings */ "Automatically Approve" = "Automatically Approve"; @@ -1280,9 +1267,6 @@ /* translators: displayed right after the block is duplicated. */ "Block duplicated" = "Block duplicated"; -/* Popup title about why this post is being opened in block editor */ -"Block editor enabled" = "Block editor enabled"; - /* translators: displayed right after the block is grouped */ "Block grouped" = "Block grouped"; @@ -1354,6 +1338,9 @@ /* Short description for the Bloganuary event, shown right below the title. */ "bloganuary.dashboard.card.description" = "For the month of January, blogging prompts will come from Bloganuary — our community challenge to build a blogging habit for the new year."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary is here!"; + /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary is coming!"; @@ -1425,9 +1412,6 @@ Note that the word 'go' here should have a closer meaning to 'start' rather than /* Description of a Quick Start Tour */ "Bring media straight from your device or camera to your site." = "Bring media straight from your device or camera to your site."; -/* Description of a Quick Start Tour */ -"Browse all our themes to find your perfect fit." = "Browse all our themes to find your perfect fit."; - /* Jetpack Settings: Brute Force Attack Protection Section */ "Brute Force Attack Protection" = "Brute Force Attack Protection"; @@ -1688,6 +1672,9 @@ Note that the word 'go' here should have a closer meaning to 'start' rather than /* Title of button that asks the users if they'd like to focus on checking their sites stats */ "Checking stats" = "Checking stats"; +/* Title for the checkout view */ +"checkout.title" = "Checkout"; + /* Screen reader text expressing the menu item is a child of another menu item. Argument is a name for another menu item. */ "Child of %@" = "Child of %@"; @@ -1713,8 +1700,7 @@ Note that the word 'go' here should have a closer meaning to 'start' rather than /* A text for title label on Login epilogue screen */ "Choose a site to open." = "Choose a site to open."; -/* Title for the screen to pick a theme and homepage for a site. - Title of a Quick Start Tour */ +/* Title for the screen to pick a theme and homepage for a site. */ "Choose a theme" = "Choose a theme"; /* Select the site's intent. Subtitle */ @@ -1812,7 +1798,6 @@ Note that the word 'go' here should have a closer meaning to 'start' rather than Action button to close edior and cancel changes or insertion of post Action button to close the editor Dismiss the current view - Dismiss the media picker for Stock Photos Dismisses the current screen Voiceover accessibility label informing the user that this button dismiss the current view */ "Close" = "Close"; @@ -1975,24 +1960,15 @@ Example: Reply to Pamela Nguyen */ /* The Quick Start Tour title after the user finished the step. */ "Completed: Check your site title" = "Completed: Check your site title"; -/* The Quick Start Tour title after the user finished the step. */ -"Completed: Choose a theme" = "Completed: Choose a theme"; - /* The Quick Start Tour title after the user finished the step. */ "Completed: Choose a unique site icon" = "Completed: Choose a unique site icon"; /* The Quick Start Tour title after the user finished the step. */ "Completed: Connect with other sites" = "Completed: Connect with other sites"; -/* The Quick Start Tour title after the user finished the step. */ -"Completed: Continue with site setup" = "Completed: Continue with site setup"; - /* The Quick Start Tour title after the user finished the step. */ "Completed: Create your site" = "Completed: Create your site"; -/* The Quick Start Tour title after the user finished the step. */ -"Completed: Explore plans" = "Completed: Explore plans"; - /* The Quick Start Tour title after the user finished the step. */ "Completed: Publish a post" = "Completed: Publish a post"; @@ -2145,9 +2121,6 @@ Example: Reply to Pamela Nguyen */ /* Button title. Tapping begins log in using Google. */ "Continue with Google" = "Continue with Google"; -/* Title of a Quick Start Tour */ -"Continue with site setup" = "Continue with site setup"; - /* Button title. Takes the user to the login with WordPress.com flow. */ "Continue With WordPress.com" = "Continue With WordPress.com"; @@ -2248,13 +2221,13 @@ Example: Reply to Pamela Nguyen */ "Couldn't connect to the WordPress site. There is no valid WordPress site at this address. Check the site address (URL) you entered." = "Couldn't connect to the WordPress site. There is no valid WordPress site at this address. Check the site address (URL) you entered."; /* Message to show to user when he tries to add a self-hosted site with RSD link present, but xmlrpc is missing. */ -"Couldn't connect. Required XML-RPC methods are missing on the server." = "Couldn't connect. Required XML-RPC methods are missing on the server."; +"Couldn't connect. Required XML-RPC methods are missing on the server. Please contact your hosting provider to solve this problem." = "Couldn't connect. Required XML-RPC methods are missing on the server. Please contact your hosting provider to solve this problem."; /* Message to show to user when he tries to add a self-hosted site but the host returned a 403 error, meaning that the access to the /xmlrpc.php file is forbidden. */ -"Couldn't connect. We received a 403 error when trying to access your site's XMLRPC endpoint. The app needs that in order to communicate with your site. Contact your host to solve this problem." = "Couldn't connect. We received a 403 error when trying to access your site's XMLRPC endpoint. The app needs that in order to communicate with your site. Contact your host to solve this problem."; +"Couldn't connect. We received a 403 error when trying to access your site's XMLRPC endpoint. The app needs that in order to communicate with your site. Please contact your hosting provider to solve this problem." = "Couldn't connect. We received a 403 error when trying to access your site's XMLRPC endpoint. The app needs that in order to communicate with your site. Please contact your hosting provider to solve this problem."; /* Message to show to user when he tries to add a self-hosted site but the host returned a 405 error, meaning that the host is blocking POST requests on /xmlrpc.php file. */ -"Couldn't connect. Your host is blocking POST requests, and the app needs that in order to communicate with your site. Contact your host to solve this problem." = "Couldn't connect. Your host is blocking POST requests, and the app needs that in order to communicate with your site. Contact your host to solve this problem."; +"Couldn't connect. Your host is blocking POST requests, and the app needs that in order to communicate with your site. Please contact your hosting provider to solve this problem." = "Couldn't connect. Your host is blocking POST requests, and the app needs that in order to communicate with your site. Please contact your hosting provider to solve this problem."; /* Error message when tag loading failed */ "Couldn't load tags." = "Couldn't load tags."; @@ -2287,9 +2260,6 @@ Example: Reply to Pamela Nguyen */ /* Register Domain - Address information field Country Code */ "Country Code" = "Country Code"; -/* Title of a section on the debug screen that shows a list of actions related to crash logging */ -"Crash Logging" = "Crash Logging"; - /* Label for switch to turn on/off sending crashes info */ "Crash reports" = "Crash reports"; @@ -2342,9 +2312,6 @@ Example: Reply to Pamela Nguyen */ /* Create New header text */ "Create New" = "Create New"; -/* Title for the site creation flow. */ -"Create New Site" = "Create New Site"; - /* Button for selecting the current page template. Button title, encourages users to create their first page on their blog. Title for button to make a page with the contents of the selected layout */ @@ -2599,30 +2566,58 @@ Example: Reply to Pamela Nguyen */ /* Navigates to debug menu only available in development builds */ "Debug" = "Debug"; -/* Debug settings title */ -"Debug Settings" = "Debug Settings"; +/* Debug menu item title */ +"debugMenu.analytics" = "Analytics"; /* Feature flags menu item */ "debugMenu.featureFlags" = "Feature Flags"; -/* General section title */ -"debugMenu.generalSectionTitle" = "General"; +/* Title of the screen that allows the user to change the Reader CSS URL for debug builds */ +"debugMenu.readerCellTitle" = "Reader CSS URL"; + +/* Placeholder for the reader CSS URL */ +"debugMenu.readerDefaultURL" = "Default URL"; + +/* Hint for the reader CSS URL field */ +"debugMenu.readerHit" = "Add a custom CSS URL here to be loaded in Reader. If you're running Calypso locally this can be something like: http://192.168.15.23:3000/calypso/reader-mobile.css"; + +/* Remote Config Debug Menu section title */ +"debugMenu.remoteConfig.currentValue" = "Current Value"; + +/* Remote Config Debug Menu section title */ +"debugMenu.remoteConfig.defaultValue" = "Default Value"; -/* Remote config params debug menu footer explaining the meaning of a cell with a checkmark. */ -"debugMenu.remoteConfig.footer" = "Overridden parameters are denoted by a checkmark."; +/* Remote Config Debug Menu section title */ +"debugMenu.remoteConfig.overridenValue" = "Remote Config"; -/* Hint for overriding remote config params */ -"debugMenu.remoteConfig.hint" = "Override the chosen param by defining a new value here."; +/* Remote Config Debug Menu section title */ +"debugMenu.remoteConfig.remoteConfigValue" = "Remote Config Value"; -/* Placeholder for overriding remote config params */ -"debugMenu.remoteConfig.placeholder" = "No remote or default value"; +/* Remote Config Debug Menu reset button title */ +"debugMenu.remoteConfig.reset" = "Reset"; -/* Remote Config debug menu title */ +/* Remote Config Debug Menu screen title + Remote Config debug menu title */ "debugMenu.remoteConfig.title" = "Remote Config"; /* Remove current quick start tour menu item */ "debugMenu.removeQuickStart" = "Remove Current Tour"; +/* Debug Menu section title */ +"debugMenu.section.logging" = "Logging"; + +/* Debug Menu section title */ +"debugMenu.section.quickStart" = "Quick Start"; + +/* Debug Menu section title */ +"debugMenu.section.settings" = "Settings"; + +/* Title for debug menu screen */ +"debugMenu.title" = "Developer"; + +/* Weekly Roundup debug menu item */ +"debugMenu.weeklyRoundup" = "Weekly Roundup"; + /* Only December needs to be translated */ "December 17, 2017" = "December 17, 2017"; @@ -2644,9 +2639,6 @@ Example: Reply to Pamela Nguyen */ Title for screen to select a default post format for a blog */ "Default Post Format" = "Default Post Format"; -/* Placeholder for the reader CSS URL */ -"Default URL" = "Default URL"; - /* Discussion Settings: Posts Section */ "Defaults for New Posts" = "Defaults for New Posts"; @@ -2830,6 +2822,9 @@ Example: Reply to Pamela Nguyen */ /* The expired label of the domain card in All Domains screen. */ "domain.management.card.expired.label" = "Expired"; +/* Label indicating that a domain name registration has no expiry date. */ +"domain.management.card.neverExpires.label" = "Never expires"; + /* The renews label of the domain card in All Domains screen. */ "domain.management.card.renews.label" = "Renews"; @@ -2926,12 +2921,6 @@ Example: Reply to Pamela Nguyen */ /* Noun. Title. Links to the Domains screen. */ "Domains" = "Domains"; -/* Description for the first domain purchased with a free plan. */ -"Domains purchased on this site will redirect to %@" = "Domains purchased on this site will redirect to %@"; - -/* Description for the first domain purchased with a free plan. */ -"Domains purchased on this site will redirect users to " = "Domains purchased on this site will redirect users to "; - /* Title for the checkout screen. */ "domains.checkout.title" = "Checkout"; @@ -2950,6 +2939,15 @@ Example: Reply to Pamela Nguyen */ /* Title of screen where user chooses a site to connect to their selected domain */ "domains.sitePicker.title" = "Choose Site"; +/* Help button */ +"domainSelection.helpButton.title" = "Help"; + +/* Description for the first domain purchased with a free plan. */ +"domainSelection.redirectPrompt.title" = "Domains purchased on this site will redirect to %1$@"; + +/* Search domain - Title for the Suggested domains screen */ +"domainSelection.search.title" = "Search domains"; + /* Label for button to log in using your site address. The underscores _..._ denote underline */ "Don't have an account? _Sign up_" = "Don't have an account? _Sign up_"; @@ -3122,8 +3120,7 @@ Example: Reply to Pamela Nguyen */ /* Editing GIF alert default action button. Edits a Comment Edits the comment - User action to edit media details. - Verb, edit a comment */ + User action to edit media details. */ "Edit" = "Edit"; /* Title for the edit more button section */ @@ -3188,9 +3185,6 @@ Example: Reply to Pamela Nguyen */ /* Title for the editor settings section */ "Editor" = "Editor"; -/* Edit Action Spoken hint. */ -"Edits a comment" = "Edits a comment"; - /* VoiceOver accessibility hint, informing the user the button can be used to Edit the Comment. */ "Edits the comment." = "Edits the comment."; @@ -3324,9 +3318,6 @@ Example: Reply to Pamela Nguyen */ /* Message explaining why the user might enter a password. */ "Enter a password to protect this post" = "Enter a password to protect this post"; -/* Secondary message shown when there are no domains that match the user entered text. */ -"Enter different words above and we'll look for an address that matches it." = "Enter different words above and we'll look for an address that matches it."; - /* Accessibility Label for the enter full screen button on the comment reply text view */ "Enter Full Screen" = "Enter Full Screen"; @@ -3489,7 +3480,6 @@ Example: Reply to Pamela Nguyen */ "Example story title" = "Example story title"; /* Placeholder for the site url textfield. - Provides a sample of what a domain name looks like. Site Address placeholder */ "example.com" = "example.com"; @@ -3520,24 +3510,15 @@ Example: Reply to Pamela Nguyen */ /* Screen reader hint (non-imperative) about what does the site menu area selector button do. */ "Expands to select a different menu area" = "Expands to select a different menu area"; -/* Label indicating that a domain name registration has expired. */ -"Expired" = "Expired"; - /* Title for the error view when the user scanned an expired log in code */ "Expired log in code" = "Expired log in code"; /* Title. Indicates an expiration date. */ "Expires on" = "Expires on"; -/* Label indicating the date on which a domain name registration will expire. The %@ placeholder will be replaced with a date at runtime. */ -"Expires on %@" = "Expires on %@"; - /* Placeholder text for the tagline of a site */ "Explain what this site is about." = "Explain what this site is about."; -/* Title of a Quick Start Tour */ -"Explore plans" = "Explore plans"; - /* Export Content confirmation action title Label for selecting the Export Content Settings item */ "Export Content" = "Export Content"; @@ -3651,6 +3632,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the file type (.JPG, .PNG, etc) for a media asset (image / video) */ "File type" = "File type"; +/* No comment provided by engineer. */ +"File type not supported as a media file." = "File type not supported as a media file."; + /* Film & Television site intent topic */ "Film & Television" = "Film & Television"; @@ -3741,8 +3725,7 @@ Example: Reply to Pamela Nguyen */ Label for number of followers. */ "Followers" = "Followers"; -/* Accessibility label for following buttons. - Title of the Following Reader tab +/* Title of the Following Reader tab User is following the blog. Verb. Button title. The user is following a blog. */ "Following" = "Following"; @@ -3759,9 +3742,6 @@ Example: Reply to Pamela Nguyen */ /* Filters Follows Notifications */ "Follows" = "Follows"; -/* Spoken hint describing action for unselected following buttons. */ -"Follows blog" = "Follows blog"; - /* VoiceOver accessibility hint, informing the user the button can be used to follow a blog. */ "Follows the blog." = "Follows the blog."; @@ -3771,6 +3751,9 @@ Example: Reply to Pamela Nguyen */ /* No comment provided by engineer. */ "Font Size" = "Font Size"; +/* translators: %1$s: Font size name e.g. Small */ +"Font Size, %1$s" = "Font Size, %1$s"; + /* Food site intent topic */ "Food" = "Food"; @@ -3795,9 +3778,6 @@ Example: Reply to Pamela Nguyen */ /* Browse free themes selection title */ "Free" = "Free"; -/* Label shown for domains that will be free for the first year due to the user having a premium plan with available domain credit. */ -"Free for the first year " = "Free for the first year "; - /* One of the options when selecting More in the Post Editor's format bar */ "Free GIF Library" = "Free GIF Library"; @@ -3916,9 +3896,6 @@ Example: Reply to Pamela Nguyen */ /* Name of the Quick Start list that guides users through a few tasks to explore the WordPress app. */ "Get to know the WordPress app" = "Get to know the WordPress app"; -/* Title of the card that starts the purchase of the first redirected domain in the Domains Dashboard. */ -"Get your domain" = "Get your domain"; - /* Title of the second alert preparing users to grant permission for us to send them push notifications. */ "Get your notifications faster" = "Get your notifications faster"; @@ -3946,9 +3923,6 @@ Example: Reply to Pamela Nguyen */ /* Option to select the Gmail app when logging in with magic links */ "Gmail" = "Gmail"; -/* No comment provided by engineer. */ -"Go back" = "Go back"; - /* Button title. Tapping lets the user view the sites they follow. */ "Go to Following" = "Go to Following"; @@ -4044,18 +4018,12 @@ Example: Reply to Pamela Nguyen */ /* This value is used to set the accessibility hint text for viewing the user's notifications. */ "Guides you through the process of checking your notifications." = "Guides you through the process of checking your notifications."; -/* This value is used to set the accessibility hint text for choosing a theme for the user's site. */ -"Guides you through the process of choosing a theme for your site." = "Guides you through the process of choosing a theme for your site."; - /* This value is used to set the accessibility hint text for creating a new page for the user's site. */ "Guides you through the process of creating a new page for your site." = "Guides you through the process of creating a new page for your site."; /* This value is used to set the accessibility hint text for creating the user's site. */ "Guides you through the process of creating your site." = "Guides you through the process of creating your site."; -/* This value is used to set the accessibility hint text for exploring plans on the user's site. */ -"Guides you through the process of exploring plans for your site." = "Guides you through the process of exploring plans for your site."; - /* This value is used to set the accessibility hint text for following the sites of other users. */ "Guides you through the process of following other sites." = "Guides you through the process of following other sites."; @@ -4071,9 +4039,6 @@ Example: Reply to Pamela Nguyen */ /* This value is used to set the accessibility hint text for setting the site title. */ "Guides you through the process of setting a title for your site." = "Guides you through the process of setting a title for your site."; -/* This value is used to set the accessibility hint text for setting up the user's site. */ -"Guides you through the process of setting up your site." = "Guides you through the process of setting up your site."; - /* This value is used to set the accessibility hint text for uploading a site icon. */ "Guides you through the process of uploading an icon for your site." = "Guides you through the process of uploading an icon for your site."; @@ -4230,9 +4195,6 @@ Example: Reply to Pamela Nguyen */ /* Message to show when site icon update failed */ "Icon update failed" = "Icon update failed"; -/* Message explaining that they will need to install Jetpack on one of their sites. */ -"If you already have a site, you’ll need to install the free Jetpack plugin and connect it to your WordPress.com account." = "If you already have a site, you’ll need to install the free Jetpack plugin and connect it to your WordPress.com account."; - /* The instructions text about not being able to find the magic link email. */ "If you can’t find the email, please check your junk or spam email folder" = "If you can’t find the email, please check your junk or spam email folder"; @@ -4904,9 +4866,6 @@ Please install the %3$@ to use the app with this site."; /* Body text of the first alert preparing users to grant permission for us to send them push notifications. */ "Learn about new comments, likes, and follows in seconds." = "Learn about new comments, likes, and follows in seconds."; -/* Description of a Quick Start Tour */ -"Learn about the marketing and SEO tools in our paid plans." = "Learn about the marketing and SEO tools in our paid plans."; - /* A button title. Link to cookie policy Menu title to show the prompts feature introduction modal. @@ -5055,9 +5014,6 @@ Please install the %3$@ to use the app with this site."; /* Displayed while a comment is being loaded. */ "Loading comment..." = "Loading comment..."; -/* Shown while the app waits for the domain suggestions web service to return during the site creation process. */ -"Loading domains" = "Loading domains"; - /* Displayed while a call is loading the history. */ "Loading history..." = "Loading history..."; @@ -5291,9 +5247,6 @@ Please install the %3$@ to use the app with this site."; Title of the 'Me' tab - used for spotlight indexing on iOS. */ "Me" = "Me"; -/* Products header text in Me Screen. */ -"me.products.header" = "Products"; - /* Noun. Title. Links to the blog's Media library. The menu item to select during a guided tour. Title for the media section in site settings screen @@ -5790,6 +5743,9 @@ Please install the %3$@ to use the app with this site."; /* Message title for when a user has no sites. */ "mySite.noSites.title" = "You don't have any sites"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Add site"; + /* Button that reveals more site actions */ "mySite.siteActions.button" = "Site Actions"; @@ -5853,8 +5809,11 @@ Please install the %3$@ to use the app with this site."; /* Describes a status of a plugin */ "Needs Update" = "Needs Update"; -/* Label indicating that a domain name registration has no expiry date. */ -"Never expires" = "Never expires"; +/* No comment provided by engineer. */ +"Network connection lost, working offline" = "Network connection lost, working offline"; + +/* No comment provided by engineer. */ +"Network connection re-established" = "Network connection re-established"; /* No comment provided by engineer. */ "NEW" = "NEW"; @@ -6027,9 +5986,6 @@ Please install the %3$@ to use the app with this site."; /* List Editor Empty State Message */ "No Items" = "No Items"; -/* Title when users have no Jetpack sites. */ -"No Jetpack sites found" = "No Jetpack sites found"; - /* Displayed in the Notifications Tab as a title, when the Likes Filter shows no notifications */ "No likes yet" = "No likes yet"; @@ -6154,9 +6110,6 @@ Please install the %3$@ to use the app with this site."; /* Error message to show to users when trying to upload a media object with file size is larger than the available site disk quota */ "Not enough space to upload" = "Not enough space to upload"; -/* Accessibility label for unselected following buttons. */ -"Not following" = "Not following"; - /* Button label for denying our request to allow push notifications Not now button title shown in alert preparing users to grant permission for us to send them push notifications. Phrase displayed to dismiss a quick start tour suggestion. */ @@ -6279,7 +6232,6 @@ Please install the %3$@ to use the app with this site."; Ok button for dismissing alert helping users understand their site address OK button title for the warning shown to the user when the app realizes there should be an auth token but there isn't one. OK Button title shown in alert informing users about the Reader Save for Later feature. - OK button to close the informative dialog on Gutenberg editor Submit button on prompt for user information. Title of a button that dismisses a prompt Title of an OK button. Pressing the button acknowledges and dismisses a prompt. @@ -6668,13 +6620,13 @@ Please install the %3$@ to use the app with this site."; /* Caption for the recommended sections in site designs. */ "PICKED FOR YOU" = "PICKED FOR YOU"; -/* The item to select during a guided tour. */ -"Plan" = "Plan"; - /* Action title. Noun. Links to a blog's Plans screen. Title for the plan selector */ "Plans" = "Plans"; +/* Title for the plan selection view */ +"planSelection.title" = "Plans"; + /* User action to play a video on the editor. */ "Play video" = "Play video"; @@ -7196,9 +7148,6 @@ Tapping on this row allows the user to edit the sharing message. */ Primary Web Site */ "Primary Site" = "Primary Site"; -/* Primary site address label, used in the site address section of the Domains Dashboard. */ -"Primary site address" = "Primary site address"; - /* Label for the privacy setting Privacy settings section header */ "Privacy" = "Privacy"; @@ -7258,12 +7207,6 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "View all responses"; -/* Subtitle of the notification when prompts are hidden from the dashboard card */ -"prompts.notification.removed.subtitle" = "Visit Site Settings to turn back on"; - -/* Title of the notification when prompts are hidden from the dashboard card */ -"prompts.notification.removed.title" = "Blogging Prompts hidden"; - /* Privacy setting for posts set to 'Public' (default). Should be the same as in core WP. */ "Public" = "Public"; @@ -7315,9 +7258,6 @@ Tapping on this row allows the user to edit the sharing message. */ /* A short message that informs the user a post is being published to the server from the share extension. */ "Publishing post..." = "Publishing post..."; -/* Label that describes in which blog the user is publishing to */ -"Publishing To" = "Publishing To"; - /* Text displayed in HUD while a post is being published. */ "Publishing..." = "Publishing..."; @@ -7342,9 +7282,6 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for the success view when the user has successfully logged in */ "qrLoginVerifyAuthorization.completedInstructions.title" = "You're logged in!"; -/* The menu item to select during a guided tour. */ -"Quick Start" = "Quick Start"; - /* The quick tour actions item to select during a guided tour. */ "quickStart.moreMenu" = "More"; @@ -7366,13 +7303,9 @@ Tapping on this row allows the user to edit the sharing message. */ The accessibility value of the reader tab. The default title of the Reader The menu item to select during a guided tour. - Title of the 'Reader' tab - used for spotlight indexing on iOS. - Title of the Reader section of the debug screen used in debug builds of the app */ + Title of the 'Reader' tab - used for spotlight indexing on iOS. */ "Reader" = "Reader"; -/* Title of the screen that allows the user to change the Reader CSS URL for debug builds */ -"Reader CSS URL" = "Reader CSS URL"; - /* Accessibility hint to inform that the author section can be tapped to see posts from the site. */ "reader.detail.header.authorInfo.a11y.hint" = "Views posts from the site"; @@ -7712,9 +7645,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Explanation of what will happen if the user confirms this alert. */ "Removing Next Steps will hide all tours on this site. This action cannot be undone." = "Removing Next Steps will hide all tours on this site. This action cannot be undone."; -/* Label indicating the date on which a domain name registration will be renewed. The %@ placeholder will be replaced with a date at runtime. */ -"Renews on %@" = "Renews on %@"; - /* No comment provided by engineer. */ "Replace audio" = "Replace audio"; @@ -7847,7 +7777,6 @@ Example: given a notice format "Following %@" and empty site name, this will be Retry. Verb – retry a failed media upload. The Jetpack view button title used when an error occurred Title for accessory view in the empty state table view cell in the Verticals step of Enhanced Site Creation - title for action that tries to connect to the reader after a loading error. User action to retry media upload. */ "Retry" = "Retry"; @@ -7895,6 +7824,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Button label to open web page in Safari */ "Safari" = "Safari"; +/* Title of a row displayed on the debug screen used to configure the sandbox store use in the App. */ +"Sandbox Store" = "Sandbox Store"; + /* Menus save button title Save Action Save button label (saving content, ex: Post, Page, Comment, Category). @@ -8066,9 +7998,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Caption displayed in promotional screens shown during the login flow. */ "See comments and notifications in real time." = "See comments and notifications in real time."; -/* Action button linking to instructions for installing Jetpack.Presented when logging in with a site address that does not have a valid Jetpack installation */ -"See Instructions" = "See Instructions"; - /* Select action on the app extension category picker screen. Saves the selected categories for the post. Select action on the app extension post type picker screen. Saves the selected post type for the post. */ "Select" = "Select"; @@ -8082,24 +8011,15 @@ Example: given a notice format "Following %@" and empty site name, this will be /* A step in a guided tour for quick start. %@ will be the name of the item to select. */ "Select %@ to create a new post" = "Select %@ to create a new post"; -/* A step in a guided tour for quick start. %@ will be the name of the item to select. */ -"Select %@ to discover new themes" = "Select %@ to discover new themes"; - /* A step in a guided tour for quick start. %@ will be the name of the item to select. */ "Select %@ to find other sites." = "Select %@ to find other sites."; /* A step in a guided tour for quick start. %@ will be the name of the item to select. */ "Select %@ to see how your site is performing." = "Select %@ to see how your site is performing."; -/* A step in a guided tour for quick start. %@ will be the name of the item to select. */ -"Select %@ to see your checklist" = "Select %@ to see your checklist"; - /* A step in a guided tour for quick start. %@ will be the name of the item to select. */ "Select %@ to see your current library." = "Select %@ to see your current library."; -/* A step in a guided tour for quick start. %@ will be the name of the item to select. */ -"Select %@ to see your current plan and other available plans." = "Select %@ to see your current plan and other available plans."; - /* A step in a guided tour for quick start. %@ will be the name of the item to select. */ "Select %@ to see your page list." = "Select %@ to see your page list."; @@ -8496,9 +8416,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Describes a site redirect domain */ "Site Redirect" = "Site Redirect"; -/* Prologue title label, the \n force splits it into 2 lines. */ -"Site security and performance\nfrom your pocket" = "Site security and performance\nfrom your pocket"; - /* Noun. Title. Links to the blog's Settings screen. */ "Site Settings" = "Site Settings"; @@ -8533,6 +8450,30 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Site name that is placed in the tooltip view. */ "site.creation.domain.tooltip.site.name" = "YourSiteName.com"; +/* Header of the secondary domains list section in the Domains Dashboard. %1$@ is the name of the site. */ +"site.domains.domainSection.title" = "Other domains for %1$@"; + +/* A section title which displays a row with a free WP.com domain */ +"site.domains.freeDomainSection.title" = "Your Free WordPress.com domain"; + +/* Description for the first domain purchased with a paid plan. */ +"site.domains.freeDomainWithPaidPlan.description" = "Get a free one-year domain registration or transfer with any annual paid plan."; + +/* Title of the card that starts the purchase of the first domain with a paid plan. */ +"site.domains.freeDomainWithPaidPlan.title" = "Get your domain"; + +/* Footer of the primary site section in the Domains Dashboard. */ +"site.domains.primaryDomain" = "Your primary site address is what visitors will see in their address bar when visiting your website."; + +/* Primary domain label, used in the site address section of the Domains Dashboard. */ +"site.domains.primaryDomain.title" = "Primary domain"; + +/* Title for a button that opens domain purchasing flow. */ +"site.domains.purchaseDirectly.buttons.title" = "Just search for a domain"; + +/* Title for a button that opens plan and domain purchasing flow. */ +"site.domains.purchaseWithPlan.buttons.title" = "Upgrade to a plan"; + /* Back button title shown in Site Creation flow to come back from Plan selection to Domain selection */ "siteCreation.domain.backButton.title" = "Domains"; @@ -8689,9 +8630,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Title shown on the dashboard when it fails to load */ "Some data wasn't loaded" = "Some data wasn't loaded"; -/* Confirms with the user if they save the post all media that failed to upload will be removed from it. */ -"Some media uploads failed. This action will remove all failed media from the post.\nSave anyway?" = "Some media uploads failed. This action will remove all failed media from the post.\nSave anyway?"; - /* Title for a label that appears when the scan failed Title for the error view when the scan start has failed */ "Something went wrong" = "Something went wrong"; @@ -9121,9 +9059,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Support email label. */ "support.row.email.title" = "Email"; -/* Option in Support view to view the Forums. */ -"support.row.forums.title" = "WordPress Forums"; - /* Option in Support view to launch the Help Center. */ "support.row.helpCenter.title" = "WordPress Help Center"; @@ -9481,7 +9416,8 @@ Example: given a notice format "Following %@" and empty site name, this will be /* No comment provided by engineer. */ "The site at %@ uses WordPress %@. We recommend to update to the latest version, or at least %@" = "The site at %1$@ uses WordPress %2$@. We recommend to update to the latest version, or at least %3$@"; -/* Error message shown a URL does not point to an existing site. */ +/* Error message shown a URL does not point to an existing site. + Error message shown when a URL does not point to an existing site. */ "The site at this address is not a WordPress site. For us to connect to it, the site must use WordPress." = "The site at this address is not a WordPress site. For us to connect to it, the site must use WordPress."; /* Message shown when site deletion API failed */ @@ -9536,7 +9472,6 @@ Example: given a notice format "Following %@" and empty site name, this will be "Theme Activated" = "Theme Activated"; /* Noun. Name of the Themes feature - The menu item to select during a guided tour. Themes option in the blog details Title of Themes browser page */ "Themes" = "Themes"; @@ -9781,9 +9716,6 @@ Example: given a notice format "Following %@" and empty site name, this will be Writing Time Format Settings Title */ "Time Format" = "Time Format"; -/* Description of a Quick Start Tour */ -"Time to finish setting up your site! Our checklist walks you through the next steps." = "Time to finish setting up your site! Our checklist walks you through the next steps."; - /* Label for the timezone setting Title for the time zone selector */ "Time Zone" = "Time Zone"; @@ -9839,9 +9771,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Message asking the user if they want to set up Jetpack from stats */ "To use stats on your site, you'll need to install the Jetpack plugin." = "To use stats on your site, you'll need to install the Jetpack plugin."; -/* Message explaining that Jetpack needs to be installed for a particular site. Reads like 'To use this app for example.com you'll need to have... */ -"To use this app for %@ you'll need to have the Jetpack plugin installed and activated." = "To use this app for %@ you'll need to have the Jetpack plugin installed and activated."; - /* Comments Today Section Header Insights 'Today' header Notifications Today Section Header */ @@ -9862,9 +9791,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility Identifier for the Aztec Unordered List Style */ "Toggles the unordered list style" = "Toggles the unordered list style"; -/* Title of the Tools section of the debug screen used in debug builds of the app */ -"Tools" = "Tools"; - /* Insights 'Top Commenters' header */ "Top Commenters" = "Top Commenters"; @@ -9875,8 +9801,7 @@ Example: given a notice format "Following %@" and empty site name, this will be /* The part of the nudge title that should be emphasized, this content needs to match a string in 'If you want to try get more...' */ "top tips" = "top tips"; -/* Shortened version of the main title to be used in back navigation - Topic page title */ +/* Shortened version of the main title to be used in back navigation */ "Topic" = "Topic"; /* Used when a Reader Topic is not found for a specific id */ @@ -9979,9 +9904,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* The title of a notice telling users that the classic editor is deprecated and will be removed in a future version of the app. */ "Try the new Block Editor" = "Try the new Block Editor"; -/* Action button that will restart the login flow.Presented when logging in with a site address that does not have a valid Jetpack installation */ -"Try With Another Account" = "Try With Another Account"; - /* When social login fails, this button offers to let the user try again with a differen email address */ "Try with another email" = "Try with another email"; @@ -10036,9 +9958,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* No comment provided by engineer. */ "Type a URL" = "Type a URL"; -/* Register domain - Search field placeholder for the Suggested Domain screen */ -"Type to get more suggestions" = "Type to get more suggestions"; - /* Notice title when blocking a site fails. */ "Unable to block site" = "Unable to block site"; @@ -10138,12 +10057,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Alert displayed to the user when a single post has failed to upload. */ "Unable to upload 1 draft post" = "Unable to upload 1 draft post"; -/* Alert displayed to the user when a single post and multiple files have failed to upload. */ -"Unable to upload 1 draft post, %ld files" = "Unable to upload 1 draft post, %ld files"; - -/* Alert displayed to the user when a single post and 1 file has failed to upload. */ -"Unable to upload 1 draft post, 1 file" = "Unable to upload 1 draft post, 1 file"; - /* Alert displayed to the user when a single post has failed to upload. */ "Unable to upload 1 post" = "Unable to upload 1 post"; @@ -10198,8 +10111,7 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Label of the table view cell's delete button, when unfollowing a site. */ "Unfollow" = "Unfollow"; -/* Accessibility label for unfollowing a site - Accessibility label for unfollowing a tag */ +/* Accessibility label for unfollowing a tag */ "Unfollow %@" = "Unfollow %@"; /* Title for a button that unsubscribes the user from the post. */ @@ -10215,9 +10127,6 @@ Example: given a notice format "Following %@" and empty site name, this will be User unfollowed a site. */ "Unfollowed site" = "Unfollowed site"; -/* Spoken hint describing action for selected following buttons. */ -"Unfollows blog" = "Unfollows blog"; - /* VoiceOver accessibility hint, informing the user the button can be used to unfollow a blog. */ "Unfollows the blog." = "Unfollows the blog."; @@ -10396,9 +10305,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* No comment provided by engineer. */ "Uploading…" = "Uploading…"; -/* Title for alert when trying to save post with failed media items */ -"Uploads failed" = "Uploads failed"; - /* URL text field placeholder */ "URL" = "URL"; @@ -10420,9 +10326,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* The button title text for logging in with WP.com password instead of magic link. */ "Use password to sign in" = "Use password to sign in"; -/* Title of a row displayed on the debug screen used to configure the sandbox store use in the App. */ -"Use Sandbox Store" = "Use Sandbox Store"; - /* Description of a Quick Start Tour */ "Used across the web: in browser tabs, social media previews, and the WordPress.com Reader." = "Used across the web: in browser tabs, social media previews, and the WordPress.com Reader."; @@ -10461,9 +10364,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Push Authentication Alert Title */ "Verify Log In" = "Verify Log In"; -/* Notice displayed after domain credit redemption success. */ -"Verify your email address - instructions sent to %@" = "Verify your email address - instructions sent to %@"; - /* Description for the version label in the What's new page. */ "Version " = "Version "; @@ -10669,9 +10569,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Message for error displayed when preparing a backup fails. */ "We couldn't create your backup. Please try again later." = "We couldn't create your backup. Please try again later."; -/* Primary message shown when there are no domains that match the user entered text. */ -"We couldn't find any available address with the words you entered - let's try again." = "We couldn't find any available address with the words you entered - let's try again."; - /* Text displayed in notice after the app fails to upload a page, it will attempt to upload it later. */ "We couldn't publish this page, but we'll try again later." = "We couldn't publish this page, but we'll try again later."; @@ -10747,9 +10644,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* The subtitle text on the magic link requested screen followed by the email address. */ "We just sent a magic link to" = "We just sent a magic link to"; -/* Popup content about why this post is being opened in block editor */ -"We made big improvements to the block editor and think it's worth a try!\n\nWe enabled it for new posts and pages but if you'd like to change to the classic editor, go to 'My Site' > 'Site Settings'." = "We made big improvements to the block editor and think it's worth a try!\n\nWe enabled it for new posts and pages but if you'd like to change to the classic editor, go to 'My Site' > 'Site Settings'."; - /* Message displayed when a backup has finished */ "We successfully created a backup of your site as of %@" = "We successfully created a backup of your site as of %@"; @@ -10759,9 +10653,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Informational text about link to other tracking tools */ "We use other tracking tools, including some from third parties. Read about these and how to control them." = "We use other tracking tools, including some from third parties. Read about these and how to control them."; -/* Message explaining that WordPress was not detected. */ -"We were not able to detect a WordPress site at the address you entered. Please make sure WordPress is installed and that you are running the latest available version." = "We were not able to detect a WordPress site at the address you entered. Please make sure WordPress is installed and that you are running the latest available version."; - /* Error message displayed when an error occurred sending the magic link email. */ "We were unable to send you an email at this time. Please try again later." = "We were unable to send you an email at this time. Please try again later."; @@ -10850,9 +10741,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Instruction text after a signup Magic Link was requested. */ "We've emailed you a signup link to create your new WordPress.com account. Check your email on this device, and tap the link in the email you receive from WordPress.com." = "We've emailed you a signup link to create your new WordPress.com account. Check your email on this device, and tap the link in the email you receive from WordPress.com."; -/* Register Domain - error displayed when a domain was purchased succesfully, but there was a problem setting it to a primary domain for the site */ -"We've had problems changing the primary domain on your site — but don't worry, your domain was successfully purchased." = "We've had problems changing the primary domain on your site — but don't worry, your domain was successfully purchased."; - /* Account Settings Web Address label Header for a comment author's web address, shown when editing a comment. */ "Web Address" = "Web Address"; @@ -11195,6 +11083,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* This is a comma separated list of keywords used for spotlight indexing of the 'My Sites' tab. */ "wordpress, sites, site, blogs, blog" = "wordpress, sites, site, blogs, blog"; +/* Error message that describes an unknown error had occured */ +"wordpress-api.error.unknown" = "Something went wrong, please try again later."; + /* Label for WordPress.com followers */ "WordPress.com" = "WordPress.com"; @@ -11241,6 +11132,9 @@ from anywhere."; /* Title of button that displays the Automattic Work With Us web page */ "Work With Us" = "Work With Us"; +/* No comment provided by engineer. */ +"Working Offline" = "Working Offline"; + /* Accessibility label for the Stats' world map. */ "World map showing views by country." = "World map showing views by country."; @@ -11299,8 +11193,7 @@ from anywhere."; /* Title of Years stats filter. */ "Years" = "Years"; -/* Accept Action - Button title. Confirms that the user wants to proceed with a pending action. +/* Button title. Confirms that the user wants to proceed with a pending action. Label for a button that clears all old activity logs Yes */ "Yes" = "Yes"; @@ -11400,7 +11293,7 @@ from anywhere."; "You have 1 hidden WordPress site." = "You have 1 hidden WordPress site."; /* Description for the first domain purchased with a paid plan. */ -"You have a free one-year domain registration with your plan" = "You have a free one-year domain registration with your plan"; +"You have a free one-year domain registration with your plan." = "You have a free one-year domain registration with your plan."; /* Message alert when attempting to delete site with purchases */ "You have active premium upgrades on your site. Please cancel your upgrades prior to deleting your site." = "You have active premium upgrades on your site. Please cancel your upgrades prior to deleting your site."; @@ -11503,9 +11396,6 @@ from anywhere."; /* Title for the view when there aren't any Backups to display */ "Your first backup will be ready soon" = "Your first backup will be ready soon"; -/* Title of the site address section in the Domains Dashboard. */ -"Your free WordPress.com address is" = "Your free WordPress.com address is"; - /* Details about recently acquired domain on domain credit redemption success screen */ "Your new domain %@ is being set up. It may take up to 30 minutes for your domain to start working." = "Your new domain %@ is being set up. It may take up to 30 minutes for your domain to start working."; @@ -11521,18 +11411,12 @@ from anywhere."; /* Message of Export Content confirmation alert; substitution is user's email address */ "Your posts, pages, and settings will be mailed to you at %@." = "Your posts, pages, and settings will be mailed to you at %@."; -/* Footer of the primary site section in the Domains Dashboard. */ -"Your primary site address is what visitors will see in their address bar when visiting your website." = "Your primary site address is what visitors will see in their address bar when visiting your website."; - /* Text displayed when a site restore takes too long. */ "Your restore is taking longer than usual, please check again in a few minutes." = "Your restore is taking longer than usual, please check again in a few minutes."; /* This is shown to the user when their domain search query contains invalid characters. */ "Your search includes characters not supported in WordPress.com domains. The following characters are allowed: A–Z, a–z, 0–9." = "Your search includes characters not supported in WordPress.com domains. The following characters are allowed: A–Z, a–z, 0–9."; -/* Placeholder for site url, if the url is unknown.Presented when logging in with a site address that does not have a valid Jetpack installation.The error would read: to use this app for your site you'll need... */ -"your site" = "your site"; - /* Body text of alert helping users understand their site address */ "Your site address appears in the bar at the top of the screen when you visit your site in Safari." = "Your site address appears in the bar at the top of the screen when you visit your site in Safari."; @@ -11542,9 +11426,6 @@ from anywhere."; /* Example notification content displayed on the Enable Notifications prompt that is personalized based on a users selection. Words marked between * characters will be displayed as bold text. */ "Your site appears to be getting *more traffic* than usual!" = "Your site appears to be getting *more traffic* than usual!"; -/* Header of the domains list section in the Domains Dashboard. */ -"Your Site Domains" = "Your Site Domains"; - /* User-facing string, presented to reflect that site assembly completed successfully. */ "Your site has been created!" = "Your site has been created!"; @@ -11596,12 +11477,6 @@ from anywhere."; /* Describes the expected behavior when the user enables in-app notifications in Reader Comments. */ "You’re following this conversation. You will receive an email whenever a new comment is made." = "You’re following this conversation. You will receive an email whenever a new comment is made."; -/* Popup content about why this post is being opened in block editor */ -"You’re now using the block editor for new pages — great! If you’d like to change to the classic editor, go to ‘My Site’ > ‘Site Settings’." = "You’re now using the block editor for new pages — great! If you’d like to change to the classic editor, go to ‘My Site’ > ‘Site Settings’."; - -/* Popup content about why this post is being opened in block editor */ -"You’re now using the block editor for new posts — great! If you’d like to change to the classic editor, go to ‘My Site’ > ‘Site Settings’." = "You’re now using the block editor for new posts — great! If you’d like to change to the classic editor, go to ‘My Site’ > ‘Site Settings’."; - /* Label for button to log in using Google. The {G} will be replaced with the Google logo. */ "{G} Log in with Google." = "{G} Log in with Google."; diff --git a/WordPress/Resources/es.lproj/Localizable.strings b/WordPress/Resources/es.lproj/Localizable.strings index 080165b6b956..c3e3bcdae385 100644 --- a/WordPress/Resources/es.lproj/Localizable.strings +++ b/WordPress/Resources/es.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-07 10:50:54+0000 */ +/* Translation-Revision-Date: 2024-01-03 08:48:15+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: es */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Todo"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Todos los planes anuales de WordPress.com incluyen un nombre de dominio personalizado. Registra tu dominio gratis ahora."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Todos los planes de WordPress.com incluyen un nombre de dominio personalizado. Registra ahora tu dominio premium gratuito."; @@ -809,7 +812,7 @@ translators: Block name. %s: The localized block name */ /* Title for a call-to-action button in the create new bottom action sheet. Title for a call-to-action button on the prompts card. */ -"Answer Prompt" = "Responder estímulo"; +"Answer Prompt" = "Responder sugerencia"; /* Title of answered Blogging Prompts filter. */ "Answered" = "Contestada"; @@ -3663,7 +3666,7 @@ translators: Block name. %s: The localized block name */ "Inactive, Autoupdates on" = "Inactivo, actualizaciones automáticas activas"; /* Title of the switch to turn on or off the blogging prompts feature. */ -"Include a Blogging Prompt" = "Incluir un estímulo para bloguear"; +"Include a Blogging Prompt" = "Incluir una sugerencia de publicación"; /* Describes a standard *.wordpress.com site domain */ "Included with Site" = "Incluido con el sitio"; @@ -3772,7 +3775,7 @@ translators: Block name. %s: The localized block name */ "Interior Design" = "Diseño de interiores"; /* Title displayed on the feature introduction view. */ -"Introducing Blogging Prompts" = "Presentamos los estímulos para bloguear"; +"Introducing Blogging Prompts" = "Presentamos las sugerencias de publicación"; /* Stories intro header title */ "Introducing Story Posts" = "Presentando las entradas de historias"; @@ -4007,7 +4010,7 @@ translators: Block name. %s: The localized block name */ /* Accessibility label for the blogging prompts info button on the Blogging Reminders Settings screen. Accessibility label for the blogging prompts info button on the prompts header view. */ -"Learn more about prompts" = "Más información sobre los estímulos"; +"Learn more about prompts" = "Más información sobre las sugerencias"; /* Footer text for Invite People role field. */ "Learn more about roles" = "Saber más sobre los perfiles"; @@ -4175,7 +4178,7 @@ translators: Block name. %s: The localized block name */ "Loading plugins..." = "Cargando plugins…"; /* Displayed while blogging prompts are being loaded. */ -"Loading prompts..." = "Cargando estímulos…"; +"Loading prompts..." = "Cargando sugerencias…"; /* A short message to inform the user the requested stream is being loaded. */ "Loading stream..." = "Cargando hilo…"; @@ -4857,7 +4860,7 @@ translators: %s: Select control button label e.g. \"Button width\" */ "No primary site address found" = "No se ha encontrado ninguna dirección del sitio principal"; /* Title displayed when there are no blogging prompts to display. */ -"No prompts yet" = "Todavía no hay estímulos"; +"No prompts yet" = "Todavía no hay sugerencias"; /* A message title */ "No recent posts" = "No hay entradas recientes"; @@ -5691,12 +5694,12 @@ translators: %s: Select control button label e.g. \"Button width\" */ "Projects" = "Proyectos"; /* Title of the notification presented when a prompt is skipped */ -"Prompt skipped" = "Se ha omitido el estímulo"; +"Prompt skipped" = "Se ha omitido la sugerencia"; /* Title label for blogging prompts in the create new bottom action sheet. Title label for the Prompts card in My Sites tab. View title for Blogging Prompts list. */ -"Prompts" = "Estímulos"; +"Prompts" = "Sugerencias"; /* Privacy setting for posts set to 'Public' (default). Should be the same as in core WP. */ "Public" = "Pública"; @@ -7150,7 +7153,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "Tap to hide the keyboard" = "Toca para ocultar el teclado"; /* Title for a push notification with fixed content that invites the user to load today's blogging prompt. */ -"Tap to load today's prompt..." = "Toca para cargar el estímulo de hoy…"; +"Tap to load today's prompt..." = "Toca para cargar la sugerencia de hoy…"; /* Accessibility hint for referrer action row. */ "Tap to mark referrer as not spam." = "Toca para marcar al referente como no spam."; @@ -7277,7 +7280,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "The basics" = "Lo básico"; /* Subtitle displayed on the feature introduction view. */ -"The best way to become a better writer is to build a writing habit and share with others - that’s where Prompts come in!" = "El mejor modo de convertirte en un mejor escritor es crear un hábito de escritura y compartir con otros - ¡aquí es donde entran los estímulos! "; +"The best way to become a better writer is to build a writing habit and share with others - that’s where Prompts come in!" = "El mejor modo de convertirte en un mejor escritor es crear un hábito de escritura y compartir con otros - ¡aquí es donde entran las sugerencias!"; /* No comment provided by engineer. */ "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “%@” which could put your confidential information at risk.\n\nWould you like to trust the certificate anyway?" = "El certificado para este servidor no es válido. Puede que estés conectando con un servidor que aparenta ser «%@», lo que podría poner en peligro tu información confidencial.\n\n¿Deseas confiar en el certificado de todos modos?"; @@ -7472,7 +7475,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "There was an error loading plugins" = "Hubo un error al cargar los plugins"; /* Text displayed when there is a failure loading blogging prompts. */ -"There was an error loading prompts." = "Se ha producido un error al cargar los estímulos."; +"There was an error loading prompts." = "Se ha producido un error al cargar las sugerencias."; /* Text displayed when there is a failure loading a comment. */ "There was an error loading the comment." = "Se ha producido un error al cargar el comentario."; @@ -7694,7 +7697,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "Today" = "Hoy"; /* Title for a push notification showing today's blogging prompt. */ -"Today's Prompt 💡" = "Estímulo de hoy 💡"; +"Today's Prompt 💡" = "Sugerencia de hoy 💡"; /* Insights Management 'Today's Stats' title */ "Today's Stats" = "Estadísticas de hoy"; @@ -7832,7 +7835,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "Try with the site address" = "Prueba con la dirección del sitio"; /* Destructive menu title to remove the prompt card from the dashboard. */ -"Turn off prompts" = "Desactivar estímulos"; +"Turn off prompts" = "Desactivar las sugerencias"; /* Verb. An option to switch off site notifications. */ "Turn off site notifications" = "Desactivar los avisos del sitio"; @@ -8362,7 +8365,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "View more" = "Ver más"; /* Menu title to show more prompts. */ -"View more prompts" = "Ver más estímulos"; +"View more prompts" = "Ver más sugerencias"; /* Title of a Quick Start Tour */ "View your site" = "Ver tu sitio"; @@ -8748,7 +8751,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "We’ll notify you when its done." = "Te avisaremos cuando hayamos terminado."; /* Description of Blogging Prompts displayed in the Feature Introduction view. */ -"We’ll show you a new prompt each day on your dashboard to help get those creative juices flowing!" = "¡Te mostraremos un nuevo estímulo cada día en tu escritorio para ayudarte a que fluyan esos fluidos creativos!"; +"We’ll show you a new prompt each day on your dashboard to help get those creative juices flowing!" = "¡Te mostraremos una nueva sugerencia cada día en tu escritorio para ayudarte a que fluya la creatividad!"; /* Hint displayed when we fail to fetch the status of the backup in progress. */ "We’ll still attempt to backup your site." = "Volveremos a intentar hacer copia de seguridad de tu sitio."; @@ -9008,10 +9011,10 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "You can always log in with a link like the one you just used, but you can also set up a password if you prefer." = "Siempre puedes acceder con un enlace como el que acabas de usar, pero también puedes configurar una contraseña si lo prefieres."; /* Note displayed in the Feature Introduction view. */ -"You can control Blogging Prompts and Reminders at any time in My Site > Settings > Blogging" = "Puedes gestionar los recordatorios y estímulos para bloguear en cualquier momento desde Mi sitio > Ajustes > Bloguear."; +"You can control Blogging Prompts and Reminders at any time in My Site > Settings > Blogging" = "Puedes gestionar los recordatorios y las sugerencias de publicación en cualquier momento desde Mi sitio > Ajustes > Bloguear."; /* Accessibility hint for Note displayed in the Feature Introduction view. */ -"You can control Blogging Prompts and Reminders at any time in My Site, Settings, Blogging" = "Puedes gestionar los recordatorios y estímulos para bloguear en cualquier momento desde Mi sitio > Ajustes > Bloguear."; +"You can control Blogging Prompts and Reminders at any time in My Site, Settings, Blogging" = "Puedes gestionar los recordatorios y las sugerencias de publicación en cualquier momento desde «Mi sitio > Ajustes > Bloguear»."; /* No comment provided by engineer. */ "You can edit this block using the web version of the editor." = "Puedes editar este bloque usando la versión web del editor."; @@ -9454,11 +9457,17 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "Copiar URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Visitar sitio"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Más información"; /* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Durante el mes de enero, recibirás sugerencias de publicación de Bloganuary, nuestro reto de la comunidad para que crees un hábito de publicación sólido para el nuevo año."; +"bloganuary.dashboard.card.description" = "Durante el mes de enero, las sugerencias para escribir en el blog provendrán de Bloganuary, nuestro reto comunitario para crear un hábito de blogueo para el nuevo año."; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "¡Bloganuary ya está aquí!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "¡Bloganuary está a la vuelta de la esquina!"; @@ -9814,6 +9823,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Añadir"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Seleccionar imágenes"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Ver seleccionados (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Detalles de la campaña"; @@ -10267,6 +10285,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "¡Borrado!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "Todos"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Audio"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Documentos"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Imágenes"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Vídeos"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "Borrar"; @@ -10345,6 +10378,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "Grabar vídeo"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ de %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × %2$d px"; @@ -10498,6 +10534,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "No tienes sitios"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Añadir sitio"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Acciones del sitio"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Toca para mostrar más acciones del sitio"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Personalizar Inicio"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Cambiar icono del sitio"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Cambiar título del sitio"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Cambiar de sitio"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Visitar sitio"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Descartar"; @@ -10815,7 +10875,7 @@ Tapping on this row allows the user to edit the sharing message. */ "prompts.notification.removed.subtitle" = "Visitar los ajustes del sitio para volver a activar"; /* Title of the notification when prompts are hidden from the dashboard card */ -"prompts.notification.removed.title" = "Se han ocultado los estímulos para bloguear."; +"prompts.notification.removed.title" = "Se han ocultado las sugerencias de publicación"; /* Button label that dismisses the qr log in flow and returns the user back to the previous screen */ "qrLoginVerifyAuthorization.completedInstructions.dismiss" = "Descartar"; @@ -11071,6 +11131,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "Previsualizar medios"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Añadir"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "Anular selección"; @@ -11245,6 +11308,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Descartar"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Fotos proporcionadas por Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "¡Busca fotos gratuitas que añadir a tu biblioteca de medios!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "En esta conversación"; @@ -11392,6 +11461,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Ayuda"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "¡Busca GIFs para añadir a tu biblioteca de medios!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "Se eliminarán estos elementos:"; diff --git a/WordPress/Resources/fr.lproj/Localizable.strings b/WordPress/Resources/fr.lproj/Localizable.strings index 47731fbf7124..d97bbdc5f565 100644 --- a/WordPress/Resources/fr.lproj/Localizable.strings +++ b/WordPress/Resources/fr.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-07 19:48:45+0000 */ +/* Translation-Revision-Date: 2024-01-03 18:54:08+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: fr */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Toutes"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Tous les plans annuels WordPress.com incluent un nom de domaine personnalisé. Enregistrez votre domaine gratuit dès maintenant."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Tous les plans WordPress.com inclus un nom de domaine personnalisé. Enregistrer votre domaine Premium gratuit dès maintenant."; @@ -9421,11 +9424,14 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "Copier l’URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Aller sur le site"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Lire la suite"; -/* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Pendant le mois de janvier, vous recevrez des invites à bloguer de Bloganuary, notre défi commun pour ancrer l’habitude de bloguer à l’occasion de la nouvelle année."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary, c’est parti !"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "C’est bientôt Bloganuary !"; @@ -9775,6 +9781,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "exemple.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Ajouter"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Sélectionner des images"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Voir la sélection (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Détails de la campagne"; @@ -10228,6 +10243,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "Supprimé !"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "Tout"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Audio"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Documents"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Images"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Vidéos"; + /* Verb. Button title. Tapping dismisses a prompt. */ "mediaLibrary.retryOptionsAlert.dismissButton" = "Ignorer"; @@ -10300,6 +10330,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "Prendre une vidéo"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ sur %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d x %2$d px"; @@ -10450,6 +10483,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Vous n’avez pas de sites."; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Ajouter un site"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Afficher les actions du site"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Appuyer pour afficher plus d’actions sur le site"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Personnaliser l’accueil"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Modifier l’icône du site"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Modifier le titre de site"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Changer de site"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Aller sur le site"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Ignorer"; @@ -11005,6 +11062,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility hint for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityHint" = "Appuyer pour voir le média en plein écran"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Ajouter"; + /* Media screen navigation title */ "siteMediaPicker.title" = "Médias"; @@ -11173,6 +11233,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Ignorer"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Photos mises à disposition par Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "Recherchez des photos gratuites pour les ajouter à votre bibliothèque de médias !"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "Dans cette conversation"; @@ -11320,6 +11386,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Aide"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Recherchez des GIF pour les ajouter à votre bibliothèque de médias !"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "ces éléments vont être supprimés :"; diff --git a/WordPress/Resources/he.lproj/Localizable.strings b/WordPress/Resources/he.lproj/Localizable.strings index 0806e38f70f0..36bbae12a556 100644 --- a/WordPress/Resources/he.lproj/Localizable.strings +++ b/WordPress/Resources/he.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-06 16:54:09+0000 */ +/* Translation-Revision-Date: 2024-01-04 14:54:09+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: he_IL */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "הכל"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "כל התוכניות השנתיות של WordPress.com כוללות דומיין אישי. ניתן לרשום את הדומיין החינמי שלך עכשיו."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "כל התוכניות של WordPress.com כוללות דומיין אישי. ניתן להירשם ולקבל דומיין פרימים בחינם עכשיו."; @@ -9445,11 +9448,17 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "להעתיק כתובת URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "לבקר באתר"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "למידע נוסף"; /* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "לאורך כל חודש ינואר, יתקבלו מ-Bloganuary הנחיות יומיות לפרסום בבלוג. זהו האתגר הקהילתי שלנו לביסוס הרגלי פרסום בבלוג לשנה החדשה."; +"bloganuary.dashboard.card.description" = "לאורך כל חודש ינואר יתקבלו מ-Bloganuary הצעות כתיבה יומיות בבלוג. זהו האתגר הקהילתי שלנו לביסוס הרגלי פרסום בבלוג לשנה החדשה."; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary כבר כאן!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary מתקרב!"; @@ -9805,6 +9814,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "להוסיף"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "לבחור תמונות"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "תצוגה נבחרה (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "פרטי הקמפיין"; @@ -10258,6 +10276,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "הפריט נמחק!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "הכל"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "אודיו"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "מסמכים"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "תמונות"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "סרטוני וידאו"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "למחוק"; @@ -10336,6 +10369,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "צילום וידאו"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ מתוך %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "פיקסלים.⁦%1$d⁩%2$d"; @@ -10489,6 +10525,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "אין לך עוד אתרים"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "הוספת אתר"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "פעולות באתר"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "יש ללחוץ כדי להציג עוד פעולות באתר"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "להתאים אישית את הלשונית 'בית'"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "לשנות את סמל אתר"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "לשנות את שם האתר"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "להחליף אתר"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "לבקר באתר"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "ביטול"; @@ -11053,6 +11113,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "תצוגה מקדימה של מדיה"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "להוסיף"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "ביטול בחירה"; @@ -11227,6 +11290,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "ביטול"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "התמונות מובאות בחסות Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "אפשר לחפש תמונות חינמיות ולהוסיף אותן לספריית המדיה שלך!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "בשיחה הזאת"; @@ -11374,6 +11443,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "עזרה"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "אפשר לחפש קובצי GIF ולהוסיף אותם לספריית המדיה שלך!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "פריטים אלה יימחקו:"; diff --git a/WordPress/Resources/id.lproj/Localizable.strings b/WordPress/Resources/id.lproj/Localizable.strings index fcaa355aaf13..31c94dac31a8 100644 --- a/WordPress/Resources/id.lproj/Localizable.strings +++ b/WordPress/Resources/id.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-05 11:54:08+0000 */ +/* Translation-Revision-Date: 2024-01-04 09:54:09+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: id */ @@ -643,6 +643,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Semua"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Semua paket tahunan WordPress.com menyertakan nama domain kustom. Daftarkan domain gratis Anda sekarang."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Semua paket WordPress.com menyertakan nama domain kustom. Daftarkan domain premium gratis Anda sekarang."; @@ -9445,11 +9448,14 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "Salin URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Kunjungi situs"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Baca selengkapnya"; -/* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Selama bulan Januari, prompt blogging akan datang dari Bloganuary—tantangan kami untuk komunitas guna membentuk kebiasaan blogging di tahun yang baru."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary telah hadir!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary segera hadir!"; @@ -9802,6 +9808,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "contoh.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Tambah"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Pilih Gambar"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Tampilan Dipilih (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Detail Kampanye"; @@ -10255,6 +10270,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "Dihapus!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "Semua"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Audio"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Dokumen"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Gambar"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Video"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "Hapus"; @@ -10333,6 +10363,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "Ambil Video"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ dari %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × %2$d piksel"; @@ -10486,6 +10519,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Anda belum memiliki situs"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Tambahkan situs"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Tindakan di Situs"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Ketuk untuk menunjukkan tindakan lain di situs"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Personalisasikan beranda"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Ubah ikon situs"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Ubah judul situs"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Alihkan sistus"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Kunjungi situs"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Tutup"; @@ -11059,6 +11116,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "Pratinjau media"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Tambah"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "Batalkan Pilihan"; @@ -11233,6 +11293,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Tutup"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Foto disediakan oleh Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "Cari foto gratis untuk ditambahkan ke Pustaka Media Anda!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "Dalam percakapan ini"; @@ -11380,6 +11446,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Bantuan"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Cari GIF untuk ditambahkan ke Pustaka Media Anda!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "item berikut akan dihapus:"; diff --git a/WordPress/Resources/it.lproj/Localizable.strings b/WordPress/Resources/it.lproj/Localizable.strings index cc1d65d6a8f3..d20331712d87 100644 --- a/WordPress/Resources/it.lproj/Localizable.strings +++ b/WordPress/Resources/it.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-05 13:54:10+0000 */ +/* Translation-Revision-Date: 2024-01-04 11:54:09+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: it */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Tutti"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Tutti i piani WordPress.com annuali comprendono un nome di dominio personalizzato. Registra subito il tuo dominio gratuito."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Tutti i piani WordPress.com comprendono un nome di dominio personalizzato. Registra il tuo dominio premium gratuito ora."; @@ -9451,11 +9454,14 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "Copia URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Visualizza il sito"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Scopri di più"; -/* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Per il mese di gennaio, le richieste di blogging arriveranno da Bloganuary: la sfida della nostra community per creare delle abitudini di blogging per il nuovo anno."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "È arrivato Bloganuary!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary è in arrivo."; @@ -9811,6 +9817,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "esempio.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Aggiungi"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Seleziona le immagini"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Vista selezionata (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Dettagli della campagna"; @@ -10264,6 +10279,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "Elemento eliminato."; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "Tutti"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Audio"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Documenti"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Immagini"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Video"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "Elimina"; @@ -10339,6 +10369,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "Fai un video"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ di %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$dx%2$d px"; @@ -10492,6 +10525,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Non sono presenti siti"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Aggiungi sito"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Azioni del sito"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Clicca per mostrare più azioni del sito"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Personalizza la home"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Cambia l'icona del sito"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Modifica il titolo del sito"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Cambia sito"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Visualizza il sito"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Ignora"; @@ -11059,6 +11116,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "Visualizza in anteprima elemento multimediale"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Aggiungi"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "Deseleziona"; @@ -11230,6 +11290,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Ignora"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Foto fornite da Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "Cerca per trovare foto gratuite da aggiungere alla tua Libreria multimediale!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "In questa conversazione"; @@ -11377,6 +11443,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Aiuto"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Cerca per trovare GIF da aggiungere alla tua Libreria multimediale!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "questi elementi verranno eliminati:"; diff --git a/WordPress/Resources/ja.lproj/Localizable.strings b/WordPress/Resources/ja.lproj/Localizable.strings index cad563e8c02f..f60bcce90293 100644 --- a/WordPress/Resources/ja.lproj/Localizable.strings +++ b/WordPress/Resources/ja.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-05 09:54:09+0000 */ +/* Translation-Revision-Date: 2024-01-04 11:54:09+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: ja_JP */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "すべて"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "WordPress.com のすべての年間プランにはカスタムドメイン名が含まれています。 無料ドメインを登録してください。"; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "WordPress.com のすべてのプランにはカスタムドメイン名があります。無料プレミアムドメインを登録してください。"; @@ -9448,11 +9451,14 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "URL をコピー"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "サイトを表示"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "さらに詳しく"; -/* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "1月のブログ作成のプロンプトは Bloganuary より、新年にブログ投稿を習慣付けるためのコミュニティの課題が提示されます。"; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary が登場 !"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "まもなく Bloganuary です !"; @@ -9808,6 +9814,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "追加"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "画像を選択"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "選択した項目を表示 (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "キャンペーン詳細"; @@ -10261,6 +10276,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "削除しました。"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "すべて"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "音声ファイル"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "文書"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "画像"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "動画"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "削除"; @@ -10339,6 +10369,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "動画を撮影"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ \/ %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × %2$d px"; @@ -10492,6 +10525,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "サイトがありません"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "サイトを追加"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "サイトのアクション"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "タップするとその他のサイトのアクションが表示されます"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "ホームをパーソナライズ"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "サイトアイコンを変更"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "サイトタイトルを変更"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "サイトを切り替え"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "サイトを表示"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "閉じる"; @@ -11062,6 +11119,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "メディアをプレビュー"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "追加"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "選択を解除"; @@ -11236,6 +11296,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "削除"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "写真提供元: Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "無料の写真を検索して、メディアライブラリに追加してください。"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "この会話内"; @@ -11383,6 +11449,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "ヘルプ"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "GIF を検索して、メディアライブラリに追加してください。"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "次の項目が削除されます:"; diff --git a/WordPress/Resources/ko.lproj/Localizable.strings b/WordPress/Resources/ko.lproj/Localizable.strings index fff0691fbaca..0d5a42f16621 100644 --- a/WordPress/Resources/ko.lproj/Localizable.strings +++ b/WordPress/Resources/ko.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-05 11:54:08+0000 */ +/* Translation-Revision-Date: 2024-01-04 10:54:08+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: ko_KR */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "모든"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "모든 워드프레스닷컴 연간 요금제에는 사용자 정의 도메인 네임이 포함됩니다. 지금 무료 도메인을 등록하세요."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "모든 워드프레스닷컴 요금제에는 사용자 정의 도메인 네임이 포함됩니다. 지금 무료 프리미엄 도메인을 등록하세요."; @@ -9455,7 +9458,10 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "bloganuary.dashboard.card.button.learnMore" = "더 알아보기"; /* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "1월 한 달 동안 새해에 블로깅 습관을 들이는 커뮤니티 챌린지인 Bloganuary에서 1월 한 달 동안 블로깅 프롬프트가 제공됩니다."; +"bloganuary.dashboard.card.description" = "새해에 블로깅 습관을 들이는 커뮤니티 챌린지인 Bloganuary에서 1월 한 달 동안 블로깅 프롬프트가 제공됩니다."; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary가 여기에 있습니다!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary가 다가오고 있습니다!"; @@ -9811,6 +9817,12 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "추가"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "선택한 내용 보기(%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "캠페인 상세"; @@ -10495,6 +10507,21 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "사이트가 없습니다"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "사이트 추가"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "사이트 조치"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "눌러서 추가 사이트 조치 표시"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "홈 개인 설정"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "사이트 제목 변경"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "해제"; @@ -11242,6 +11269,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "무시"; +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "미디어 라이브러리에 추가할 무료 사진을 검색하여 찾으세요!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "대화"; @@ -11389,6 +11419,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "도움말"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "미디어 라이브러리에 추가할 GIF를 검색하여 찾으세요!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "다음 항목이 삭제됩니다."; diff --git a/WordPress/Resources/nl.lproj/Localizable.strings b/WordPress/Resources/nl.lproj/Localizable.strings index da3f7fa84482..0e51f31cf752 100644 --- a/WordPress/Resources/nl.lproj/Localizable.strings +++ b/WordPress/Resources/nl.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-06 12:54:08+0000 */ +/* Translation-Revision-Date: 2024-01-03 14:54:08+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: nl */ @@ -646,6 +646,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Alles"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Alle jaarlijkse abonnementen van WordPress.com zijn inclusief aangepaste domeinnaam. Registreer nu je gratis domein."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Alle WordPress.com abonnementen zijn inclusief een aangepaste domeinnaam. Registreer je gratis domein nu."; @@ -9417,11 +9420,14 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "URL kopiëren"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Site bekijken"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Meer informatie"; -/* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Alle blogmeldingen komen in de maand januari van Bloganuary: onze community-challenge om in het nieuwe jaar betere bloggewoonten op te bouwen."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary is aangebroken!"; /* Title of a button that calls the user to enable the Blogging Prompts feature. */ "bloganuary.learnMore.modal.button.promptsDisabled" = "Blogmeldingen inschakelen"; @@ -9610,6 +9616,9 @@ Example: Reply to Pamela Nguyen */ /* Title for a menu action in the context menu on the Jetpack install card. */ "domain.dashboard.card.menu.hide" = "Dit verbergen"; +/* Search domain - Title for the Suggested domains screen */ +"domain.management.addDomain.search.title" = "Een domein zoeken"; + /* Domain management buy domain card button title */ "domain.management.buy.card.button.title" = "Neem een domein"; @@ -9661,6 +9670,9 @@ Example: Reply to Pamela Nguyen */ /* Domain management choose site card subtitle */ "domain.management.site.card.footer" = "Een gratis domein voor het eerste jaar"; +/* Domain management choose site card subtitle */ +"domain.management.site.card.subtitle" = "Gebruik met een site die je al hebt aangemaakt."; + /* Domain management choose site card title */ "domain.management.site.card.title" = "Bestaande WordPress.com-site"; @@ -9723,6 +9735,9 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "voorbeeld.nl"; +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Geselecteerde weergeven (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Campagnedetails"; @@ -10338,6 +10353,24 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Je hebt geen sites"; +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Siteacties"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Tik om meer siteactie weer te geven"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Startpagina personaliseren"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Favicon wijzigen"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Sitetitel wijzigen"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Site bekijken"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Negeren"; @@ -10356,6 +10389,9 @@ Example: Reply to Pamela Nguyen */ /* Badge for page cells */ "pageList.badgeHomepage" = "Startpagina"; +/* Badge for page cells */ +"pageList.badgePendingReview" = "Beoordeling in behandeling"; + /* Subtitle of the theme template homepage cell */ "pages.template.subtitle" = "Jouw homepage gebruikt een themasjabloon en wordt in de webeditor geopend."; @@ -10374,6 +10410,9 @@ Example: Reply to Pamela Nguyen */ /* Trash option in the trash page confirmation alert. */ "pagesList.trash.actionTitle" = "Verplaatsen naar prullenbak"; +/* Message of the trash page confirmation alert. */ +"pagesList.trash.alertMessage" = "Weet je zeker dat je deze pagina naar de prullenbak wilt verplaatsen?"; + /* No comment provided by engineer. */ "password" = "wachtwoord"; @@ -10410,6 +10449,9 @@ Example: Reply to Pamela Nguyen */ /* Register Domain - Domain contact information field Phone */ "phone number" = "Telefoonnummer"; +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.createdTimeAgo" = "%@ aangemaakt"; + /* Status mesasge for post cells */ "post.deletingPostPermanentlyStatusMessage" = "Bericht verwijderen"; @@ -10422,6 +10464,12 @@ Example: Reply to Pamela Nguyen */ /* Post status and date for list cells with %@ a placeholder for the date. */ "post.publishedTimeAgo" = "%@ gepubliceerd"; +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.scheduledForDate" = "%@ gepland"; + +/* Post status and date for list cells with %@ a placeholder for the date. */ +"post.trashedTimeAgo" = "%@ verplaatst naar prullenbak"; + /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "Weergave"; @@ -11006,6 +11054,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Help"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Zoek gratis GIF's om toe te voegen aan je mediabibliotheek!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "Deze items worden verwijderd:"; diff --git a/WordPress/Resources/pt-BR.lproj/Localizable.strings b/WordPress/Resources/pt-BR.lproj/Localizable.strings index 0bf9d5476934..acdc95ebcd3a 100644 --- a/WordPress/Resources/pt-BR.lproj/Localizable.strings +++ b/WordPress/Resources/pt-BR.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-12 18:15:31+0000 */ +/* Translation-Revision-Date: 2024-01-04 16:02:17+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: pt_BR */ @@ -9231,8 +9231,8 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Saiba mais"; -/* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Durante o mês de janeiro, as sugestões de publicação virão do Bloganuary, nosso desafio para a comunidade criar um hábito de publicação no ano que se inicia."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "O Bloganuary está aqui!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "O Bloganuary está chegando!"; diff --git a/WordPress/Resources/release_notes.txt b/WordPress/Resources/release_notes.txt index 50f66336896a..52afb2999ddb 100644 --- a/WordPress/Resources/release_notes.txt +++ b/WordPress/Resources/release_notes.txt @@ -1,5 +1,14 @@ -We updated the classic editor with new media pickers for Photos and Site Media. Don’t worry, you can still upload images, videos, and more to your site. +* [**] [internal] A minor refactor in authentication flow, including but not limited to social sign-in and two factor authentication. [#22086] +* [**] [internal] Refactor domain selection flows to use the same domain selection UI. [22254] +* [**] Re-enable the support for using Security Keys as a second factor during login [#22258] +* [*] Fix crash in editor that sometimes happens after modifying tags or categories [#22265] +* [*] Add defensive code to make sure the retain cycles in the editor don't lead to crashes [#22252] +* [**] [internal] Add support for the Phase One Fast Media Uploads banner [#22330] +* [*] [internal] Remove personalizeHomeTab feature flag [#22280] +* [*] Fix a rare crash in post search related to tags [#22275] +* [*] Fix a rare crash when deleting posts [#22277] +* [*] Fix a rare crash in Site Media prefetching cancellation [#22278] +* [*] Fix an issue with BlogDashboardPersonalizationService being used on the background thread [#22335] +* [***] Block Editor: Avoid keyboard dismiss when interacting with text blocks [https://github.com/WordPress/gutenberg/pull/57070] +* [**] Block Editor: Auto-scroll upon block insertion [https://github.com/WordPress/gutenberg/pull/57273] -Speaking of media types—you can now add media filters to the Site Media screen. If you’re using an iPhone, you’ll notice the new aspect ratio mode, too. Both options are available when you tap the title menu. - -Finally, we fixed the broken compliance pop-up that appears while you’re checking stats during the onboarding process. Sweet. diff --git a/WordPress/Resources/ro.lproj/Localizable.strings b/WordPress/Resources/ro.lproj/Localizable.strings index 6a154e1e298e..0880f032a6ed 100644 --- a/WordPress/Resources/ro.lproj/Localizable.strings +++ b/WordPress/Resources/ro.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-13 08:15:49+0000 */ +/* Translation-Revision-Date: 2024-01-04 07:30:34+0000 */ /* Plural-Forms: nplurals=3; plural=(n == 1) ? 0 : ((n == 0 || n % 100 >= 2 && n % 100 <= 19) ? 1 : 2); */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: ro */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Toate"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Toate planurile WordPress.com cu plată anuală includ un nume de domeniu personalizat. Înregistrează-ți domeniul gratuit acum."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Toate planurile WordPress.com includ un nume de domeniu personalizat. Înregistrează-ți domeniul premium gratuit acum."; @@ -9463,6 +9466,9 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Short description for the Bloganuary event, shown right below the title. */ "bloganuary.dashboard.card.description" = "În luna ianuarie, îndemnurile de a scrie vor veni de la Bloganuary - provocarea comunității noastre de a crea un obicei de a scrie pe bloguri în noul an."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary este aici!"; + /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary vine în curând!"; @@ -10528,6 +10534,9 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Nu ai niciun site"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Adaugă site"; + /* Button that reveals more site actions */ "mySite.siteActions.button" = "Acțiuni pe site"; diff --git a/WordPress/Resources/ru.lproj/Localizable.strings b/WordPress/Resources/ru.lproj/Localizable.strings index c1485c4dd6f8..c10b87ebb087 100644 --- a/WordPress/Resources/ru.lproj/Localizable.strings +++ b/WordPress/Resources/ru.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-06 08:54:08+0000 */ +/* Translation-Revision-Date: 2024-01-03 14:54:09+0000 */ /* Plural-Forms: nplurals=3; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2); */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: ru */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Все"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Все годовые тарифные планы WordPress.com включают пользовательский домен. Зарегистрируйте ваш бесплатный домен."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Все тарифы WordPress.com включают возможность зарегистрировать пользовательское имя домена. Сделайте это сейчас."; @@ -9454,12 +9457,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "Копировать URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Перейти на сайт"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Подробнее"; /* Short description for the Bloganuary event, shown right below the title. */ "bloganuary.dashboard.card.description" = "В январе подсказки по ведению блога будут рассылаться через Bloganuary — челлендж сообщества, призванный выработать привычку ежедневно вести блог в новом году."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary уже идёт!"; + /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary уже на пороге!"; @@ -9814,6 +9823,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Добавить"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Выбрать изображения"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Показать выбранное (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Детали кампании"; @@ -10267,6 +10285,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "Удалено!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "Все"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Аудио"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Документы"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Изображения"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Видео"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "Удалить"; @@ -10345,6 +10378,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "Записать видео"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ из %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × %2$d пикс."; @@ -10498,6 +10534,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "У вас нет сайтов"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Добавить сайт"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Действия с сайтом"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Нажмите, чтобы показать больше действий с сайтом"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Настроить страницу «Главная»"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Сменить значок сайта"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Изменить название сайта"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Переключиться на другой сайт"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Перейти на сайт"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Закрыть"; @@ -11071,6 +11131,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "Предварительный просмотр медиафайла"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Добавить"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "Отменить выбор"; @@ -11245,6 +11308,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Закрыть"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Фотографии предоставлены Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "Найдите бесплатные фотографии и добавьте их в вашу Библиотеку файлов!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "В этом обсуждении"; @@ -11392,6 +11461,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Справка"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Найдите GIF-изображения и добавьте их в вашу Библиотеку файлов!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "эти элементы будут удалены:"; diff --git a/WordPress/Resources/sq.lproj/Localizable.strings b/WordPress/Resources/sq.lproj/Localizable.strings index e60f2ebf191b..c9c4ffd4f5ed 100644 --- a/WordPress/Resources/sq.lproj/Localizable.strings +++ b/WordPress/Resources/sq.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-09 19:17:57+0000 */ +/* Translation-Revision-Date: 2024-01-04 11:04:02+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: sq_AL */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Krejt"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Krejt planet vjetore WordPress.com përmbajnë një emër përkatësie vetjake. Regjistrohuni që tani për të marrë falas përkatësinë tuaj."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Krejt planet WordPress.com përfshijnë një emër vetjak përkatësie. Regjistroni falas që tani përkatësinë tuaj."; @@ -9436,11 +9439,17 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "Kopjoji URL-në"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "Vizitoni sajtin"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "Mësoni më tepër"; /* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Për muajin janar, cytjet për blogim do të vijnë nga Bloganuary - sfida e bashkësisë tonë për të krijuar një zakon blogimi për vitin e ri."; +"bloganuary.dashboard.card.description" = "Për muajin janar, cytjet për blogim do të vijnë nga Bloganuary — sfida e bashkësisë tonë për të krijuar një zakon blogimi për vitin e ri."; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary erdhi!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary po vjen!"; @@ -9461,6 +9470,12 @@ Note that the word 'go' here should have a closer meaning to 'start' rather than /* The first line of the description shown in the Bloganuary modal sheet. */ "bloganuary.learnMore.modal.descriptions.first" = "Merrni një cytje të re, për t’ju frymëzuar çdo ditë."; +/* An additional piece of information shown in case the user has the Blogging Prompts feature disabled. */ +"bloganuary.learnMore.modal.footer.addition" = "Për të marrë pjesë në Bloganuary lypset të aktivizoni Cytje Blogimi."; + +/* An informative excerpt shown in a subtler tone. */ +"bloganuary.learnMore.modal.footer.text" = "Bloganuary do të përdorë Cytje të Përditshme Blogimi për t’ju dërguar tema për muajin janar."; + /* The headline text of the Bloganuary modal sheet. */ "bloganuary.learnMore.modal.headline" = "Merrni pjesë te sfida jonë, e gjatë një muaj, për shkrim"; @@ -9787,6 +9802,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "Shtoje"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "Përzgjidhni Figura"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "Shihni të Përzgjedhurat (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "Hollësi Fushate"; @@ -10237,6 +10261,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "U fshi!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "Krejt"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "Audio"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "Dokumente"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "Figura"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "Video"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "Fshije"; @@ -10315,6 +10354,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "Bëni Video"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "%1$@ nga %2$@"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × %2$d px"; @@ -10468,6 +10510,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "S’keni ndonjë sajt"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Shtoni sajt"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "Veprime Sajti"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "Prekeni, që të shfaqen më tepër veprime sajti"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "Personalizoni kreun"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "Ndryshoni ikonë sajti"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "Ndryshoni titull sajti"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "Ndërroni sajt"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "Vizitoni sajtin"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "Hidhe tej"; @@ -11038,6 +11104,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "Bëni paraparje të medias"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "Shtoje"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "Shpërzgjidhe"; @@ -11212,6 +11281,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Hidhe tej"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Foto të furnizuara nga Pexels"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "Kërkoni për të gjetur foto të lira që t’i shtoni te Mediateka juaj!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "Në këtë bisedë"; @@ -11359,6 +11434,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "Ndihmë"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "Kërkoni për të gjetur GIF-e që t’i shtoni te Mediateka juaj!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "këto objekte do të fshihen:"; diff --git a/WordPress/Resources/sv.lproj/Localizable.strings b/WordPress/Resources/sv.lproj/Localizable.strings index 20c489403a1d..34b66ea1c16f 100644 --- a/WordPress/Resources/sv.lproj/Localizable.strings +++ b/WordPress/Resources/sv.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-12 15:08:37+0000 */ +/* Translation-Revision-Date: 2024-01-03 12:51:39+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: sv_SE */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Alla"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Alla årliga WordPress.com-paket inkluderar ett anpassat domännamn. Registrera din gratis domän nu."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Nu innehåller alla prispaket hos WordPress.com ett anpassat domännamn. Registrera ditt fria domännamn nu."; @@ -9461,7 +9464,10 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "bloganuary.dashboard.card.button.learnMore" = "Läs mer"; /* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "För månaden januari kommer bloggningsförslagen att komma från Bloganuary – vår communityutmaning avsedd att bygga upp en bloggvana för det nya året."; +"bloganuary.dashboard.card.description" = "För januari månad kommer bloggningsförslagen från Bloganuary – vår community-utmaning att bygga en bloggvana för det nya året."; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary är här!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary är på väg!"; @@ -10528,6 +10534,9 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Du har inga webbplatser"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Lägg till webbplats"; + /* Button that reveals more site actions */ "mySite.siteActions.button" = "Webbplatsåtgärder"; diff --git a/WordPress/Resources/tr.lproj/Localizable.strings b/WordPress/Resources/tr.lproj/Localizable.strings index 8e592e0c74e0..fff87e6c9bd8 100644 --- a/WordPress/Resources/tr.lproj/Localizable.strings +++ b/WordPress/Resources/tr.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-13 13:55:02+0000 */ +/* Translation-Revision-Date: 2024-01-03 12:54:08+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: tr */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "Tümü"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "Tüm WordPress.com yıllık paketleri kişisel bir alan adı içerir. Ücretsiz alan adınızı hemen kaydedin."; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "Tüm WordPress.com planları kişisel bir alan adı içerir. Ücretsiz premium alan adınızı hemen kaydedin."; @@ -9463,6 +9466,9 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Short description for the Bloganuary event, shown right below the title. */ "bloganuary.dashboard.card.description" = "Yeni yılda blog yazma alışkanlığı oluşturmayı hedefleyen topluluk etkinliğimiz Bloganuary'den Ocak ayı boyunca blog istemleri alacaksınız."; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary burada!"; + /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary yaklaşıyor!"; @@ -10528,6 +10534,9 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "Hiçbir siteniz yok"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "Site ekleyin"; + /* Button that reveals more site actions */ "mySite.siteActions.button" = "Site eylemleri"; diff --git a/WordPress/Resources/zh-Hans.lproj/Localizable.strings b/WordPress/Resources/zh-Hans.lproj/Localizable.strings index 6a3617bacf72..fa4934032067 100644 --- a/WordPress/Resources/zh-Hans.lproj/Localizable.strings +++ b/WordPress/Resources/zh-Hans.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-05 09:54:09+0000 */ +/* Translation-Revision-Date: 2024-01-04 10:54:08+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: zh_CN */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "全部"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "所有 WordPress.com 年度套餐均包含一个自定义域名。 立即注册您的免费域名。"; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "全部 WordPress.com 套餐包含自定义域名。立即注册您的免费高级域。"; @@ -9448,11 +9451,14 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "复制 URL"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "查看站点"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "了解更多"; -/* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "在一月份,Bloganuary 将向您发送博客提示,这是我们发起的一项社区挑战,旨在帮助广大用户在新的一年养成写博客的习惯。"; +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary 上线了!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary 即将上线!"; @@ -9808,6 +9814,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "添加"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "选择图片"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "查看已选项 (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "活动详细信息"; @@ -10261,6 +10276,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "已删除!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "所有"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "音频"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "文档"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "图像"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "视频"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "删除"; @@ -10339,6 +10369,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "拍摄视频"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "第 %1$@ 步,共 %2$@ 步"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × %2$d 像素"; @@ -10492,6 +10525,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "您没有任何站点"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "添加站点"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "站点操作"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "轻点以显示更多站点操作"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "对首页进行个性化设置"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "更改站点图标"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "更改站点标题"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "切换站点"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "查看站点"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "忽略"; @@ -11065,6 +11122,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "预览媒体"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "添加"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "取消选择"; @@ -11239,6 +11299,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "忽略"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "Pexels 提供的照片"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "搜索查找要添加到您的媒体库中的免费照片!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "此会话中"; @@ -11386,6 +11452,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "帮助"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "搜索查找要添加到您的媒体库中的 GIF!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "即将删除以下项目:"; diff --git a/WordPress/Resources/zh-Hant.lproj/Localizable.strings b/WordPress/Resources/zh-Hant.lproj/Localizable.strings index 2e90907d288c..0007f3b3cc7f 100644 --- a/WordPress/Resources/zh-Hant.lproj/Localizable.strings +++ b/WordPress/Resources/zh-Hant.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-12-12 00:09:06+0000 */ +/* Translation-Revision-Date: 2024-01-04 10:54:08+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.0-alpha.11 */ /* Language: zh_TW */ @@ -649,6 +649,9 @@ translators: Block name. %s: The localized block name */ Title of the drafts filter. This filter shows a list of draft posts. */ "All" = "全部"; +/* Information about redeeming domain credit on site dashboard. */ +"All WordPress.com annual plans include a custom domain name. Register your free domain now." = "所有 WordPress.com 年繳方案皆隨附一個自訂網域名稱。 立即註冊你的免費網域。"; + /* Footer of the free domain registration section for a paid plan. Information about redeeming domain credit on site dashboard. */ "All WordPress.com plans include a custom domain name. Register your free premium domain now." = "所有 WordPress.com 方案都包含一個自訂網域名稱。立即註冊你的免費進階網域。"; @@ -1346,7 +1349,7 @@ translators: Block name. %s: The localized block name */ /* Image caption field label (for editing) Noun. Label for the caption for a media asset (image / video) */ -"Caption" = "說明文字"; +"Caption" = "媒體說明文字"; /* Alert title. Title for the warning shown to the user when he refuses to re-login when the authToken is missing. */ @@ -9445,11 +9448,17 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Context menu button title */ "blogHeader.actionCopyURL" = "複製網址"; +/* Context menu button title */ +"blogHeader.actionVisitSite" = "造訪網站"; + /* Title for a button that, when tapped, shows more info about participating in Bloganuary. */ "bloganuary.dashboard.card.button.learnMore" = "深入瞭解"; /* Short description for the Bloganuary event, shown right below the title. */ -"bloganuary.dashboard.card.description" = "Bloganuary 會在 1 月送上網誌提示;這個社群挑戰的目的是協助你在新的一年養成寫網誌習慣。"; +"bloganuary.dashboard.card.description" = "Bloganuary 會在 1 月送上網誌提示:這個社群挑戰的目的,是為了協助你在新的一年養成寫網誌的習慣。"; + +/* Title for the Bloganuary dashboard card while Bloganuary is running. */ +"bloganuary.dashboard.card.runningTitle" = "Bloganuary 正式開跑!"; /* Title for the Bloganuary dashboard card. */ "bloganuary.dashboard.card.title" = "Bloganuary 挑戰即將來臨!"; @@ -9805,6 +9814,15 @@ Example: Reply to Pamela Nguyen */ Site Address placeholder */ "example.com" = "example.com"; +/* Title for confirmation navigation bar button item */ +"externalMediaPicker.add" = "新增"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarSelectItemsPrompt" = "選取圖片"; + +/* Bottom toolbar title in the selection mode */ +"externalMediaPicker.toolbarViewSelected" = "檢視選擇項目 (%@)"; + /* Title of screen the displays the details of an advertisement campaign. */ "feature.blaze.campaignDetails.title" = "行銷活動詳細資訊"; @@ -10258,6 +10276,21 @@ Example: Reply to Pamela Nguyen */ /* Text displayed in HUD after successfully deleting a media item */ "mediaLibrary.deletionSuccessMessage" = "已刪除!"; +/* The name of the media filter */ +"mediaLibrary.filterAll" = "全部"; + +/* The name of the media filter */ +"mediaLibrary.filterAudio" = "音訊"; + +/* The name of the media filter */ +"mediaLibrary.filterDocuments" = "文件"; + +/* The name of the media filter */ +"mediaLibrary.filterImages" = "圖片"; + +/* The name of the media filter */ +"mediaLibrary.filterVideos" = "影片"; + /* User action to delete un-uploaded media. */ "mediaLibrary.retryOptionsAlert.delete" = "刪除"; @@ -10336,6 +10369,9 @@ Example: Reply to Pamela Nguyen */ /* The name of the action in the context menu */ "mediaPicker.takeVideo" = "拍攝影片"; +/* Navigation title for media preview. Example: 1 of 3 */ +"mediaPreview.NofM" = "第 %1$@ 步,共 %2$@ 步"; + /* Max image size in pixels (e.g. 300x300px) */ "mediaSizeSlider.valueFormat" = "%1$d × %2$d 像素"; @@ -10489,6 +10525,30 @@ Example: Reply to Pamela Nguyen */ /* Message title for when a user has no sites. */ "mySite.noSites.title" = "你沒有任何網站"; +/* Menu title for the add site option */ +"mySite.siteActions.addSite" = "新增網站"; + +/* Button that reveals more site actions */ +"mySite.siteActions.button" = "網站動作"; + +/* Accessibility hint for button used to show more site actions */ +"mySite.siteActions.hint" = "點選以顯示更多網站動作"; + +/* Menu title for the personalize home option */ +"mySite.siteActions.personalizeHome" = "打造個人版首頁"; + +/* Menu title for the change site icon option */ +"mySite.siteActions.siteIcon" = "變更網站圖示"; + +/* Menu title for the change site title option */ +"mySite.siteActions.siteTitle" = "變更網站標題"; + +/* Menu title for the switch site option */ +"mySite.siteActions.switchSite" = "切換網站"; + +/* Menu title for the visit site option */ +"mySite.siteActions.visitSite" = "造訪網站"; + /* Dismiss button title. */ "noResultsViewController.dismissButton" = "關閉"; @@ -11062,6 +11122,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Accessibility label for media item preview for user's viewing an item in their media library */ "siteMediaItem.contentViewAccessibilityLabel" = "預覽媒體"; +/* Title for confirmation navigation bar button item */ +"siteMediaPicker.add" = "新增"; + /* Button selection media in media picker */ "siteMediaPicker.deselect" = "取消選取"; @@ -11236,6 +11299,12 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "關閉"; +/* Subtitle for placeholder in Free Photos. The company name 'Pexels' should always be written as it is. */ +"stockPhotos.subtitle" = "由 Pexels 提供的相片"; + +/* Title for placeholder in Free Photos */ +"stockPhotos.title" = "搜尋免費相片以新增至你的媒體庫!"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "在此討論中"; @@ -11383,6 +11452,9 @@ Example: given a notice format "Following %@" and empty site name, this will be /* View title for Help & Support page. */ "support.title" = "說明"; +/* Title for placeholder in Tenor picker */ +"tenor.welcomeMessage" = "搜尋 GIF 以新增至你的媒體庫!"; + /* Header of delete screen section listing things that will be deleted. */ "these items will be deleted:" = "這些項目將會刪除:"; diff --git a/WordPress/UITests/JetpackUITests.xctestplan b/WordPress/UITests/JetpackUITests.xctestplan index 88d76bc5a9e6..036ebc079947 100644 --- a/WordPress/UITests/JetpackUITests.xctestplan +++ b/WordPress/UITests/JetpackUITests.xctestplan @@ -21,7 +21,6 @@ }, "testTargets" : [ { - "parallelizable" : true, "skippedTests" : [ "EditorAztecTests", "LoginTests\/testEmailMagicLinkLogin()", diff --git a/WordPress/UITests/Tests/LoginTests.swift b/WordPress/UITests/Tests/LoginTests.swift index 822dc50e0fb5..7da16cd8226d 100644 --- a/WordPress/UITests/Tests/LoginTests.swift +++ b/WordPress/UITests/Tests/LoginTests.swift @@ -24,7 +24,6 @@ class LoginTests: XCTestCase { siteUrl: WPUITestCredentials.testWPcomPaidSite ) .continueWithSelectedSite() - .dismissNotificationAlertIfNeeded() try TabNavComponent() .goToMeScreen() .logoutToPrologue() diff --git a/WordPress/UITests/Tests/StatsTests.swift b/WordPress/UITests/Tests/StatsTests.swift index a56ee15e3774..479da2a2de05 100644 --- a/WordPress/UITests/Tests/StatsTests.swift +++ b/WordPress/UITests/Tests/StatsTests.swift @@ -22,42 +22,43 @@ class StatsTests: XCTestCase { takeScreenshotOfFailedTest() } - let insightsStats: [String] = [ - "Your views in the last 7-days are -9 (-82%) lower than the previous 7-days. ", - "Thursday", - "34% of views", - "Best Hour", - "4 AM", - "25% of views" - ] - - let yearsStats: [String] = [ - "9,148", - "+7,933 (653%)", - "United States, 60", - "Canada, 44", - "Germany, 15", - "France, 14", - "United Kingdom, 12", - "India, 121" - ] - - let yearsChartBars: [String] = [ - "Views, 2019: 9148", - "Visitors, 2019: 4216", - "Views, 2018: 1215", - "Visitors, 2018: 632", - "Views, 2017: 788", - "Visitors, 2017: 465" - ] - func testInsightsStatsLoadProperly() throws { + let insightsStats: [String] = [ + "Your views in the last 7-days are -9 (-82%) lower than the previous 7-days. ", + "Thursday", + "34% of views", + "Best Hour", + "4 AM", + "25% of views" + ] + try StatsScreen() .switchTo(mode: "insights") .assertStatsAreLoaded(insightsStats) } func testYearsStatsLoadProperly() throws { + let yearsStats: [String] = [ + "9,148", + "+7,933 (653%)", + "United States, 60", + "Canada, 44", + "Germany, 15", + "France, 14", + "United Kingdom, 12", + "India, 121" + ] + + let currentYear = Calendar.current.component(.year, from: Date()) + let yearsChartBars: [String] = [ + "Views, \(currentYear): 9148", + "Visitors, \(currentYear): 4216", + "Views, \(currentYear - 1): 1215", + "Visitors, \(currentYear - 1): 632", + "Views, \(currentYear - 2): 788", + "Visitors, \(currentYear - 2): 465" + ] + try StatsScreen() .switchTo(mode: "years") .assertStatsAreLoaded(yearsStats) diff --git a/WordPress/UITestsFoundation/Globals.swift b/WordPress/UITestsFoundation/Globals.swift index 27e60927a1ee..cac30515d342 100644 --- a/WordPress/UITestsFoundation/Globals.swift +++ b/WordPress/UITestsFoundation/Globals.swift @@ -62,23 +62,6 @@ public func waitAndTap( _ element: XCUIElement, maxRetries: Int = 20) { } } -public func tapUntilCondition(element: XCUIElement, condition: Bool, description: String, maxRetries: Int = 10) { - var retries = 0 - while retries < maxRetries { - if !condition { - element.tap() - break - } - - usleep(500000) // a 0.5 second delay before retrying - retries += 1 - } - - if retries == maxRetries { - XCTFail("Condition \(description) still not met after \(maxRetries) tries.") - } -} - public func waitForElementToDisappear( _ element: XCUIElement, maxRetries: Int = 10) { var retries = 0 while retries < maxRetries { diff --git a/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift b/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift index 4f804fc5e5f2..d05a37823b64 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift @@ -152,7 +152,7 @@ public class EditorPostSettings: ScreenObject { // To ensure that the day tap happens on the correct month let nextMonth = monthLabel.value as! String if nextMonth != currentMonth { - tapUntilCondition(element: firstCalendarDayButton, condition: firstCalendarDayButton.isSelected, description: "First Day button selected") + firstCalendarDayButton.tapUntil(.selected, failureMessage: "First Day button not selected!") } doneButton.tap() diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift index a83320603dfb..fedaffef865d 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift @@ -40,13 +40,11 @@ public class LoginUsernamePasswordScreen: ScreenObject { public func proceedWithSelfHostedSiteAddedFromSitesList(username: String, password: String) throws -> MySitesScreen { fill(username: username, password: password) - return try MySitesScreen() } public func proceedWithSelfHosted(username: String, password: String) throws -> MySiteScreen { fill(username: username, password: password) - return try MySiteScreen() } @@ -66,6 +64,10 @@ public class LoginUsernamePasswordScreen: ScreenObject { passwordTextField.typeText(password) } nextButton.tap() + + if #available(iOS 17.2, *) { + app.dismissSavePasswordPrompt() + } } private func dismissQuickStartPromptIfNeeded() throws { diff --git a/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift b/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift index a16961a7d098..3cd51366cb9e 100644 --- a/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift @@ -29,7 +29,6 @@ public class PasswordScreen: ScreenObject { @discardableResult public func proceedWithValidPassword() throws -> LoginEpilogueScreen { try tryProceed(password: "pw") - return try LoginEpilogueScreen() } @@ -59,6 +58,15 @@ public class PasswordScreen: ScreenObject { passwordTextField.typeText(password) continueButton.tap() + + // iOS 16.4 introduced a prompt to save passwords in the keychain. + // Prior to iOS 17.2, we used a test observer (see TestObserver.swift) to disable storing passwords before the tests started. + // Xcode 15.1 and iOS 17.2 have what at the time of writing looks like a bug in the Settings app which breaks that approach. + // As soon as the passwords screen is pushed in the Settings navigation stack, it's immediately popped back. + // For the time being, let's manually dismiss the prompt on demand. + if #available(iOS 17.2, *) { + app.dismissSavePasswordPrompt() + } } @discardableResult diff --git a/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift b/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift index 53229cb60a07..56ede9a43f83 100644 --- a/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift @@ -84,7 +84,11 @@ public class NotificationsScreen: ScreenObject { } public func replyToComment(withText text: String) -> Self { - tapUntilCondition(element: replyCommentButton, condition: replyTextView.exists, description: "Reply Text View exists") + replyCommentButton.tapUntil( + element: replyTextView, + matches: .exists, + failureMessage: "Reply Text View does not exists!" + ) replyTextView.typeText(text) replyButton.tap() @@ -115,9 +119,9 @@ public class NotificationsScreen: ScreenObject { public func likeComment() -> Self { - let isCommentTextDisplayed = app.webViews.staticTexts.firstMatch.waitForExistence(timeout: 5) + let isCommentOnTextDisplayed = app.staticTexts["Comment on"].firstMatch.waitForExistence(timeout: 5) - if isCommentTextDisplayed { + if isCommentOnTextDisplayed { likeCommentButton.tap() } diff --git a/WordPress/UITestsFoundation/Screens/StatsScreen.swift b/WordPress/UITestsFoundation/Screens/StatsScreen.swift index 5bd93c4b4a68..e9c1b64bccea 100644 --- a/WordPress/UITestsFoundation/Screens/StatsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/StatsScreen.swift @@ -23,7 +23,7 @@ public class StatsScreen: ScreenObject { public func verifyStatsLoaded(_ stats: [String]) -> Bool { for stat in stats { - guard app.staticTexts[stat].waitForExistence(timeout: 3) else { + guard app.staticTexts[stat].waitForExistence(timeout: 10) else { Logger.log(message: "Element not found: \(stat)", event: LogEvent.e) return false } @@ -33,7 +33,7 @@ public class StatsScreen: ScreenObject { public func verifyChartLoaded(_ chartElements: [String]) -> Bool { for chartElement in chartElements { - guard app.otherElements[chartElement].waitForExistence(timeout: 3) else { + guard app.otherElements[chartElement].waitForExistence(timeout: 10) else { Logger.log(message: "Element not found: \(chartElement)", event: LogEvent.e) return false } diff --git a/WordPress/UITestsFoundation/TestObserver.swift b/WordPress/UITestsFoundation/TestObserver.swift index 4cc036b0e496..a9c1695b14ae 100644 --- a/WordPress/UITestsFoundation/TestObserver.swift +++ b/WordPress/UITestsFoundation/TestObserver.swift @@ -2,8 +2,8 @@ import XCTest class TestObserver: NSObject, XCTestObservation { override init() { - super.init() - XCTestObservationCenter.shared.addTestObserver(self) + super.init() + XCTestObservationCenter.shared.addTestObserver(self) } func testBundleWillStart(_ testBundle: Bundle) { diff --git a/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift b/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift index dac04c35873d..c8d4b32ebcfe 100644 --- a/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift +++ b/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift @@ -6,11 +6,15 @@ extension XCUIApplication { // This method encapsulates the logic to dimiss the prompt. func dismissSavePasswordPrompt() { XCTContext.runActivity(named: "Dismiss save password prompt if needed.") { _ in - guard buttons["Save Password"].waitForExistence(timeout: 10) else { return } + guard buttons["Save Password"].waitForExistence(timeout: 20) else { return } // There should be no need to wait for this button to exist since it's part of the same - // alert where "Save Password" is. - buttons["Not Now"].tap() + // alert where "Save Password" is... + let notNowButton = XCUIApplication().buttons["Not Now"] + // ...but we've seen failures in CI where this cannot be found so let's check first + XCTAssertTrue(notNowButton.waitForExistence(timeout: 5)) + + notNowButton.tapUntil(.dismissed, failureMessage: "Save Password Prompt not dismissed!") } } } diff --git a/WordPress/UITestsFoundation/XCUIElement+TapUntil.swift b/WordPress/UITestsFoundation/XCUIElement+TapUntil.swift new file mode 100644 index 000000000000..010e12e1fa5b --- /dev/null +++ b/WordPress/UITestsFoundation/XCUIElement+TapUntil.swift @@ -0,0 +1,93 @@ +import XCTest + +public extension XCUIElement { + + /// Abstraction do describe possible "states" an `XCUIElement` can be in. + /// + /// The goal of this `enum` is to make checking against the possible states a safe operation thanks to the compiler enforcing all and only the states represented by the `enum` `case`s are handled. + enum State { + case exists + case dismissed + case selected + } + + /// Attempt to tap `self` until the given `XCUIElement` is in the given `State` or the `maxRetries` number of retries has been reached. + /// + /// Useful to make tests robusts against UI changes that may have some lag. + func tapUntil( + element: XCUIElement, + matches state: State, + failureMessage: String, + maxRetries: Int = 10, + retryInterval: TimeInterval = 1 + ) { + tapUntil( + Condition(element: element, state: state), + retriedCount: 0, + failureMessage: failureMessage, + maxRetries: maxRetries, + retryInterval: retryInterval + ) + } + + /// Attempt to tap `self` until its "state" matches `Condition.State` or the `maxRetries` number of retries has been reached. + /// + /// Useful to make tests robusts against UI changes that may have some lag. + func tapUntil( + _ state: State, + failureMessage: String, + maxRetries: Int = 10, + retryInterval: TimeInterval = 1 + ) { + tapUntil( + Condition(element: self, state: state), + retriedCount: 0, + failureMessage: failureMessage, + maxRetries: maxRetries, + retryInterval: retryInterval + ) + } + + /// Describe the expectation for a given `XCUIElement` to be in a certain `Condition.State`. + /// + /// Example: `Condition(element: myButton, state: .selected)`. + struct Condition { + + let element: XCUIElement + let state: XCUIElement.State + + fileprivate func isMet() -> Bool { + switch state { + case .exists: return element.exists + case .dismissed: return element.isHittable == false + case .selected: return element.isSelected + } + } + } + + private func tapUntil( + _ condition: Condition, + retriedCount: Int, + failureMessage: String, + maxRetries: Int, + retryInterval: TimeInterval + ) { + guard retriedCount < maxRetries else { + return XCTFail("\(failureMessage) after \(retriedCount) tries.") + } + + tap() + + guard condition.isMet() else { + sleep(UInt32(retryInterval)) + + return tapUntil( + condition, + retriedCount: retriedCount + 1, + failureMessage: failureMessage, + maxRetries: maxRetries, + retryInterval: retryInterval + ) + } + } +} diff --git a/WordPress/WordPress-Alpha.entitlements b/WordPress/WordPress-Alpha.entitlements index 8fa91cad0128..c95dbafba6ed 100644 --- a/WordPress/WordPress-Alpha.entitlements +++ b/WordPress/WordPress-Alpha.entitlements @@ -5,6 +5,7 @@ com.apple.developer.associated-domains webcredentials:wordpress.com + webcredentials:*.wordpress.com applinks:wordpress.com applinks:*.wordpress.com applinks:apps.wordpress.com diff --git a/WordPress/WordPress-Internal.entitlements b/WordPress/WordPress-Internal.entitlements index 5657a7899828..0c0d7121ca56 100644 --- a/WordPress/WordPress-Internal.entitlements +++ b/WordPress/WordPress-Internal.entitlements @@ -7,6 +7,7 @@ com.apple.developer.associated-domains webcredentials:wordpress.com + webcredentials:*.wordpress.com applinks:wordpress.com applinks:*.wordpress.com applinks:apps.wordpress.com diff --git a/WordPress/WordPress.entitlements b/WordPress/WordPress.entitlements index 811676606ba7..2fd4f24ae0b5 100644 --- a/WordPress/WordPress.entitlements +++ b/WordPress/WordPress.entitlements @@ -11,6 +11,7 @@ com.apple.developer.associated-domains webcredentials:wordpress.com + webcredentials:*.wordpress.com applinks:wordpress.com applinks:*.wordpress.com applinks:apps.wordpress.com diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index e6bec82ab0ab..042088b33b09 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -72,7 +72,6 @@ 0107E0C028F97D5000DE87DB /* HomeWidgetToday.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F526C522538CF2A0069706C /* HomeWidgetToday.swift */; }; 0107E0C128F97D5000DE87DB /* FlexibleCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F568A2E254216550048A9E4 /* FlexibleCard.swift */; }; 0107E0C228F97D5000DE87DB /* VerticalCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F568A1E254213B60048A9E4 /* VerticalCard.swift */; }; - 0107E0C328F97D5000DE87DB /* ThisWeekWidgetStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F93181239AF64800E4E96E /* ThisWeekWidgetStats.swift */; }; 0107E0C428F97D5000DE87DB /* HomeWidgetAllTimeData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F5C861925C9EA2500BABE64 /* HomeWidgetAllTimeData.swift */; }; 0107E0C528F97D5000DE87DB /* GroupedViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE20C1425CF165700A15525 /* GroupedViewData.swift */; }; 0107E0C628F97D5000DE87DB /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D690151F828FF000200E30 /* FeatureFlag.swift */; }; @@ -107,7 +106,6 @@ 0107E11528FD7FE500DE87DB /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA25FA332609AAAA0005E08F /* AppConfiguration.swift */; }; 0107E11628FD7FE800DE87DB /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA25FA332609AAAA0005E08F /* AppConfiguration.swift */; }; 0107E13B28FE9DB200DE87DB /* Sites.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 3F46AB0225BF5D6300CE2E98 /* Sites.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; - 0107E13C28FE9DB200DE87DB /* ThisWeekWidgetStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F93181239AF64800E4E96E /* ThisWeekWidgetStats.swift */; }; 0107E13D28FE9DB200DE87DB /* HomeWidgetAllTimeData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F5C861925C9EA2500BABE64 /* HomeWidgetAllTimeData.swift */; }; 0107E13E28FE9DB200DE87DB /* SitesDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1482CDF2575BDA4007E4DD6 /* SitesDataProvider.swift */; }; 0107E13F28FE9DB200DE87DB /* HomeWidgetTodayData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB34ACA25672A90001A74A6 /* HomeWidgetTodayData.swift */; }; @@ -172,7 +170,11 @@ 014ACD152A1E5034008A706C /* WebKitViewController+SandboxStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014ACD132A1E5033008A706C /* WebKitViewController+SandboxStore.swift */; }; 014D7E8F2AA9FBDE00F8C9E3 /* WidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0107E15C28FFE99300DE87DB /* WidgetConfiguration.swift */; }; 015BA4EB29A788A300920F4B /* StatsTotalInsightsCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015BA4EA29A788A300920F4B /* StatsTotalInsightsCellTests.swift */; }; + 016231502B3B3CAD0010E377 /* PrimaryDomainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0162314F2B3B3CAD0010E377 /* PrimaryDomainView.swift */; }; + 016231512B3B3CAD0010E377 /* PrimaryDomainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0162314F2B3B3CAD0010E377 /* PrimaryDomainView.swift */; }; 0167F4B62AAA0342005B9E42 /* WidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0107E15C28FFE99300DE87DB /* WidgetConfiguration.swift */; }; + 017008452B35C25C00C80490 /* SiteDomainsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017008442B35C25C00C80490 /* SiteDomainsViewModel.swift */; }; + 017008462B35C25C00C80490 /* SiteDomainsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017008442B35C25C00C80490 /* SiteDomainsViewModel.swift */; }; 017C57BB2B2B5555001E7687 /* DomainSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017C57BA2B2B5555001E7687 /* DomainSelectionViewController.swift */; }; 017C57BC2B2B5555001E7687 /* DomainSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017C57BA2B2B5555001E7687 /* DomainSelectionViewController.swift */; }; 018635842A8109DE00915532 /* SupportChatBotViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018635832A8109DE00915532 /* SupportChatBotViewController.swift */; }; @@ -191,6 +193,8 @@ 0188FE4C2AA62F800093EDA5 /* LockScreenTodayLikesCommentsStatWidgetConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0188FE4A2AA62F800093EDA5 /* LockScreenTodayLikesCommentsStatWidgetConfig.swift */; }; 0189AF052ACAD89700F63393 /* ShoppingCartService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0189AF042ACAD89700F63393 /* ShoppingCartService.swift */; }; 0189AF062ACAD89700F63393 /* ShoppingCartService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0189AF042ACAD89700F63393 /* ShoppingCartService.swift */; }; + 018FF1352AE6771A00F301C3 /* LockScreenVerticalCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018FF1342AE6771A00F301C3 /* LockScreenVerticalCard.swift */; }; + 018FF1372AE67C2600F301C3 /* LockScreenFlexibleCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018FF1362AE67C2600F301C3 /* LockScreenFlexibleCard.swift */; }; 019D699E2A5EA963003B676D /* RootViewCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019D699D2A5EA963003B676D /* RootViewCoordinatorTests.swift */; }; 019D69A02A5EBF47003B676D /* WordPressAuthenticatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019D699F2A5EBF47003B676D /* WordPressAuthenticatorProtocol.swift */; }; 019D69A12A5EBF47003B676D /* WordPressAuthenticatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019D699F2A5EBF47003B676D /* WordPressAuthenticatorProtocol.swift */; }; @@ -200,6 +204,12 @@ 01ABF1712AD578B3004331BD /* WidgetAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ABF16F2AD578B3004331BD /* WidgetAnalytics.swift */; }; 01B5C3C72AE7FC61007055BB /* UITestConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B5C3C62AE7FC61007055BB /* UITestConfigurator.swift */; }; 01B5C3C82AE7FC61007055BB /* UITestConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B5C3C62AE7FC61007055BB /* UITestConfigurator.swift */; }; + 01B759062B3ECA7300179AE6 /* DomainsStateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46546302AF2F8D20017E3D1 /* DomainsStateViewModel.swift */; }; + 01B759082B3ECAF300179AE6 /* DomainsStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B759072B3ECAF300179AE6 /* DomainsStateView.swift */; }; + 01B759092B3ECAF300179AE6 /* DomainsStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B759072B3ECAF300179AE6 /* DomainsStateView.swift */; }; + 01B7590B2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B7590A2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift */; }; + 01B7590C2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B7590A2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift */; }; + 01B7590E2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B7590D2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift */; }; 01CE5007290A889F00A9C2E0 /* TracksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */; }; 01CE5008290A88BD00A9C2E0 /* TracksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */; }; 01CE500E290A88C100A9C2E0 /* TracksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */; }; @@ -219,6 +229,7 @@ 01E258032ACC36FA00F09666 /* PlanStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E258012ACC36FA00F09666 /* PlanStep.swift */; }; 01E258052ACC373800F09666 /* PlanWizardContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E258042ACC373800F09666 /* PlanWizardContent.swift */; }; 01E258062ACC373800F09666 /* PlanWizardContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E258042ACC373800F09666 /* PlanWizardContent.swift */; }; + 01E258092ACC3AA000F09666 /* iOS17WidgetAPIs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E258082ACC3AA000F09666 /* iOS17WidgetAPIs.swift */; }; 01E2580B2ACDC72C00F09666 /* PlanWizardContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E2580A2ACDC72C00F09666 /* PlanWizardContentViewModel.swift */; }; 01E2580C2ACDC72C00F09666 /* PlanWizardContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E2580A2ACDC72C00F09666 /* PlanWizardContentViewModel.swift */; }; 01E2580E2ACDC88100F09666 /* PlanWizardContentViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E2580D2ACDC88100F09666 /* PlanWizardContentViewModelTests.swift */; }; @@ -308,11 +319,8 @@ 08A250FD28D9F0E200F50420 /* CommentDetailInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A250FB28D9F0E200F50420 /* CommentDetailInfoViewModel.swift */; }; 08A2AD791CCED2A800E84454 /* PostTagServiceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 08A2AD781CCED2A800E84454 /* PostTagServiceTests.m */; }; 08A2AD7B1CCED8E500E84454 /* PostCategoryServiceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 08A2AD7A1CCED8E500E84454 /* PostCategoryServiceTests.m */; }; - 08A4E129289D202F001D9EC7 /* UserPersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A4E128289D202F001D9EC7 /* UserPersistentStore.swift */; }; - 08A4E12A289D202F001D9EC7 /* UserPersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A4E128289D202F001D9EC7 /* UserPersistentStore.swift */; }; 08A4E12C289D2337001D9EC7 /* UserPersistentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A4E12B289D2337001D9EC7 /* UserPersistentRepository.swift */; }; 08A4E12D289D2337001D9EC7 /* UserPersistentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A4E12B289D2337001D9EC7 /* UserPersistentRepository.swift */; }; - 08A4E12F289D2795001D9EC7 /* UserPersistentStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A4E12E289D2795001D9EC7 /* UserPersistentStoreTests.swift */; }; 08A7343F298AB68000F925C7 /* JetpackPluginOverlayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A7343E298AB68000F925C7 /* JetpackPluginOverlayViewModel.swift */; }; 08A73440298AB68000F925C7 /* JetpackPluginOverlayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A7343E298AB68000F925C7 /* JetpackPluginOverlayViewModel.swift */; }; 08AA64052A84FFF40076E38D /* DashboardGoogleDomainsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AA64042A84FFF40076E38D /* DashboardGoogleDomainsViewModel.swift */; }; @@ -562,7 +570,6 @@ 1717139F265FE59700F3A022 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717139E265FE59700F3A022 /* ButtonStyles.swift */; }; 171713A0265FE59700F3A022 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717139E265FE59700F3A022 /* ButtonStyles.swift */; }; 171963401D378D5100898E8B /* SearchWrapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1719633F1D378D5100898E8B /* SearchWrapperView.swift */; }; - 171CC15824FCEBF7008B7180 /* UINavigationBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171CC15724FCEBF7008B7180 /* UINavigationBar+Appearance.swift */; }; 17222D80261DDDF90047B163 /* celadon-classic-icon-app-76x76.png in Resources */ = {isa = PBXBuildFile; fileRef = 17222D45261DDDF10047B163 /* celadon-classic-icon-app-76x76.png */; }; 17222D81261DDDF90047B163 /* celadon-classic-icon-app-76x76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17222D46261DDDF10047B163 /* celadon-classic-icon-app-76x76@2x.png */; }; 17222D82261DDDF90047B163 /* celadon-classic-icon-app-83.5x83.5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17222D47261DDDF10047B163 /* celadon-classic-icon-app-83.5x83.5@2x.png */; }; @@ -649,9 +656,6 @@ 1759F1721FE017F20003EC81 /* QueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1759F1711FE017F20003EC81 /* QueueTests.swift */; }; 1759F1801FE1460C0003EC81 /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1759F17F1FE1460C0003EC81 /* NoticeView.swift */; }; 175A650C20B6F7280023E71B /* ReaderSaveForLater+Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175A650B20B6F7280023E71B /* ReaderSaveForLater+Analytics.swift */; }; - 175CC1702720548700622FB4 /* DomainExpiryDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175CC16F2720548700622FB4 /* DomainExpiryDateFormatter.swift */; }; - 175CC1712720548700622FB4 /* DomainExpiryDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175CC16F2720548700622FB4 /* DomainExpiryDateFormatter.swift */; }; - 175CC17527205BFB00622FB4 /* DomainExpiryDateFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175CC17427205BFB00622FB4 /* DomainExpiryDateFormatterTests.swift */; }; 175CC1772721814C00622FB4 /* domain-service-updated-domains.json in Resources */ = {isa = PBXBuildFile; fileRef = 175CC1762721814B00622FB4 /* domain-service-updated-domains.json */; }; 175CC17927230DC900622FB4 /* Bool+StringRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175CC17827230DC900622FB4 /* Bool+StringRepresentation.swift */; }; 175CC17A27230DC900622FB4 /* Bool+StringRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175CC17827230DC900622FB4 /* Bool+StringRepresentation.swift */; }; @@ -879,6 +883,7 @@ 37022D931981C19000F322B7 /* VerticallyStackedButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 37022D901981BF9200F322B7 /* VerticallyStackedButton.m */; }; 374CB16215B93C0800DD0EBC /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374CB16115B93C0800DD0EBC /* AudioToolbox.framework */; }; 37EAAF4D1A11799A006D6306 /* CircularImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAAF4C1A11799A006D6306 /* CircularImageView.swift */; }; + 3F03F2BD2B45041E00A9CE99 /* XCUIElement+TapUntil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F03F2BC2B45041E00A9CE99 /* XCUIElement+TapUntil.swift */; }; 3F09CCA82428FF3300D00A8C /* ReaderTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F09CCA72428FF3300D00A8C /* ReaderTabViewController.swift */; }; 3F09CCAA2428FF8300D00A8C /* ReaderTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F09CCA92428FF8300D00A8C /* ReaderTabView.swift */; }; 3F09CCAE24292EFD00D00A8C /* ReaderTabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F09CCAD24292EFD00D00A8C /* ReaderTabItem.swift */; }; @@ -948,7 +953,6 @@ 3F43704428932F0100475B6E /* JetpackBrandingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F43704328932F0100475B6E /* JetpackBrandingCoordinator.swift */; }; 3F44DD58289C379C006334CD /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 3F44DD57289C379C006334CD /* Lottie */; }; 3F46AAFE25BF5D6300CE2E98 /* Sites.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 3F46AB0225BF5D6300CE2E98 /* Sites.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; - 3F46EEC728BC4935004F02B2 /* JetpackPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F46EEC628BC4935004F02B2 /* JetpackPrompt.swift */; }; 3F46EECE28BC4962004F02B2 /* JetpackLandingScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F46EECB28BC4962004F02B2 /* JetpackLandingScreenView.swift */; }; 3F46EED128BFF339004F02B2 /* JetpackPromptsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F46EED028BFF339004F02B2 /* JetpackPromptsConfiguration.swift */; }; 3F4A4C212AD39CB100DE5DF8 /* TruthTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4A4C202AD39CB100DE5DF8 /* TruthTable.swift */; }; @@ -1231,8 +1235,6 @@ 465F8A0A263B692600F4C950 /* wp-block-editor-v1-settings-success-ThemeJSON.json in Resources */ = {isa = PBXBuildFile; fileRef = 465F8A09263B692600F4C950 /* wp-block-editor-v1-settings-success-ThemeJSON.json */; }; 46638DF6244904A3006E8439 /* GutenbergBlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46638DF5244904A3006E8439 /* GutenbergBlockProcessor.swift */; }; 4666534A2501552A00165DD4 /* LayoutPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466653492501552A00165DD4 /* LayoutPreviewViewController.swift */; }; - 467D3DFA25E4436000EB9CB0 /* SitePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 467D3DF925E4436000EB9CB0 /* SitePromptView.swift */; }; - 467D3E0C25E4436D00EB9CB0 /* SitePromptView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 467D3E0B25E4436D00EB9CB0 /* SitePromptView.xib */; }; 4688E6CC26AB571D00A5D894 /* RequestAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4688E6CB26AB571D00A5D894 /* RequestAuthenticatorTests.swift */; }; 469CE06D24BCED75003BDC8B /* CategorySectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469CE06B24BCED75003BDC8B /* CategorySectionTableViewCell.swift */; }; 469CE06E24BCED75003BDC8B /* CategorySectionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 469CE06C24BCED75003BDC8B /* CategorySectionTableViewCell.xib */; }; @@ -1490,7 +1492,6 @@ 738B9A5621B85CF20005062B /* ModelSettableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A4921B85CF20005062B /* ModelSettableCell.swift */; }; 738B9A5721B85CF20005062B /* TableDataCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A4A21B85CF20005062B /* TableDataCoordinator.swift */; }; 738B9A5821B85CF20005062B /* TitleSubtitleHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A4B21B85CF20005062B /* TitleSubtitleHeader.swift */; }; - 738B9A5921B85CF20005062B /* KeyboardInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A4C21B85CF20005062B /* KeyboardInfo.swift */; }; 738B9A5A21B85CF20005062B /* SiteCreationHeaderData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A4D21B85CF20005062B /* SiteCreationHeaderData.swift */; }; 738B9A5C21B85EB00005062B /* UIView+ContentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A5B21B85EB00005062B /* UIView+ContentLayout.swift */; }; 738B9A5E21B8632E0005062B /* UITableView+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A5D21B8632E0005062B /* UITableView+Header.swift */; }; @@ -1509,7 +1510,6 @@ 73C8F06621BEF76B00DDDF7E /* SiteAssemblyViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C8F06521BEF76B00DDDF7E /* SiteAssemblyViewTests.swift */; }; 73C8F06821BF1A5E00DDDF7E /* SiteAssemblyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C8F06721BF1A5E00DDDF7E /* SiteAssemblyContentView.swift */; }; 73CB13972289BEFB00265F49 /* Charts+LargeValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73CB13962289BEFB00265F49 /* Charts+LargeValueFormatter.swift */; }; - 73CE3E0E21F7F9D3007C9C85 /* TableViewOffsetCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73CE3E0D21F7F9D3007C9C85 /* TableViewOffsetCoordinator.swift */; }; 73D5AC63212622B200ADDDD2 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D5AC5C212622B200ADDDD2 /* NotificationViewController.swift */; }; 73D86969223AF4040064920F /* StatsChartLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D86968223AF4040064920F /* StatsChartLegendView.swift */; }; 73E40D8921238BF50012ABA6 /* Tracks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FA22821C99F6180016CA7C /* Tracks.swift */; }; @@ -2211,8 +2211,6 @@ 8B260D7E2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B260D7D2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift */; }; 8B2D4F5327ECE089009B085C /* dashboard-200-without-posts.json in Resources */ = {isa = PBXBuildFile; fileRef = 8B2D4F5227ECE089009B085C /* dashboard-200-without-posts.json */; }; 8B2D4F5527ECE376009B085C /* BlogDashboardPostsParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B2D4F5427ECE376009B085C /* BlogDashboardPostsParserTests.swift */; }; - 8B33BC9527A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B33BC9427A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift */; }; - 8B33BC9627A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B33BC9427A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift */; }; 8B36256625A60CCA00D7CCE3 /* BackupListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B36256525A60CCA00D7CCE3 /* BackupListViewController.swift */; }; 8B3626F925A665E500D7CCE3 /* UIApplication+mainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3626F825A665E500D7CCE3 /* UIApplication+mainWindow.swift */; }; 8B3DECAB2388506400A459C2 /* SentryStartupEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3DECAA2388506400A459C2 /* SentryStartupEvent.swift */; }; @@ -2337,8 +2335,6 @@ 8F228B22E190FF92D05E53DB /* TimeZoneSearchHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F228848D5DEACE6798CE7E2 /* TimeZoneSearchHeaderView.swift */; }; 8F228F2923045666AE456D2C /* TimeZoneSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2283367263B37B0681F988 /* TimeZoneSelectorViewController.swift */; }; 91138455228373EB00FB02B7 /* GutenbergVideoUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91138454228373EB00FB02B7 /* GutenbergVideoUploadProcessor.swift */; }; - 912347192213484300BD9F97 /* GutenbergViewController+InformativeDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 912347182213484300BD9F97 /* GutenbergViewController+InformativeDialog.swift */; }; - 9123471B221449E200BD9F97 /* GutenbergInformativeDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9123471A221449E200BD9F97 /* GutenbergInformativeDialogTests.swift */; }; 912347762216E27200BD9F97 /* GutenbergViewController+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 912347752216E27200BD9F97 /* GutenbergViewController+Localization.swift */; }; 91D8364121946EFB008340B2 /* GutenbergMediaPickerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D8364021946EFB008340B2 /* GutenbergMediaPickerHelper.swift */; }; 91DCE84621A6A7F50062F134 /* PostEditor+MoreOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91DCE84521A6A7F50062F134 /* PostEditor+MoreOptions.swift */; }; @@ -2593,7 +2589,6 @@ 98ED5964265EBD0000A0B33E /* ReaderDetailLikesListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98ED5962265EBD0000A0B33E /* ReaderDetailLikesListController.swift */; }; 98F537A722496CF300B334F9 /* SiteStatsTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F537A622496CF300B334F9 /* SiteStatsTableHeaderView.swift */; }; 98F537A922496D0D00B334F9 /* SiteStatsTableHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98F537A822496D0D00B334F9 /* SiteStatsTableHeaderView.xib */; }; - 98F93182239AF64800E4E96E /* ThisWeekWidgetStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F93181239AF64800E4E96E /* ThisWeekWidgetStats.swift */; }; 98FCFC232231DF43006ECDD4 /* PostStatsTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98FCFC212231DF43006ECDD4 /* PostStatsTitleCell.swift */; }; 98FCFC242231DF43006ECDD4 /* PostStatsTitleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98FCFC222231DF43006ECDD4 /* PostStatsTitleCell.xib */; }; 98FF6A3E23A30A250025FD72 /* QuickStartNavigationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98FF6A3D23A30A240025FD72 /* QuickStartNavigationSettings.swift */; }; @@ -2760,7 +2755,6 @@ B54E1DF11A0A7BAA00807537 /* ReplyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54E1DEE1A0A7BAA00807537 /* ReplyTextView.swift */; }; B54E1DF21A0A7BAA00807537 /* ReplyTextView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54E1DEF1A0A7BAA00807537 /* ReplyTextView.xib */; }; B54E1DF41A0A7BBF00807537 /* NotificationMediaDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54E1DF31A0A7BBF00807537 /* NotificationMediaDownloader.swift */; }; - B55086211CC15CCB004EADB4 /* PromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55086201CC15CCB004EADB4 /* PromptViewController.swift */; }; B5552D7E1CD101A600B26DF6 /* NSExtensionContext+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5552D7D1CD101A600B26DF6 /* NSExtensionContext+Extensions.swift */; }; B5552D801CD1028C00B26DF6 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5552D7F1CD1028C00B26DF6 /* String+Extensions.swift */; }; B5552D821CD1061F00B26DF6 /* StringExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5552D811CD1061F00B26DF6 /* StringExtensionsTests.swift */; }; @@ -2925,7 +2919,6 @@ C700FAB3258020DB0090938E /* JetpackScanThreatCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C700FAB1258020DB0090938E /* JetpackScanThreatCell.xib */; }; C7124E4E2638528F00929318 /* JetpackPrologueViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7124E4C2638528F00929318 /* JetpackPrologueViewController.xib */; }; C7124E4F2638528F00929318 /* JetpackPrologueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7124E4D2638528F00929318 /* JetpackPrologueViewController.swift */; }; - C7124E922638905B00929318 /* StarFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7124E912638905B00929318 /* StarFieldView.swift */; }; C7192ECF25E8432D00C3020D /* ReaderTopicsCardCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7192ECE25E8432D00C3020D /* ReaderTopicsCardCell.xib */; }; C71AF533281064DE00F9E99E /* OnboardingQuestionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71AF532281064DE00F9E99E /* OnboardingQuestionsCoordinator.swift */; }; C71AF534281064DE00F9E99E /* OnboardingQuestionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71AF532281064DE00F9E99E /* OnboardingQuestionsCoordinator.swift */; }; @@ -2940,9 +2933,6 @@ C7234A4F2832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7234A4C2832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.swift */; }; C7234A502832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7234A4D2832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.xib */; }; C7234A512832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7234A4D2832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.xib */; }; - C72A4F68264088E4009CA633 /* JetpackNotFoundErrorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72A4F67264088E4009CA633 /* JetpackNotFoundErrorViewModel.swift */; }; - C72A4F7B26408943009CA633 /* JetpackNotWPErrorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72A4F7A26408943009CA633 /* JetpackNotWPErrorViewModel.swift */; }; - C72A4F8E26408C73009CA633 /* JetpackNoSitesErrorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72A4F8D26408C73009CA633 /* JetpackNoSitesErrorViewModel.swift */; }; C72A52CF2649B158009CA633 /* JetpackWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72A52CE2649B157009CA633 /* JetpackWindowManager.swift */; }; C737553E27C80DD500C6E9A1 /* String+CondenseWhitespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = C737553D27C80DD500C6E9A1 /* String+CondenseWhitespace.swift */; }; C737553F27C80DD500C6E9A1 /* String+CondenseWhitespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = C737553D27C80DD500C6E9A1 /* String+CondenseWhitespace.swift */; }; @@ -3010,9 +3000,6 @@ C7F79369260D14C100CE547F /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA25F9FD2609AA830005E08F /* AppConfiguration.swift */; }; C7F7936A260D14C200CE547F /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA25F9FD2609AA830005E08F /* AppConfiguration.swift */; }; C7F7ABD6261CED7A00CE547F /* JetpackAuthenticationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F7ABD5261CED7A00CE547F /* JetpackAuthenticationManager.swift */; }; - C7F7AC75261CF1F300CE547F /* JetpackLoginErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F7AC73261CF1F300CE547F /* JetpackLoginErrorViewController.swift */; }; - C7F7AC76261CF1F300CE547F /* JetpackLoginErrorViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7F7AC74261CF1F300CE547F /* JetpackLoginErrorViewController.xib */; }; - C7F7ACBE261E4F0600CE547F /* JetpackErrorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F7ACBD261E4F0600CE547F /* JetpackErrorViewModel.swift */; }; C7F7BDBD26262A1B00CE547F /* AppDependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F7BDBC26262A1B00CE547F /* AppDependency.swift */; }; C7F7BDD026262A4C00CE547F /* AppDependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F7BDCF26262A4C00CE547F /* AppDependency.swift */; }; C7F7BE0726262B9A00CE547F /* AuthenticationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F7BE0626262B9900CE547F /* AuthenticationHandler.swift */; }; @@ -3182,7 +3169,6 @@ D8A3A5AF206A442800992576 /* StockPhotosDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A3A5AE206A442800992576 /* StockPhotosDataSource.swift */; }; D8A3A5B3206A49BF00992576 /* StockPhotosMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A3A5B2206A49BF00992576 /* StockPhotosMedia.swift */; }; D8A468E02181C6450094B82F /* site-segment.json in Resources */ = {isa = PBXBuildFile; fileRef = D8A468DF2181C6450094B82F /* site-segment.json */; }; - D8A468E521828D940094B82F /* SiteVerticalsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A468E421828D940094B82F /* SiteVerticalsService.swift */; }; D8B6BEB7203E11F2007C8A19 /* Bundle+LoadFromNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B6BEB6203E11F2007C8A19 /* Bundle+LoadFromNib.swift */; }; D8B9B58F204F4EA1003C6042 /* NetworkAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B9B58E204F4EA1003C6042 /* NetworkAware.swift */; }; D8BA274D20FDEA2E007A5C77 /* NotificationTextContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BA274C20FDEA2E007A5C77 /* NotificationTextContentTests.swift */; }; @@ -3573,6 +3559,10 @@ F1F083F6241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F083F5241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift */; }; F4026B1D2A1BC88A00CC7781 /* DashboardDomainRegistrationCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4026B1C2A1BC88A00CC7781 /* DashboardDomainRegistrationCardCell.swift */; }; F4026B1E2A1BC88A00CC7781 /* DashboardDomainRegistrationCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4026B1C2A1BC88A00CC7781 /* DashboardDomainRegistrationCardCell.swift */; }; + F413F77A2B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F413F7792B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift */; }; + F413F77B2B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F413F7792B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift */; }; + F413F7882B2B253A00A64A94 /* DashboardCard+Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F413F7872B2B253A00A64A94 /* DashboardCard+Personalization.swift */; }; + F413F7892B2B253A00A64A94 /* DashboardCard+Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F413F7872B2B253A00A64A94 /* DashboardCard+Personalization.swift */; }; F4141EE42AE7152F000D2AAE /* AllDomainsListViewController+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4141EE22AE7152F000D2AAE /* AllDomainsListViewController+Strings.swift */; }; F4141EE62AE71AF0000D2AAE /* AllDomainsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4141EE52AE71AF0000D2AAE /* AllDomainsListViewModel.swift */; }; F4141EE82AE72DC4000D2AAE /* AllDomainsListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4141EE72AE72DC4000D2AAE /* AllDomainsListTableViewCell.swift */; }; @@ -3581,6 +3571,12 @@ 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 */; }; + F41D98D72B389735004EC050 /* DashboardDynamicCardAnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D98D62B389735004EC050 /* DashboardDynamicCardAnalyticsEvent.swift */; }; + F41D98D92B3901F5004EC050 /* DashboardDynamicCardAnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D98D62B389735004EC050 /* DashboardDynamicCardAnalyticsEvent.swift */; }; + F41D98E12B39C5CE004EC050 /* BlogDashboardDynamicCardCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D98E02B39C5CE004EC050 /* BlogDashboardDynamicCardCoordinatorTests.swift */; }; + F41D98E42B39CAA5004EC050 /* BlogDashboardDynamicCardCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D98E22B39C9E7004EC050 /* BlogDashboardDynamicCardCoordinator.swift */; }; + F41D98E52B39CAAA004EC050 /* BlogDashboardDynamicCardCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D98E22B39C9E7004EC050 /* BlogDashboardDynamicCardCoordinator.swift */; }; + F41D98E82B39E14F004EC050 /* DashboardDynamicCardAnalyticsEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D98E72B39E14F004EC050 /* DashboardDynamicCardAnalyticsEventTests.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 */; }; @@ -3646,7 +3642,7 @@ F4552086299D147B00D9F6A8 /* BlockedSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48D44B5298992C30051EAA6 /* BlockedSite.swift */; }; F46546292AED89790017E3D1 /* AllDomainsListEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46546282AED89790017E3D1 /* AllDomainsListEmptyView.swift */; }; F465462D2AEF22070017E3D1 /* AllDomainsListViewModel+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F465462C2AEF22070017E3D1 /* AllDomainsListViewModel+Strings.swift */; }; - F46546312AF2F8D30017E3D1 /* AllDomainsListMessageStateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46546302AF2F8D20017E3D1 /* AllDomainsListMessageStateViewModel.swift */; }; + F46546312AF2F8D30017E3D1 /* DomainsStateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46546302AF2F8D20017E3D1 /* DomainsStateViewModel.swift */; }; F46546332AF54DCD0017E3D1 /* AllDomainsListItemViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46546322AF54DCD0017E3D1 /* AllDomainsListItemViewModelTests.swift */; }; F46546352AF550A20017E3D1 /* AllDomainsListItem+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46546342AF550A20017E3D1 /* AllDomainsListItem+Helpers.swift */; }; F465976E28E4669200D5F49A /* cool-green-icon-app-76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F465976928E4669200D5F49A /* cool-green-icon-app-76@2x.png */; }; @@ -3732,6 +3728,10 @@ F48D44BB2989A9070051EAA6 /* ReaderSiteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48D44B92989A58C0051EAA6 /* ReaderSiteService.swift */; }; F48D44BC2989AA8A0051EAA6 /* ReaderSiteService.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D44EB371986D8BA008B7175 /* ReaderSiteService.m */; }; F48D44BD2989AA8C0051EAA6 /* ReaderSiteService.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D44EB371986D8BA008B7175 /* ReaderSiteService.m */; }; + F48EBF8A2B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48EBF892B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift */; }; + F48EBF8B2B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48EBF892B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift */; }; + F48EBF942B333550004CD561 /* dashboard-200-with-only-one-dynamic-card.json in Resources */ = {isa = PBXBuildFile; fileRef = F48EBF912B333111004CD561 /* dashboard-200-with-only-one-dynamic-card.json */; }; + F48EBF952B333B31004CD561 /* dashboard-200-with-multiple-dynamic-cards.json in Resources */ = {isa = PBXBuildFile; fileRef = F48EBF8C2B3262D5004CD561 /* dashboard-200-with-multiple-dynamic-cards.json */; }; F49B99FF2937C9B4000CEFCE /* MigrationEmailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49B99FE2937C9B4000CEFCE /* MigrationEmailService.swift */; }; F49B9A0029393049000CEFCE /* MigrationAppDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33A5ADB2935848F00961E3A /* MigrationAppDetection.swift */; }; F49B9A06293A21BF000CEFCE /* MigrationAnalyticsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49B9A05293A21BF000CEFCE /* MigrationAnalyticsTracker.swift */; }; @@ -3805,7 +3805,6 @@ F52CACCC24512EA700661380 /* EmptyActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F52CACCB24512EA700661380 /* EmptyActionView.swift */; }; F532AD61253B81320013B42E /* StoriesIntroDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F532AD60253B81320013B42E /* StoriesIntroDataSource.swift */; }; F532AE1C253E55D40013B42E /* CreateButtonActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F532AE1B253E55D40013B42E /* CreateButtonActionSheet.swift */; }; - F53FF3A823EA723D001AD596 /* ActionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53FF3A723EA723D001AD596 /* ActionRow.swift */; }; F53FF3AA23EA725C001AD596 /* SiteIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53FF3A923EA725C001AD596 /* SiteIconView.swift */; }; F543AF5723A84E4D0022F595 /* PublishSettingsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F543AF5623A84E4D0022F595 /* PublishSettingsControllerTests.swift */; }; F551E7F523F6EA3100751212 /* FloatingActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F551E7F423F6EA3100751212 /* FloatingActionButton.swift */; }; @@ -4053,7 +4052,6 @@ FABB1FFE2602FC2C00C8785C /* Notifications.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B558541019631A1000FAF6C3 /* Notifications.storyboard */; }; FABB20022602FC2C00C8785C /* StatsTableFooter.xib in Resources */ = {isa = PBXBuildFile; fileRef = 983DBBA822125DD300753988 /* StatsTableFooter.xib */; }; FABB20052602FC2C00C8785C /* ThemeBrowserSectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 820ADD6F1F3A1F88002D7F93 /* ThemeBrowserSectionHeaderView.xib */; }; - FABB20072602FC2C00C8785C /* SitePromptView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 467D3E0B25E4436D00EB9CB0 /* SitePromptView.xib */; }; FABB20082602FC2C00C8785C /* DeleteSite.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 746A6F561E71C691003B67E3 /* DeleteSite.storyboard */; }; FABB200A2602FC2C00C8785C /* Noticons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F5A34D0C25DF2F7700C9654B /* Noticons.ttf */; }; FABB200B2602FC2C00C8785C /* LoginEpilogue.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B51AD77A2056C31100A6C545 /* LoginEpilogue.storyboard */; }; @@ -4495,7 +4493,6 @@ FABB22382602FC2C00C8785C /* EventLoggingDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F913BB0D24B3C58B00C19032 /* EventLoggingDelegate.swift */; }; FABB223B2602FC2C00C8785C /* AbstractPost+Searchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74729CAD205722E300D1394D /* AbstractPost+Searchable.swift */; }; FABB223C2602FC2C00C8785C /* EditCommentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2906F810110CDA8900169D56 /* EditCommentViewController.m */; }; - FABB223D2602FC2C00C8785C /* ThisWeekWidgetStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F93181239AF64800E4E96E /* ThisWeekWidgetStats.swift */; }; FABB223E2602FC2C00C8785C /* PostAutoUploadMessageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16C35D923F3F76C00C81331 /* PostAutoUploadMessageProvider.swift */; }; FABB223F2602FC2C00C8785C /* GutenbergMediaPickerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D8364021946EFB008340B2 /* GutenbergMediaPickerHelper.swift */; }; FABB22402602FC2C00C8785C /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D690151F828FF000200E30 /* FeatureFlag.swift */; }; @@ -4567,7 +4564,6 @@ FABB228C2602FC2C00C8785C /* ReaderSiteStreamHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6D2E1641B8AAD7E0000ED14 /* ReaderSiteStreamHeader.swift */; }; FABB228D2602FC2C00C8785C /* VideoUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126FDFD20A33BDB0010EB6E /* VideoUploadProcessor.swift */; }; FABB228E2602FC2C00C8785C /* PeopleCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E166FA1A1BB0656B00374B5B /* PeopleCellViewModel.swift */; }; - FABB228F2602FC2C00C8785C /* TableViewOffsetCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73CE3E0D21F7F9D3007C9C85 /* TableViewOffsetCoordinator.swift */; }; FABB22902602FC2C00C8785C /* RegisterDomainDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436D56192117312700CEAA33 /* RegisterDomainDetailsViewController.swift */; }; FABB22912602FC2C00C8785C /* FilterProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E29035243E4F5F00C19CA5 /* FilterProvider.swift */; }; FABB22922602FC2C00C8785C /* DomainCreditEligibilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027AC51C227896540033E56E /* DomainCreditEligibilityChecker.swift */; }; @@ -4636,7 +4632,6 @@ FABB22DA2602FC2C00C8785C /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15644E81CE0E47C00D96E64 /* RoundedButton.swift */; }; FABB22DC2602FC2C00C8785C /* MenuLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 08CC677D1C49B65A00153AD7 /* MenuLocation.m */; }; FABB22DD2602FC2C00C8785C /* RevisionsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4349B0AD218A477F0034118A /* RevisionsTableViewCell.swift */; }; - FABB22DE2602FC2C00C8785C /* KeyboardInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B9A4C21B85CF20005062B /* KeyboardInfo.swift */; }; FABB22DF2602FC2C00C8785C /* PlanDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15644F21CE0E5A500D96E64 /* PlanDetailViewModel.swift */; }; FABB22E02602FC2C00C8785C /* DebugMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E4CD0B238C33F300C56916 /* DebugMenuViewController.swift */; }; FABB22E12602FC2C00C8785C /* MenuItemCheckButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 08D978501CD2AF7D0054F19A /* MenuItemCheckButtonView.m */; }; @@ -4775,7 +4770,6 @@ FABB23752602FC2C00C8785C /* ReaderInterestsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3236F77124ABB6C90088E8F3 /* ReaderInterestsDataSource.swift */; }; FABB23762602FC2C00C8785C /* ActivityContentGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E4123C920F4184200DF8486 /* ActivityContentGroup.swift */; }; FABB23772602FC2C00C8785C /* MediaImageExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F8CD2E1EBD29440049D0C0 /* MediaImageExporter.swift */; }; - FABB23782602FC2C00C8785C /* GutenbergViewController+InformativeDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 912347182213484300BD9F97 /* GutenbergViewController+InformativeDialog.swift */; }; FABB23792602FC2C00C8785C /* ChangePasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA6511621F26A24009AA935 /* ChangePasswordViewController.swift */; }; FABB237A2602FC2C00C8785C /* LikeComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D816C1EF20E0893A00C4D82F /* LikeComment.swift */; }; FABB237B2602FC2C00C8785C /* Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59E1D46C1CEF77B500126697 /* Page.swift */; }; @@ -4890,7 +4884,6 @@ FABB23F72602FC2C00C8785C /* NSObject+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = B57B99DD19A2DBF200506504 /* NSObject+Helpers.m */; }; FABB23F82602FC2C00C8785C /* ImmuTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E49CE31C4902EE002393A4 /* ImmuTableViewController.swift */; }; FABB23FA2602FC2C00C8785C /* RevisionPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A162F2221C26D7500FDC035 /* RevisionPreviewViewController.swift */; }; - FABB23FC2602FC2C00C8785C /* SitePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 467D3DF925E4436000EB9CB0 /* SitePromptView.swift */; }; FABB23FD2602FC2C00C8785C /* BaseRestoreCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB8AA2125AF031200F9F8A0 /* BaseRestoreCompleteViewController.swift */; }; FABB23FE2602FC2C00C8785C /* SharingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E616E4B21C480896002C024E /* SharingService.swift */; }; FABB24002602FC2C00C8785C /* BottomSheetPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E032E52408D537003AF350 /* BottomSheetPresentationController.swift */; }; @@ -5049,7 +5042,6 @@ FABB24A52602FC2C00C8785C /* SiteIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53FF3A923EA725C001AD596 /* SiteIconView.swift */; }; FABB24A62602FC2C00C8785C /* JetpackRestoreCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3536F425B01A2C0005A3A0 /* JetpackRestoreCompleteViewController.swift */; }; FABB24A72602FC2C00C8785C /* FormattableNoticonRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7947AC210BAC7B005BB851 /* FormattableNoticonRange.swift */; }; - FABB24A82602FC2C00C8785C /* PromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55086201CC15CCB004EADB4 /* PromptViewController.swift */; }; FABB24AA2602FC2C00C8785C /* ActivityListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82FC612B1FA8B7FC00A1757E /* ActivityListRow.swift */; }; FABB24AB2602FC2C00C8785C /* UIColor+MurielColorsObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436110DF22C4241A000773AD /* UIColor+MurielColorsObjC.swift */; }; FABB24AC2602FC2C00C8785C /* InteractiveNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B68BD31C19AAED00EB59E0 /* InteractiveNotificationsManager.swift */; }; @@ -5178,7 +5170,6 @@ FABB253A2602FC2C00C8785C /* WordPressAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1749965E2271BF08007021BD /* WordPressAppDelegate.swift */; }; FABB253B2602FC2C00C8785C /* MediaService.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA3EE151925090A00294E0B /* MediaService.m */; }; FABB253C2602FC2C00C8785C /* FormattableContentAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E4123B820F4097B00DF8486 /* FormattableContentAction.swift */; }; - FABB253D2602FC2C00C8785C /* SiteVerticalsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A468E421828D940094B82F /* SiteVerticalsService.swift */; }; FABB253E2602FC2C00C8785C /* ReaderBlockedSiteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65219FA1B8D10DA000B1217 /* ReaderBlockedSiteCell.swift */; }; FABB253F2602FC2C00C8785C /* JetpackRestoreWarningViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF13C5225A57ABD003EE470 /* JetpackRestoreWarningViewController.swift */; }; FABB25402602FC2C00C8785C /* SiteAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73178C3021BEE45300E37C9A /* SiteAssembly.swift */; }; @@ -5265,7 +5256,6 @@ FABB259C2602FC2C00C8785C /* ReaderPostService+RelatedPosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8E1F7625EEFA7300063673 /* ReaderPostService+RelatedPosts.swift */; }; FABB259D2602FC2C00C8785C /* Blog+Capabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518E1641CCAA19200ADFE75 /* Blog+Capabilities.swift */; }; FABB259F2602FC2C00C8785C /* MenuItemCategoriesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 08216FAF1CDBF96000304BA7 /* MenuItemCategoriesViewController.m */; }; - FABB25A02602FC2C00C8785C /* UINavigationBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171CC15724FCEBF7008B7180 /* UINavigationBar+Appearance.swift */; }; FABB25A12602FC2C00C8785C /* QuickStartChecklistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C9908D21067E22009EFFEB /* QuickStartChecklistViewController.swift */; }; FABB25A22602FC2C00C8785C /* MenuItemsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 082635BA1CEA69280088030C /* MenuItemsViewController.m */; }; FABB25A32602FC2C00C8785C /* ReachabilityUtils+OnlineActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822876F01E929CFD00696BF7 /* ReachabilityUtils+OnlineActions.swift */; }; @@ -5374,7 +5364,6 @@ FABB26162602FC2C00C8785C /* WPUploadStatusButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 740BD8341A0D4C3600F04D18 /* WPUploadStatusButton.m */; }; FABB26172602FC2C00C8785C /* MediaExternalExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EAD7CCF206D761200BEDCFD /* MediaExternalExporter.swift */; }; FABB26182602FC2C00C8785C /* RegisterDomainDetailsViewModel+SectionDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436D56102117312700CEAA33 /* RegisterDomainDetailsViewModel+SectionDefinitions.swift */; }; - FABB26192602FC2C00C8785C /* ActionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53FF3A723EA723D001AD596 /* ActionRow.swift */; }; FABB261A2602FC2C00C8785C /* AppSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA162301CB7031A00E2E110 /* AppSettingsViewController.swift */; }; FABB261B2602FC2C00C8785C /* ReaderPostStreamService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B16CE9925251C89007BE5A9 /* ReaderPostStreamService.swift */; }; FABB26202602FC2C00C8785C /* iAd.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E21C760202BBC8D00837CF5 /* iAd.framework */; }; @@ -5866,6 +5855,7 @@ 0148CC2A2859C87000CF5D96 /* BlogServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogServiceMock.swift; sourceTree = ""; }; 014ACD132A1E5033008A706C /* WebKitViewController+SandboxStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebKitViewController+SandboxStore.swift"; sourceTree = ""; }; 015BA4EA29A788A300920F4B /* StatsTotalInsightsCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTotalInsightsCellTests.swift; sourceTree = ""; }; + 0162314F2B3B3CAD0010E377 /* PrimaryDomainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryDomainView.swift; sourceTree = ""; }; 0167F4AD2AAA0250005B9E42 /* JetpackIntents.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = JetpackIntents.entitlements; sourceTree = ""; }; 0167F4AE2AAA0250005B9E42 /* JetpackIntentsRelease-Alpha.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "JetpackIntentsRelease-Alpha.entitlements"; sourceTree = ""; }; 0167F4AF2AAA0250005B9E42 /* JetpackIntentsRelease-Internal.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "JetpackIntentsRelease-Internal.entitlements"; sourceTree = ""; }; @@ -5874,6 +5864,7 @@ 0167F4B32AAA02BD005B9E42 /* JetpackStatsWidgetsRelease-Internal.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "JetpackStatsWidgetsRelease-Internal.entitlements"; sourceTree = ""; }; 0167F4B42AAA02BD005B9E42 /* JetpackStatsWidgetsRelease-Alpha.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "JetpackStatsWidgetsRelease-Alpha.entitlements"; sourceTree = ""; }; 0167F4B52AAA02BD005B9E42 /* JetpackStatsWidgets-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JetpackStatsWidgets-Bridging-Header.h"; sourceTree = ""; }; + 017008442B35C25C00C80490 /* SiteDomainsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteDomainsViewModel.swift; sourceTree = ""; }; 017C57BA2B2B5555001E7687 /* DomainSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSelectionViewController.swift; sourceTree = ""; }; 018635832A8109DE00915532 /* SupportChatBotViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportChatBotViewController.swift; sourceTree = ""; }; 018635862A8109F900915532 /* SupportChatBotViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportChatBotViewModel.swift; sourceTree = ""; }; @@ -5886,11 +5877,16 @@ 0188FE472AA62D080093EDA5 /* LockScreenMultiStatWidgetViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenMultiStatWidgetViewProvider.swift; sourceTree = ""; }; 0188FE4A2AA62F800093EDA5 /* LockScreenTodayLikesCommentsStatWidgetConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenTodayLikesCommentsStatWidgetConfig.swift; sourceTree = ""; }; 0189AF042ACAD89700F63393 /* ShoppingCartService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShoppingCartService.swift; sourceTree = ""; }; + 018FF1342AE6771A00F301C3 /* LockScreenVerticalCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenVerticalCard.swift; sourceTree = ""; }; + 018FF1362AE67C2600F301C3 /* LockScreenFlexibleCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenFlexibleCard.swift; sourceTree = ""; }; 019D699D2A5EA963003B676D /* RootViewCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewCoordinatorTests.swift; sourceTree = ""; }; 019D699F2A5EBF47003B676D /* WordPressAuthenticatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressAuthenticatorProtocol.swift; sourceTree = ""; }; 01A8508A2A8A126400BD8A97 /* support_chat_widget.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = support_chat_widget.css; sourceTree = ""; }; 01ABF16F2AD578B3004331BD /* WidgetAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetAnalytics.swift; sourceTree = ""; }; 01B5C3C62AE7FC61007055BB /* UITestConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestConfigurator.swift; sourceTree = ""; }; + 01B759072B3ECAF300179AE6 /* DomainsStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainsStateView.swift; sourceTree = ""; }; + 01B7590A2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainDetailsWebViewControllerWrapper.swift; sourceTree = ""; }; + 01B7590D2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteDomainsViewModelTests.swift; sourceTree = ""; }; 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracksConfiguration.swift; sourceTree = ""; }; 01CE5010290A890300A9C2E0 /* TracksConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracksConfiguration.swift; sourceTree = ""; }; 01D2FF5D2AA733690038E040 /* LockScreenFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenFieldView.swift; sourceTree = ""; }; @@ -5901,6 +5897,7 @@ 01DBFD8629BDCBF200F3720F /* JetpackNativeConnectionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackNativeConnectionService.swift; sourceTree = ""; }; 01E258012ACC36FA00F09666 /* PlanStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanStep.swift; sourceTree = ""; }; 01E258042ACC373800F09666 /* PlanWizardContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanWizardContent.swift; sourceTree = ""; }; + 01E258082ACC3AA000F09666 /* iOS17WidgetAPIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOS17WidgetAPIs.swift; sourceTree = ""; }; 01E2580A2ACDC72C00F09666 /* PlanWizardContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanWizardContentViewModel.swift; sourceTree = ""; }; 01E2580D2ACDC88100F09666 /* PlanWizardContentViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanWizardContentViewModelTests.swift; sourceTree = ""; }; 01E78D1C296EA54F00FB6863 /* StatsPeriodHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsPeriodHelperTests.swift; sourceTree = ""; }; @@ -5998,9 +5995,7 @@ 08A250FB28D9F0E200F50420 /* CommentDetailInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentDetailInfoViewModel.swift; sourceTree = ""; }; 08A2AD781CCED2A800E84454 /* PostTagServiceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostTagServiceTests.m; sourceTree = ""; }; 08A2AD7A1CCED8E500E84454 /* PostCategoryServiceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostCategoryServiceTests.m; sourceTree = ""; }; - 08A4E128289D202F001D9EC7 /* UserPersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPersistentStore.swift; sourceTree = ""; }; 08A4E12B289D2337001D9EC7 /* UserPersistentRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPersistentRepository.swift; sourceTree = ""; }; - 08A4E12E289D2795001D9EC7 /* UserPersistentStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPersistentStoreTests.swift; sourceTree = ""; }; 08A7343E298AB68000F925C7 /* JetpackPluginOverlayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackPluginOverlayViewModel.swift; sourceTree = ""; }; 08AA64042A84FFF40076E38D /* DashboardGoogleDomainsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardGoogleDomainsViewModel.swift; sourceTree = ""; }; 08AA640B2A8511FB0076E38D /* DashboardGoogleDomainsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardGoogleDomainsViewModelTests.swift; sourceTree = ""; }; @@ -6216,7 +6211,6 @@ 17171373265FAA8A00F3A022 /* BloggingRemindersNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloggingRemindersNavigationController.swift; sourceTree = ""; }; 1717139E265FE59700F3A022 /* ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; }; 1719633F1D378D5100898E8B /* SearchWrapperView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchWrapperView.swift; sourceTree = ""; }; - 171CC15724FCEBF7008B7180 /* UINavigationBar+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Appearance.swift"; sourceTree = ""; }; 17222D45261DDDF10047B163 /* celadon-classic-icon-app-76x76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "celadon-classic-icon-app-76x76.png"; sourceTree = ""; }; 17222D46261DDDF10047B163 /* celadon-classic-icon-app-76x76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "celadon-classic-icon-app-76x76@2x.png"; sourceTree = ""; }; 17222D47261DDDF10047B163 /* celadon-classic-icon-app-83.5x83.5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "celadon-classic-icon-app-83.5x83.5@2x.png"; sourceTree = ""; }; @@ -6294,8 +6288,6 @@ 1759F1711FE017F20003EC81 /* QueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTests.swift; sourceTree = ""; }; 1759F17F1FE1460C0003EC81 /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = ""; }; 175A650B20B6F7280023E71B /* ReaderSaveForLater+Analytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReaderSaveForLater+Analytics.swift"; sourceTree = ""; }; - 175CC16F2720548700622FB4 /* DomainExpiryDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainExpiryDateFormatter.swift; sourceTree = ""; }; - 175CC17427205BFB00622FB4 /* DomainExpiryDateFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainExpiryDateFormatterTests.swift; sourceTree = ""; }; 175CC1762721814B00622FB4 /* domain-service-updated-domains.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "domain-service-updated-domains.json"; sourceTree = ""; }; 175CC17827230DC900622FB4 /* Bool+StringRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bool+StringRepresentation.swift"; sourceTree = ""; }; 175CC17B2723103000622FB4 /* WPAnalytics+Domains.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WPAnalytics+Domains.swift"; sourceTree = ""; }; @@ -6575,6 +6567,7 @@ 37EAAF4C1A11799A006D6306 /* CircularImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularImageView.swift; sourceTree = ""; }; 3AB6A3B516053EA8D0BC3B17 /* Pods-JetpackStatsWidgets.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JetpackStatsWidgets.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-JetpackStatsWidgets/Pods-JetpackStatsWidgets.release-alpha.xcconfig"; sourceTree = ""; }; 3C8DE270EF0498A2129349B0 /* Pods-JetpackNotificationServiceExtension.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JetpackNotificationServiceExtension.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-JetpackNotificationServiceExtension/Pods-JetpackNotificationServiceExtension.release-alpha.xcconfig"; sourceTree = ""; }; + 3F03F2BC2B45041E00A9CE99 /* XCUIElement+TapUntil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+TapUntil.swift"; sourceTree = ""; }; 3F09CCA72428FF3300D00A8C /* ReaderTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderTabViewController.swift; sourceTree = ""; }; 3F09CCA92428FF8300D00A8C /* ReaderTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderTabView.swift; sourceTree = ""; }; 3F09CCAD24292EFD00D00A8C /* ReaderTabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderTabItem.swift; sourceTree = ""; }; @@ -6605,7 +6598,6 @@ 3F4370402893207C00475B6E /* JetpackOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackOverlayView.swift; sourceTree = ""; }; 3F43704328932F0100475B6E /* JetpackBrandingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackBrandingCoordinator.swift; sourceTree = ""; }; 3F46AB0125BF5D6300CE2E98 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Sites.intentdefinition; sourceTree = ""; }; - 3F46EEC628BC4935004F02B2 /* JetpackPrompt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JetpackPrompt.swift; sourceTree = ""; }; 3F46EECB28BC4962004F02B2 /* JetpackLandingScreenView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JetpackLandingScreenView.swift; sourceTree = ""; }; 3F46EED028BFF339004F02B2 /* JetpackPromptsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackPromptsConfiguration.swift; sourceTree = ""; }; 3F4A4C202AD39CB100DE5DF8 /* TruthTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruthTable.swift; sourceTree = ""; }; @@ -6850,8 +6842,6 @@ 465F8A09263B692600F4C950 /* wp-block-editor-v1-settings-success-ThemeJSON.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wp-block-editor-v1-settings-success-ThemeJSON.json"; sourceTree = ""; }; 46638DF5244904A3006E8439 /* GutenbergBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergBlockProcessor.swift; sourceTree = ""; }; 466653492501552A00165DD4 /* LayoutPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutPreviewViewController.swift; sourceTree = ""; }; - 467D3DF925E4436000EB9CB0 /* SitePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePromptView.swift; sourceTree = ""; }; - 467D3E0B25E4436D00EB9CB0 /* SitePromptView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SitePromptView.xib; sourceTree = ""; }; 4688E6CB26AB571D00A5D894 /* RequestAuthenticatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAuthenticatorTests.swift; sourceTree = ""; }; 469CE06B24BCED75003BDC8B /* CategorySectionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategorySectionTableViewCell.swift; sourceTree = ""; }; 469CE06C24BCED75003BDC8B /* CategorySectionTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CategorySectionTableViewCell.xib; sourceTree = ""; }; @@ -7122,7 +7112,6 @@ 738B9A4921B85CF20005062B /* ModelSettableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelSettableCell.swift; sourceTree = ""; }; 738B9A4A21B85CF20005062B /* TableDataCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDataCoordinator.swift; sourceTree = ""; }; 738B9A4B21B85CF20005062B /* TitleSubtitleHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleSubtitleHeader.swift; sourceTree = ""; }; - 738B9A4C21B85CF20005062B /* KeyboardInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardInfo.swift; sourceTree = ""; }; 738B9A4D21B85CF20005062B /* SiteCreationHeaderData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteCreationHeaderData.swift; sourceTree = ""; }; 738B9A5B21B85EB00005062B /* UIView+ContentLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ContentLayout.swift"; sourceTree = ""; }; 738B9A5D21B8632E0005062B /* UITableView+Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Header.swift"; sourceTree = ""; }; @@ -7137,7 +7126,6 @@ 73C8F06521BEF76B00DDDF7E /* SiteAssemblyViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteAssemblyViewTests.swift; sourceTree = ""; }; 73C8F06721BF1A5E00DDDF7E /* SiteAssemblyContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteAssemblyContentView.swift; sourceTree = ""; }; 73CB13962289BEFB00265F49 /* Charts+LargeValueFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Charts+LargeValueFormatter.swift"; sourceTree = ""; }; - 73CE3E0D21F7F9D3007C9C85 /* TableViewOffsetCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewOffsetCoordinator.swift; sourceTree = ""; }; 73D5AC5C212622B200ADDDD2 /* NotificationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = ""; }; 73D5AC662126236600ADDDD2 /* Info-Alpha.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Alpha.plist"; sourceTree = ""; }; 73D5AC672126236600ADDDD2 /* Info-Internal.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Internal.plist"; sourceTree = ""; }; @@ -7551,7 +7539,6 @@ 8B260D7D2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostVisibilitySelectorViewController.swift; sourceTree = ""; }; 8B2D4F5227ECE089009B085C /* dashboard-200-without-posts.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "dashboard-200-without-posts.json"; sourceTree = ""; }; 8B2D4F5427ECE376009B085C /* BlogDashboardPostsParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPostsParserTests.swift; sourceTree = ""; }; - 8B33BC9427A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlogDetailsViewController+QuickActions.swift"; sourceTree = ""; }; 8B36256525A60CCA00D7CCE3 /* BackupListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupListViewController.swift; sourceTree = ""; }; 8B3626F825A665E500D7CCE3 /* UIApplication+mainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+mainWindow.swift"; sourceTree = ""; }; 8B3DECAA2388506400A459C2 /* SentryStartupEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStartupEvent.swift; sourceTree = ""; }; @@ -7646,8 +7633,6 @@ 8F228848D5DEACE6798CE7E2 /* TimeZoneSearchHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZoneSearchHeaderView.swift; sourceTree = ""; }; 8F228AE62B771552F0F971BE /* TimeZoneSearchHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimeZoneSearchHeaderView.xib; sourceTree = ""; }; 91138454228373EB00FB02B7 /* GutenbergVideoUploadProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergVideoUploadProcessor.swift; sourceTree = ""; }; - 912347182213484300BD9F97 /* GutenbergViewController+InformativeDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GutenbergViewController+InformativeDialog.swift"; sourceTree = ""; }; - 9123471A221449E200BD9F97 /* GutenbergInformativeDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergInformativeDialogTests.swift; sourceTree = ""; }; 912347752216E27200BD9F97 /* GutenbergViewController+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GutenbergViewController+Localization.swift"; sourceTree = ""; }; 9149D34BF5182F360C84EDB9 /* Pods-JetpackDraftActionExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JetpackDraftActionExtension.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JetpackDraftActionExtension/Pods-JetpackDraftActionExtension.debug.xcconfig"; sourceTree = ""; }; 91D8364021946EFB008340B2 /* GutenbergMediaPickerHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergMediaPickerHelper.swift; sourceTree = ""; }; @@ -7901,7 +7886,6 @@ 98F4044E26BB69A000BBD8B9 /* WordPress 131.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 131.xcdatamodel"; sourceTree = ""; }; 98F537A622496CF300B334F9 /* SiteStatsTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteStatsTableHeaderView.swift; sourceTree = ""; }; 98F537A822496D0D00B334F9 /* SiteStatsTableHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SiteStatsTableHeaderView.xib; sourceTree = ""; }; - 98F93181239AF64800E4E96E /* ThisWeekWidgetStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThisWeekWidgetStats.swift; sourceTree = ""; }; 98FB6E9F23074CE5002DDC8D /* Common.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; 98FBA05426B228CB004E610A /* WordPress 129.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 129.xcdatamodel"; sourceTree = ""; }; 98FCFC212231DF43006ECDD4 /* PostStatsTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatsTitleCell.swift; sourceTree = ""; }; @@ -8093,7 +8077,6 @@ B54E1DEE1A0A7BAA00807537 /* ReplyTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyTextView.swift; sourceTree = ""; }; B54E1DEF1A0A7BAA00807537 /* ReplyTextView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReplyTextView.xib; sourceTree = ""; }; B54E1DF31A0A7BBF00807537 /* NotificationMediaDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationMediaDownloader.swift; sourceTree = ""; }; - B55086201CC15CCB004EADB4 /* PromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromptViewController.swift; sourceTree = ""; }; B5552D7D1CD101A600B26DF6 /* NSExtensionContext+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSExtensionContext+Extensions.swift"; path = "WordPressShareExtension/NSExtensionContext+Extensions.swift"; sourceTree = SOURCE_ROOT; }; B5552D7F1CD1028C00B26DF6 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "String+Extensions.swift"; path = "WordPressShareExtension/String+Extensions.swift"; sourceTree = SOURCE_ROOT; }; B5552D811CD1061F00B26DF6 /* StringExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionsTests.swift; sourceTree = ""; }; @@ -8266,7 +8249,6 @@ C700FAB1258020DB0090938E /* JetpackScanThreatCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JetpackScanThreatCell.xib; sourceTree = ""; }; C7124E4C2638528F00929318 /* JetpackPrologueViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JetpackPrologueViewController.xib; sourceTree = ""; }; C7124E4D2638528F00929318 /* JetpackPrologueViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JetpackPrologueViewController.swift; sourceTree = ""; }; - C7124E912638905B00929318 /* StarFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarFieldView.swift; sourceTree = ""; }; C7192ECE25E8432D00C3020D /* ReaderTopicsCardCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReaderTopicsCardCell.xib; sourceTree = ""; }; C71AF532281064DE00F9E99E /* OnboardingQuestionsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingQuestionsCoordinator.swift; sourceTree = ""; }; C71BC73E25A652410023D789 /* JetpackScanStatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackScanStatusViewModel.swift; sourceTree = ""; }; @@ -8275,9 +8257,6 @@ C7234A412832C2BA0045C63F /* QRLoginScanningViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QRLoginScanningViewController.xib; sourceTree = ""; }; C7234A4C2832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRLoginVerifyAuthorizationViewController.swift; sourceTree = ""; }; C7234A4D2832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QRLoginVerifyAuthorizationViewController.xib; sourceTree = ""; }; - C72A4F67264088E4009CA633 /* JetpackNotFoundErrorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackNotFoundErrorViewModel.swift; sourceTree = ""; }; - C72A4F7A26408943009CA633 /* JetpackNotWPErrorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackNotWPErrorViewModel.swift; sourceTree = ""; }; - C72A4F8D26408C73009CA633 /* JetpackNoSitesErrorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackNoSitesErrorViewModel.swift; sourceTree = ""; }; C72A52CE2649B157009CA633 /* JetpackWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackWindowManager.swift; sourceTree = ""; }; C737553D27C80DD500C6E9A1 /* String+CondenseWhitespace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+CondenseWhitespace.swift"; sourceTree = ""; }; C73868C425C9F9820072532C /* JetpackScanThreatSectionGrouping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JetpackScanThreatSectionGrouping.swift; sourceTree = ""; }; @@ -8320,9 +8299,6 @@ C7E5F2592799C2B0009BC263 /* blue-icon-app-83.5x83.5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "blue-icon-app-83.5x83.5@2x.png"; sourceTree = ""; }; C7F1EB4425A4B845009D1AA2 /* WordPress 110.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 110.xcdatamodel"; sourceTree = ""; }; C7F7ABD5261CED7A00CE547F /* JetpackAuthenticationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JetpackAuthenticationManager.swift; sourceTree = ""; }; - C7F7AC73261CF1F300CE547F /* JetpackLoginErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackLoginErrorViewController.swift; sourceTree = ""; }; - C7F7AC74261CF1F300CE547F /* JetpackLoginErrorViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = JetpackLoginErrorViewController.xib; sourceTree = ""; }; - C7F7ACBD261E4F0600CE547F /* JetpackErrorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackErrorViewModel.swift; sourceTree = ""; }; C7F7BDBC26262A1B00CE547F /* AppDependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDependency.swift; sourceTree = ""; }; C7F7BDCF26262A4C00CE547F /* AppDependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDependency.swift; sourceTree = ""; }; C7F7BE0626262B9900CE547F /* AuthenticationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationHandler.swift; sourceTree = ""; }; @@ -8517,7 +8493,6 @@ D8A3A5AE206A442800992576 /* StockPhotosDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StockPhotosDataSource.swift; sourceTree = ""; }; D8A3A5B2206A49BF00992576 /* StockPhotosMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StockPhotosMedia.swift; sourceTree = ""; }; D8A468DF2181C6450094B82F /* site-segment.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-segment.json"; sourceTree = ""; }; - D8A468E421828D940094B82F /* SiteVerticalsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVerticalsService.swift; sourceTree = ""; }; D8B6BEB6203E11F2007C8A19 /* Bundle+LoadFromNib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+LoadFromNib.swift"; sourceTree = ""; }; D8B9B58E204F4EA1003C6042 /* NetworkAware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkAware.swift; sourceTree = ""; }; D8B9B592204F6C93003C6042 /* CommentsViewController+Network.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CommentsViewController+Network.h"; sourceTree = ""; }; @@ -8750,7 +8725,6 @@ E1EBC3721C118ED200F638E0 /* ImmuTableTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImmuTableTest.swift; sourceTree = ""; }; E1EBC3741C118EDE00F638E0 /* ImmuTableTestViewCellWithNib.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ImmuTableTestViewCellWithNib.xib; sourceTree = ""; }; E1ECE34E1FA88DA2007FA37A /* StoreContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreContainer.swift; sourceTree = ""; }; - E1EEFAD91CC4CC5700126533 /* Confirmable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Confirmable.h; sourceTree = ""; }; E1F47D4C1FE0290C00C1D44E /* PluginListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginListCell.swift; sourceTree = ""; }; E1FD45DF1C030B3800750F4C /* AccountSettingsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountSettingsService.swift; sourceTree = ""; }; E240859A183D82AE002EB0EF /* WPAnimatedBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WPAnimatedBox.h; sourceTree = ""; }; @@ -8947,6 +8921,8 @@ F373612EEEEF10E500093FF3 /* Pods-Apps-WordPress.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-WordPress.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-WordPress/Pods-Apps-WordPress.release-alpha.xcconfig"; sourceTree = ""; }; F4026B1C2A1BC88A00CC7781 /* DashboardDomainRegistrationCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardDomainRegistrationCardCell.swift; sourceTree = ""; }; F40CC35C2954991C00D75A95 /* WordPress 146.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 146.xcdatamodel"; sourceTree = ""; }; + F413F7792B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardDynamicCardCell.swift; sourceTree = ""; }; + F413F7872B2B253A00A64A94 /* DashboardCard+Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashboardCard+Personalization.swift"; sourceTree = ""; }; F4141EE22AE7152F000D2AAE /* AllDomainsListViewController+Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AllDomainsListViewController+Strings.swift"; sourceTree = ""; }; F4141EE52AE71AF0000D2AAE /* AllDomainsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDomainsListViewModel.swift; sourceTree = ""; }; F4141EE72AE72DC4000D2AAE /* AllDomainsListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDomainsListTableViewCell.swift; sourceTree = ""; }; @@ -8955,6 +8931,10 @@ 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 = ""; }; + F41D98D62B389735004EC050 /* DashboardDynamicCardAnalyticsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardDynamicCardAnalyticsEvent.swift; sourceTree = ""; }; + F41D98E02B39C5CE004EC050 /* BlogDashboardDynamicCardCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardDynamicCardCoordinatorTests.swift; sourceTree = ""; }; + F41D98E22B39C9E7004EC050 /* BlogDashboardDynamicCardCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardDynamicCardCoordinator.swift; sourceTree = ""; }; + F41D98E72B39E14F004EC050 /* DashboardDynamicCardAnalyticsEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardDynamicCardAnalyticsEventTests.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 = ""; }; @@ -9016,7 +8996,7 @@ F44FB6D02878A1020001E3CE /* user-suggestions.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "user-suggestions.json"; sourceTree = ""; }; F46546282AED89790017E3D1 /* AllDomainsListEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDomainsListEmptyView.swift; sourceTree = ""; }; F465462C2AEF22070017E3D1 /* AllDomainsListViewModel+Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AllDomainsListViewModel+Strings.swift"; sourceTree = ""; }; - F46546302AF2F8D20017E3D1 /* AllDomainsListMessageStateViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllDomainsListMessageStateViewModel.swift; sourceTree = ""; }; + F46546302AF2F8D20017E3D1 /* DomainsStateViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsStateViewModel.swift; sourceTree = ""; }; F46546322AF54DCD0017E3D1 /* AllDomainsListItemViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDomainsListItemViewModelTests.swift; sourceTree = ""; }; F46546342AF550A20017E3D1 /* AllDomainsListItem+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AllDomainsListItem+Helpers.swift"; sourceTree = ""; }; F465976928E4669200D5F49A /* cool-green-icon-app-76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cool-green-icon-app-76@2x.png"; sourceTree = ""; }; @@ -9097,6 +9077,9 @@ F48D44B5298992C30051EAA6 /* BlockedSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedSite.swift; sourceTree = ""; }; F48D44B7298993900051EAA6 /* WordPress 147.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 147.xcdatamodel"; sourceTree = ""; }; F48D44B92989A58C0051EAA6 /* ReaderSiteService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSiteService.swift; sourceTree = ""; }; + F48EBF892B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardAnalyticPropertiesProviding.swift; sourceTree = ""; }; + F48EBF8C2B3262D5004CD561 /* dashboard-200-with-multiple-dynamic-cards.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "dashboard-200-with-multiple-dynamic-cards.json"; sourceTree = ""; }; + F48EBF912B333111004CD561 /* dashboard-200-with-only-one-dynamic-card.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "dashboard-200-with-only-one-dynamic-card.json"; sourceTree = ""; }; F49B99FE2937C9B4000CEFCE /* MigrationEmailService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationEmailService.swift; sourceTree = ""; }; F49B9A05293A21BF000CEFCE /* MigrationAnalyticsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationAnalyticsTracker.swift; sourceTree = ""; }; F49B9A07293A21F4000CEFCE /* MigrationEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationEvent.swift; sourceTree = ""; }; @@ -9146,7 +9129,6 @@ F52CACCB24512EA700661380 /* EmptyActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyActionView.swift; sourceTree = ""; }; F532AD60253B81320013B42E /* StoriesIntroDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoriesIntroDataSource.swift; sourceTree = ""; }; F532AE1B253E55D40013B42E /* CreateButtonActionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateButtonActionSheet.swift; sourceTree = ""; }; - F53FF3A723EA723D001AD596 /* ActionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRow.swift; sourceTree = ""; }; F53FF3A923EA725C001AD596 /* SiteIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIconView.swift; sourceTree = ""; }; F543AF5623A84E4D0022F595 /* PublishSettingsControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishSettingsControllerTests.swift; sourceTree = ""; }; F551E7F423F6EA3100751212 /* FloatingActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingActionButton.swift; sourceTree = ""; }; @@ -9852,6 +9834,15 @@ path = "Supporting Files"; sourceTree = ""; }; + 017008402B35BC1A00C80490 /* View Models */ = { + isa = PBXGroup; + children = ( + F46546302AF2F8D20017E3D1 /* DomainsStateViewModel.swift */, + 017008442B35C25C00C80490 /* SiteDomainsViewModel.swift */, + ); + path = "View Models"; + sourceTree = ""; + }; 018635822A81098300915532 /* SupportChatBot */ = { isa = PBXGroup; children = ( @@ -9916,6 +9907,14 @@ path = Plan; sourceTree = ""; }; + 01E258072ACC3A9000F09666 /* Helpers */ = { + isa = PBXGroup; + children = ( + 01E258082ACC3AA000F09666 /* iOS17WidgetAPIs.swift */, + ); + path = Helpers; + sourceTree = ""; + }; 027AC51F2278982D0033E56E /* DomainCredit */ = { isa = PBXGroup; children = ( @@ -9999,6 +9998,7 @@ isa = PBXGroup; children = ( 0830538B2B2732E400B889FE /* DynamicDashboardCard.swift */, + F41D98E22B39C9E7004EC050 /* BlogDashboardDynamicCardCoordinator.swift */, ); path = Dynamic; sourceTree = ""; @@ -10505,6 +10505,7 @@ 173BCE711CEB365400AE8817 /* Domains */ = { isa = PBXGroup; children = ( + 017008402B35BC1A00C80490 /* View Models */, 017C57BA2B2B5555001E7687 /* DomainSelectionViewController.swift */, F4D1401E2AFD9B8200961797 /* Transfer Domains */, 02BF30512271D76F00616558 /* Domain credit */, @@ -10540,10 +10541,10 @@ 175CC17327205BDC00622FB4 /* Domains */ = { isa = PBXGroup; children = ( - 175CC17427205BFB00622FB4 /* DomainExpiryDateFormatterTests.swift */, F46546322AF54DCD0017E3D1 /* AllDomainsListItemViewModelTests.swift */, F46546342AF550A20017E3D1 /* AllDomainsListItem+Helpers.swift */, F4F7B2522AFA585700207282 /* DomainDetailsWebViewControllerTests.swift */, + 01B7590D2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift */, ); path = Domains; sourceTree = ""; @@ -11478,7 +11479,6 @@ isa = PBXGroup; children = ( 3F3DD0B526FD18EB00F5F121 /* Blog+DomainsDashboardView.swift */, - 175CC16F2720548700622FB4 /* DomainExpiryDateFormatter.swift */, 014ACD132A1E5033008A706C /* WebKitViewController+SandboxStore.swift */, ); path = Utility; @@ -11499,7 +11499,6 @@ 462F4E0618369F0B0028D2F8 /* BlogDetailsViewController.h */, 462F4E0718369F0B0028D2F8 /* BlogDetailsViewController.m */, 74989B8B2088E3650054290B /* BlogDetailsViewController+Activity.swift */, - 8B33BC9427A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift */, 435D10192130C2AB00BB2AA8 /* BlogDetailsViewController+FancyAlerts.swift */, 02761EBF2270072F009BAF0F /* BlogDetailsViewController+SectionHelpers.swift */, FAFC065027D27241002F0483 /* BlogDetailsViewController+Dashboard.swift */, @@ -11581,21 +11580,12 @@ 3F46EEC028BC48D1004F02B2 /* New Landing Screen */ = { isa = PBXGroup; children = ( - 3F46EEC328BC4913004F02B2 /* Model */, 3F46EEC528BC4922004F02B2 /* ViewModel */, 3F46EEC428BC491B004F02B2 /* Views */, ); path = "New Landing Screen"; sourceTree = ""; }; - 3F46EEC328BC4913004F02B2 /* Model */ = { - isa = PBXGroup; - children = ( - 3F46EEC628BC4935004F02B2 /* JetpackPrompt.swift */, - ); - path = Model; - sourceTree = ""; - }; 3F46EEC428BC491B004F02B2 /* Views */ = { isa = PBXGroup; children = ( @@ -11627,6 +11617,7 @@ 3F526D2B2539F9D60069706C /* Views */ = { isa = PBXGroup; children = ( + 01E258072ACC3A9000F09666 /* Helpers */, 3FCF66E825CAF8C50047F337 /* ListStatsView.swift */, 3F5689FF25420DE80048A9E4 /* MultiStatsView.swift */, 3F5689EF254209790048A9E4 /* SingleStatView.swift */, @@ -11648,6 +11639,9 @@ 011896A429D5B72500D34BA9 /* DomainsDashboardCoordinator.swift */, 011896A729D5BBB400D34BA9 /* DomainsDashboardFactory.swift */, B0DE91B42AF9778200D51A02 /* DomainSetupNoticeView.swift */, + 0162314F2B3B3CAD0010E377 /* PrimaryDomainView.swift */, + 01B759072B3ECAF300179AE6 /* DomainsStateView.swift */, + 01B7590A2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift */, ); path = Views; sourceTree = ""; @@ -11658,7 +11652,9 @@ 3FCF66FA25CAF8E00047F337 /* ListRow.swift */, 3F568A2E254216550048A9E4 /* FlexibleCard.swift */, 3F568A1E254213B60048A9E4 /* VerticalCard.swift */, + 018FF1342AE6771A00F301C3 /* LockScreenVerticalCard.swift */, 3FA59B99258289E30073772F /* StatsValueView.swift */, + 018FF1362AE67C2600F301C3 /* LockScreenFlexibleCard.swift */, ); path = Cards; sourceTree = ""; @@ -11788,15 +11784,16 @@ 3FA6405A2670CCD40064401E /* Info.plist */, 3F762E9226784A950088CD45 /* Logger.swift */, 3FE39A4326F8391D006E2B3A /* Screens */, - 3FA640592670CCD40064401E /* UITestsFoundation.h */, EA85B7A92A6860370096E097 /* TestObserver.swift */, + 3FA640592670CCD40064401E /* UITestsFoundation.h */, 3F762E9426784B540088CD45 /* WireMock.swift */, 3F107B1829B6F7E0009B3658 /* XCTestCase+Utils.swift */, 3F6A8CDF2A246357009DBC2B /* XCUIApplication+SavePassword.swift */, + D8E7529A2A29DC4C00E73B2D /* XCUIApplication+ScrollDownToElement.swift */, 3FB5C2B227059AC8007D0ECE /* XCUIElement+Scroll.swift */, + 3F03F2BC2B45041E00A9CE99 /* XCUIElement+TapUntil.swift */, 3F762E9A26784D2A0088CD45 /* XCUIElement+Utils.swift */, 3F762E9826784CC90088CD45 /* XCUIElementQuery+Utils.swift */, - D8E7529A2A29DC4C00E73B2D /* XCUIApplication+ScrollDownToElement.swift */, ); path = UITestsFoundation; sourceTree = ""; @@ -11815,7 +11812,6 @@ isa = PBXGroup; children = ( 98BFF57D23984344008A1DCB /* AllTimeWidgetStats.swift */, - 98F93181239AF64800E4E96E /* ThisWeekWidgetStats.swift */, 98E58A2E2360D23400E5534B /* TodayWidgetStats.swift */, 3F6DA04025646F96002AB88F /* HomeWidgetData.swift */, 3F5C861925C9EA2500BABE64 /* HomeWidgetAllTimeData.swift */, @@ -12298,7 +12294,6 @@ DC13DB7D293FD09F00E33561 /* StatsInsightsStoreTests.swift */, 937250ED267A492D0086075F /* StatsPeriodStoreTests.swift */, 0148CC282859127F00CF5D96 /* StatsWidgetsStoreTests.swift */, - 08A4E12E289D2795001D9EC7 /* UserPersistentStoreTests.swift */, 0147D650294B6EA600AA6410 /* StatsRevampStoreTests.swift */, ); path = Stores; @@ -12808,12 +12803,10 @@ isa = PBXGroup; children = ( 731E88C521C9A10A0055C014 /* ErrorStates */, - 738B9A4C21B85CF20005062B /* KeyboardInfo.swift */, 738B9A4921B85CF20005062B /* ModelSettableCell.swift */, D813D67E21AA8BBF0055CCA1 /* ShadowView.swift */, 738B9A4D21B85CF20005062B /* SiteCreationHeaderData.swift */, 738B9A4A21B85CF20005062B /* TableDataCoordinator.swift */, - 73CE3E0D21F7F9D3007C9C85 /* TableViewOffsetCoordinator.swift */, 738B9A4B21B85CF20005062B /* TitleSubtitleHeader.swift */, 738B9A4821B85CF20005062B /* TitleSubtitleTextfieldHeader.swift */, 738B9A5D21B8632E0005062B /* UITableView+Header.swift */, @@ -13088,7 +13081,6 @@ FF8C54AC21F677260003ABCF /* GutenbergMediaInserterHelper.swift */, 7EA30DB421ADA20F0092F894 /* AztecAttachmentDelegate.swift */, 7EA30DB321ADA20F0092F894 /* EditorMediaUtility.swift */, - 912347182213484300BD9F97 /* GutenbergViewController+InformativeDialog.swift */, 912347752216E27200BD9F97 /* GutenbergViewController+Localization.swift */, FFC02B82222687BF00E64FDE /* GutenbergImageLoader.swift */, 4625B5472537875E00C04AAD /* Collapsable Header */, @@ -13608,8 +13600,8 @@ 82301B8E1E787420009C9C4E /* AppRatingUtilityTests.swift */, F551E7F623FC9A5C00751212 /* Collection+RotateTests.swift */, E180BD4B1FB462FF00D0D781 /* CookieJarTests.swift */, - 4A266B90282B13A70089CF3D /* CoreDataTestCase.swift */, E1AB5A391E0C464700574B4E /* DelayTests.swift */, + 4A266B90282B13A70089CF3D /* CoreDataTestCase.swift */, 173D82E6238EE2A7008432DA /* FeatureFlagTests.swift */, E1EBC3721C118ED200F638E0 /* ImmuTableTest.swift */, 4A266B8E282B05210089CF3D /* JSONObjectTests.swift */, @@ -13819,6 +13811,7 @@ 85A1B6721742E7DB00BA5E35 /* Analytics */ = { isa = PBXGroup; children = ( + F41D98D62B389735004EC050 /* DashboardDynamicCardAnalyticsEvent.swift */, 3F28CEA62A4ACA3500B79686 /* AnalyticsEventTracking.swift */, FE7FAABC299A98B90032A6F2 /* EventTracker.swift */, 175CC17B2723103000622FB4 /* WPAnalytics+Domains.swift */, @@ -13888,6 +13881,7 @@ F4026B1C2A1BC88A00CC7781 /* DashboardDomainRegistrationCardCell.swift */, 83796698299C048E004A92B9 /* DashboardJetpackInstallCardCell.swift */, 83BFAE472A6EBF1F00C7B683 /* DashboardJetpackSocialCardCell.swift */, + F413F7792B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift */, ); path = Cards; sourceTree = ""; @@ -13906,9 +13900,9 @@ 8B6214E127B1B2D6001DF7B6 /* Service */ = { isa = PBXGroup; children = ( + 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */, 8B6214E227B1B2F3001DF7B6 /* BlogDashboardService.swift */, 8BBC778A27B5531700DBA087 /* BlogDashboardPersistence.swift */, - 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */, 8BAC9D9D27BAB97E008EA44C /* BlogDashboardRemoteEntity.swift */, 8B15CDAA27EB89AC00A75749 /* BlogDashboardPostsParser.swift */, ); @@ -13918,6 +13912,7 @@ 8B6214E427B1B420001DF7B6 /* Dashboard */ = { isa = PBXGroup; children = ( + F41D98E62B39D01B004EC050 /* Dynamic Cards */, 8B6214E527B1B446001DF7B6 /* BlogDashboardServiceTests.swift */, 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */, 8BE9AB8727B6B5A300708E45 /* BlogDashboardPersistenceTests.swift */, @@ -14080,6 +14075,8 @@ 8BEE845927B1DC9D0001A93C /* dashboard-200-with-drafts-and-scheduled.json */, 8B45C12527B2A27400EA3257 /* dashboard-200-with-drafts-only.json */, 8B2D4F5227ECE089009B085C /* dashboard-200-without-posts.json */, + F48EBF8C2B3262D5004CD561 /* dashboard-200-with-multiple-dynamic-cards.json */, + F48EBF912B333111004CD561 /* dashboard-200-with-only-one-dynamic-card.json */, ); path = Dashboard; sourceTree = ""; @@ -14088,7 +14085,6 @@ isa = PBXGroup; children = ( 8B074A4F27AC3A64003A2EB8 /* BlogDashboardViewModel.swift */, - 8BEE846027B1DE0E0001A93C /* DashboardCardModel.swift */, 806E53E027E01C7F0064315E /* DashboardStatsViewModel.swift */, ); path = ViewModel; @@ -14298,7 +14294,6 @@ FA4ADAD71C50687400F858D7 /* SiteManagementService.swift */, D8CB561F2181A8CE00554EAE /* SiteSegmentsService.swift */, B0F2EFBE259378E600C7EB6D /* SiteSuggestionService.swift */, - D8A468E421828D940094B82F /* SiteVerticalsService.swift */, B03B9233250BC593000A40AF /* SuggestionService.swift */, 59A9AB331B4C33A500A433DC /* ThemeService.h */, 59A9AB341B4C33A500A433DC /* ThemeService.m */, @@ -15151,8 +15146,6 @@ isa = PBXGroup; children = ( DC590CFE26F205C400EB0F73 /* Time Zone */, - E1EEFAD91CC4CC5700126533 /* Confirmable.h */, - B55086201CC15CCB004EADB4 /* PromptViewController.swift */, E185042E1EE6ABD9005C234C /* Restorer.swift */, 3F43602E23F31D48001DEE70 /* ScenePresenter.swift */, E14B40FE1C58B93F005046F6 /* SettingsCommon.swift */, @@ -15385,7 +15378,6 @@ B5E94D141FE04815000E7C20 /* UIImageView+SiteIcon.swift */, 1790A4521E28F0ED00AE54C2 /* UINavigationController+Helpers.swift */, 177E7DAC1DD0D1E600890467 /* UINavigationController+SplitViewFullscreen.swift */, - 171CC15724FCEBF7008B7180 /* UINavigationBar+Appearance.swift */, 7326A4A7221C8F4100B4EB8C /* UIStackView+Subviews.swift */, 8BF0B606247D88EB009A7457 /* UITableViewCell+enableDisable.swift */, D829C33A21B12EFE00B09F12 /* UIView+Borders.swift */, @@ -16005,7 +15997,6 @@ isa = PBXGroup; children = ( 3F46EEC028BC48D1004F02B2 /* New Landing Screen */, - C7124E912638905B00929318 /* StarFieldView.swift */, C7124E4D2638528F00929318 /* JetpackPrologueViewController.swift */, C7124E4C2638528F00929318 /* JetpackPrologueViewController.xib */, C7D30C642638B07A00A1695B /* JetpackPrologueStyleGuide.swift */, @@ -16042,27 +16033,6 @@ path = Coordinators; sourceTree = ""; }; - C72A4F66264088D1009CA633 /* View Models */ = { - isa = PBXGroup; - children = ( - C7F7ACBD261E4F0600CE547F /* JetpackErrorViewModel.swift */, - C72A4F67264088E4009CA633 /* JetpackNotFoundErrorViewModel.swift */, - C72A4F7A26408943009CA633 /* JetpackNotWPErrorViewModel.swift */, - C72A4F8D26408C73009CA633 /* JetpackNoSitesErrorViewModel.swift */, - ); - path = "View Models"; - sourceTree = ""; - }; - C72A4FB12641837A009CA633 /* Login Error */ = { - isa = PBXGroup; - children = ( - C72A4F66264088D1009CA633 /* View Models */, - C7F7AC73261CF1F300CE547F /* JetpackLoginErrorViewController.swift */, - C7F7AC74261CF1F300CE547F /* JetpackLoginErrorViewController.xib */, - ); - path = "Login Error"; - sourceTree = ""; - }; C72A52CD2649B14B009CA633 /* System */ = { isa = PBXGroup; children = ( @@ -16145,7 +16115,6 @@ isa = PBXGroup; children = ( F4F9D5E82909615D00502576 /* WordPress-to-Jetpack Migration */, - C72A4FB12641837A009CA633 /* Login Error */, ); path = ViewRelated; sourceTree = ""; @@ -16414,8 +16383,6 @@ children = ( D82253E3219956540014D0E2 /* AddressTableViewCell.swift */, D853723921952DAF0076F461 /* WebAddressStep.swift */, - 467D3DF925E4436000EB9CB0 /* SitePromptView.swift */, - 467D3E0B25E4436D00EB9CB0 /* SitePromptView.xib */, 08CBC77829AE6CC4000026E7 /* SiteCreationEmptySiteTemplate.swift */, F4FE743329C3767300AC2729 /* AddressTableViewCell+ViewModel.swift */, ); @@ -16967,7 +16934,6 @@ 9A09F914230C3E9700F42AB7 /* StoreFetchingStatus.swift */, 24ADA24B24F9A4CB001B5DAE /* RemoteFeatureFlagStore.swift */, 3F3CA64F25D3003C00642A89 /* StatsWidgetsStore.swift */, - 08A4E128289D202F001D9EC7 /* UserPersistentStore.swift */, 08A4E12B289D2337001D9EC7 /* UserPersistentRepository.swift */, 08E39B4428A3DEB200874CB8 /* UserPersistentStoreFactory.swift */, 0878580228B4CF950069F96C /* UserPersistentRepositoryUtility.swift */, @@ -17309,6 +17275,17 @@ path = Font; sourceTree = ""; }; + F413F7832B2B251A00A64A94 /* Models */ = { + isa = PBXGroup; + children = ( + 8BF9E03227B1A8A800915B27 /* DashboardCard.swift */, + 8BEE846027B1DE0E0001A93C /* DashboardCardModel.swift */, + F413F7872B2B253A00A64A94 /* DashboardCard+Personalization.swift */, + F48EBF892B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift */, + ); + path = Models; + sourceTree = ""; + }; F4141EEF2AE99EE2000D2AAE /* Views */ = { isa = PBXGroup; children = ( @@ -17326,7 +17303,6 @@ F4141EF02AE99F14000D2AAE /* View Models */ = { isa = PBXGroup; children = ( - F46546302AF2F8D20017E3D1 /* AllDomainsListMessageStateViewModel.swift */, F4141EE52AE71AF0000D2AAE /* AllDomainsListViewModel.swift */, F4141EEB2AE945C7000D2AAE /* AllDomainsListItemViewModel.swift */, F465462C2AEF22070017E3D1 /* AllDomainsListViewModel+Strings.swift */, @@ -17344,6 +17320,15 @@ path = Navigation; sourceTree = ""; }; + F41D98E62B39D01B004EC050 /* Dynamic Cards */ = { + isa = PBXGroup; + children = ( + F41D98E02B39C5CE004EC050 /* BlogDashboardDynamicCardCoordinatorTests.swift */, + F41D98E72B39E14F004EC050 /* DashboardDynamicCardAnalyticsEventTests.swift */, + ); + path = "Dynamic Cards"; + sourceTree = ""; + }; F41E4E8F28F1949D001880C6 /* App Icons */ = { isa = PBXGroup; children = ( @@ -17848,7 +17833,6 @@ F53FF3A623EA722F001AD596 /* Detail Header */ = { isa = PBXGroup; children = ( - F53FF3A723EA723D001AD596 /* ActionRow.swift */, F1112AB1255C2D4600F1F746 /* BlogDetailHeaderView.swift */, F53FF3A923EA725C001AD596 /* SiteIconView.swift */, ); @@ -18115,12 +18099,12 @@ FA73D7D7278D9E6300DF24B3 /* Blog Dashboard */ = { isa = PBXGroup; children = ( + F413F7832B2B251A00A64A94 /* Models */, 8B4DDF23278F3AED0022494D /* Cards */, 8B6214E127B1B2D6001DF7B6 /* Service */, 8BEE845B27B1DD8E0001A93C /* ViewModel */, 8BC81D6327CFC0C60057F790 /* Helpers */, FA73D7D5278D9E5D00DF24B3 /* BlogDashboardViewController.swift */, - 8BF9E03227B1A8A800915B27 /* DashboardCard.swift */, 80D9CFF929E5E6FE00FE3400 /* DashboardCardTableView.swift */, ); path = "Blog Dashboard"; @@ -18559,7 +18543,6 @@ FF9A6E7021F9361700D36D14 /* MediaUploadHashTests.swift */, FF2EC3C12209AC19006176E1 /* GutenbergImgUploadProcessorTests.swift */, FF1B11E6238FE27A0038B93E /* GutenbergGalleryUploadProcessorTests.swift */, - 9123471A221449E200BD9F97 /* GutenbergInformativeDialogTests.swift */, 1D19C56529C9DB0A00FB0087 /* GutenbergVideoPressUploadProcessorTests.swift */, FF0B2566237A023C004E255F /* GutenbergVideoUploadProcessorTests.swift */, 4629E4222440C8160002E15C /* GutenbergCoverUploadProcessorTests.swift */, @@ -18846,7 +18829,7 @@ E16AB92514D978240047A2E5 /* Sources */, E16AB92614D978240047A2E5 /* Frameworks */, E16AB92714D978240047A2E5 /* Resources */, - CDB91045FD48AA779DCF9C53 /* [CP] Embed Pods Frameworks */, + E42C39F1A003092A4AE8F2A2 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -19292,7 +19275,6 @@ B558541419631A1000FAF6C3 /* Notifications.storyboard in Resources */, 983DBBAA22125DD500753988 /* StatsTableFooter.xib in Resources */, 820ADD701F3A1F88002D7F93 /* ThemeBrowserSectionHeaderView.xib in Resources */, - 467D3E0C25E4436D00EB9CB0 /* SitePromptView.xib in Resources */, 17222D8F261DDDF90047B163 /* blue-classic-icon-app-60x60@3x.png in Resources */, 746A6F571E71C691003B67E3 /* DeleteSite.storyboard in Resources */, 8091019529078CFE00FCB4EA /* JetpackFullscreenOverlayViewController.xib in Resources */, @@ -19599,6 +19581,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F48EBF952B333B31004CD561 /* dashboard-200-with-multiple-dynamic-cards.json in Resources */, + F48EBF942B333550004CD561 /* dashboard-200-with-only-one-dynamic-card.json in Resources */, E1EBC3751C118EDE00F638E0 /* ImmuTableTestViewCellWithNib.xib in Resources */, E15027631E03E51500B847E3 /* notes-action-unsupported.json in Resources */, F4426FDB287F066400218003 /* site-suggestions.json in Resources */, @@ -19814,11 +19798,9 @@ FABB20022602FC2C00C8785C /* StatsTableFooter.xib in Resources */, F41E4EB728F225DB001880C6 /* stroke-dark-icon-app-60@2x.png in Resources */, F41E4EB928F225DB001880C6 /* stroke-dark-icon-app-76@2x.png in Resources */, - C7F7AC76261CF1F300CE547F /* JetpackLoginErrorViewController.xib in Resources */, F46597F628E669D400D5F49A /* spectrum-on-white-icon-app-60@2x.png in Resources */, F46597A828E6600800D5F49A /* jetpack-light-icon-app-60@2x.png in Resources */, FABB20052602FC2C00C8785C /* ThemeBrowserSectionHeaderView.xib in Resources */, - FABB20072602FC2C00C8785C /* SitePromptView.xib in Resources */, 0133A7C22A8E4F6100B36E58 /* support_chat_widget_page.css in Resources */, FABB20082602FC2C00C8785C /* DeleteSite.storyboard in Resources */, FABB200A2602FC2C00C8785C /* Noticons.ttf in Resources */, @@ -20648,26 +20630,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - CDB91045FD48AA779DCF9C53 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-WordPressTest/Pods-WordPressTest-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/Gutenberg/Gutenberg.framework/Gutenberg", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/Gutenberg/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Gutenberg.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WordPressTest/Pods-WordPressTest-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; D880C306E1943EA76DA53078 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -20751,6 +20713,26 @@ shellPath = /bin/sh; shellScript = "\"$SRCROOT/../Scripts/BuildPhases/CopyGutenbergJS.sh\"\n"; }; + E42C39F1A003092A4AE8F2A2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-WordPressTest/Pods-WordPressTest-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/Gutenberg/Gutenberg.framework/Gutenberg", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/Gutenberg/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Gutenberg.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WordPressTest/Pods-WordPressTest-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; F9C5CF0222CD5DB0007CEF56 /* Copy Alternate Internal Icons (if needed) */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -20961,10 +20943,12 @@ 0107E0BA28F97D5000DE87DB /* TodayWidgetStats.swift in Sources */, C9B477AD29CC15D9008CBF49 /* WidgetDataReader.swift in Sources */, C9FE384129C2A3D200D39841 /* LockScreenTodayViewsStatWidgetConfig.swift in Sources */, + 01E258092ACC3AA000F09666 /* iOS17WidgetAPIs.swift in Sources */, 0188FE402AA613850093EDA5 /* LockScreenMultiStatView.swift in Sources */, 0107E16128FFE99300DE87DB /* WidgetConfiguration.swift in Sources */, 0107E0BB28F97D5000DE87DB /* StatsWidgetsService.swift in Sources */, C9B477B729CD2EF7008CBF49 /* LockScreenUnconfiguredView.swift in Sources */, + 018FF1372AE67C2600F301C3 /* LockScreenFlexibleCard.swift in Sources */, 0107E0BC28F97D5000DE87DB /* StatsWidgetsView.swift in Sources */, 01D2FF6B2AA782720038E040 /* LockScreenAllTimePostsBestViewsStatWidgetConfig.swift in Sources */, 0107E0BD28F97D5000DE87DB /* AppLocalizedString.swift in Sources */, @@ -20978,7 +20962,6 @@ 0107E0C128F97D5000DE87DB /* FlexibleCard.swift in Sources */, 0107E0C228F97D5000DE87DB /* VerticalCard.swift in Sources */, C9B477A929CC13CB008CBF49 /* LockScreenSiteListProvider.swift in Sources */, - 0107E0C328F97D5000DE87DB /* ThisWeekWidgetStats.swift in Sources */, 0107E0C428F97D5000DE87DB /* HomeWidgetAllTimeData.swift in Sources */, 0107E0C528F97D5000DE87DB /* GroupedViewData.swift in Sources */, C9C21D7829BECFC7009F68E5 /* LockScreenStatsWidget.swift in Sources */, @@ -21008,6 +20991,7 @@ 01D2FF652AA77F790038E040 /* LockScreenTodayViewsVisitorsStatWidgetConfig.swift in Sources */, 0107E0D528F97D5000DE87DB /* HomeWidgetTodayData.swift in Sources */, 0107E0D628F97D5000DE87DB /* AllTimeWidgetStats.swift in Sources */, + 018FF1352AE6771A00F301C3 /* LockScreenVerticalCard.swift in Sources */, 0107E0D728F97D5000DE87DB /* Sites.intentdefinition in Sources */, 0107E0D828F97D5000DE87DB /* LocalizableStrings.swift in Sources */, C9FE383229C2053300D39841 /* LockScreenSingleStatView.swift in Sources */, @@ -21026,7 +21010,6 @@ files = ( 0167F4B62AAA0342005B9E42 /* WidgetConfiguration.swift in Sources */, 0107E13B28FE9DB200DE87DB /* Sites.intentdefinition in Sources */, - 0107E13C28FE9DB200DE87DB /* ThisWeekWidgetStats.swift in Sources */, 0107E16F28FFEF4500DE87DB /* AppConfiguration.swift in Sources */, 0107E13D28FE9DB200DE87DB /* HomeWidgetAllTimeData.swift in Sources */, 0107E13E28FE9DB200DE87DB /* SitesDataProvider.swift in Sources */, @@ -21542,7 +21525,6 @@ DC772AF5282009BA00664C02 /* StatsLineChartView.swift in Sources */, 74729CAE205722E300D1394D /* AbstractPost+Searchable.swift in Sources */, 2906F812110CDA8900169D56 /* EditCommentViewController.m in Sources */, - 98F93182239AF64800E4E96E /* ThisWeekWidgetStats.swift in Sources */, 0C0AE7592A8FAD6A007D9D6C /* MediaPickerMenu.swift in Sources */, F16C35DA23F3F76C00C81331 /* PostAutoUploadMessageProvider.swift in Sources */, 91D8364121946EFB008340B2 /* GutenbergMediaPickerHelper.swift in Sources */, @@ -21609,6 +21591,7 @@ E66969E21B9E67A000EC9C00 /* ReaderTopicToReaderSiteTopic37to38.swift in Sources */, 80535DC0294B7D3200873161 /* BlogDetailsViewController+JetpackBrandingMenuCard.swift in Sources */, B543D2B520570B5A00D3D4CC /* WordPressComSyncService.swift in Sources */, + 017008452B35C25C00C80490 /* SiteDomainsViewModel.swift in Sources */, E14A52371E39F43E00EE203E /* AppRatingsUtility.swift in Sources */, 46638DF6244904A3006E8439 /* GutenbergBlockProcessor.swift in Sources */, 46241C0F2540BD01002B8A12 /* SiteDesignStep.swift in Sources */, @@ -21646,7 +21629,6 @@ E6D2E1651B8AAD7E0000ED14 /* ReaderSiteStreamHeader.swift in Sources */, F126FE0020A33BDB0010EB6E /* VideoUploadProcessor.swift in Sources */, E166FA1B1BB0656B00374B5B /* PeopleCellViewModel.swift in Sources */, - 73CE3E0E21F7F9D3007C9C85 /* TableViewOffsetCoordinator.swift in Sources */, 436D56292117312700CEAA33 /* RegisterDomainDetailsViewController.swift in Sources */, F4EDAA4C29A516EA00622D3D /* ReaderPostService.swift in Sources */, F5E29036243E4F5F00C19CA5 /* FilterProvider.swift in Sources */, @@ -21728,7 +21710,6 @@ D817799420ABFDB300330998 /* ReaderPostCellActions.swift in Sources */, 402B2A7920ACD7690027C1DC /* ActivityStore.swift in Sources */, E62AFB6A1DC8E593007484FC /* NSAttributedString+WPRichText.swift in Sources */, - 8B33BC9527A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift in Sources */, 98812966219CE42A0075FF33 /* StatsTotalRow.swift in Sources */, 46D6114F2555DAED00B0B7BB /* SiteCreationAnalyticsHelper.swift in Sources */, 98A047722821CEBF001B4E2D /* BloggingPromptsViewController.swift in Sources */, @@ -21741,6 +21722,7 @@ FE3E83E526A58646008CE851 /* ListSimpleOverlayView.swift in Sources */, 98B88452261E4E09007ED7F8 /* LikeUserTableViewCell.swift in Sources */, E16FB7E31F8B61040004DD9F /* WebKitViewController.swift in Sources */, + 016231502B3B3CAD0010E377 /* PrimaryDomainView.swift in Sources */, 8067340A27E3A50900ABC95E /* UIViewController+RemoveQuickStart.m in Sources */, 3F8CBE0B24EEB0EA00F71234 /* AnnouncementsDataSource.swift in Sources */, FE7FAABE299A998E0032A6F2 /* EventTracker.swift in Sources */, @@ -21771,7 +21753,6 @@ 0CED95602A460F4B0020F420 /* DebugFeatureFlagsView.swift in Sources */, FA73D7D6278D9E5D00DF24B3 /* BlogDashboardViewController.swift in Sources */, 4349B0AF218A477F0034118A /* RevisionsTableViewCell.swift in Sources */, - 738B9A5921B85CF20005062B /* KeyboardInfo.swift in Sources */, E15644F31CE0E5A500D96E64 /* PlanDetailViewModel.swift in Sources */, FA8E2FE527C6AE4500DA0982 /* QuickStartChecklistView.swift in Sources */, 80D9D04629F760C400FE3400 /* FailableDecodable.swift in Sources */, @@ -21877,6 +21858,7 @@ E6374DC01C444D8B00F24720 /* PublicizeConnection.swift in Sources */, C81CCD7C243BF7A600A83E27 /* TenorPageable.swift in Sources */, E6C0ED3B231DA23400A08B57 /* AccountService+MergeDuplicates.swift in Sources */, + F413F77A2B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift in Sources */, 0845B8C61E833C56001BA771 /* URL+Helpers.swift in Sources */, 17D975AF1EF7F6F100303D63 /* WPStyleGuide+Aztec.swift in Sources */, E14B40FF1C58B93F005046F6 /* SettingsCommon.swift in Sources */, @@ -21976,7 +21958,6 @@ 3236F77224ABB6C90088E8F3 /* ReaderInterestsDataSource.swift in Sources */, 7E4123CA20F4184200DF8486 /* ActivityContentGroup.swift in Sources */, 08F8CD2F1EBD29440049D0C0 /* MediaImageExporter.swift in Sources */, - 912347192213484300BD9F97 /* GutenbergViewController+InformativeDialog.swift in Sources */, 2FA6511721F26A24009AA935 /* ChangePasswordViewController.swift in Sources */, D816C1F020E0893A00C4D82F /* LikeComment.swift in Sources */, 982DA9A7263B1E2F00E5743B /* CommentService+Likes.swift in Sources */, @@ -21989,6 +21970,7 @@ 9887560C2810BA7A00AD7589 /* BloggingPromptsIntroductionPresenter.swift in Sources */, 7E21C765202BBF4400837CF5 /* SearchAdsAttribution.swift in Sources */, 5DF8D26119E82B1000A2CD95 /* ReaderCommentsViewController.m in Sources */, + 01B759082B3ECAF300179AE6 /* DomainsStateView.swift in Sources */, C3DD4DCE28BE5D4D0046C68E /* SplashPrologueViewController.swift in Sources */, 1716AEFC25F2927600CF49EC /* MySiteViewController.swift in Sources */, F18CB8962642E58700B90794 /* FixedSizeImageView.swift in Sources */, @@ -22056,6 +22038,7 @@ 4AA33EF829963ABE005B6E23 /* ReaderAbstractTopic+Lookup.swift in Sources */, 73BFDA8A211D054800907245 /* Notifiable.swift in Sources */, 404B35D322E9BA0800AD0B37 /* RegisterDomainDetailsViewModel+CountryDialCodes.swift in Sources */, + F41D98D72B389735004EC050 /* DashboardDynamicCardAnalyticsEvent.swift in Sources */, D8EB1FD121900810002AE1C4 /* BlogListViewController+SiteCreation.swift in Sources */, 80B016D12803AB9F00D15566 /* DashboardPostsListCardCell.swift in Sources */, E64595F0256B5D7800F7F90C /* CommentAnalytics.swift in Sources */, @@ -22144,7 +22127,6 @@ B57B99DE19A2DBF200506504 /* NSObject+Helpers.m in Sources */, E1E49CE41C4902EE002393A4 /* ImmuTableViewController.swift in Sources */, 9A162F2321C26D7500FDC035 /* RevisionPreviewViewController.swift in Sources */, - 467D3DFA25E4436000EB9CB0 /* SitePromptView.swift in Sources */, FAB8AA2225AF031200F9F8A0 /* BaseRestoreCompleteViewController.swift in Sources */, E616E4B31C480896002C024E /* SharingService.swift in Sources */, F5E032E82408D537003AF350 /* BottomSheetPresentationController.swift in Sources */, @@ -22303,6 +22285,7 @@ 85B125461B0294F6008A3D95 /* UIAlertControllerProxy.m in Sources */, 73FF7030221F43CD00541798 /* StatsBarChartView.swift in Sources */, F5E1BBE0253B74240091E9A6 /* URLQueryItem+Parameters.swift in Sources */, + F48EBF8A2B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift in Sources */, 0857C27A1CE5375F0014AE99 /* MenuItemView.m in Sources */, B5E51B7B203477DF00151ECD /* WordPressAuthenticationManager.swift in Sources */, FA4ADAD81C50687400F858D7 /* SiteManagementService.swift in Sources */, @@ -22388,7 +22371,6 @@ DCF892CC282FA3BB00BB71E1 /* SiteStatsImmuTableRows.swift in Sources */, 08D553662821286300AA1E8D /* Tooltip.swift in Sources */, FAB985C12697550C00B172A3 /* NoResultsViewController+StatsModule.swift in Sources */, - B55086211CC15CCB004EADB4 /* PromptViewController.swift in Sources */, FE3D058326C419C4002A51B0 /* ShareAppContentPresenter+TableView.swift in Sources */, 82FC612C1FA8B7FC00A1757E /* ActivityListRow.swift in Sources */, 436110E022C4241A000773AD /* UIColor+MurielColorsObjC.swift in Sources */, @@ -22404,7 +22386,6 @@ 013A8CB62AB83B40004FF5D0 /* DashboardDomainsCardSearchView.swift in Sources */, F11C9F78243B3C9600921DDC /* MediaHost+ReaderPostContentProvider.swift in Sources */, FA1A55EF25A6F0740033967D /* RestoreStatusView.swift in Sources */, - 175CC1702720548700622FB4 /* DomainExpiryDateFormatter.swift in Sources */, 985793C822F23D7000643DBF /* CustomizeInsightsCell.swift in Sources */, 93E6336F272C1074009DACF8 /* LoginEpilogueCreateNewSiteCell.swift in Sources */, 8261B4CC1EA8E13700668298 /* SVProgressHUD+Dismiss.m in Sources */, @@ -22588,7 +22569,6 @@ 5DA3EE161925090A00294E0B /* MediaService.m in Sources */, 8BF9E03327B1A8A800915B27 /* DashboardCard.swift in Sources */, 7E4123C520F4097B00DF8486 /* FormattableContentAction.swift in Sources */, - D8A468E521828D940094B82F /* SiteVerticalsService.swift in Sources */, 3F8D988926153484003619E5 /* UnifiedPrologueBackgroundView.swift in Sources */, E65219FB1B8D10DA000B1217 /* ReaderBlockedSiteCell.swift in Sources */, FAF13C5325A57ABD003EE470 /* JetpackRestoreWarningViewController.swift in Sources */, @@ -22724,7 +22704,6 @@ B518E1651CCAA19200ADFE75 /* Blog+Capabilities.swift in Sources */, FADC40AE2A8D2E8D00C19997 /* ImageDownloader+Gravatar.swift in Sources */, 08216FC91CDBF96000304BA7 /* MenuItemCategoriesViewController.m in Sources */, - 171CC15824FCEBF7008B7180 /* UINavigationBar+Appearance.swift in Sources */, 43C9908E21067E22009EFFEB /* QuickStartChecklistViewController.swift in Sources */, 80EF929028105CFA0064A971 /* QuickStartFactory.swift in Sources */, 082635BB1CEA69280088030C /* MenuItemsViewController.m in Sources */, @@ -22784,6 +22763,7 @@ C81CCD6F243AF7D700A83E27 /* TenorReponseParser.swift in Sources */, 4020B2BD2007AC850002C963 /* WPStyleGuide+Gridicon.swift in Sources */, 982D261F2788DDF200A41286 /* ReaderCommentsFollowPresenter.swift in Sources */, + F413F7882B2B253A00A64A94 /* DashboardCard+Personalization.swift in Sources */, F5E032D6240889EB003AF350 /* CreateButtonCoordinator.swift in Sources */, 74729CA32056FA0900D1394D /* SearchManager.swift in Sources */, 7E8980CA22E8C7A600C567B0 /* BlogToBlogMigration87to88.swift in Sources */, @@ -22795,6 +22775,7 @@ E17FEAD8221490F7006E1D2D /* PostEditorAnalyticsSession.swift in Sources */, 738B9A5221B85CF20005062B /* SiteCreationWizardLauncher.swift in Sources */, 46F583AF2624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift in Sources */, + F41D98E42B39CAA5004EC050 /* BlogDashboardDynamicCardCoordinator.swift in Sources */, 3234B8E7252FA0930068DA40 /* ReaderSitesCardCell.swift in Sources */, C7234A4E2832C47D0045C63F /* QRLoginVerifyAuthorizationViewController.swift in Sources */, 08CBC77929AE6CC4000026E7 /* SiteCreationEmptySiteTemplate.swift in Sources */, @@ -22819,7 +22800,6 @@ B5CABB171C0E382C0050AB9F /* PickerTableViewCell.swift in Sources */, F5AE43E425DD02C1003675F4 /* StoryEditor.swift in Sources */, F4C1FC662A44836300AD7CB0 /* PrivacySettingsAnalytics.swift in Sources */, - 08A4E129289D202F001D9EC7 /* UserPersistentStore.swift in Sources */, E1D95EB817A28F5E00A3E9F3 /* WPActivityDefaults.m in Sources */, C31852A129670F8100A78BE9 /* JetpackScanViewController+JetpackBannerViewController.swift in Sources */, 436D56302117410C00CEAA33 /* RegisterDomainDetailsViewModel+CellIndex.swift in Sources */, @@ -22848,6 +22828,7 @@ 591AA5021CEF9BF20074934F /* Post+CoreDataProperties.swift in Sources */, E19B17AE1E5C6944007517C6 /* BasePost.swift in Sources */, C71AF533281064DE00F9E99E /* OnboardingQuestionsCoordinator.swift in Sources */, + 01B759062B3ECA7300179AE6 /* DomainsStateViewModel.swift in Sources */, 0CD223DF2AA8ADFD002BD761 /* DashboardQuickActionsViewModel.swift in Sources */, 7E7947A9210BAC1D005BB851 /* NotificationContentRange.swift in Sources */, 8B6EA62323FDE50B004BA312 /* PostServiceUploadingList.swift in Sources */, @@ -22876,6 +22857,7 @@ 836498CE281735CC00A2C170 /* BloggingPromptsHeaderView.swift in Sources */, 3F43703F2893201400475B6E /* JetpackOverlayViewController.swift in Sources */, 319D6E8519E44F7F0013871C /* SuggestionsTableViewCell.m in Sources */, + 01B7590B2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */, 0CB424F12ADEE52A0080B807 /* PostSearchToken.swift in Sources */, 98AA6D1126B8CE7200920C8B /* Comment+CoreDataClass.swift in Sources */, 7E4A773720F802A8001C706D /* ActivityRangesFactory.swift in Sources */, @@ -22887,7 +22869,6 @@ 0C0453282AC73343003079C8 /* SiteMediaVideoDurationView.swift in Sources */, 837B49D9283C2AE80061A657 /* BloggingPromptSettings+CoreDataProperties.swift in Sources */, 436D56212117312700CEAA33 /* RegisterDomainDetailsViewModel+SectionDefinitions.swift in Sources */, - F53FF3A823EA723D001AD596 /* ActionRow.swift in Sources */, FFA162311CB7031A00E2E110 /* AppSettingsViewController.swift in Sources */, F111B87826580FCE00057942 /* BloggingRemindersStore.swift in Sources */, 8B16CE9A25251C89007BE5A9 /* ReaderPostStreamService.swift in Sources */, @@ -22911,6 +22892,7 @@ 3F2F855A26FAF227000FCDA5 /* EditorNoticeComponent.swift in Sources */, 01281E9A2A0456CB00464F8F /* DomainsSelectionScreen.swift in Sources */, 3F2F855D26FAF227000FCDA5 /* LoginCheckMagicLinkScreen.swift in Sources */, + 3F03F2BD2B45041E00A9CE99 /* XCUIElement+TapUntil.swift in Sources */, EA78189427596B2F00554DFA /* ContactUsScreen.swift in Sources */, D82E087829EEB7AF0098F500 /* DomainsScreen.swift in Sources */, 3F6A8CE02A246357009DBC2B /* XCUIApplication+SavePassword.swift in Sources */, @@ -23482,6 +23464,7 @@ B0A6DEBF2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift in Sources */, 93B853231B4416A30064FE72 /* WPAnalyticsTrackerAutomatticTracksTests.m in Sources */, C738CB0B28623CED001BE107 /* QRLoginCoordinatorTests.swift in Sources */, + F41D98E12B39C5CE004EC050 /* BlogDashboardDynamicCardCoordinatorTests.swift in Sources */, FE2E3729281C839C00A1E82A /* BloggingPromptsServiceTests.swift in Sources */, D848CC0720FF2BE200A9038F /* NotificationContentRangeFactoryTests.swift in Sources */, 732A473F21878EB10015DA74 /* WPRichContentViewTests.swift in Sources */, @@ -23500,7 +23483,6 @@ 80EF92932810FA5A0064A971 /* QuickStartFactoryTests.swift in Sources */, 80C523AB29AE6C2200B1C14B /* BlazeCreateCampaignWebViewModelTests.swift in Sources */, D848CBFF20FF010F00A9038F /* FormattableCommentContentTests.swift in Sources */, - 9123471B221449E200BD9F97 /* GutenbergInformativeDialogTests.swift in Sources */, 8332DD2829259BEB00802F7D /* DataMigratorTests.swift in Sources */, C80512FE243FFD4B00B6B04D /* TenorDataSouceTests.swift in Sources */, 323F8F3023A22C4C000BA49C /* SiteCreationRotatingMessageViewTests.swift in Sources */, @@ -23564,6 +23546,7 @@ C738CB0D28623F07001BE107 /* QRLoginURLParserTests.swift in Sources */, D809E686203F0215001AA0DE /* OldReaderPostCardCellTests.swift in Sources */, 4AAD69082A6F68A5007FE77E /* MediaRepositoryTests.swift in Sources */, + 01B7590E2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift in Sources */, FEFC0F8C273131A6001F7F1D /* CommentService+RepliesTests.swift in Sources */, 40E4698F2017E0700030DB5F /* PluginDirectoryEntryStateTests.swift in Sources */, 8BC6020D2390412000EFE3D0 /* NullBlogPropertySanitizerTests.swift in Sources */, @@ -23573,6 +23556,7 @@ E1AB5A3A1E0C464700574B4E /* DelayTests.swift in Sources */, 8B7F51CB24EED8A8008CF5B5 /* ReaderTrackerTests.swift in Sources */, D848CC0320FF04FA00A9038F /* FormattableUserContentTests.swift in Sources */, + F41D98E82B39E14F004EC050 /* DashboardDynamicCardAnalyticsEventTests.swift in Sources */, 5948AD111AB73D19006E8882 /* WPAppAnalyticsTests.m in Sources */, 0C8FC9AA2A8C57000059DCE4 /* ItemProviderMediaExporterTests.swift in Sources */, 4AD862E52AFAEF1700A07557 /* PostsListAPIStub.swift in Sources */, @@ -23624,7 +23608,6 @@ 7E8980B922E73F4000C567B0 /* EditorSettingsServiceTests.swift in Sources */, 1797373720EBAA4100377B4E /* RouteMatcherTests.swift in Sources */, 73178C2A21BEE09300E37C9A /* SiteSegmentsCellTests.swift in Sources */, - 175CC17527205BFB00622FB4 /* DomainExpiryDateFormatterTests.swift in Sources */, B5416CFE1C1756B900006DD8 /* PushNotificationsManagerTests.m in Sources */, 321955C124BE4EBF00E3F316 /* ReaderSelectInterestsCoordinatorTests.swift in Sources */, F4EF4BAB291D3D4700147B61 /* SiteIconTests.swift in Sources */, @@ -23633,7 +23616,6 @@ C3E42AB027F4D30E00546706 /* MenuItemsViewControllerTests.swift in Sources */, D842EA4021FABB1800210E96 /* SiteSegmentTests.swift in Sources */, C3C70C562835C5BB00DD2546 /* SiteDesignSectionLoaderTests.swift in Sources */, - 08A4E12F289D2795001D9EC7 /* UserPersistentStoreTests.swift in Sources */, 436D55F5211632B700CEAA33 /* RegisterDomainDetailsViewModelTests.swift in Sources */, E180BD4C1FB462FF00D0D781 /* CookieJarTests.swift in Sources */, F4394D1F2A3AB06F003955C6 /* WPCrashLoggingDataProviderTests.swift in Sources */, @@ -23969,6 +23951,7 @@ 3F5AAC242877791900AEF5DD /* JetpackButton.swift in Sources */, 83E1E55A2A58B5C2000B576F /* JetpackSocialError.swift in Sources */, FABB211C2602FC2C00C8785C /* EncryptedLogTableViewController.swift in Sources */, + 01B7590C2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */, FABB211D2602FC2C00C8785C /* ActivityDateFormatting.swift in Sources */, FABB211E2602FC2C00C8785C /* UIView+Subviews.m in Sources */, FABB211F2602FC2C00C8785C /* WordPress-20-21.xcmappingmodel in Sources */, @@ -24232,7 +24215,6 @@ FABB21D72602FC2C00C8785C /* Routes+Stats.swift in Sources */, 8BF1C81B27BC00AF00F1C203 /* BlogDashboardCardFrameView.swift in Sources */, FABB21D92602FC2C00C8785C /* SearchIdentifierGenerator.swift in Sources */, - C7124E922638905B00929318 /* StarFieldView.swift in Sources */, FABB21DA2602FC2C00C8785C /* StatsTwoColumnRow.swift in Sources */, 982DDF91263238A6002B3904 /* LikeUser+CoreDataClass.swift in Sources */, FABB21DC2602FC2C00C8785C /* ReaderSaveForLaterRemovedPosts.swift in Sources */, @@ -24245,7 +24227,6 @@ FABB21E02602FC2C00C8785C /* SupportTableViewController.swift in Sources */, FABB21E12602FC2C00C8785C /* DetailDataCell.swift in Sources */, 83B1D038282C62620061D911 /* BloggingPromptsAttribution.swift in Sources */, - C7F7AC75261CF1F300CE547F /* JetpackLoginErrorViewController.swift in Sources */, FABB21E22602FC2C00C8785C /* PluginViewModel.swift in Sources */, FABB21E32602FC2C00C8785C /* NSCalendar+Helpers.swift in Sources */, FABB21E42602FC2C00C8785C /* GutenbergCoverUploadProcessor.swift in Sources */, @@ -24276,7 +24257,6 @@ FABB21F52602FC2C00C8785C /* PostNoticeNavigationCoordinator.swift in Sources */, FABB21F62602FC2C00C8785C /* SearchableActivityConvertable.swift in Sources */, FABB21F72602FC2C00C8785C /* GridCell.swift in Sources */, - C72A4F68264088E4009CA633 /* JetpackNotFoundErrorViewModel.swift in Sources */, FE1E201B2A473E0800CE7C90 /* JetpackSocialService.swift in Sources */, FABB21F82602FC2C00C8785C /* AdaptiveNavigationController.swift in Sources */, FABB21F92602FC2C00C8785C /* RemotePostCategory+Extensions.swift in Sources */, @@ -24340,6 +24320,7 @@ 0C0AD10B2B0CCFA400EC06E6 /* MediaPreviewController.swift in Sources */, FABB222A2602FC2C00C8785C /* JetpackScanThreatSectionGrouping.swift in Sources */, 0839F88D2993C1B600415038 /* JetpackPluginOverlayCoordinator.swift in Sources */, + F413F7892B2B253A00A64A94 /* DashboardCard+Personalization.swift in Sources */, FABB222B2602FC2C00C8785C /* MediaHelper.swift in Sources */, FABB222C2602FC2C00C8785C /* ReaderActionHelpers.swift in Sources */, FABB222D2602FC2C00C8785C /* NoteBlockUserTableViewCell.swift in Sources */, @@ -24348,6 +24329,7 @@ 8B074A5127AC3A64003A2EB8 /* BlogDashboardViewModel.swift in Sources */, FEAC916F28001FC4005026E7 /* AvatarTrainView.swift in Sources */, FA4BC0D12996A589005EB077 /* BlazeService.swift in Sources */, + 01B759092B3ECAF300179AE6 /* DomainsStateView.swift in Sources */, FABB22302602FC2C00C8785C /* Double+Stats.swift in Sources */, FABB22312602FC2C00C8785C /* CircularProgressView.swift in Sources */, FABB22322602FC2C00C8785C /* StatsNoDataRow.swift in Sources */, @@ -24362,7 +24344,6 @@ 08E6E07F2A4C405500B807B0 /* CompliancePopoverViewModel.swift in Sources */, FAA9084D27BD60710093FFA8 /* MySiteViewController+QuickStart.swift in Sources */, FABB223C2602FC2C00C8785C /* EditCommentViewController.m in Sources */, - FABB223D2602FC2C00C8785C /* ThisWeekWidgetStats.swift in Sources */, FABB223E2602FC2C00C8785C /* PostAutoUploadMessageProvider.swift in Sources */, FABB223F2602FC2C00C8785C /* GutenbergMediaPickerHelper.swift in Sources */, 0CB424EF2ADEE3CD0080B807 /* PostSearchTokenTableCell.swift in Sources */, @@ -24394,6 +24375,7 @@ FABB22522602FC2C00C8785C /* ActivityTableViewCell.swift in Sources */, F49D7BEC29DF329500CB93A5 /* UIPopoverPresentationController+PopoverAnchor.swift in Sources */, FABB22532602FC2C00C8785C /* BlogDetailsViewController+FancyAlerts.swift in Sources */, + F41D98E52B39CAAA004EC050 /* BlogDashboardDynamicCardCoordinator.swift in Sources */, FABB22542602FC2C00C8785C /* StoriesIntroDataSource.swift in Sources */, FABB22552602FC2C00C8785C /* StoreContainer.swift in Sources */, FABB22562602FC2C00C8785C /* AbstractPost+HashHelpers.m in Sources */, @@ -24466,13 +24448,11 @@ FABB22882602FC2C00C8785C /* UserSuggestion+CoreDataProperties.swift in Sources */, C743535727BD7144008C2644 /* AnimatedGifAttachmentViewProvider.swift in Sources */, FABB228A2602FC2C00C8785C /* ReaderHeaderAction.swift in Sources */, - C72A4F8E26408C73009CA633 /* JetpackNoSitesErrorViewModel.swift in Sources */, FABB228B2602FC2C00C8785C /* FancyAlerts+VerificationPrompt.swift in Sources */, FABB228C2602FC2C00C8785C /* ReaderSiteStreamHeader.swift in Sources */, FABB228D2602FC2C00C8785C /* VideoUploadProcessor.swift in Sources */, FE34ACD02B1661EB00108B3C /* DashboardBloganuaryCardCell.swift in Sources */, FABB228E2602FC2C00C8785C /* PeopleCellViewModel.swift in Sources */, - FABB228F2602FC2C00C8785C /* TableViewOffsetCoordinator.swift in Sources */, FABB22902602FC2C00C8785C /* RegisterDomainDetailsViewController.swift in Sources */, 1770BD0E267A368100D5F8C0 /* BloggingRemindersPushPromptViewController.swift in Sources */, C7BB601A2863AF9700748FD9 /* QRLoginProtocols.swift in Sources */, @@ -24586,7 +24566,6 @@ E6D6A1312683ABE6004C24A7 /* ReaderSubscribeCommentsAction.swift in Sources */, F49B9A06293A21BF000CEFCE /* MigrationAnalyticsTracker.swift in Sources */, E62CE58F26B1D14200C9D147 /* AccountService+Cookies.swift in Sources */, - FABB22DE2602FC2C00C8785C /* KeyboardInfo.swift in Sources */, 0C391E622A3002950040EA91 /* BlazeCampaignStatusView.swift in Sources */, FABB22DF2602FC2C00C8785C /* PlanDetailViewModel.swift in Sources */, FABB22E02602FC2C00C8785C /* DebugMenuViewController.swift in Sources */, @@ -24622,8 +24601,6 @@ FABB22F22602FC2C00C8785C /* Route.swift in Sources */, FABB22F32602FC2C00C8785C /* RegisterDomainDetailsViewController+Cells.swift in Sources */, 4A526BE0296BE9A50007B5BA /* CoreDataService.m in Sources */, - 3F46EEC728BC4935004F02B2 /* JetpackPrompt.swift in Sources */, - 175CC1712720548700622FB4 /* DomainExpiryDateFormatter.swift in Sources */, FABB22F42602FC2C00C8785C /* ReaderStreamViewController+Sharing.swift in Sources */, B0DE91B62AF9778200D51A02 /* DomainSetupNoticeView.swift in Sources */, FABB22F52602FC2C00C8785C /* FilterTabBar.swift in Sources */, @@ -24692,6 +24669,7 @@ FABB23232602FC2C00C8785C /* Delay.swift in Sources */, FABB23242602FC2C00C8785C /* Pageable.swift in Sources */, FABB23252602FC2C00C8785C /* AppIconViewController.swift in Sources */, + F413F77B2B2A183E00A64A94 /* BlogDashboardDynamicCardCell.swift in Sources */, FABB23262602FC2C00C8785C /* ThemeBrowserCell.swift in Sources */, FA8E2FE627C6AE4500DA0982 /* QuickStartChecklistView.swift in Sources */, FABB23272602FC2C00C8785C /* ImageCropViewController.swift in Sources */, @@ -24802,7 +24780,6 @@ FABB23762602FC2C00C8785C /* ActivityContentGroup.swift in Sources */, 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 */, @@ -24826,7 +24803,7 @@ 0A9610FA28B2E56300076EBA /* UserSuggestion+Comparable.swift in Sources */, 80C740FC2989FC4600199027 /* PostStatsTableViewController+JetpackBannerViewController.swift in Sources */, 83914BD52A2EA03A0017A588 /* PostSettingsViewController+JetpackSocial.swift in Sources */, - F46546312AF2F8D30017E3D1 /* AllDomainsListMessageStateViewModel.swift in Sources */, + F46546312AF2F8D30017E3D1 /* DomainsStateViewModel.swift in Sources */, FABB23852602FC2C00C8785C /* WPError.m in Sources */, FABB23862602FC2C00C8785C /* ContentRouter.swift in Sources */, FABB23872602FC2C00C8785C /* BlogToBlog32to33.swift in Sources */, @@ -24888,7 +24865,6 @@ FABB23B52602FC2C00C8785C /* JetpackScanStatusCell.swift in Sources */, FABB23B62602FC2C00C8785C /* NoteBlockCommentTableViewCell.swift in Sources */, 801D951B291AC0B00051993E /* OverlayFrequencyTracker.swift in Sources */, - C7F7ACBE261E4F0600CE547F /* JetpackErrorViewModel.swift in Sources */, FABB23B72602FC2C00C8785C /* Notifiable.swift in Sources */, 80EF672627F3D63B0063B138 /* DashboardStatsStackView.swift in Sources */, FABB23B82602FC2C00C8785C /* RegisterDomainDetailsViewModel+CountryDialCodes.swift in Sources */, @@ -24971,7 +24947,6 @@ FABB23FA2602FC2C00C8785C /* RevisionPreviewViewController.swift in Sources */, 80EF928B280D28140064A971 /* Atomic.swift in Sources */, 3FFDEF9129187F2100B625CE /* MigrationActionsConfiguration.swift in Sources */, - FABB23FC2602FC2C00C8785C /* SitePromptView.swift in Sources */, 0C896DE32A3A7BD700D7D4E7 /* SettingsPicker.swift in Sources */, 8B4EDADE27DF9D5E004073B6 /* Blog+MySite.swift in Sources */, FABB23FD2602FC2C00C8785C /* BaseRestoreCompleteViewController.swift in Sources */, @@ -25040,6 +25015,7 @@ FABB242E2602FC2C00C8785C /* OffsetTableViewHandler.swift in Sources */, FABB242F2602FC2C00C8785C /* UINavigationController+SplitViewFullscreen.swift in Sources */, 803BB99029667BAF00B3F6D6 /* JetpackBrandingTextProvider.swift in Sources */, + F48EBF8B2B2F94DD004CD561 /* BlogDashboardAnalyticPropertiesProviding.swift in Sources */, 0CE7833E2B08F3C300B114EB /* ExternalMediaPickerViewController.swift in Sources */, FABB24302602FC2C00C8785C /* ReaderReblogPresenter.swift in Sources */, 0CE538D12B0E317000834BA2 /* StockPhotosWelcomeView.swift in Sources */, @@ -25221,7 +25197,6 @@ 3FF15A56291B4EEA00E1B4E5 /* MigrationCenterView.swift in Sources */, FABB24A62602FC2C00C8785C /* JetpackRestoreCompleteViewController.swift in Sources */, FABB24A72602FC2C00C8785C /* FormattableNoticonRange.swift in Sources */, - FABB24A82602FC2C00C8785C /* PromptViewController.swift in Sources */, 46F583AC2624CE790010A723 /* BlockEditorSettings+CoreDataProperties.swift in Sources */, FABB24AA2602FC2C00C8785C /* ActivityListRow.swift in Sources */, FABB24AB2602FC2C00C8785C /* UIColor+MurielColorsObjC.swift in Sources */, @@ -25344,7 +25319,6 @@ C31852A329673BFC00A78BE9 /* MenusViewController+JetpackBannerViewController.swift in Sources */, FABB25002602FC2C00C8785C /* PlansLoadingIndicatorView.swift in Sources */, FABB25012602FC2C00C8785C /* JetpackScanThreatDetailsViewController.swift in Sources */, - C72A4F7B26408943009CA633 /* JetpackNotWPErrorViewModel.swift in Sources */, FABB25022602FC2C00C8785C /* NotificationTextContent.swift in Sources */, FABB25032602FC2C00C8785C /* PostEditor+Publish.swift in Sources */, FABB25042602FC2C00C8785C /* MediaCoordinator.swift in Sources */, @@ -25399,7 +25373,6 @@ 175CC17A27230DC900622FB4 /* Bool+StringRepresentation.swift in Sources */, F1BC842F27035A1800C39993 /* BlogService+Domains.swift in Sources */, FE7FAABF299A998F0032A6F2 /* EventTracker.swift in Sources */, - 8B33BC9627A0C14C00DB5985 /* BlogDetailsViewController+QuickActions.swift in Sources */, FABB25272602FC2C00C8785C /* UIImage+Exporters.swift in Sources */, 0133A7BF2A8CEADD00B36E58 /* SupportCoordinator.swift in Sources */, FABB25282602FC2C00C8785C /* PostStatsTitleCell.swift in Sources */, @@ -25429,7 +25402,6 @@ FEA6517C281C491C002EA086 /* BloggingPromptsService.swift in Sources */, FABB253C2602FC2C00C8785C /* FormattableContentAction.swift in Sources */, 3F3DD0B326FD176800F5F121 /* SiteDomainsPresentationCard.swift in Sources */, - FABB253D2602FC2C00C8785C /* SiteVerticalsService.swift in Sources */, FABB253E2602FC2C00C8785C /* ReaderBlockedSiteCell.swift in Sources */, FE32F007275F62620040BE67 /* WebCommentContentRenderer.swift in Sources */, FABB253F2602FC2C00C8785C /* JetpackRestoreWarningViewController.swift in Sources */, @@ -25461,6 +25433,7 @@ FABB25552602FC2C00C8785C /* Charts+AxisFormatters.swift in Sources */, 80D9CFFE29E711E200FE3400 /* DashboardPageCell.swift in Sources */, FABB25572602FC2C00C8785C /* EpilogueSectionHeaderFooter.swift in Sources */, + 016231512B3B3CAD0010E377 /* PrimaryDomainView.swift in Sources */, FABB25582602FC2C00C8785C /* PostCategoriesViewController.swift in Sources */, FABB25592602FC2C00C8785C /* NSManagedObject+Lookup.swift in Sources */, FABB255A2602FC2C00C8785C /* StatsPeriodHelper.swift in Sources */, @@ -25495,7 +25468,6 @@ FABB25702602FC2C00C8785C /* ReaderReblogAction.swift in Sources */, F479995F2AFD241E0023F4FB /* RegisterDomainTransferFooterView.swift in Sources */, FABB25712602FC2C00C8785C /* SiteAssemblyWizardContent.swift in Sources */, - 08A4E12A289D202F001D9EC7 /* UserPersistentStore.swift in Sources */, FEDDD47026A03DE900F8942B /* ListTableViewCell+Notifications.swift in Sources */, FABB25722602FC2C00C8785C /* UITableView+Header.swift in Sources */, FABB25732602FC2C00C8785C /* RichTextContentStyles.swift in Sources */, @@ -25566,7 +25538,6 @@ FABB259D2602FC2C00C8785C /* Blog+Capabilities.swift in Sources */, 1756F1E02822BB6F00CD0915 /* SparklineView.swift in Sources */, FABB259F2602FC2C00C8785C /* MenuItemCategoriesViewController.m in Sources */, - FABB25A02602FC2C00C8785C /* UINavigationBar+Appearance.swift in Sources */, FABB25A12602FC2C00C8785C /* QuickStartChecklistViewController.swift in Sources */, FABB25A22602FC2C00C8785C /* MenuItemsViewController.m in Sources */, FABB25A32602FC2C00C8785C /* ReachabilityUtils+OnlineActions.swift in Sources */, @@ -25695,12 +25666,14 @@ FABB25FD2602FC2C00C8785C /* JetpackRemoteInstallState.swift in Sources */, FABB25FE2602FC2C00C8785C /* Scheduler.swift in Sources */, 4A2172F928EAACFF0006F4F1 /* BlogQuery.swift in Sources */, + 017008462B35C25C00C80490 /* SiteDomainsViewModel.swift in Sources */, F1C740C026B1D4D2005D0809 /* StoreSandboxSecretScreen.swift in Sources */, 801D94F02919E7D70051993E /* JetpackFullscreenOverlayGeneralViewModel.swift in Sources */, FABB25FF2602FC2C00C8785C /* RegisterDomainDetailsViewModel+RowDefinitions.swift in Sources */, 0CAE8EF72A9E9EE30073EEB9 /* SiteMediaCollectionCellViewModel.swift in Sources */, 98E082A02637545C00537BF1 /* PostService+Likes.swift in Sources */, FABB26002602FC2C00C8785C /* GravatarProfile.swift in Sources */, + F41D98D92B3901F5004EC050 /* DashboardDynamicCardAnalyticsEvent.swift in Sources */, FABB26012602FC2C00C8785C /* ReaderShareAction.swift in Sources */, C7124E4F2638528F00929318 /* JetpackPrologueViewController.swift in Sources */, FE341706275FA157005D5CA7 /* RichCommentContentRenderer.swift in Sources */, @@ -25735,7 +25708,6 @@ F4141EE62AE71AF0000D2AAE /* AllDomainsListViewModel.swift in Sources */, FABB26172602FC2C00C8785C /* MediaExternalExporter.swift in Sources */, FABB26182602FC2C00C8785C /* RegisterDomainDetailsViewModel+SectionDefinitions.swift in Sources */, - FABB26192602FC2C00C8785C /* ActionRow.swift in Sources */, C395FB272822148400AE7C11 /* RemoteSiteDesign+Thumbnail.swift in Sources */, 0CD382842A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift in Sources */, 9815D0B426B49A0600DF7226 /* Comment+CoreDataProperties.swift in Sources */, diff --git a/WordPress/WordPressShareExtension/ShareExtensionEditorViewController.swift b/WordPress/WordPressShareExtension/ShareExtensionEditorViewController.swift index ac11c5e1a405..acfbd2822f3f 100644 --- a/WordPress/WordPressShareExtension/ShareExtensionEditorViewController.swift +++ b/WordPress/WordPressShareExtension/ShareExtensionEditorViewController.swift @@ -1309,7 +1309,6 @@ fileprivate extension ShareExtensionEditorViewController { static let aztecFormatBarDisabledColor = UIColor.neutral(.shade10) static let aztecFormatBarDividerColor = UIColor.divider static let aztecCursorColor = UIColor.primary - static let aztecFormatBarBackgroundColor = UIColor.basicBackground static let aztecFormatBarInactiveColor = UIColor.toolbarInactive static let aztecFormatBarActiveColor = UIColor.primary diff --git a/WordPress/WordPressShareExtension/ShareNoticeConstants.swift b/WordPress/WordPressShareExtension/ShareNoticeConstants.swift index 2c139e66f915..8919cfdb4b9e 100644 --- a/WordPress/WordPressShareExtension/ShareNoticeConstants.swift +++ b/WordPress/WordPressShareExtension/ShareNoticeConstants.swift @@ -26,9 +26,7 @@ struct ShareNoticeText { static let failureDraftTitleDefault = AppLocalizedString("Unable to upload 1 draft post", comment: "Alert displayed to the user when a single post has failed to upload.") static let failureTitleDefault = AppLocalizedString("Unable to upload 1 post", comment: "Alert displayed to the user when a single post has failed to upload.") - static let failureDraftTitleSingular = AppLocalizedString("Unable to upload 1 draft post, 1 file", comment: "Alert displayed to the user when a single post and 1 file has failed to upload.") static let failureTitleSingular = AppLocalizedString("Unable to upload 1 post, 1 file", comment: "Alert displayed to the user when a single post and 1 file has failed to upload.") - static let failureDraftTitlePlural = AppLocalizedString("Unable to upload 1 draft post, %ld files", comment: "Alert displayed to the user when a single post and multiple files have failed to upload.") static let failureTitlePlural = AppLocalizedString("Unable to upload 1 post, %ld files", comment: "Alert displayed to the user when a single post and multiple files have failed to upload.") /// Helper method to provide the formatted version of a success title based on the media item count. diff --git a/WordPress/WordPressShareExtension/String+Extensions.swift b/WordPress/WordPressShareExtension/String+Extensions.swift index 0b03e29ac93c..ce2e8f2273ef 100644 --- a/WordPress/WordPressShareExtension/String+Extensions.swift +++ b/WordPress/WordPressShareExtension/String+Extensions.swift @@ -95,9 +95,4 @@ extension String { let fullOptions = options.union([.anchored]) return range(of: prefix, options: fullOptions) != nil } - - /// Returns true if this String consists of digits - var isNumeric: Bool { - return !isEmpty && rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil - } } diff --git a/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardBloganuaryCardCellTests.swift b/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardBloganuaryCardCellTests.swift index e4fac569b033..0063835fd739 100644 --- a/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardBloganuaryCardCellTests.swift +++ b/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardBloganuaryCardCellTests.swift @@ -49,14 +49,14 @@ final class DashboardBloganuaryCardCellTests: CoreDataTestCase { XCTAssertFalse(result) } - func testCardIsNotShownForEligibleSitesOutsideDecember() throws { + func testCardIsNotShownForEligibleSitesOutsideEligibleMonths() throws { // Given let blog = makeBlog() makeBloggingPromptSettings() try mainContext.save() // When - let result = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInJanuary) + let result = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInFebruary) // Then XCTAssertFalse(result) @@ -69,10 +69,12 @@ final class DashboardBloganuaryCardCellTests: CoreDataTestCase { try mainContext.save() // When - let result = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInDecember) + let resultForDecember = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInDecember) + let resultForJanuary = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInJanuary) // Then - XCTAssertTrue(result) + XCTAssertTrue(resultForDecember) + XCTAssertTrue(resultForJanuary) } func testCardIsShownForEligibleSitesThatHavePromptsDisabled() throws { @@ -113,6 +115,16 @@ private extension DashboardBloganuaryCardCellTests { return Self.calendar.date(from: components) ?? date } + var sometimeInFebruary: Date { + let date = Date() + var components = Self.calendar.dateComponents([.year, .month, .day], from: date) + components.month = 2 + components.year = 2024 + components.day = 10 + + return Self.calendar.date(from: components) ?? date + } + func prepareData() -> (Blog, BloggingPromptSettings) { return (makeBlog(), makeBloggingPromptSettings()) } diff --git a/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardJetpackSocialCardCellTests.swift b/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardJetpackSocialCardCellTests.swift index c53c93cac627..667541e910b8 100644 --- a/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardJetpackSocialCardCellTests.swift +++ b/WordPress/WordPressTest/Blog Dashboard/Cards/DashboardJetpackSocialCardCellTests.swift @@ -80,6 +80,14 @@ class DashboardJetpackSocialCardCellTests: CoreDataTestCase { XCTAssertFalse(shouldShowCard(for: blog)) } + func testCardDisplaysWhenJetpackSiteRunsOutOfShares() throws { + // Given, when + let blog = createTestBlog(hasConnections: true, publicizeInfoState: .exceedingLimit) + + // Then + XCTAssertTrue(shouldShowCard(for: blog)) + } + // MARK: - Card state tests func testInitialCardState() { @@ -115,6 +123,27 @@ class DashboardJetpackSocialCardCellTests: CoreDataTestCase { // Then XCTAssertEqual(subject.displayState, .none) } + + // MARK: Atomic Site Tests + + // In some cases, atomic sites could sometimes get limited sharing from the API. + // We'll need to ignore any sharing limit information if it's a Simple or Atomic site. + // Refs: p9F6qB-dLk-p2#comment-56603 + func testCardOutOfSharesDoesNotDisplayForAtomicSites() throws { + // Given, when + let blog = createTestBlog(isAtomic: true, hasConnections: true, publicizeInfoState: .exceedingLimit) + + // Then + XCTAssertFalse(shouldShowCard(for: blog)) + } + + func testCardNoConnectionDisplaysForAtomicSites() throws { + // Given, when + let blog = createTestBlog(isAtomic: true) + + // Then + XCTAssertTrue(shouldShowCard(for: blog)) + } } // MARK: - Helpers @@ -125,13 +154,22 @@ private extension DashboardJetpackSocialCardCellTests { return DashboardJetpackSocialCardCell.shouldShowCard(for: blog) } + enum PublicizeInfoState { + case none + case belowLimit + case exceedingLimit + } + func createTestBlog(isPublicizeSupported: Bool = true, + isAtomic: Bool = false, hasServices: Bool = true, - hasConnections: Bool = false) -> Blog { + hasConnections: Bool = false, + publicizeInfoState: PublicizeInfoState = .none) -> Blog { var builder = BlogBuilder(mainContext) .withAnAccount() .with(dotComID: 12345) .with(capabilities: [.PublishPosts]) + .with(atomic: isAtomic) if isPublicizeSupported { builder = builder.with(modules: ["publicize"]) @@ -143,9 +181,30 @@ private extension DashboardJetpackSocialCardCellTests { if hasConnections { let connection = PublicizeConnection(context: mainContext) + connection.status = "ok" builder = builder.with(connections: [connection]) } - return builder.build() + + let blog = builder.build() + + switch publicizeInfoState { + case .belowLimit: + let publicizeInfo = PublicizeInfo(context: mainContext) + publicizeInfo.shareLimit = 30 + publicizeInfo.sharesRemaining = 25 + blog.publicizeInfo = publicizeInfo + break + case .exceedingLimit: + let publicizeInfo = PublicizeInfo(context: mainContext) + publicizeInfo.shareLimit = 30 + publicizeInfo.sharesRemaining = 0 + blog.publicizeInfo = publicizeInfo + break + default: + break + } + + return blog } func createPublicizeService() -> PublicizeService { diff --git a/WordPress/WordPressTest/BlogBuilder.swift b/WordPress/WordPressTest/BlogBuilder.swift index fe03ef8fc072..9ea54aaee42f 100644 --- a/WordPress/WordPressTest/BlogBuilder.swift +++ b/WordPress/WordPressTest/BlogBuilder.swift @@ -141,11 +141,12 @@ final class BlogBuilder { return self } - func with(domainCount: Int, of type: DomainType) -> Self { + func with(domainCount: Int, of type: DomainType, domainName: String = "") -> Self { var domains: [ManagedDomain] = [] for _ in 0.. BlogDashboardDynamicCardCoordinator { + let payload = DashboardDynamicCardModel.Payload( + id: id, + remoteFeatureFlag: "default", + title: "Domain Management", + featuredImage: "https://wordpress.com", + url: url, + action: "Read more", + order: .top, + rows: nil + ) + return .init( + viewController: UIViewController(), + model: .init(payload: payload, dotComID: 1), + linkRouter: linkRouter, + analyticsTracker: AnalyticsEventTrackingSpy.self + ) + } +} diff --git a/WordPress/WordPressTest/Dashboard/Dynamic Cards/DashboardDynamicCardAnalyticsEventTests.swift b/WordPress/WordPressTest/Dashboard/Dynamic Cards/DashboardDynamicCardAnalyticsEventTests.swift new file mode 100644 index 000000000000..127345466136 --- /dev/null +++ b/WordPress/WordPressTest/Dashboard/Dynamic Cards/DashboardDynamicCardAnalyticsEventTests.swift @@ -0,0 +1,24 @@ +import Nimble +@testable import WordPress +import XCTest + +final class DashboardDynamicCardAnalyticsEventTests: XCTestCase { + + func testNamesAndProperties() { + // Given + let (id, url) = ("123", "https://wordpress.com") + + // When + let cardShownEvent = DashboardDynamicCardAnalyticsEvent.cardShown(id: id) + let cardTappedEvent = DashboardDynamicCardAnalyticsEvent.cardTapped(id: id, url: url) + let cardCTATappedEvent = DashboardDynamicCardAnalyticsEvent.cardCtaTapped(id: id, url: url) + + // Then + XCTAssertEqual(cardShownEvent.name, "dynamic_dashboard_card_shown") + XCTAssertEqual(cardTappedEvent.name, "dynamic_dashboard_card_tapped") + XCTAssertEqual(cardCTATappedEvent.name, "dynamic_dashboard_card_cta_tapped") + XCTAssertEqual(cardShownEvent.properties, ["id": id]) + XCTAssertEqual(cardTappedEvent.properties, ["id": id, "url": url]) + XCTAssertEqual(cardCTATappedEvent.properties, ["id": id, "url": url]) + } +} diff --git a/WordPress/WordPressTest/Domains/AllDomainsListItemViewModelTests.swift b/WordPress/WordPressTest/Domains/AllDomainsListItemViewModelTests.swift index 723aed4aaddd..aa19c48452aa 100644 --- a/WordPress/WordPressTest/Domains/AllDomainsListItemViewModelTests.swift +++ b/WordPress/WordPressTest/Domains/AllDomainsListItemViewModelTests.swift @@ -32,7 +32,7 @@ final class AllDomainsListItemViewModelTests: XCTestCase { func testMappingWithUnregisteredDomain() throws { self.assert( viewModelFromDomain: try .make(hasRegistration: false), - equalTo: .make(expiryDate: nil) + equalTo: .make(expiryDate: "Never expires") ) } diff --git a/WordPress/WordPressTest/Domains/DomainExpiryDateFormatterTests.swift b/WordPress/WordPressTest/Domains/DomainExpiryDateFormatterTests.swift deleted file mode 100644 index 5ba260e54fab..000000000000 --- a/WordPress/WordPressTest/Domains/DomainExpiryDateFormatterTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// DomainExpiryDateFormatterTests.swift -// WordPressTest -// -// Created by James Frost on 20/10/2021. -// Copyright © 2021 WordPress. All rights reserved. -// - -import XCTest -@testable import WordPress - -class DomainExpiryDateFormatterTests: XCTestCase { - typealias Localized = DomainExpiryDateFormatter.Localized - - func testDomainWithNoExpiry() { - let domain = Domain(domainName: "mycooldomain.com", - isPrimaryDomain: false, - domainType: .registered, - expiryDate: nil) - - let expiryDate = DomainExpiryDateFormatter.expiryDate(for: domain) - XCTAssertEqual(expiryDate, Localized.neverExpires) - } - - func testAutoRenewingDomain() { - let domain = Domain(domainName: "mycooldomain.com", - isPrimaryDomain: false, - domainType: .registered, - autoRenewing: true, - autoRenewalDate: "5th August, 2022", - expirySoon: false, - expired: false, - expiryDate: "4th August, 2022") - - let expiryDate = DomainExpiryDateFormatter.expiryDate(for: domain) - let formattedString = String(format: Localized.renewsOn, domain.autoRenewalDate) - - XCTAssertEqual(expiryDate, formattedString) - } - - func testAutoRenewingDomainWithNoDate() { - // I think this should never happen, but it's good to cover the case anyway. - let domain = Domain(domainName: "mycooldomain.com", - isPrimaryDomain: false, - domainType: .registered, - autoRenewing: true, - autoRenewalDate: "", - expirySoon: false, - expired: false, - expiryDate: "4th August, 2022") - - let expiryDate = DomainExpiryDateFormatter.expiryDate(for: domain) - XCTAssertEqual(expiryDate, Localized.autoRenews) - } - - func testExpiredDomain() { - let domain = Domain(domainName: "mycooldomain.com", - isPrimaryDomain: false, - domainType: .registered, - autoRenewing: false, - autoRenewalDate: "", - expirySoon: false, - expired: true, - expiryDate: "4th August, 2021") - - let expiryDate = DomainExpiryDateFormatter.expiryDate(for: domain) - XCTAssertEqual(expiryDate, Localized.expired) - } - - func testExpiringDomain() { - let domain = Domain(domainName: "mycooldomain.com", - isPrimaryDomain: false, - domainType: .registered, - expired: false, - expiryDate: "4th August, 2022") - - let expiryDate = DomainExpiryDateFormatter.expiryDate(for: domain) - let formattedString = String(format: Localized.expiresOn, domain.expiryDate) - - XCTAssertEqual(expiryDate, formattedString) - } -} diff --git a/WordPress/WordPressTest/Domains/SiteDomainsViewModelTests.swift b/WordPress/WordPressTest/Domains/SiteDomainsViewModelTests.swift new file mode 100644 index 000000000000..3ecb7ac9377d --- /dev/null +++ b/WordPress/WordPressTest/Domains/SiteDomainsViewModelTests.swift @@ -0,0 +1,185 @@ +import XCTest +@testable import WordPress + +final class SiteDomainsViewModelTests: CoreDataTestCase { + private var viewModel: SiteDomainsViewModel! + private var mockDomainsService: MockDomainsService! + + override func setUp() { + super.setUp() + mockDomainsService = MockDomainsService() + viewModel = SiteDomainsViewModel(blog: BlogBuilder(mainContext).build(), domainsService: mockDomainsService) + } + + override func tearDown() { + viewModel = nil + mockDomainsService = nil + super.tearDown() + } + + func testInitialState_isLoading() { + viewModel.refresh() + XCTAssertTrue(viewModel.state == SiteDomainsViewModel.State.loading, "Initial state should be loading") + XCTAssertTrue(mockDomainsService.resolveStatus) + XCTAssertFalse(mockDomainsService.noWPCOM) + } + + func testRefresh_onlyFreeDomain() throws { + let blog = BlogBuilder(mainContext) + .with(blogID: 111) + .with(supportsDomains: true) + .build() + viewModel = SiteDomainsViewModel(blog: blog, domainsService: mockDomainsService) + + mockDomainsService.fetchResult = .success([try .make(domain: "Test", blogId: 111, wpcomDomain: true)]) + viewModel.refresh() + + if case .normal(let sections) = viewModel.state, + case .rows(let rows) = sections[0].content { + XCTAssertEqual(sections[0].title, SiteDomainsViewModel.Strings.freeDomainSectionTitle) + XCTAssertEqual(sections[1].content, .upgradePlan) + XCTAssertEqual(rows[0].viewModel.name, "Test") + } else { + XCTFail("Expected state not loaded") + } + } + + func testRefresh_stagingAndSimpleFreeDomain() throws { + let blog = BlogBuilder(mainContext) + .with(blogID: 111) + .with(supportsDomains: true) + .build() + viewModel = SiteDomainsViewModel(blog: blog, domainsService: mockDomainsService) + + mockDomainsService.fetchResult = .success([ + try .make(domain: "test.wordpress.com", blogId: 111, isWpcomStagingDomain: false, wpcomDomain: true), + try .make(domain: "test.wpcomstaging.com", blogId: 111, isWpcomStagingDomain: true, wpcomDomain: true) + ]) + viewModel.refresh() + + if case .normal(let sections) = viewModel.state, + case .rows(let rows) = sections[0].content { + XCTAssertEqual(sections[0].title, SiteDomainsViewModel.Strings.freeDomainSectionTitle) + XCTAssertEqual(sections[1].content, .upgradePlan) + XCTAssertEqual(rows[0].viewModel.name, "test.wpcomstaging.com") + } else { + XCTFail("Expected state not loaded") + } + } + + func testRefresh_paidDomain() throws { + let blog = BlogBuilder(mainContext) + .with(blogID: 123) + .with(supportsDomains: true) + .with(domainCount: 1, of: .wpCom, domainName: "Test") + .build() + viewModel = SiteDomainsViewModel(blog: blog, domainsService: mockDomainsService) + + mockDomainsService.fetchResult = .success([ + try .make(blogId: 123), + try .make(domain: "Test", blogId: 123, wpcomDomain: true) + ]) + viewModel.refresh() + + if case .normal(let sections) = viewModel.state, + case .rows(let secondSectionRows) = sections[1].content { + XCTAssertEqual(secondSectionRows[0].viewModel.name, DomainsService.AllDomainsListItem.Defaults.domain) + XCTAssertEqual(sections[2].content, .addDomain) + } else { + XCTFail("Expected state not loaded") + } + } + + func testRefresh_paidDomainForOtherBlog() throws { + let blog = BlogBuilder(mainContext) + .with(blogID: 123) + .with(supportsDomains: true) + .build() + viewModel = SiteDomainsViewModel(blog: blog, domainsService: mockDomainsService) + + mockDomainsService.fetchResult = .success([ + try .make(blogId: 1), + try .make(domain: "Test", blogId: 123, wpcomDomain: true) + ]) + viewModel.refresh() + + if case .normal(let sections) = viewModel.state { + XCTAssertEqual(sections[1].content, .upgradePlan) + } else { + XCTFail("Expected state not loaded") + } + } + + func testRefresh_error() { + let blog = BlogBuilder(mainContext) + .with(blogID: 123) + .with(supportsDomains: true) + .build() + viewModel = SiteDomainsViewModel(blog: blog, domainsService: mockDomainsService) + + mockDomainsService.fetchResult = .failure(NSError(domain: NSURLErrorDomain, code: NSURLErrorNotConnectedToInternet)) + viewModel.refresh() + + if case .message(let message) = viewModel.state { + XCTAssertEqual(message.title, DomainsStateViewModel.Strings.offlineEmptyStateTitle) + } else { + XCTFail("Expected state not loaded") + } + } +} + +// MARK: - Helpers + +private class MockDomainsService: NSObject, DomainsServiceAllDomainsFetching { + var fetchResult: Result<[DomainsService.AllDomainsListItem], Error>? + var resolveStatus: Bool = false + var noWPCOM: Bool = false + + func fetchAllDomains(resolveStatus: Bool, noWPCOM: Bool, completion: @escaping (DomainsServiceRemote.AllDomainsEndpointResult) -> Void) { + self.resolveStatus = resolveStatus + self.noWPCOM = noWPCOM + if let result = fetchResult { + completion(result) + } + } +} + +extension SiteDomainsViewModel.State: Equatable { + public static func == (lhs: SiteDomainsViewModel.State, rhs: SiteDomainsViewModel.State) -> Bool { + switch (lhs, rhs) { + case (.normal(let lhsSections), .normal(let rhsSections)): + return lhsSections == rhsSections + case (.loading, .loading): + return true + case (.message(let lhsMessage), .message(let rhsMessage)): + return lhsMessage.title == rhsMessage.title + default: + return false + } + } +} + +extension SiteDomainsViewModel.Section: Equatable { + public static func == (lhs: SiteDomainsViewModel.Section, rhs: SiteDomainsViewModel.Section) -> Bool { + return lhs.id == rhs.id && lhs.title == rhs.title + } +} + +extension SiteDomainsViewModel.Section.SectionKind: Equatable { + public static func == (lhs: SiteDomainsViewModel.Section.SectionKind, rhs: SiteDomainsViewModel.Section.SectionKind) -> Bool { + switch (lhs, rhs) { + case (.rows(let lhsRows), .rows(let rhsRows)): + return lhsRows == rhsRows + case (.addDomain, .addDomain), (.upgradePlan, .upgradePlan): + return true + default: + return false + } + } +} + +extension SiteDomainsViewModel.Section.Row: Equatable { + public static func == (lhs: SiteDomainsViewModel.Section.Row, rhs: SiteDomainsViewModel.Section.Row) -> Bool { + return lhs.id == rhs.id && lhs.viewModel == rhs.viewModel + } +} diff --git a/WordPress/WordPressTest/GutenbergInformativeDialogTests.swift b/WordPress/WordPressTest/GutenbergInformativeDialogTests.swift deleted file mode 100644 index 1c5f2a0cf795..000000000000 --- a/WordPress/WordPressTest/GutenbergInformativeDialogTests.swift +++ /dev/null @@ -1,41 +0,0 @@ -import XCTest -import UIKit -@testable import WordPress - -fileprivate class MockUIViewController: UIViewController, UIViewControllerTransitioningDelegate { - @objc func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { - return FancyAlertPresentationController(presentedViewController: presented, presenting: presenting) - } -} - -class GutenbergInformativeDialogTests: XCTestCase { - private var rootWindow: UIWindow! - private var viewController: MockUIViewController! - - override func setUp() { - viewController = MockUIViewController() - rootWindow = UIWindow(frame: UIScreen.main.bounds) - rootWindow.isHidden = false - rootWindow.rootViewController = viewController - } - - override func tearDown() { - rootWindow.rootViewController = nil - rootWindow.isHidden = true - rootWindow = nil - viewController = nil - } - - func testShowInformativeDialog() { - showInformativeDialog() - XCTAssertNotNil(viewController.presentedViewController as? FancyAlertViewController) - } - - private func showInformativeDialog() { - GutenbergViewController.showInformativeDialog( - on: viewController, - message: GutenbergViewController.InfoDialog.postMessage, - animated: false - ) - } -} diff --git a/WordPress/WordPressTest/MBarRouteTests.swift b/WordPress/WordPressTest/MBarRouteTests.swift index 60fd5f3bcc3d..56224f62f3b7 100644 --- a/WordPress/WordPressTest/MBarRouteTests.swift +++ b/WordPress/WordPressTest/MBarRouteTests.swift @@ -4,6 +4,7 @@ import OHHTTPStubs struct MockRouter: LinkRouter { let matcher: RouteMatcher + var canHandle: Bool = true var completion: ((URL, DeepLinkSource?) -> Void)? init(routes: [Route]) { @@ -11,7 +12,7 @@ struct MockRouter: LinkRouter { } func canHandle(url: URL) -> Bool { - return true + canHandle } func handle(url: URL, shouldTrack track: Bool, source: DeepLinkSource?) { diff --git a/WordPress/WordPressTest/RemoteFeatureFlagStoreMock.swift b/WordPress/WordPressTest/RemoteFeatureFlagStoreMock.swift index 5c2b1933fdd7..2cb63959fd14 100644 --- a/WordPress/WordPressTest/RemoteFeatureFlagStoreMock.swift +++ b/WordPress/WordPressTest/RemoteFeatureFlagStoreMock.swift @@ -11,7 +11,17 @@ class RemoteFeatureFlagStoreMock: RemoteFeatureFlagStore { var removalPhaseSelfHosted = false var removalPhaseStaticScreens = false + var enabledFeatureFlags = Set() + var disabledFeatureFlag = Set() + + // MARK: - Access Remote Feature Flag Value + override func value(for flagKey: String) -> Bool? { + if enabledFeatureFlags.contains(flagKey) { + return true + } else if disabledFeatureFlag.contains(flagKey) { + return false + } switch flagKey { case RemoteFeatureFlag.jetpackFeaturesRemovalPhaseOne.remoteKey: return removalPhaseOne diff --git a/WordPress/WordPressTest/RouteMatcherTests.swift b/WordPress/WordPressTest/RouteMatcherTests.swift index 39228392439d..fd2b93841c9d 100644 --- a/WordPress/WordPressTest/RouteMatcherTests.swift +++ b/WordPress/WordPressTest/RouteMatcherTests.swift @@ -147,4 +147,31 @@ class RouteMatcherTests: XCTestCase { XCTAssertEqual(match.values[MatchedRouteURLComponentKey.source.rawValue], "widget") XCTAssertEqual(match.source, DeepLinkSource.widget) } + + // MARK: - AppBanner + + func testAppBannerRouter() throws { + // GIVEN a banner URL with a redirect in a fragment + let route = "https://apps.wordpress.com/get/?campaign=qr-code-media#%2Fmedia%2F1234567" + + // WHEN + routes = [AppBannerRoute()] + matcher = RouteMatcher(routes: routes) + let matches = matcher.routesMatching(URL(string: route)!) + + // THEN a match if found + let match = try XCTUnwrap(matches.first) + XCTAssert(match.values.count == 2) + XCTAssertNotNil(match.values[MatchedRouteURLComponentKey.url.rawValue]) + XCTAssertEqual(match.values[MatchedRouteURLComponentKey.fragment.rawValue], "%2Fmedia%2F1234567") + + // WHEN invoking a URL + var router = MockRouter(routes: []) + router.completion = { url, source in + // THEN it opens a universal link from a fragement and passes the campaign + XCTAssertEqual(url, URL(string: "https://wordpress.com/media/1234567?campaign=qr-code-media")) + XCTAssertEqual(source, DeepLinkSource.banner(campaign: "qr-code-media")) + } + AppBannerRoute().perform(match.values, router: router) + } } diff --git a/WordPress/WordPressTest/Test Data/Dashboard/dashboard-200-with-multiple-dynamic-cards.json b/WordPress/WordPressTest/Test Data/Dashboard/dashboard-200-with-multiple-dynamic-cards.json new file mode 100644 index 000000000000..51800bfc5d63 --- /dev/null +++ b/WordPress/WordPressTest/Test Data/Dashboard/dashboard-200-with-multiple-dynamic-cards.json @@ -0,0 +1,178 @@ +{ + "dynamic": [ + { + "id": "id_12345", + "title": "Title 12345", + "remote_feature_flag": "feature_flag_12345", + "featured_image": "https://example.com/image12345", + "url": "https://example.com/url12345", + "action": "Action 12345", + "order": "top", + "rows": [ + { + "icon": "https://example.com/icon12345", + "title": "Row Title 1", + "description": "Row Description 1" + }, + { + "icon": "https://example.com/icon67890", + "title": "Row Title 2", + "description": "Row Description 2" + } + ] + }, + { + "id": "id_67890", + "title": "Title 67890", + "remote_feature_flag": "feature_flag_67890", + "featured_image": "https://example.com/image67890", + "url": "https://example.com/url67890", + "action": "Action 67890", + "order": "top", + "rows": [ + { + "icon": "https://example.com/icon54321", + "title": "Row Title 3", + "description": "Row Description 3" + }, + { + "icon": "https://example.com/icon98765", + "title": "Row Title 4", + "description": "Row Description 4" + } + ] + }, + { + "id": "id_13579", + "title": "Title 13579", + "remote_feature_flag": "feature_flag_13579", + "featured_image": "https://example.com/image13579", + "url": "https://example.com/url13579", + "action": "Action 13579", + "order": "bottom", + "rows": [ + { + "icon": "https://example.com/icon24680", + "title": "Row Title 5", + "description": "Row Description 5" + }, + { + "icon": "https://example.com/icon112233", + "title": "Row Title 6", + "description": "Row Description 6" + } + ] + } + ], + "posts": { + "has_published": true, + "draft": [{ + "id": 3246, + "title": "Foo", + "content": "", + "featured_image": null, + "date": "2022-01-13 00:30:56" + }, { + "id": 3120, + "title": "Bar", + "content": "", + "featured_image": null, + "date": "2021-03-05 21:04:44" + }, { + "id": 3109, + "title": "Foobar", + "content": "", + "featured_image": null, + "date": "0000-00-00 00:00:00" + }], + "scheduled": [{ + "id": 3109, + "title": "Foobar", + "content": "", + "featured_image": null, + "date": "0000-00-00 00:00:00" + }] + }, + "todays_stats": { + "views": 0, + "visitors": 0, + "likes": 0, + "comments": 0 + }, + "pages": [ + { + "id": 0, + "title": "string", + "date": "0000-00-00 00:00:00", + "modified": "0000-00-00 00:00:00", + "status": "publish" + }, + { + "id": 1, + "title": "string", + "date": "0000-00-00 00:00:00", + "modified": "0000-00-00 00:00:00", + "status": "publish" + } + ], + "activity": { + "current": { + "orderedItems": [ + { + "summary": "Setting changed", + "content": { + "text": "Encouraged search engines to index the site" + }, + "name": "setting__changed_blog_public", + "actor": { + "type": "Person", + "name": "John Doe", + "external_user_id": 0, + "wpcom_user_id": 1, + "icon": null, + "role": "administrator" + }, + "type": "Announce", + "published": "2023-03-13T16:03:15.230+00:00", + "generator": { + "jetpack_version": 0, + "blog_id": 1 + }, + "is_rewindable": false, + "rewind_id": "", + "gridicon": "cog", + "status": null, + "activity_id": "", + "is_discarded": false + }, + { + "summary": "Setting changed Two", + "content": { + "text": "Encouraged search engines to index the site" + }, + "name": "setting__changed_blog_public", + "actor": { + "type": "Person", + "name": "John Doe", + "external_user_id": 0, + "wpcom_user_id": 1, + "icon": null, + "role": "administrator" + }, + "type": "Announce", + "published": "2023-03-13T16:03:15.230+00:00", + "generator": { + "jetpack_version": 0, + "blog_id": 1 + }, + "is_rewindable": false, + "rewind_id": "", + "gridicon": "cog", + "status": null, + "activity_id": "", + "is_discarded": false + } + ] + } + } +} diff --git a/WordPress/WordPressTest/Test Data/Dashboard/dashboard-200-with-only-one-dynamic-card.json b/WordPress/WordPressTest/Test Data/Dashboard/dashboard-200-with-only-one-dynamic-card.json new file mode 100644 index 000000000000..88e239b41588 --- /dev/null +++ b/WordPress/WordPressTest/Test Data/Dashboard/dashboard-200-with-only-one-dynamic-card.json @@ -0,0 +1,23 @@ +{ + "dynamic": [ + { + "id": "id_12345", + "title": "Title 12345", + "remote_feature_flag": "feature_flag_12345", + "featured_image": "https://example.com/image12345", + "url": "https://example.com/url12345", + "action": "Action 12345", + "order": "top", + "rows": [ + { + "icon": "https://example.com/icon12345", + "title": "Row Title 1" + }, + { + "title": "Row Title 2", + "description": "Row Description 2" + } + ] + } + ] +} diff --git a/WordPress/WordPressTest/ViewRelated/Tools/Time Zone/TimeZoneFormatterTests.swift b/WordPress/WordPressTest/ViewRelated/Tools/Time Zone/TimeZoneFormatterTests.swift index f1207bbca331..dcfeb9e21072 100644 --- a/WordPress/WordPressTest/ViewRelated/Tools/Time Zone/TimeZoneFormatterTests.swift +++ b/WordPress/WordPressTest/ViewRelated/Tools/Time Zone/TimeZoneFormatterTests.swift @@ -38,13 +38,30 @@ class TimeZoneFormatterTests: XCTestCase { // Then TimeAtZone = "6:00 PM" var timeAtZone = formatter.getTimeAtZone(timeZone) - XCTAssertEqual("6:00 PM", timeAtZone) + // As of iOS 17.0, `DateFormatter` uses a narrow non-breaking space (U+202F) in output such as "7:00 PM". + // + // See: + // - https://unicode-explorer.com/c/202F + // - https://href.li/?https://developer.apple.com/forums/thread/731850 + // + // An argument could be made to modify these tests, or the whole component, so that we don't need + // to assert on what `DateFormatter` does for us. In the meantime, let's use the proper Unicode + // character in the expectation. + if #available(iOS 17.0, *) { + XCTAssertEqual("6:00\u{202F}PM", timeAtZone) + } else { + XCTAssertEqual("6:00 PM", timeAtZone) + } // When end of May date formatter = TimeZoneFormatter(currentDate: testEndOfMayDate) // Then TimeAtZone = "7:00 PM" timeAtZone = formatter.getTimeAtZone(timeZone) - XCTAssertEqual("7:00 PM", timeAtZone) + if #available(iOS 17.0, *) { + XCTAssertEqual("7:00\u{202F}PM", timeAtZone) + } else { + XCTAssertEqual("7:00 PM", timeAtZone) + } } } diff --git a/WordPress/WordPressTest/WKCookieJarTests.swift b/WordPress/WordPressTest/WKCookieJarTests.swift index f90ddb5c3300..0822af226ecc 100644 --- a/WordPress/WordPressTest/WKCookieJarTests.swift +++ b/WordPress/WordPressTest/WKCookieJarTests.swift @@ -3,7 +3,7 @@ import WebKit @testable import WordPress class WKCookieJarTests: XCTestCase { - var wkCookieStore = WKWebsiteDataStore.nonPersistent().httpCookieStore + var wkCookieStore: WKHTTPCookieStore! var cookieJar: CookieJar { return wkCookieStore } @@ -20,6 +20,11 @@ class WKCookieJarTests: XCTestCase { } func testGetCookies() { + XCTExpectFailure( + "WKHTTPCookieStore tests fail on Xcode 15+. The calling setCookie on the store does not seem to set the cookie...", + options: .nonStrict() + ) + let expectation = self.expectation(description: "getCookies completion called") cookieJar.getCookies(url: wordPressComLoginURL) { (cookies) in XCTAssertEqual(cookies.count, 1, "Should be one cookie for wordpress.com") @@ -29,6 +34,11 @@ class WKCookieJarTests: XCTestCase { } func testHasCookieMatching() { + XCTExpectFailure( + "WKHTTPCookieStore tests fail on Xcode 15+. The calling setCookie on the store does not seem to set the cookie...", + options: .nonStrict() + ) + let expectation = self.expectation(description: "hasCookie completion called") cookieJar.hasWordPressComAuthCookie(username: "testuser", atomicSite: false) { (matches) in XCTAssertTrue(matches, "Cookies should exist for wordpress.com + testuser") @@ -37,7 +47,13 @@ class WKCookieJarTests: XCTestCase { waitForExpectations(timeout: 5, handler: nil) } + func testHasCookieNotMatching() { + XCTExpectFailure( + "WKHTTPCookieStore tests fail on Xcode 15+. The calling setCookie on the store does not seem to set the cookie...", + options: .nonStrict() + ) + let expectation = self.expectation(description: "hasCookie completion called") cookieJar.hasWordPressComAuthCookie(username: "anotheruser", atomicSite: false) { (matches) in XCTAssertFalse(matches, "Cookies should not exist for wordpress.com + anotheruser") @@ -47,9 +63,14 @@ class WKCookieJarTests: XCTestCase { } func testRemoveCookies() { + XCTExpectFailure( + "WKHTTPCookieStore tests fail on Xcode 15+. The calling setCookie on the store does not seem to set the cookie...", + options: .nonStrict() + ) + let expectation = self.expectation(description: "removeCookies completion called") cookieJar.removeWordPressComCookies { [wkCookieStore] in - wkCookieStore.getAllCookies { cookies in + wkCookieStore!.getAllCookies { cookies in XCTAssertEqual(cookies.count, 1) expectation.fulfill() } diff --git a/WordPress/WordPressTest/WPUserAgentTests.m b/WordPress/WordPressTest/WPUserAgentTests.m index bac4c7379911..7c83f1b140a7 100644 --- a/WordPress/WordPressTest/WPUserAgentTests.m +++ b/WordPress/WordPressTest/WPUserAgentTests.m @@ -40,13 +40,20 @@ - (void)testUseWordPressUserAgentInWebViews XCTAssertEqualObjects([self currentUserAgentFromUserDefaults], defaultUA); XCTAssertEqualObjects([self currentUserAgentFromWebView], defaultUA); + if (@available(iOS 17, *)) { + XCTSkip("In iOS 17, WKWebView no longer reads User Agent from UserDefaults. Skipping while working on an alternative setup."); + } + [WPUserAgent useWordPressUserAgentInWebViews]; - XCTAssertEqualObjects([self currentUserAgentFromUserDefaults], wordPressUA); XCTAssertEqualObjects([self currentUserAgentFromWebView], wordPressUA); } - (void)testThatOriginalRemovalOfWPUseKeyUserAgentDoesntWork { + if (@available(iOS 17, *)) { + XCTSkip("In iOS 17, WKWebView no longer reads User Agent from UserDefaults. Skipping while working on an alternative setup."); + } + // get the original user agent NSString *originalUserAgentInWebView = [self currentUserAgentFromWebView]; NSLog(@"OriginalUserAgent (WebView): %@", originalUserAgentInWebView); @@ -64,7 +71,7 @@ - (void)testThatOriginalRemovalOfWPUseKeyUserAgentDoesntWork { NSString *shouldBeOriginalInWebView = [self currentUserAgentFromWebView]; NSLog(@"shouldBeOriginal (WebView): %@", shouldBeOriginalInWebView); - XCTAssertNotEqualObjects(originalUserAgentInWebView, shouldBeOriginalInWebView, "This agent should be the same"); + XCTAssertNotEqualObjects(originalUserAgentInWebView, shouldBeOriginalInWebView); } - (void)testThatCallingFromAnotherThreadWorks { diff --git a/WordPress/WordPressTest/WordPressUnitTests.xctestplan b/WordPress/WordPressTest/WordPressUnitTests.xctestplan index 345cc6db4580..a42c2f0bf5ae 100644 --- a/WordPress/WordPressTest/WordPressUnitTests.xctestplan +++ b/WordPress/WordPressTest/WordPressUnitTests.xctestplan @@ -21,6 +21,8 @@ "environmentVariableEntries" : [ ], + "language" : "en", + "region" : "US", "targetForVariableExpansion" : { "containerPath" : "container:WordPress.xcodeproj", "identifier" : "1D6058900D05DD3D006BFB54", diff --git a/config/Version.internal.xcconfig b/config/Version.internal.xcconfig index 76892ea47d2b..8016e20c9511 100644 --- a/config/Version.internal.xcconfig +++ b/config/Version.internal.xcconfig @@ -1,2 +1,2 @@ -VERSION_LONG = 23.9.0.20231214 -VERSION_SHORT = 23.9 +VERSION_LONG = 24.0.0.20240108 +VERSION_SHORT = 24.0 diff --git a/config/Version.public.xcconfig b/config/Version.public.xcconfig index 429886feb246..1636e87e6260 100644 --- a/config/Version.public.xcconfig +++ b/config/Version.public.xcconfig @@ -1,2 +1,2 @@ -VERSION_LONG = 23.9.0.2 -VERSION_SHORT = 23.9 +VERSION_LONG = 24.0.0.0 +VERSION_SHORT = 24.0 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 72c9c7924200..f5db36ffd890 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -73,6 +73,9 @@ PUBLIC_VERSION_FILE = Fastlane::Wpmreleasetoolkit::Versioning::IOSVersionFile.ne INTERNAL_BUILD_CODE_CALCULATOR = Fastlane::Wpmreleasetoolkit::Versioning::DateBuildCodeCalculator.new INTERNAL_VERSION_FILE = Fastlane::Wpmreleasetoolkit::Versioning::IOSVersionFile.new(xcconfig_path: INTERNAL_CONFIG_FILE) +BUILDKITE_ORGANIZATION = 'automattic' +BUILDKITE_PIPELINE = 'wordpress-ios' + # Use this instead of getting values from ENV directly. It will throw an error if the requested value is missing def get_required_env(key) UI.user_error!("Environment variable '#{key}' is not set. Have you setup #{USER_ENV_FILE_PATH} correctly?") unless ENV.key?(key) @@ -257,6 +260,7 @@ import 'lanes/codesign.rb' import 'lanes/localization.rb' import 'lanes/release.rb' import 'lanes/screenshots.rb' +import 'lanes/release_management_in_ci.rb' ######################################################################## @@ -301,6 +305,10 @@ def release_branch_name(version: release_version_current) "release/#{version}" end +def editorial_branch_name(version: release_version_current) + "release_notes/#{version}" +end + def ensure_git_branch_is_release_branch # Verify that the current branch is a release branch. Notice that `ensure_git_branch` expects a RegEx parameter ensure_git_branch(branch: '^release/') diff --git a/fastlane/jetpack_metadata/ar-SA/release_notes.txt b/fastlane/jetpack_metadata/ar-SA/release_notes.txt new file mode 100644 index 000000000000..bbce8b261bff --- /dev/null +++ b/fastlane/jetpack_metadata/ar-SA/release_notes.txt @@ -0,0 +1,5 @@ +قمنا بتحديث المحرر التقليدي من خلال أدوات انتقاء الوسائط الجديدة في الصور ووسائط الموقع. لا داعي للقلق، لا يزال بإمكانك رفع الوسائط والفيديوهات والمزيد إلى موقعك. + +عند الحديث عن أنواع الوسائط، أصبح بإمكانك الآن إضافة عوامل تصفية الوسائط إلى شاشة وسائط الموقع. إذا كنت تستخدم iPhone، فستلاحظ وضع نسبة الارتفاع إلى العرض الجديد كذلك. يتوافر كلا الخيارين عند النقر على قائمة العنوان. + +أخيرًا، أصلحنا النافذة المنبثقة للامتثال المعطّلة التي تظهر في أثناء التحقق من الإحصاءات خلال عملية الإعداد. أصلحنا أيضًا عطلاً نادرًا حدث في أثناء تسجيل الخروج. رائع. diff --git a/fastlane/jetpack_metadata/de-DE/release_notes.txt b/fastlane/jetpack_metadata/de-DE/release_notes.txt new file mode 100644 index 000000000000..876e13b36c82 --- /dev/null +++ b/fastlane/jetpack_metadata/de-DE/release_notes.txt @@ -0,0 +1,5 @@ +Der klassische Editor wurde mit neuen Medienauswahlen für Fotos und Website-Medien ersetzt. Keine Sorge: Du kannst weiterhin Bilder, Videos und mehr auf deine Website hochladen. + +Apropos Medientypen: Ab sofort kannst du Medienfilter zum Bildschirm für Website-Medien hinzufügen. iPhone-Benutzern wird auch der neue Modus für das Bildformat auffallen. Beide Optionen sind verfügbar, wenn du auf das Titelmenü tippst. + +Außerdem haben wir das fehlerhafte Compliance-Pop-up korrigiert, das angezeigt wurde, wenn du Statistiken während des Onboarding-Prozesses überprüft hast. Zu guter Letzt wurde ein seltener Fehler während der Abmeldung behoben. Das ist doch super. diff --git a/fastlane/jetpack_metadata/es-ES/release_notes.txt b/fastlane/jetpack_metadata/es-ES/release_notes.txt new file mode 100644 index 000000000000..6239cda1574f --- /dev/null +++ b/fastlane/jetpack_metadata/es-ES/release_notes.txt @@ -0,0 +1,5 @@ +Hemos actualizado el editor clásico con nuevos selectores de medios para fotos y medios del sitio. No te preocupes: puedes seguir subiendo imágenes, vídeos y mucho más a tu sitio. + +Hablando de tipos de medios: ahora puedes añadir filtros de medios a la pantalla de medios del sitio. Si usas un iPhone, también notarás el nuevo modo de relación de aspecto. Ambas opciones están disponibles al tocar el menú de títulos. + +Por último, hemos corregido la ventana emergente de cumplimiento que aparecía mientras comprobabas las estadísticas durante el proceso de incorporación. También hemos corregido un fallo poco frecuente que se producía al salir de la sesión. Perfecto. diff --git a/fastlane/jetpack_metadata/fr-FR/release_notes.txt b/fastlane/jetpack_metadata/fr-FR/release_notes.txt new file mode 100644 index 000000000000..745c60cdc32d --- /dev/null +++ b/fastlane/jetpack_metadata/fr-FR/release_notes.txt @@ -0,0 +1,5 @@ +Nous avons mis à jour l’éditeur classique avec de nouveaux sélecteurs de médias pour les photos et les médias du site. Pas d’inquiétude, vous pouvez toujours mettre en ligne des images, des vidéos, et plus encore sur votre site. + +En parlant de types de médias : vous pouvez désormais ajouter des filtres médias à l’écran Médias du site. Si vous utilisez un iPhone, vous remarquerez également le nouveau mode Proportions. Les deux options sont disponibles lorsque vous appuyez sur le menu Titre. + +Enfin, nous avons réparé la pop-up qui apparaît lorsque vous consultez les statistiques à l’occasion du processus de configuration. Nous avons par ailleurs corrigé un incident rare qui se produisait lors de la déconnexion. Pas mal. diff --git a/fastlane/jetpack_metadata/he/release_notes.txt b/fastlane/jetpack_metadata/he/release_notes.txt new file mode 100644 index 000000000000..81455740c244 --- /dev/null +++ b/fastlane/jetpack_metadata/he/release_notes.txt @@ -0,0 +1,5 @@ +עדכנו את העורך הקלאסי בבוררי מדיה חדשים לתמונות מצולמות ולמדיה באתר. לא לדאוג – אין בעיה להמשיך להעלות לאתר תמונות, סרטונים ועוד. + +ואם כבר מדברים על סוגי מדיה – מעכשיו אפשר להוסיף מסנני מדיה למסך 'מדיה באתר'. משתמשי iPhone יבחינו גם במצב יחס תצוגה חדש. שתי האפשרויות זמינות בהקשה על תפריט שם האתר. + +ולבסוף, תוקנו החלונות הקופצים השבורים של התאמה לדרישות, שצצו תוך כדי בדיקת נתונים סטטיסטיים בתהליך ההצטרפות. תיקנו גם בעיית קריסה נדירה שהייתה מתרחשת בעת התנתקות. נחמד. diff --git a/fastlane/jetpack_metadata/id/release_notes.txt b/fastlane/jetpack_metadata/id/release_notes.txt new file mode 100644 index 000000000000..17aa0e3eaa84 --- /dev/null +++ b/fastlane/jetpack_metadata/id/release_notes.txt @@ -0,0 +1,5 @@ +Kami memperbarui editor klasik dengan pemilih media baru untuk Foto dan Media Situs. Namun, Anda masih dapat mengunggah gambar, video, dan lain-lain ke situs Anda. + +Terkait dengan tipe media, kini Anda dapat menambahkan filter media ke layar Media Situs. Jika menggunakan iPhone, Anda pasti juga akan melihat mode rasio aspek yang baru. Kedua pilihan tersedia jika Anda mengetuk menu judul. + +Kami telah memperbaiki kerusakan pop-up kepatuhan yang muncul ketika Anda memeriksa statistik selama proses penyiapan. Kami juga memperbaiki crash yang jarang terjadi selama logout. Mantap. diff --git a/fastlane/jetpack_metadata/it/release_notes.txt b/fastlane/jetpack_metadata/it/release_notes.txt new file mode 100644 index 000000000000..51e7377b14b1 --- /dev/null +++ b/fastlane/jetpack_metadata/it/release_notes.txt @@ -0,0 +1,5 @@ +Abbiamo aggiornato l'editor classico con nuovi contenuti multimediali per Foto e Media sito. Non preoccuparti, puoi ancora caricare immagini, video e altro sul tuo sito. + +A proposito di tipi di media, ora puoi aggiungere filtri per i contenuti multimediali nella schermata Media sito. Se usi un iPhone, noterai anche la nuova modalità di rapporto d'aspetto. Entrambe le opzioni sono disponibili quando clicchi sul titolo del menu. + +Infine, abbiamo sistemato il pop-up di conformità non funzionante che appare mentre si controllano le statistiche durante il processo di onboarding. Abbiamo anche risolto un raro crash che si verificava durante la disconnessione. Carino. diff --git a/fastlane/jetpack_metadata/ja/release_notes.txt b/fastlane/jetpack_metadata/ja/release_notes.txt new file mode 100644 index 000000000000..23b58dafb7dd --- /dev/null +++ b/fastlane/jetpack_metadata/ja/release_notes.txt @@ -0,0 +1,5 @@ +クラシックエディターを更新し、写真とサイトメディア用の新しいメディアピッカーを追加しました。 引き続き、画像や動画などをサイトにアップロードできます。 + +メディアタイプでは、サイトメディア画面にメディアフィルターを追加できるようになりました。 iPhone を使用している場合、新しい縦横比モードでも表示されます。 タイトルメニューをタップすると、両方のオプションが利用可能になります。 + +ようやく、オンボーディングプロセス中に統計情報を確認しているときに表示される、機能しないコンプライアンスポップアップを修正しました。 ログアウト中にまれに発生するクラッシュも修正されました。 ぜひ活用してください。 diff --git a/fastlane/jetpack_metadata/ko/release_notes.txt b/fastlane/jetpack_metadata/ko/release_notes.txt new file mode 100644 index 000000000000..d123967af569 --- /dev/null +++ b/fastlane/jetpack_metadata/ko/release_notes.txt @@ -0,0 +1,5 @@ +사진 및 사이트 미디어에 대한 새로운 미디어 선택기로 구 버전 편집기를 업데이트했습니다. 걱정하지 마세요. 여전히 사이트에 이미지, 비디오 등을 업로드할 수 있습니다. + +미디어 유형의 경우 이제 사이트 미디어 화면에 미디어 필터를 추가할 수 있습니다. iPhone을 사용하는 경우 새로운 화면 비율 모드도 표시됩니다. 두 가지 옵션 모두 제목 메뉴를 눌러서 이용할 수 있습니다. + +마지막으로, 온보딩 프로세스 도중에 통계를 확인하는 동안 나타나는 손상된 규정 준수 팝업을 해결했습니다. 로그아웃하는 동안 드물게 발생하는 충돌도 해결했습니다. 상쾌합니다. diff --git a/fastlane/jetpack_metadata/nl-NL/release_notes.txt b/fastlane/jetpack_metadata/nl-NL/release_notes.txt new file mode 100644 index 000000000000..1d6040799515 --- /dev/null +++ b/fastlane/jetpack_metadata/nl-NL/release_notes.txt @@ -0,0 +1,5 @@ +We hebben de klassieke editor bijgewerkt met nieuwe mediakiezers voor foto's en sitemedia. Geen zorgen, je kan nog steeds afbeeldingen, video's en meer uploaden naar je site. + +Over mediatypen gesproken: je kan nu mediafilters toevoegen aan het scherm Sitemedia. Als je een iPhone gebruikt, zie je ook de nieuwe modus voor beeldverhouding. Beide opties zijn beschikbaar als je op het titelmenu tikt. + +Tot slot hebben we de defecte pop-up voor naleving gemaakt die verschijnt als je statistieken bekijkt tijdens het onboardingproces. We hebben ook een zeldzame crash bij het uitloggen opgelost. Handig, toch? diff --git a/fastlane/jetpack_metadata/pt-BR/release_notes.txt b/fastlane/jetpack_metadata/pt-BR/release_notes.txt new file mode 100644 index 000000000000..6cee530ca0bd --- /dev/null +++ b/fastlane/jetpack_metadata/pt-BR/release_notes.txt @@ -0,0 +1,5 @@ +Atualizamos o editor clássico com novos seletores de mídia para Mídia do site e Fotos. Não se preocupe, você ainda pode fazer upload de imagens, vídeos e muito mais no seu site. + +E por falar nisso, agora é possível adicionar filtros de mídia à tela Mídia do site. Se você estiver usando um iPhone, notará um novo modo de proporção de tela também. Ambas as opções estão disponíveis ao tocar no menu de título. + +Por fim, corrigimos o pop-up de conformidade corrompido que aparece ao verificar as estatísticas durante o processo de integração. Também corrigimos uma falha rara que ocorria ao fazer logout. Incrível. diff --git a/fastlane/jetpack_metadata/ru/release_notes.txt b/fastlane/jetpack_metadata/ru/release_notes.txt new file mode 100644 index 000000000000..835ef24019ec --- /dev/null +++ b/fastlane/jetpack_metadata/ru/release_notes.txt @@ -0,0 +1,5 @@ +Мы обновили классический редактор, добавив новые инструменты выбора медиафайлов в разделы «Фотографии» и «Медиафайлы сайта». Не беспокойтесь, вы по-прежнему можете загружать на свой сайт изображения, видео и всё остальное. + +Что касается типов медиафайлов, теперь можно добавлять их фильтры на экран «Медиафайлы сайта». Если вы пользуетесь iPhone, вы также заметите новый режим соотношения сторон. Доступ к обеим опциям открывается при нажатии меню заголовка. + +Ну и наконец, мы исправили сбой всплывающего окна с предупреждением о соответствии требованиям, которое появляется, когда вы проверяете статистику в процессе регистрации. Мы также устранили ошибку, которая иногда приводила к аварийному завершению работы при выходе из системы. Отлично. diff --git a/fastlane/jetpack_metadata/sv/release_notes.txt b/fastlane/jetpack_metadata/sv/release_notes.txt new file mode 100644 index 000000000000..9fbb706f3d2a --- /dev/null +++ b/fastlane/jetpack_metadata/sv/release_notes.txt @@ -0,0 +1,5 @@ +Vi har uppdaterat den klassiska redigeraren med nya mediaväljare för foton och webbplatsmedia. Oroa dig inte, du kan fortfarande ladda upp bilder, videoklipp och annat till din webbplats. + +På tal om olika typer av media – du kan nu lägga till mediafilter på skärmen Webbplatsmedia. Om du använder en iPhone kommer du även att märka det nya bildförhållandeläget. Båda alternativen är tillgängliga när du trycker på rubrikmenyn. + +Slutligen har vi åtgärdat det trasiga popup-fönstret rörande efterlevnad som visas när man kollar statistik under onboardingprocessen. Vi har också åtgärdat en sällsynt krasch som kunde uppstå vid utloggning. Perfekt. diff --git a/fastlane/jetpack_metadata/tr/release_notes.txt b/fastlane/jetpack_metadata/tr/release_notes.txt new file mode 100644 index 000000000000..b13e1ca5241b --- /dev/null +++ b/fastlane/jetpack_metadata/tr/release_notes.txt @@ -0,0 +1,5 @@ +Fotoğraflar ve Site Ortamı için yeni ortam seçicilerle klasik düzenleyiciyi güncelledik. Endişelenmeyin; görselleri, videoları ve dahasını sitenize yüklemeye devam edebilirsiniz. + +Ortam türleriyle ilgili konuşuyorken artık Site Ortamı ekranına ortam filtreleri ekleyebileceğinizi de paylaşmak isteriz. iPhone kullanıyorsanız yeni en boy oranı modunu da fark edeceksiniz. Başlık menüsüne dokunduğunuzda iki seçenek de kullanılabilir. + +Son olarak, siz hazırlık süreci sırasında istatistikleri kontrol ederken görünen bozuk uyumluluk açılır penceresini düzelttik. Ayrıca oturum kapatılırken gerçekleşen nadir bir kilitlenme sorununu da düzelttik. Çok hoş. diff --git a/fastlane/jetpack_metadata/zh-Hans/release_notes.txt b/fastlane/jetpack_metadata/zh-Hans/release_notes.txt new file mode 100644 index 000000000000..ffd34b42f11c --- /dev/null +++ b/fastlane/jetpack_metadata/zh-Hans/release_notes.txt @@ -0,0 +1,5 @@ +我们更新了经典编辑器,添加了用于照片和站点媒体的新媒体选择器。 别担心,您仍然可以将图片、视频等内容上传至您的站点。 + +至于媒体类型,您现在可以在“站点媒体”屏幕上添加媒体过滤器。 如果您使用的是 iPhone,您还会注意到新的宽高比模式。 轻点标题菜单,即可在两个选项中进行切换。 + +最后,我们修复了在入门流程中查看统计信息时出现的合规性弹窗不完整的问题。 我们还修复了一个在注销时极少出现的崩溃问题。 很贴心。 diff --git a/fastlane/jetpack_metadata/zh-Hant/release_notes.txt b/fastlane/jetpack_metadata/zh-Hant/release_notes.txt new file mode 100644 index 000000000000..24003017c2a2 --- /dev/null +++ b/fastlane/jetpack_metadata/zh-Hant/release_notes.txt @@ -0,0 +1,5 @@ +我們更新了傳統編輯器,為照片和網站媒體加入全新的媒體選擇器。 別擔心,你仍可以上傳圖片、影片和更多內容到網站上。 + +說到媒體類型,你現在可以在「網站媒體」畫面新增媒體篩選條件。 若你使用 iPhone,也會注意到新加入的畫面比例模式。 點選標題選單時,會出現兩種選項。 + +最後,我們修復了在新手體驗流程中,當你在查看統計資料時,會出現的故障合規快顯視窗。 我們也修正了登出時偶爾會出現的當機問題。 真是太棒了。 diff --git a/fastlane/lanes/build.rb b/fastlane/lanes/build.rb index 626b20178d60..362d25fa7db8 100644 --- a/fastlane/lanes/build.rb +++ b/fastlane/lanes/build.rb @@ -101,7 +101,16 @@ # Only run Jetpack UI tests in parallel. # At the time of writing, we need to explicitly set this value despite using test plans that configure parallelism. - parallel_testing_value = options[:name].include?('Jetpack') + # + # Disabled to test if it makes a difference performance wise in Xcode 15.0.1 in CI as we've seen errors such as this one: + # https://github.com/wordpress-mobile/WordPress-iOS/pull/21921#issuecomment-1820707121 + # + # Also, simply disabling at the test plan level doesn't seem to have effect. + # In this CI run, it can be seen that there are at least two clones (UI tests logs on iPad, lines 1930 to 1934): + # https://buildkite.com/automattic/wpios-macv2-test/builds/14#018bfb60-6b6e-4a31-9acd-d27ee6f053e8/398-1930 + # + # parallel_testing_value = options[:name].include?('Jetpack') + parallel_testing_value = false run_tests( workspace: WORKSPACE_PATH, diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb index 8c1d84cd3dc2..cad385821e8c 100644 --- a/fastlane/lanes/localization.rb +++ b/fastlane/lanes/localization.rb @@ -187,8 +187,33 @@ # desc 'Updates the AppStoreStrings.po file with the latest data' lane :update_appstore_strings do |options| + ensure_git_status_clean + + release_version = release_version_current + + unless Fastlane::Helper::GitHelper.checkout_and_pull(editorial_branch_name(version: release_version)) + UI.user_error!("Editorialization branch for version #{release_version} doesn't exist.") + end + update_wordpress_appstore_strings(options) update_jetpack_appstore_strings(options) + + unless options[:skip_confirm] || UI.confirm('Ready to push changes to remote and continue with the editorialization process?') + UI.message("Aborting as requested. Don't forget to push the changes and create the integration PR manually.") + next + end + + push_to_git_remote(tags: false) + + pr_url = create_release_management_pull_request( + base_branch: compute_release_branch_name(options:, version: release_version), + title: "Merge editorialized release notes in #{release_version}" + ) + + message = <<~MESSAGE + Release notes and metadata localization sources successfully generated. Next, review and merge the [integration PR](#{pr_url}). + MESSAGE + buildkite_annotate(context: 'editorialization-completed', style: 'success', message:) if is_ci end # Updates the `AppStoreStrings.po` file for WordPress, with the latest content from the `release_notes.txt` file and the other text sources diff --git a/fastlane/lanes/release.rb b/fastlane/lanes/release.rb index cc67e5afe26d..50b545a14fa3 100644 --- a/fastlane/lanes/release.rb +++ b/fastlane/lanes/release.rb @@ -104,11 +104,37 @@ push_to_git_remote(tags: false) - set_branch_protection(repository: GITHUB_REPO, branch: release_branch_name) + attempts = 0 + begin + attempts += 1 + set_branch_protection(repository: GITHUB_REPO, branch: release_branch_name) + rescue StandardError => e + if attempts < 2 + sleep_time = 5 + UI.message("Failed to set branch protection on GitHub. Retrying in #{sleep_time} seconds in case it was because the API hadn't noticed the new branch yet.") + sleep(sleep_time) + retry + else + UI.error("Failed to set branch protection on GitHub after #{attempts} attempts") + raise e + end + end + setfrozentag(repository: GITHUB_REPO, milestone: new_version) ios_check_beta_deps(podfile: File.join(PROJECT_ROOT_FOLDER, 'Podfile')) print_release_notes_reminder + + message = <<~MESSAGE + Code freeze started successfully. + + Next steps: + + - Checkout `#{release_branch_name}` branch locally + - Update pods and release notes + - Finalize the code freeze + MESSAGE + buildkite_annotate(context: 'code-freeze-success', style: 'success', message:) if is_ci end # Executes the final steps for the code freeze @@ -125,7 +151,9 @@ # Verify that there's nothing in progress in the working copy ensure_git_status_clean - UI.important("Completing code freeze for: #{release_version_current}") + version = release_version_current + + UI.important("Completing code freeze for: #{version}") skip_user_confirmation = options[:skip_confirm] @@ -139,7 +167,18 @@ end push_to_git_remote(tags: false) + trigger_beta_build + + pr_url = create_release_management_pull_request( + base_branch: DEFAULT_BRANCH, + title: "Merge #{version} code freeze" + ) + + message = <<~MESSAGE + Code freeze completed successfully. Next, review and merge the [integration PR](#{pr_url}). + MESSAGE + buildkite_annotate(context: 'code-freeze-completed', style: 'success', message:) if is_ci end # Creates a new beta by bumping the app version appropriately then triggering a beta build on CI @@ -148,12 +187,18 @@ # desc 'Trigger a new beta build on CI' lane :new_beta_release do |options| - ensure_git_branch_is_release_branch - - # Verify that there's nothing in progress in the working copy ensure_git_status_clean - git_pull + Fastlane::Helper::GitHelper.checkout_and_pull(DEFAULT_BRANCH) + + release_version = release_version_current + + # Check branch + unless Fastlane::Helper::GitHelper.checkout_and_pull(compute_release_branch_name(options:, version: release_version)) + UI.user_error!("Release branch for version #{release_version} doesn't exist.") + end + + ensure_git_branch_is_release_branch # This check is mostly redundant # The `release_version_next` is used as the `new internal release version` value because the external and internal # release versions are always the same. @@ -185,6 +230,45 @@ push_to_git_remote(tags: false) trigger_beta_build + + # Create an intermediate branch to avoid conflicts when integrating the changes + Fastlane::Helper::GitHelper.create_branch("new_beta/#{release_version}") + push_to_git_remote(tags: false) + + pr_url = create_release_management_pull_request( + base_branch: DEFAULT_BRANCH, + title: "Merge changes from #{build_code_current}" + ) + + message = <<~MESSAGE + Beta deployment was successful. Next, review and merge the [integration PR](#{pr_url}). + MESSAGE + buildkite_annotate(context: 'beta-completed', style: 'success', message:) if is_ci + end + + lane :create_editorial_branch do |options| + ensure_git_status_clean + + release_version = release_version_current + + unless Fastlane::Helper::GitHelper.checkout_and_pull(compute_release_branch_name(options:, version: release_version)) + UI.user_error!("Release branch for version #{release_version} doesn't exist.") + end + + ensure_git_branch_is_release_branch # This check is mostly redundant + + git_pull + + Fastlane::Helper::GitHelper.create_branch(editorial_branch_name(version: release_version)) + + unless options[:skip_confirm] || UI.confirm('Ready to push editorial branch to remote?') + UI.message("Aborting as requested. Don't forget to push the branch to the remote manually.") + next + end + + # We need to also set upstream so the branch created in our local tracks the remote counterpart. + # Otherwise, when the next automation step will run and try to push changes made on that branch, it will fail. + push_to_git_remote(tags: false, set_upstream: true) end # Sets the stage to start working on a hotfix @@ -290,35 +374,44 @@ # Verify that there's nothing in progress in the working copy ensure_git_status_clean + skip_user_confirmation = options[:skip_confirm] + UI.important("Finalizing release: #{release_version_current}") - UI.user_error!('Aborted by user request') unless options[:skip_confirm] || UI.confirm('Do you want to continue?') + UI.user_error!('Aborted by user request') unless skip_user_confirmation || UI.confirm('Do you want to continue?') git_pull - check_all_translations(interactive: true) + check_all_translations(interactive: skip_user_confirmation == false) download_localized_strings_and_metadata(options) - lint_localizations + lint_localizations(allow_retry: skip_user_confirmation == false) bump_build_codes - # Wrap up + unless skip_user_confirmation || UI.confirm('Ready to push changes to remote and trigger the release build?') + UI.message("Terminating as requested. Don't forget to run the remainder of this automation manually.") + next + end + + push_to_git_remote(tags: false) + version = release_version_current remove_branch_protection(repository: GITHUB_REPO, branch: release_branch_name) setfrozentag(repository: GITHUB_REPO, milestone: version, freeze: false) create_new_milestone(repository: GITHUB_REPO) close_milestone(repository: GITHUB_REPO, milestone: version) - if prompt_for_confirmation( - message: 'Ready to push changes to remote and trigger the release build?', - bypass: ENV.fetch('RELEASE_TOOLKIT_SKIP_PUSH_CONFIRM', false) + trigger_release_build + + pr_url = create_release_management_pull_request( + base_branch: DEFAULT_BRANCH, + title: "Merge #{version} release finalization" ) - push_to_git_remote(tags: false) - trigger_release_build - else - UI.message("Terminating as requested. Don't forget to run the remainder of this automation manually.") - next - end + + message = <<~MESSAGE + Release successfully finalized. Next, review and merge the [integration PR](#{pr_url}). + MESSAGE + buildkite_annotate(context: 'finalization-completed', style: 'success', message:) if is_ci end # Triggers a beta build on CI @@ -351,8 +444,8 @@ # def trigger_buildkite_release_build(branch:, beta:) buildkite_trigger_build( - buildkite_organization: 'automattic', - buildkite_pipeline: 'wordpress-ios', + buildkite_organization: BUILDKITE_ORGANIZATION, + buildkite_pipeline: BUILDKITE_PIPELINE, branch:, environment: { BETA_RELEASE: beta }, pipeline_file: 'release-builds.yml', @@ -454,3 +547,18 @@ def commit_version_and_build_files allow_nothing_to_commit: false ) end + +def create_release_management_pull_request(base_branch:, title:) + token = ENV.fetch('GITHUB_TOKEN', nil) + + UI.user_error!('Please export a GitHub API token in the environment as GITHUB_TOKEN') if token.nil? + + create_pull_request( + api_token: token, + repo: 'wordpress-mobile/WordPress-iOS', + title:, + head: Fastlane::Helper::GitHelper.current_git_branch, + base: base_branch, + labels: 'Releases' + ) +end diff --git a/fastlane/lanes/release_management_in_ci.rb b/fastlane/lanes/release_management_in_ci.rb new file mode 100644 index 000000000000..af94dd97b9d9 --- /dev/null +++ b/fastlane/lanes/release_management_in_ci.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +PIPELINES_ROOT = 'release-pipelines' + +platform :ios do + lane :trigger_code_freeze_in_ci do + buildkite_trigger_build( + buildkite_organization: BUILDKITE_ORGANIZATION, + buildkite_pipeline: BUILDKITE_PIPELINE, + branch: DEFAULT_BRANCH, + pipeline_file: File.join(PIPELINES_ROOT, 'code-freeze.yml'), + message: 'Code Freeze' + ) + end + + lane :trigger_complete_code_freeze_in_ci do |options| + release_version_key = :release_version + release_version = options[release_version_key] + + UI.user_error!("You must specify a release version by calling this lane with a #{release_version_key} parameter") unless release_version + + buildkite_trigger_build( + buildkite_organization: BUILDKITE_ORGANIZATION, + buildkite_pipeline: BUILDKITE_PIPELINE, + branch: compute_release_branch_name(options:, version: release_version), + pipeline_file: File.join(PIPELINES_ROOT, 'complete-code-freeze.yml'), + message: "Complete Code Freeze for #{release_version}", + environment: { RELEASE_VERSION: release_version } + ) + end + + lane :trigger_new_beta_release_in_ci do |options| + release_version_key = :release_version + release_version = options[release_version_key] + + UI.user_error!("You must specify a release version by calling this lane with a #{release_version_key} parameter") unless release_version + + buildkite_trigger_build( + buildkite_organization: BUILDKITE_ORGANIZATION, + buildkite_pipeline: BUILDKITE_PIPELINE, + branch: compute_release_branch_name(options:, version: release_version), + pipeline_file: File.join(PIPELINES_ROOT, 'new-beta-release.yml'), + message: "New Beta Release for #{release_version}", + environment: { RELEASE_VERSION: release_version } + ) + end + + lane :trigger_update_app_store_strings_in_ci do |options| + release_version_key = :release_version + release_version = options[release_version_key] + + UI.user_error!("You must specify a release version by calling this lane with a #{release_version_key} parameter") unless release_version + + buildkite_trigger_build( + buildkite_organization: BUILDKITE_ORGANIZATION, + buildkite_pipeline: BUILDKITE_PIPELINE, + branch: editorial_branch_name(version: release_version), + pipeline_file: File.join(PIPELINES_ROOT, 'update-app-store-strings.yml'), + message: "Update Editorialized Release Notes and App Store Metadata for #{release_version}" + ) + end + + lane :trigger_finalize_release_in_ci do |options| + release_version_key = :release_version + release_version = options[release_version_key] + + UI.user_error!("You must specify a release version by calling this lane with a #{release_version_key} parameter") unless release_version + + buildkite_trigger_build( + buildkite_organization: BUILDKITE_ORGANIZATION, + buildkite_pipeline: BUILDKITE_PIPELINE, + branch: compute_release_branch_name(options:, version: release_version), + pipeline_file: File.join(PIPELINES_ROOT, 'finalize-release.yml'), + message: "Finalize Release #{release_version}", + environment: { RELEASE_VERSION: release_version } + ) + end +end diff --git a/fastlane/metadata/ar-SA/release_notes.txt b/fastlane/metadata/ar-SA/release_notes.txt new file mode 100644 index 000000000000..0de2faa4c735 --- /dev/null +++ b/fastlane/metadata/ar-SA/release_notes.txt @@ -0,0 +1,5 @@ +قمنا بتحديث المحرر التقليدي من خلال أدوات انتقاء الوسائط الجديد في الصور ووسائط الموقع. لا داعي للقلق، لا يزال بإمكانك رفع الوسائط والفيديوهات والمزيد إلى موقعك. + +عند الحديث عن أنواع الوسائط، أصبح بإمكانك الآن إضافة عوامل تصفية الوسائط إلى شاشة وسائط الموقع. إذا كنت تستخدم iPhone، فستلاحظ وضع نسبة الارتفاع إلى العرض الجديد كذلك. يتوافر كلا الخيارين عند النقر على قائمة العنوان. + +أخيرًا، أصلحنا النافذة المنبثقة للامتثال المعطّلة التي تظهر في أثناء التحقق من الإحصاءات خلال عملية الإعداد. رائع. diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt new file mode 100644 index 000000000000..e69829d872ee --- /dev/null +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -0,0 +1,5 @@ +Der klassische Editor wurde mit neuen Medienauswahlen für Fotos und Website-Medien ersetzt. Keine Sorge: Du kannst weiterhin Bilder, Videos und mehr auf deine Website hochladen. + +Apropos Medientypen: Ab sofort kannst du Medienfilter zum Bildschirm für Website-Medien hinzufügen. iPhone-Benutzern wird auch der neue Modus für das Bildformat auffallen. Beide Optionen sind verfügbar, wenn du auf das Titelmenü tippst. + +Außerdem haben wir das fehlerhafte Compliance-Pop-up korrigiert, das angezeigt wurde, wenn du Statistiken während des Onboarding-Prozesses überprüft hast. Das ist doch super. diff --git a/fastlane/metadata/en-AU/description.txt b/fastlane/metadata/en-AU/description.txt new file mode 100644 index 000000000000..60819d65fc8e --- /dev/null +++ b/fastlane/metadata/en-AU/description.txt @@ -0,0 +1,11 @@ +Manage or create your WordPress blog or website right from your iOS device: create and edit posts and pages, upload your favourite photos and videos, view stats and reply to comments. + +With WordPress for iOS, you have the power to publish in the palm of your hand. Draft a spontaneous haiku from the couch. Snap and post a photo on your lunch break. Respond to your latest comments, or check your stats to see what new countries today’s visitors are coming from. + +WordPress for iOS is an Open Source project, which means you too can contribute to its development. Learn more at https://apps.wordpress.com/contribute/. + +WordPress for iOS supports WordPress.com and self-hosted WordPress.org sites running WordPress 4.0 or higher. + +Need help with the app? Visit the forums at https://wordpress.org/support/forum/mobile/ or tweet us @WordPressiOS. + +View the Privacy Notice for California Users at https://automattic.com/privacy/#california-consumer-privacy-act-ccpa. diff --git a/fastlane/metadata/en-AU/release_notes.txt b/fastlane/metadata/en-AU/release_notes.txt new file mode 100644 index 000000000000..50f66336896a --- /dev/null +++ b/fastlane/metadata/en-AU/release_notes.txt @@ -0,0 +1,5 @@ +We updated the classic editor with new media pickers for Photos and Site Media. Don’t worry, you can still upload images, videos, and more to your site. + +Speaking of media types—you can now add media filters to the Site Media screen. If you’re using an iPhone, you’ll notice the new aspect ratio mode, too. Both options are available when you tap the title menu. + +Finally, we fixed the broken compliance pop-up that appears while you’re checking stats during the onboarding process. Sweet. diff --git a/fastlane/metadata/en-GB/release_notes.txt b/fastlane/metadata/en-GB/release_notes.txt new file mode 100644 index 000000000000..fcebbfb1a1a7 --- /dev/null +++ b/fastlane/metadata/en-GB/release_notes.txt @@ -0,0 +1,5 @@ +We updated the Classic Editor with new media pickers for Photos and Site Media. Don’t worry, you can still upload images, videos, and more to your site. + +Speaking of media types – you can now add media filters to the Site Media screen. If you’re using an iPhone, you’ll notice the new aspect ratio mode, too. Both options are available when you tap the title menu. + +Finally, we fixed the broken compliance pop-up that appears while you’re checking stats during the onboarding process. Sweet. diff --git a/fastlane/metadata/es-ES/release_notes.txt b/fastlane/metadata/es-ES/release_notes.txt new file mode 100644 index 000000000000..1cf99dfd4cb9 --- /dev/null +++ b/fastlane/metadata/es-ES/release_notes.txt @@ -0,0 +1,5 @@ +Hemos actualizado el editor clásico con nuevos selectores de medios para fotos y medios del sitio. No te preocupes, puedes seguir subiendo imágenes, vídeos y mucho más a tu sitio. + +Hablando de tipos de medios—ahora puedes añadir filtros de medios a la pantalla de medios del sitio. Si utilizas un iPhone, también notarás el nuevo modo de relación de aspecto. Ambas opciones están disponibles cuando tocas el menú del título. + +Por último, hemos arreglado la ventana emergente de cumplimiento que aparece mientras compruebas las estadísticas durante el proceso de puesta en marcha. ¡Genial! diff --git a/fastlane/metadata/fr-FR/release_notes.txt b/fastlane/metadata/fr-FR/release_notes.txt new file mode 100644 index 000000000000..1ec1eeeefecb --- /dev/null +++ b/fastlane/metadata/fr-FR/release_notes.txt @@ -0,0 +1,5 @@ +Nous avons mis à jour l’éditeur classique avec de nouveaux sélecteurs de médias pour les photos et les médias du site. Pas d’inquiétude, vous pouvez toujours mettre en ligne des images, des vidéos, et plus encore sur votre site. + +En parlant de types de médias : vous pouvez désormais ajouter des filtres médias à l’écran Médias du site. Si vous utilisez un iPhone, vous remarquerez également le nouveau mode Proportions. Les deux options sont disponibles lorsque vous appuyez sur le menu Titre. + +Enfin, nous avons réparé la pop-up qui apparaît lorsque vous consultez les statistiques à l’occasion du processus de configuration. Pas mal. diff --git a/fastlane/metadata/he/release_notes.txt b/fastlane/metadata/he/release_notes.txt new file mode 100644 index 000000000000..234f1b5315d1 --- /dev/null +++ b/fastlane/metadata/he/release_notes.txt @@ -0,0 +1,5 @@ +עדכנו את העורך הקלאסי בבוררי מדיה חדשים לתמונות מצולמות ולמדיה באתר. לא לדאוג – אין בעיה להמשיך להעלות לאתר תמונות, סרטונים ועוד. + +ואם כבר מדברים על סוגי מדיה – מעכשיו אפשר להוסיף מסנני מדיה למסך 'מדיה באתר'. משתמשי iPhone יבחינו גם במצב יחס תצוגה חדש. שתי האפשרויות זמינות בהקשה על תפריט שם האתר. + +ולבסוף, תוקנו החלונות הקופצים השבורים של התאמה לדרישות, שצצו תוך כדי בדיקת נתונים סטטיסטיים בתהליך ההצטרפות. נחמד. diff --git a/fastlane/metadata/id/release_notes.txt b/fastlane/metadata/id/release_notes.txt new file mode 100644 index 000000000000..a37afdb01a63 --- /dev/null +++ b/fastlane/metadata/id/release_notes.txt @@ -0,0 +1,5 @@ +Kami memperbarui editor klasik dengan pemilih media baru untuk Foto dan Media Situs. Namun, Anda masih dapat mengunggah gambar, video, dan lain-lain ke situs Anda. + +Terkait dengan tipe media, kini Anda dapat menambahkan filter media ke layar Media Situs. Jika menggunakan iPhone, Anda pasti juga akan melihat mode rasio aspek yang baru. Kedua pilihan tersedia jika Anda mengetuk menu judul. + +Terakhir, kami memperbaiki kerusakan pop-up kepatuhan yang muncul ketika Anda memeriksa statistik selama proses penyiapan. Mantap. diff --git a/fastlane/metadata/it/release_notes.txt b/fastlane/metadata/it/release_notes.txt new file mode 100644 index 000000000000..4cdd5e40584e --- /dev/null +++ b/fastlane/metadata/it/release_notes.txt @@ -0,0 +1,5 @@ +Abbiamo aggiornato l'editor classico con nuovi contenuti multimediali per Foto e Media sito. Non preoccuparti, puoi ancora caricare immagini, video e altro sul tuo sito. + +A proposito di tipi di media, ora puoi aggiungere filtri per i contenuti multimediali nella schermata Media sito. Se usi un iPhone, noterai anche la nuova modalità di rapporto d'aspetto. Entrambe le opzioni sono disponibili quando clicchi sul titolo del menu. + +Infine, abbiamo sistemato il pop-up di conformità non funzionante che appare mentre si controllano le statistiche durante il processo di onboarding. Carino. diff --git a/fastlane/metadata/ko/release_notes.txt b/fastlane/metadata/ko/release_notes.txt new file mode 100644 index 000000000000..d70ab53a6fc7 --- /dev/null +++ b/fastlane/metadata/ko/release_notes.txt @@ -0,0 +1,5 @@ +사진 및 사이트 미디어에 대한 새로운 미디어 선택기로 구 버전 편집기를 업데이트했습니다. 걱정하지 마세요. 여전히 사이트에 이미지, 비디오 등을 업로드할 수 있습니다. + +미디어 유형의 경우 이제 사이트 미디어 화면에 미디어 필터를 추가할 수 있습니다. iPhone을 사용하는 경우 새로운 화면 비율 모드도 표시됩니다. 두 가지 옵션 모두 제목 메뉴를 눌러서 이용할 수 있습니다. + +마지막으로, 온보딩 프로세스 도중에 통계를 확인하는 동안 나타나는 손상된 규정 준수 팝업을 해결했습니다. 상쾌합니다. diff --git a/fastlane/metadata/nl-NL/release_notes.txt b/fastlane/metadata/nl-NL/release_notes.txt new file mode 100644 index 000000000000..95f6dd24cb65 --- /dev/null +++ b/fastlane/metadata/nl-NL/release_notes.txt @@ -0,0 +1,5 @@ +We hebben de klassieke editor bijgewerkt met nieuwe mediakiezers voor foto's en sitemedia. Geen zorgen, je kan nog steeds afbeeldingen, video's en meer uploaden naar je site. + +Over mediatypen gesproken: je kan nu mediafilters toevoegen aan het scherm Sitemedia. Als je een iPhone gebruikt, zie je ook de nieuwe modus voor beeldverhouding. Beide opties zijn beschikbaar als je op het titelmenu tikt. + +Tot slot hebben we de defecte pop-up voor naleving gemaakt die verschijnt als je statistieken bekijkt tijdens het onboardingproces. Handig, toch? diff --git a/fastlane/metadata/ru/release_notes.txt b/fastlane/metadata/ru/release_notes.txt new file mode 100644 index 000000000000..1cb58413a4ad --- /dev/null +++ b/fastlane/metadata/ru/release_notes.txt @@ -0,0 +1,5 @@ +Мы обновили классический редактор, добавив новые инструменты выбора медиафайлов в разделы «Фотографии» и «Медиафайлы сайта». Не беспокойтесь, вы по-прежнему можете загружать на свой сайт изображения, видео и всё остальное. + +Что касается типов медиафайлов, теперь можно добавлять их фильтры на экран «Медиафайлы сайта». Если вы пользуетесь iPhone, вы также заметите новый режим соотношения сторон. Доступ к обеим опциям открывается при нажатии меню заголовка. + +Ну и наконец, мы исправили сбой всплывающего окна с предупреждением о соответствии требованиям, которое появляется, когда вы проверяете статистику в процессе регистрации. Отлично. diff --git a/fastlane/metadata/sv/release_notes.txt b/fastlane/metadata/sv/release_notes.txt new file mode 100644 index 000000000000..8fe0309fd6e2 --- /dev/null +++ b/fastlane/metadata/sv/release_notes.txt @@ -0,0 +1,5 @@ +Vi har uppdaterat den klassiska redigeraren med nya mediaväljare för foton och webbplatsmedia. Oroa dig inte, du kan fortfarande ladda upp bilder, videoklipp och annat till din webbplats. + +På tal om olika typer av media – du kan nu lägga till mediafilter på skärmen Webbplatsmedia. Om du använder en iPhone kommer du även att märka det nya bildförhållandeläget. Båda alternativen är tillgängliga när du trycker på rubrikmenyn. + +Slutligen har vi åtgärdat det trasiga popup-fönstret rörande efterlevnad som visas när man kollar statistik under onboardingprocessen. Perfekt. diff --git a/fastlane/metadata/tr/release_notes.txt b/fastlane/metadata/tr/release_notes.txt new file mode 100644 index 000000000000..2b3b1733658f --- /dev/null +++ b/fastlane/metadata/tr/release_notes.txt @@ -0,0 +1,5 @@ +Klasik düzenleyiciyi, Fotoğraflar ve Site ortamı için yeni medya seçicilerle güncelledik. Endişelenmeyin; yine de sitenize resim, video ve daha fazlasını yükleyebilirsiniz. + +Medya türlerinden bahsetmişken, artık Site ortamı ekranına medya filtreleri ekleyebilirsiniz. iPhone kullanıyorsanız yeni en-boy oranı modunu da fark edeceksiniz. Başlık menüsüne dokunduğunuzda her iki seçenek de kullanılabilir. + +Son olarak, katılım süreci sırasında istatistikleri kontrol ederken görünen bozuk uyumluluk açılır penceresini düzelttik. Tatlı. diff --git a/fastlane/metadata/zh-Hans/release_notes.txt b/fastlane/metadata/zh-Hans/release_notes.txt new file mode 100644 index 000000000000..cdcfcd2ae1b1 --- /dev/null +++ b/fastlane/metadata/zh-Hans/release_notes.txt @@ -0,0 +1,5 @@ +我们更新了经典编辑器,添加了用于照片和站点媒体的新媒体选择器。 别担心,您仍然可以将图片、视频等内容上传至您的站点。 + +至于媒体类型,您现在可以在“站点媒体”屏幕上添加媒体过滤器。 如果您使用的是 iPhone,您还会注意到新的宽高比模式。 轻点标题菜单,即可在两个选项中进行切换。 + +最后,我们修复了在入门流程中查看统计信息时出现的合规性弹窗不完整的问题。 很贴心。 diff --git a/fastlane/metadata/zh-Hant/release_notes.txt b/fastlane/metadata/zh-Hant/release_notes.txt new file mode 100644 index 000000000000..500df5b1ae76 --- /dev/null +++ b/fastlane/metadata/zh-Hant/release_notes.txt @@ -0,0 +1,5 @@ +我們更新了傳統編輯器,為照片和網站媒體加入全新的媒體選擇器。 別擔心,你仍可以上傳圖片、影片和更多內容到網站上。 + +說到媒體類型,你現在可以在「網站媒體」畫面新增媒體篩選條件。 若你使用 iPhone,也會注意到新加入的畫面比例模式。 點選標題選單時,會出現兩種選項。 + +最後,我們修復了在新手體驗流程中,當你在查看統計資料時,會出現的故障合規快顯視窗。 真是太棒了。