diff --git a/.circleci/config.yml b/.circleci/config.yml index 42567f24450a..85dd6bac6616 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,19 +20,6 @@ commands: echo $PATH fi - save-xcresult: - steps: - - run: - name: Zip xcresult - command: | - mkdir testresults - zip -r testresults/xcresult.zip test-without-building.xcresult - when: on_fail # Zips the .xcresult file when tests fail, so it can be saved - - store_artifacts: - name: Save xcresult - path: testresults - destination: logs - jobs: Build Tests: executor: @@ -51,23 +38,29 @@ jobs: paths: - DerivedData/Build/Products - Pods/WordPressMocks + - vendor/bundle Unit Tests: executor: name: ios/default xcode-version: "11.2.1" steps: - fix-path + - git/shallow-checkout - ios/boot-simulator: xcode-version: "11.2.1" device: iPhone 11 - attach_workspace: at: ./ + - run: + name: Prepare Bundle + command: bundle --path vendor/bundle - ios/wait-for-simulator - - ios/xcodebuild: - command: test-without-building - arguments: -xctestrun DerivedData/Build/Products/WordPress_WordPressUnitTests_iphonesimulator13.2-x86_64.xctestrun -destination "platform=iOS Simulator,id=$SIMULATOR_UDID" -resultBundlePath test-without-building.xcresult - - ios/save-xcodebuild-artifacts - - save-xcresult + - run: + name: Run Unit Tests + working_directory: Scripts + command: bundle exec fastlane test_without_building xctestrun:DerivedData/Build/Products/WordPress_WordPressUnitTests_iphonesimulator13.2-x86_64.xctestrun destination:"platform=iOS Simulator,id=$SIMULATOR_UDID" try_count:3 + - ios/save-xcodebuild-artifacts: + result-bundle-path: build/results UI Tests: parameters: device: @@ -81,21 +74,26 @@ jobs: xcode-version: "11.2.1" steps: - fix-path + - git/shallow-checkout - ios/boot-simulator: xcode-version: "11.2.1" device: << parameters.device >> - attach_workspace: at: ./ + - run: + name: Prepare Bundle + command: bundle --path vendor/bundle - run: name: Run mocks command: ./Pods/WordPressMocks/scripts/start.sh 8282 background: true - ios/wait-for-simulator - - ios/xcodebuild: - command: test-without-building - arguments: -xctestrun DerivedData/Build/Products/WordPress_WordPressUITests_iphonesimulator13.2-x86_64.xctestrun -destination "platform=iOS Simulator,id=$SIMULATOR_UDID" -resultBundlePath test-without-building.xcresult - - ios/save-xcodebuild-artifacts - - save-xcresult + - run: + name: Run UI Tests + working_directory: Scripts + command: bundle exec fastlane test_without_building xctestrun:DerivedData/Build/Products/WordPress_WordPressUITests_iphonesimulator13.2-x86_64.xctestrun destination:"platform=iOS Simulator,id=$SIMULATOR_UDID" try_count:3 + - ios/save-xcodebuild-artifacts: + result-bundle-path: build/results - when: condition: << parameters.post-to-slack >> steps: diff --git a/Gemfile.lock b/Gemfile.lock index 0ca1c7c09bb2..9ec605f2c618 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: https://github.com/wordpress-mobile/release-toolkit - revision: d00351ee4e04d8a5f3305116dab017b2c389b1cf - tag: 0.9.1 + revision: e426345db3afc839ee463ec1ce8432f584880957 + tag: 0.9.2 specs: - fastlane-plugin-wpmreleasetoolkit (0.9.1) + fastlane-plugin-wpmreleasetoolkit (0.9.2) activesupport (~> 4) chroma (= 0.2.0) diffy (~> 3.3) @@ -31,15 +31,15 @@ GEM httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) - aws-eventstream (1.0.3) - aws-sdk (2.11.463) - aws-sdk-resources (= 2.11.463) - aws-sdk-core (2.11.463) + aws-eventstream (1.1.0) + aws-sdk (2.11.490) + aws-sdk-resources (= 2.11.490) + aws-sdk-core (2.11.490) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.11.463) - aws-sdk-core (= 2.11.463) - aws-sigv4 (1.1.1) + aws-sdk-resources (2.11.490) + aws-sdk-core (= 2.11.490) + aws-sigv4 (1.1.2) aws-eventstream (~> 1.0, >= 1.0.2) babosa (1.0.3) chroma (0.2.0) @@ -70,7 +70,7 @@ GEM fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.2.2) + cocoapods-downloader (1.3.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) @@ -78,14 +78,15 @@ GEM cocoapods-trunk (1.4.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored (1.2) colored2 (3.1.2) + colorize (0.8.1) commander-fastlane (4.4.6) highline (~> 1.7.2) - commonmarker (0.20.1) + commonmarker (0.21.0) ruby-enum (~> 0.5) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.6) declarative (0.0.10) declarative-option (0.1.0) diffy (3.3.0) @@ -95,7 +96,7 @@ GEM dotenv (2.7.5) emoji_regex (1.0.1) escape (0.0.4) - excon (0.72.0) + excon (0.73.0) faraday (0.17.3) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -144,6 +145,12 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-appcenter (1.7.1) fastlane-plugin-sentry (1.6.0) + fastlane-plugin-test_center (3.10.2) + colorize + json + plist + xcodeproj + xctest_list (>= 1.1.8) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -163,20 +170,20 @@ GEM google-cloud-env (1.3.1) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.0) - google-cloud-storage (1.25.1) + google-cloud-storage (1.26.0) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.11.0) + googleauth (0.12.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.12) + signet (~> 0.14) highline (1.7.10) http-cookie (1.0.3) domain_name (~> 0.5) @@ -193,7 +200,7 @@ GEM mini_magick (4.10.1) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.12.2) + minitest (5.14.0) molinillo (0.6.6) multi_json (1.14.1) multi_xml (0.6.0) @@ -204,12 +211,13 @@ GEM netrc (0.11.0) nokogiri (1.10.9) mini_portile2 (~> 2.4.0) - octokit (4.14.0) + octokit (4.18.0) + faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) - oj (3.10.5) - optimist (3.0.0) + oj (3.10.6) + optimist (3.0.1) options (2.3.2) - os (1.0.1) + os (1.1.0) parallel (1.19.1) plist (3.5.0) progress_bar (1.3.1) @@ -227,7 +235,7 @@ GEM retriable (3.1.2) rmagick (3.2.0) rouge (2.0.7) - ruby-enum (0.7.2) + ruby-enum (0.8.0) i18n ruby-macho (1.4.0) rubyzip (1.3.0) @@ -235,7 +243,7 @@ GEM addressable (>= 2.3.5) faraday (> 0.8, < 2.0) security (0.1.3) - signet (0.13.0) + signet (0.14.0) addressable (~> 2.3) faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) @@ -252,15 +260,15 @@ GEM tty-screen (0.7.1) tty-spinner (0.9.3) tty-cursor (~> 0.7) - tzinfo (1.2.5) + tzinfo (1.2.7) thread_safe (~> 0.1) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.6) + unf_ext (0.0.7.7) unicode-display_width (1.7.0) word_wrap (1.0.0) - xcodeproj (1.14.0) + xcodeproj (1.16.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -270,6 +278,7 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.0) xcpretty (~> 0.2, >= 0.0.7) + xctest_list (1.1.8) PLATFORMS ruby @@ -281,6 +290,7 @@ DEPENDENCIES fastlane (= 2.143.0)! fastlane-plugin-appcenter (= 1.7.1) fastlane-plugin-sentry + fastlane-plugin-test_center fastlane-plugin-wpmreleasetoolkit! octokit (~> 4.0)! rake! @@ -289,4 +299,4 @@ DEPENDENCIES xcpretty-travis-formatter! BUNDLED WITH - 2.0.2 + 2.1.4 diff --git a/Podfile b/Podfile index b2c63f158c83..388d782404ce 100644 --- a/Podfile +++ b/Podfile @@ -34,7 +34,7 @@ end def wordpress_ui ## for production: - pod 'WordPressUI', '~> 1.5.3-beta.1' + pod 'WordPressUI', '~> 1.5.3' ## for development: #pod 'WordPressUI', :path => '../WordPressUI-iOS' @@ -44,9 +44,8 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 4.7.1-beta.1' - - #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :tag => '4.6.0-beta.3' + pod 'WordPressKit', '~> 4.8.0' + #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :tag => '' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => '' #pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => '' #pod 'WordPressKit', :path => '../WordPressKit-iOS' @@ -120,7 +119,6 @@ def gutenberg_dependencies(options) 'react-native-safe-area', 'react-native-video', 'RNSVG', - 'ReactNativeDarkMode', 'react-native-slider', 'react-native-linear-gradient' ] @@ -149,7 +147,7 @@ target 'WordPress' do ## Gutenberg (React Native) ## ===================== ## - gutenberg :commit => 'fe07af1691e4f5f869e4824ebf26a9b8326bafec' + gutenberg :commit => '330c3998bc263e727573901600be108ee88ed878' ## Third party libraries ## ===================== @@ -185,11 +183,11 @@ target 'WordPress' do pod 'Gridicons', '~> 1.0.1' - pod 'WordPressAuthenticator', '~> 1.12.1' + pod 'WordPressAuthenticator', '~> 1.13.0' # While in PR - #pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :branch => '' - #pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :commit => '' - #pod 'WordPressAuthenticator', :path => '../WordPressAuthenticator-iOS' + # pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :branch => '' + # pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :commit => '' + # pod 'WordPressAuthenticator', :path => '../WordPressAuthenticator-iOS' pod 'MediaEditor', '~> 1.0.1' # pod 'MediaEditor', :git => 'https://github.com/wordpress-mobile/MediaEditor-iOS.git', :commit => 'a4178ed9b0f3622faafb41dd12503e26c5523a32' diff --git a/Podfile.lock b/Podfile.lock index 51c2f095f93c..f3c1933b72dd 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -67,7 +67,7 @@ PODS: - "GoogleToolboxForMac/NSString+URLArguments (2.2.2)" - Gridicons (1.0.1) - GTMSessionFetcher/Core (1.3.1) - - Gutenberg (1.25.0): + - Gutenberg (1.26.0): - React (= 0.62.2) - React-CoreModules (= 0.62.2) - React-RCTImage (= 0.62.2) @@ -381,11 +381,9 @@ PODS: - React-jsi (= 0.62.2) - ReactCommon/callinvoker (= 0.62.2) - ReactCommon/turbomodule/core (= 0.62.2) - - ReactNativeDarkMode (0.0.10): - - React - RNSVG (9.13.6-gb): - React - - RNTAztecView (1.25.0): + - RNTAztecView (1.26.0): - React-Core - WordPress-Aztec-iOS (~> 1.17.1) - Sentry (4.5.0): @@ -400,7 +398,7 @@ PODS: - WordPress-Aztec-iOS (1.17.1) - WordPress-Editor-iOS (1.17.1): - WordPress-Aztec-iOS (= 1.17.1) - - WordPressAuthenticator (1.12.1): + - WordPressAuthenticator (1.13.0): - 1PasswordExtension (= 1.8.6) - Alamofire (= 4.8) - CocoaLumberjack (~> 3.5) @@ -409,10 +407,10 @@ PODS: - lottie-ios (= 3.1.6) - "NSURL+IDN (= 0.4)" - SVProgressHUD (= 2.2.5) - - WordPressKit (~> 4.7.0) + - WordPressKit (~> 4.8.0) - WordPressShared (~> 1.8.16) - WordPressUI (~> 1.5.2) - - WordPressKit (4.7.1-beta.1): + - WordPressKit (4.8.0): - Alamofire (~> 4.8.0) - CocoaLumberjack (~> 3.4) - NSObject-SafeExpectations (= 0.0.4) @@ -423,7 +421,7 @@ PODS: - WordPressShared (1.8.16): - CocoaLumberjack (~> 3.4) - FormatterKit/TimeIntervalFormatter (= 1.8.2) - - WordPressUI (1.5.3-beta.1) + - WordPressUI (1.5.3) - WPMediaPicker (1.6.1) - wpxmlrpc (0.8.5) - Yoga (1.14.0) @@ -453,15 +451,15 @@ DEPENDENCIES: - Charts (~> 3.2.2) - CocoaLumberjack (= 3.5.2) - Down (~> 0.6.6) - - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/FBLazyVector.podspec.json`) - - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/FBReactNativeSpec.podspec.json`) - - Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/Folly.podspec.json`) + - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/FBLazyVector.podspec.json`) + - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/FBReactNativeSpec.podspec.json`) + - Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/Folly.podspec.json`) - FormatterKit/TimeIntervalFormatter (= 1.8.2) - FSInteractiveMap (from `https://github.com/wordpress-mobile/FSInteractiveMap.git`, tag `0.2.0`) - Gifu (= 3.2.0) - - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/glog.podspec.json`) + - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/glog.podspec.json`) - Gridicons (~> 1.0.1) - - Gutenberg (from `http://github.com/wordpress-mobile/gutenberg-mobile/`, commit `fe07af1691e4f5f869e4824ebf26a9b8326bafec`) + - Gutenberg (from `http://github.com/wordpress-mobile/gutenberg-mobile/`, commit `330c3998bc263e727573901600be108ee88ed878`) - JTAppleCalendar (~> 8.0.2) - MediaEditor (~> 1.0.1) - MRProgress (= 0.8.3) @@ -471,45 +469,44 @@ DEPENDENCIES: - OCMock (= 3.4.3) - OHHTTPStubs (= 6.1.0) - OHHTTPStubs/Swift (= 6.1.0) - - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/RCTRequired.podspec.json`) - - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/RCTTypeSafety.podspec.json`) + - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/RCTRequired.podspec.json`) + - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/RCTTypeSafety.podspec.json`) - Reachability (= 3.2) - - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React.podspec.json`) - - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-Core.podspec.json`) - - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-CoreModules.podspec.json`) - - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-cxxreact.podspec.json`) - - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-jsi.podspec.json`) - - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-jsiexecutor.podspec.json`) - - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-jsinspector.podspec.json`) - - react-native-keyboard-aware-scroll-view (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json`) - - react-native-linear-gradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-linear-gradient.podspec.json`) - - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-safe-area.podspec.json`) - - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-slider.podspec.json`) - - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-video.podspec.json`) - - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTActionSheet.podspec.json`) - - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTAnimation.podspec.json`) - - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTBlob.podspec.json`) - - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTImage.podspec.json`) - - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTLinking.podspec.json`) - - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTNetwork.podspec.json`) - - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTSettings.podspec.json`) - - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTText.podspec.json`) - - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTVibration.podspec.json`) - - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/ReactCommon.podspec.json`) - - ReactNativeDarkMode (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/ReactNativeDarkMode.podspec.json`) - - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/RNSVG.podspec.json`) - - RNTAztecView (from `http://github.com/wordpress-mobile/gutenberg-mobile/`, commit `fe07af1691e4f5f869e4824ebf26a9b8326bafec`) + - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React.podspec.json`) + - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-Core.podspec.json`) + - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-CoreModules.podspec.json`) + - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-cxxreact.podspec.json`) + - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-jsi.podspec.json`) + - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-jsiexecutor.podspec.json`) + - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-jsinspector.podspec.json`) + - react-native-keyboard-aware-scroll-view (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json`) + - react-native-linear-gradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-linear-gradient.podspec.json`) + - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-safe-area.podspec.json`) + - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-slider.podspec.json`) + - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-video.podspec.json`) + - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTActionSheet.podspec.json`) + - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTAnimation.podspec.json`) + - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTBlob.podspec.json`) + - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTImage.podspec.json`) + - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTLinking.podspec.json`) + - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTNetwork.podspec.json`) + - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTSettings.podspec.json`) + - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTText.podspec.json`) + - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTVibration.podspec.json`) + - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/ReactCommon.podspec.json`) + - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/RNSVG.podspec.json`) + - RNTAztecView (from `http://github.com/wordpress-mobile/gutenberg-mobile/`, commit `330c3998bc263e727573901600be108ee88ed878`) - SimulatorStatusMagic - Starscream (= 3.0.6) - SVProgressHUD (= 2.2.5) - WordPress-Editor-iOS (~> 1.17.1) - - WordPressAuthenticator (~> 1.12.1) - - WordPressKit (~> 4.7.1-beta.1) + - WordPressAuthenticator (~> 1.13.0) + - WordPressKit (~> 4.8.0) - WordPressMocks (~> 0.0.8) - WordPressShared (~> 1.8.16) - - WordPressUI (~> 1.5.3-beta.1) + - WordPressUI (~> 1.5.3) - WPMediaPicker (~> 1.6.1) - - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json`) + - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.0.0) - ZIPFoundation (~> 0.9.8) @@ -569,86 +566,84 @@ SPEC REPOS: EXTERNAL SOURCES: FBLazyVector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/FBLazyVector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/FBLazyVector.podspec.json FBReactNativeSpec: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/FBReactNativeSpec.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/FBReactNativeSpec.podspec.json Folly: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/Folly.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/Folly.podspec.json FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 glog: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/glog.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/glog.podspec.json Gutenberg: - :commit: fe07af1691e4f5f869e4824ebf26a9b8326bafec + :commit: 330c3998bc263e727573901600be108ee88ed878 :git: http://github.com/wordpress-mobile/gutenberg-mobile/ RCTRequired: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/RCTRequired.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/RCTRequired.podspec.json RCTTypeSafety: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/RCTTypeSafety.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/RCTTypeSafety.podspec.json React: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React.podspec.json React-Core: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-Core.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-Core.podspec.json React-CoreModules: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-CoreModules.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-CoreModules.podspec.json React-cxxreact: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-cxxreact.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-cxxreact.podspec.json React-jsi: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-jsi.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-jsi.podspec.json React-jsiexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-jsiexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-jsiexecutor.podspec.json React-jsinspector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-jsinspector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-jsinspector.podspec.json react-native-keyboard-aware-scroll-view: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-keyboard-aware-scroll-view.podspec.json react-native-linear-gradient: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-linear-gradient.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-linear-gradient.podspec.json react-native-safe-area: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-safe-area.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-safe-area.podspec.json react-native-slider: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-slider.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-slider.podspec.json react-native-video: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/react-native-video.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/react-native-video.podspec.json React-RCTActionSheet: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTActionSheet.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTActionSheet.podspec.json React-RCTAnimation: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTAnimation.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTAnimation.podspec.json React-RCTBlob: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTBlob.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTBlob.podspec.json React-RCTImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTImage.podspec.json React-RCTLinking: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTLinking.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTLinking.podspec.json React-RCTNetwork: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTNetwork.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTNetwork.podspec.json React-RCTSettings: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTSettings.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTSettings.podspec.json React-RCTText: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTText.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTText.podspec.json React-RCTVibration: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/React-RCTVibration.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/React-RCTVibration.podspec.json ReactCommon: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/ReactCommon.podspec.json - ReactNativeDarkMode: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/ReactNativeDarkMode.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/ReactCommon.podspec.json RNSVG: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/RNSVG.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/RNSVG.podspec.json RNTAztecView: - :commit: fe07af1691e4f5f869e4824ebf26a9b8326bafec + :commit: 330c3998bc263e727573901600be108ee88ed878 :git: http://github.com/wordpress-mobile/gutenberg-mobile/ Yoga: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/fe07af1691e4f5f869e4824ebf26a9b8326bafec/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/330c3998bc263e727573901600be108ee88ed878/react-native-gutenberg-bridge/third-party-podspecs/Yoga.podspec.json CHECKOUT OPTIONS: FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 Gutenberg: - :commit: fe07af1691e4f5f869e4824ebf26a9b8326bafec + :commit: 330c3998bc263e727573901600be108ee88ed878 :git: http://github.com/wordpress-mobile/gutenberg-mobile/ RNTAztecView: - :commit: fe07af1691e4f5f869e4824ebf26a9b8326bafec + :commit: 330c3998bc263e727573901600be108ee88ed878 :git: http://github.com/wordpress-mobile/gutenberg-mobile/ SPEC CHECKSUMS: @@ -674,7 +669,7 @@ SPEC CHECKSUMS: GoogleToolboxForMac: 800648f8b3127618c1b59c7f97684427630c5ea3 Gridicons: 8e19276b20bb15d1fda1d4d0db96d066d170135b GTMSessionFetcher: cea130bbfe5a7edc8d06d3f0d17288c32ffe9925 - Gutenberg: 1fb6dfdeedddfcae87420dfcca244c1e8b6f8ea1 + Gutenberg: 5c951d2d840238bd9f0ab91bd75f1d12b2a906fc JTAppleCalendar: 932cadea40b1051beab10f67843451d48ba16c99 lottie-ios: 85ce835dd8c53e02509f20729fc7d6a4e6645a0a MediaEditor: 7296cd01d7a0548fb2bc909aa72153b376a56a61 @@ -709,9 +704,8 @@ SPEC CHECKSUMS: React-RCTText: 608321a0abd0533e15ae3dc02e35e12fc51b5f92 React-RCTVibration: 9ba85362c27d453efbbb64808bbb07ca9769c9ec ReactCommon: 99515d33368553e35eb6cc4ae23e50ea8c2945da - ReactNativeDarkMode: f61376360c5d983907e5c316e8e1c853a8c2f348 RNSVG: 68a534a5db06dcbdaebfd5079349191598caef7b - RNTAztecView: 3fb0f122cea9b3bcf502da8380de5cd552d43585 + RNTAztecView: 65e1984620590339bb1bcced6f5e914cba8df2f5 Sentry: ab6c209f23700d1460691dbc90e19ed0a05d496b SimulatorStatusMagic: 28d4a9d1a500ac7cea0b2b5a43c1c6ddb40ba56c Sodium: 63c0ca312a932e6da481689537d4b35568841bdc @@ -721,11 +715,11 @@ SPEC CHECKSUMS: UIDeviceIdentifier: 44f805037d21b94394821828f4fcaba34b38c2d0 WordPress-Aztec-iOS: 319620514af963ca519bd83b96a2c0ebdf3a0f03 WordPress-Editor-iOS: 497b55838ef0030cc6ca82eb92da84e661423521 - WordPressAuthenticator: d69a1516525a078abc0e6ed9692d4c1c3c866317 - WordPressKit: dde0a214279fb70d7150b69ae90c7224c18fe2d0 + WordPressAuthenticator: ed1ea00ad8e04ff4dc086c7be6b77370b1738230 + WordPressKit: 84045e236949248632a2c644149e5657733011bb WordPressMocks: b4064b99a073117bbc304abe82df78f2fbe60992 WordPressShared: 1bc316ed162f42af4e0fa2869437e9e28b532b01 - WordPressUI: 91b91e51b8cd7db83a2bd492c12998db711a6a57 + WordPressUI: 7519c8118596f1360f05adf9bb007ac49087fd36 WPMediaPicker: 59559813ec8a7929a91aa5a1db74998d8485fb9f wpxmlrpc: 6a9bdd6ab9d1b159b384b0df0f3f39de9af4fecf Yoga: 27ff7d4bc3a33088c0b60904f33b4ee59355b8ba @@ -738,6 +732,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: a87ab1e4badace92c75eb11dc77ede1e995b2adc ZIPFoundation: 249fa8890597086cd536bb2df5c9804d84e122b0 -PODFILE CHECKSUM: 3ccbfbcd909dd9ec3407af7f06fe56b21b7eaca4 +PODFILE CHECKSUM: 6565e90630e220577458547c72558b5ed02542f0 COCOAPODS: 1.8.4 diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index c247f3a2b754..ae3f5b1165ac 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,8 +1,26 @@ +14.8 +----- + 14.7 ----- -* Classic Editor: Fixed action sheet position for additional Media sources picker on iPad - ----- +* Classic Editor: Fixed action sheet position for additional Media sources picker on iPad +* [internal] the signup flow using email has code changes that can cause regressions. See https://git.io/JvALZ for testing details. +* [internal] Notifications tab should pop to the root of the navigation stack when tapping on the tab from within a notification detail screen. See https://git.io/Jvxka for testing details. +* Classic and Block editor: Prefill caption for image blocks when available on the Media library. +* [internal] the "login by email" flow and the self-hosted login flow have code changes that can cause regressions. See https://git.io/JfeFN for testing details. +* Block editor: Disable ripple effect in all BottomSheet's controls. +* Block editor: New block: Columns +* Block editor: New starter page template: Blog +* Block editor: Make Starter Page Template picker buttons visible only when the screen height is enough +* Block editor: Fix a bug which caused to show URL settings modal randomly when changing the device orientation multiple times during the time Starter Page Template Preview is open +* [internal] the login by email flow and the self-hosted login flow have code changes that can cause regressions. See https://git.io/JfeFN for testing details. +* Updated the appearance of the login and signup buttons to make signup more prominent. +* [internal] the navigation to the "login by site address" flow has code changes that can cause regressions. See https://git.io/JfvP9 for testing details. +* Updated site details screen title to My Site, to avoid duplicating the title of the current site which is displayed in the screen's header area. +* You can now schedule your post, add tags or change the visibility before hitting "Publish Now" — and you don't have to go to the Post Settings for this! + +* Login Epilogue: fixed issue where account information never stopped loading for some self-hosted sites. +* Updated site details screen title to My Site, to avoid duplicating the title of the current site which is displayed in the screen's header area. 14.6 ----- @@ -28,7 +46,7 @@ * Reader post detail: fix colors when switching between light and dark modes. * Fixed an issue where Continue with Apple button wouldn't respond after Jetpack Setup > Sign up flow completed. - + 14.5 ----- * Block editor: New block: Latest Posts @@ -36,7 +54,7 @@ * Block editor: Added Starter Page Templates: when you create a new page, we now show you a few templates to get started more quickly. * Block editor: Fix crash when pasting HTML content with embeded images on paragraphs * Post Settings: Fix issue where the status of a post showed "Scheduled" instead of "Published" after scheduling before the current date. -* Stats: Fix background color in Dark Mode on wider screen sizes. +* Stats: Fix background color in Dark Mode on wider screen sizes. * Post Settings: Fix issue where the calendar selection may not match the selected date when site timezone differs from device timezone. * Dark Mode fixes: - Border color on Search bars. @@ -53,11 +71,11 @@ 14.4.1 ----- * Block Editor: Fix crash when inserting a Button Block. - + 14.4 ----- * Post Settings: Fixes the displayed publish date of posts which are to be immediately published. - + 14.3 ----- * Aztec and Block Editor: Fix the presentation of ordered lists with large numbers. @@ -74,8 +92,8 @@ * Block editor: Add support for upload options in Gallery block * Aztec and Block Editor: Fix the presentation of ordered lists with large numbers. * Added Quick Action buttons on the Site Details page to access the most frequently used parts of a site. -* Post Settings: Adjusts the weekday symbols in the calendar depending on Regional settings. - +* Post Settings: Adjusts the weekday symbols in the calendar depending on Regional settings. + 14.2 ----- diff --git a/Scripts/fastlane/Deliverfile b/Scripts/fastlane/Deliverfile index 63808caf266e..a3546a299293 100644 --- a/Scripts/fastlane/Deliverfile +++ b/Scripts/fastlane/Deliverfile @@ -5,7 +5,7 @@ screenshots_path "./screenshots/" app_identifier "org.wordpress" # Make sure to update these keys for a new version -app_version "14.6" +app_version "14.7" privacy_url({ 'default' => 'https://automattic.com/privacy/', diff --git a/Scripts/fastlane/Fastfile b/Scripts/fastlane/Fastfile index 1e3172768d55..f859af2bf9d4 100644 --- a/Scripts/fastlane/Fastfile +++ b/Scripts/fastlane/Fastfile @@ -15,19 +15,22 @@ def get_required_env(key) ENV[key] end -before_all do - # Check that the env files exist - unless is_ci || File.file?(USER_ENV_FILE_PATH) - UI.user_error!("~/.wpios-env.default not found: Please copy env/user.env-example to #{USER_ENV_FILE_PATH} and fill in the values") - end - unless File.file?(PROJECT_ENV_FILE_PATH) - UI.user_error!("project.env not found: Make sure your configuration is up to date with `rake dependencies`") - end +before_all do |lane| + # Skip these checks/steps for test lane (not needed for testing) + unless lane == :test_without_building + # Check that the env files exist + unless is_ci || File.file?(USER_ENV_FILE_PATH) + UI.user_error!("~/.wpios-env.default not found: Please copy env/user.env-example to #{USER_ENV_FILE_PATH} and fill in the values") + end + unless File.file?(PROJECT_ENV_FILE_PATH) + UI.user_error!("project.env not found: Make sure your configuration is up to date with `rake dependencies`") + end - # This allows code signing to work on CircleCI - # It is skipped if this isn't running on CI - # See https://circleci.com/docs/2.0/ios-codesigning/ - setup_circle_ci + # This allows code signing to work on CircleCI + # It is skipped if this isn't running on CI + # See https://circleci.com/docs/2.0/ios-codesigning/ + setup_circle_ci + end end platform :ios do @@ -483,6 +486,35 @@ import "./ScreenshotFastfile" "org.wordpress.WordPressThisWeekWidget"]) end +######################################################################## +# Test Lanes +######################################################################## + ##################################################################################### + # test_without_building + # ----------------------------------------------------------------------------------- + # This lane runs tests without building the app. + # It requires a prebuilt xctestrun file and simulator destination where the tests will be run. + # ----------------------------------------------------------------------------------- + # Usage: + # bundle exec fastlane test_without_building [xctestrun:] [destination:] [try_count:] + # + # Example: + # bundle exec fastlane test_without_building xctestrun:WordPress_WordPressUITests_iphonesimulator13.2-x86_64.xctestrun destination:"platform=iOS Simulator,id=$SIMULATOR_UDID" try_count:3 + ##################################################################################### + desc "Run tests without building" + lane :test_without_building do | options | + multi_scan( + workspace: "../WordPress.xcworkspace", + scheme: "WordPress", + test_without_building: true, + xctestrun: "../#{options[:xctestrun]}", + destination: options[:destination], + try_count: options[:try_count], + output_directory: "../build/results", + result_bundle: true + ) + end + ######################################################################## # Helper Lanes ######################################################################## diff --git a/Scripts/fastlane/Pluginfile b/Scripts/fastlane/Pluginfile index 9d4d40aa834e..ec2af469ba06 100644 --- a/Scripts/fastlane/Pluginfile +++ b/Scripts/fastlane/Pluginfile @@ -6,6 +6,7 @@ group :screenshots, optional: true do gem 'rmagick', '~> 3.2.0' end -gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', tag: '0.9.1' +gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', tag: '0.9.2' gem 'fastlane-plugin-sentry' gem 'fastlane-plugin-appcenter', '1.7.1' +gem 'fastlane-plugin-test_center' diff --git a/Scripts/fastlane/download_metadata.swift b/Scripts/fastlane/download_metadata.swift index 17dfa697aefa..835d03416188 100755 --- a/Scripts/fastlane/download_metadata.swift +++ b/Scripts/fastlane/download_metadata.swift @@ -3,7 +3,7 @@ import Foundation let glotPressSubtitleKey = "app_store_subtitle" -let glotPressWhatsNewKey = "v14.6-whats-new" +let glotPressWhatsNewKey = "v14.7-whats-new" let glotPressDescriptionKey = "app_store_desc" let glotPressKeywordsKey = "app_store_keywords" let baseFolder = "./metadata" diff --git a/Scripts/fastlane/metadata/de-DE/description.txt b/Scripts/fastlane/metadata/de-DE/description.txt index 80707c4d3cf9..40f1328de3bb 100644 --- a/Scripts/fastlane/metadata/de-DE/description.txt +++ b/Scripts/fastlane/metadata/de-DE/description.txt @@ -4,7 +4,7 @@ Mit WordPress für iOS hältst du das Werkzeug, um Texte zu veröffentlichen dir WordPress für iOS ist ein Open-Source-Projekt, an dessen Entwicklung du dich beteiligen kannst. Um mehr zu erfahren, schau unter https://apps.wordpress.com/contribute/ nach. -WordPress für iOS unterstützt WordPress.com und selbst gehostete WordPress.org-Websites ab WordPress-Version 4.0. +WordPress für iOS unterstützt WordPress.com und selbst gehostete WordPress.org-Websites ab WordPress 4.0. Wenn du Hilfe zur App brauchst, findest du Informationen in den Foren unter https://ios.forums.wordpress.org/. Du kannst uns aber auch einen Tweet an @WordPressiOS schicken. diff --git a/Scripts/fastlane/metadata/de-DE/release_notes.txt b/Scripts/fastlane/metadata/de-DE/release_notes.txt index cca9ecda22e2..00d976c9ee61 100644 --- a/Scripts/fastlane/metadata/de-DE/release_notes.txt +++ b/Scripts/fastlane/metadata/de-DE/release_notes.txt @@ -1,15 +1,11 @@ -Neue Blöcke! Willkommen beim Block „Letzte Beiträge“, um deine neuen Beiträge und Seiten hervorzuheben. +Die Website-Erstellung wurde durch Entfernen einiger Zwischenschritte optimiert. Suche einfach deine gewünschte Art von Website aus, gib einen Domainnamen ein und schon bist du fertig. -Wenn du eine neue Seite erstellst, zeigen wir dir ein paar Vorlagen. Wähle eine aus und wir fügen alle notwendigen Blöcke zu deiner Seite hinzu, damit du schnell damit beginnen kannst. +Weitere Verbesserungen am Block-Editor: Der Cover-Block ist jetzt verfügbar. Im Überschriftsblock findest du Optionen zur Ausrichtung und eine Dropdown-Toolbar vereinfacht die Ausrichtung für Überschrifts-, Absatz-, Bild- und MediaText-Blöcke. Darüber hinaus haben wir den Dimmeffekt für nicht ausgewählte Blöcke entfernt. -Wir haben ein paar Probleme mit dem Block-Editor behoben: keine Abstürze mehr beim Einfügen von HTML-Inhalten bzw. keine fehlenden Ränder mehr bei Zitat-Blöcken im dunklen Modus. +Bedingt durch das Layout des Bildschirms „Benachrichtigungen“ wurde der Text auf einigen Smartphone-Modellen durch das Gehäuse verdeckt. Dies wurde nun behoben! -Wenn du zuvor einen Beitrag zur Veröffentlichung geplant hast, würde er als „geplant“ anstatt als „veröffentlicht“ angezeigt. Das wurde behoben. Ebenso ein Problem, bei dem Datumsangaben nicht korrekt angezeigt wurden, wenn die Zeitzone der Website nicht mit der Zeitzone des Geräts übereinstimmte. +Bei dem Versuch einer @-Erwähnung einer Person in einem Kommentar hat die App manchmal zwar Namen vorgeschlagen, ließ diese aber nicht auswählen – auch das wurde behoben. Wir haben uns auch um einige weitere Probleme gekümmert, die zum Absturz der App führten, wenn du versuchtest, auf den Bildschirm „Website-Seiten“ zuzugreifen, oder mithilfe der „Quick Action“-Buttons auf iPads zu deinen Blogbeiträgen gelangen wolltest. Und apropos Seiten und Beiträge: Die Vorschau ist auf iPads mit iOS 13 jetzt größer. -Wir haben ein paar UI-Inkonsistenzen im dunklen Modus und bei der Reader-Werkzeugleiste behoben. - -Du kannst @-Erwähnungen verwenden, wenn du Kommentare im Vollbildmodus erstellst. - -Manchmal landeten E-Mails mit magischen Registrierungs- und Anmeldelinks in deinem Spam-Ordner. Deshalb haben wir auf dem Bestätigungsbildschirm den Hinweis hinzugefügt, im Spam- oder Junk-Mail-Ordner nachzusehen, wenn die E-Mail nicht sofort gefunden wird. +Wir mögen unseren dunklen Modus so wie unseren Kaffee: echt dunkel und ohne ungewollte Zusätze. Es gab einige Störungen im hellen und dunklen Modus und das Verhalten war beim Umschalten von einem Modus zum anderen nicht immer optimal. Es gibt jetzt wesentlich weniger von diesen Störungen. Bleibt alle gesund. diff --git a/Scripts/fastlane/metadata/default/release_notes.txt b/Scripts/fastlane/metadata/default/release_notes.txt index 58dbb9e71d34..eff9d4af8fc8 100644 --- a/Scripts/fastlane/metadata/default/release_notes.txt +++ b/Scripts/fastlane/metadata/default/release_notes.txt @@ -1,15 +1,11 @@ -New blocks! Welcome the Latest Posts block, for highlighting your new stuff in posts and pages. +Site Creation has been streamlined by removing some intermediate steps. Pick the kind of site you want, enter a domain name, and you’re done. -When you create a new page, we’ll show you a few templates — pick one, and we’ll add all the necessary blocks to your page to get you started quickly. +The Block editor got more improvements: The Cover block is now available; there are alignment options in the Heading block; and a dropdown toolbar makes alignment easier for Heading, Paragraph, Image, MediaText blocks. And we removed the dimming effect on unselected blocks. -We fixed up a few block editor issues; no more crashes when pasting in HTML content or missing Quote block borders in Dark Mode. +The layout of the Notifications screen caused text to get lost under the casing of some models of mobile phone. That’s fixed! -If you scheduled a post to publish in the past, it would display as “scheduled” instead of “published.” That’s fixed, along with an issue where dates displayed incorrectly if your site’s timezone wasn’t the same as your device’s timezone. +Sometimes you’d try to @-mention someone in a comment, and the app would suggest names but then not let you select one — that’s also fixed. We also took care of a few other issues that caused the app to crash when you tried to access the Site Pages screen or use the Quick Action buttons on iPads to get to your blog posts. And speaking of pages and posts, previews are now bigger for iPads running iOS 13. -We fixed some UI inconsistencies in Dark Mode and with the Reader toolbar. +We like our Dark Mode like we like our coffee: really dark, and without any bugs floating around in it. There were some glitches in Light and Dark Modes, and things sometimes behaved oddly when you switched from one to the other. There are far fewer of those glitches now. -You can use @-mentions when writing comments in fullscreen mode. - -Sometimes Signup and Login Magic Link emails would go to your spam folder, so we added a note to the confirmation screen suggesting that folks check their spam/junk folders if they don’t see the email promptly. - -Stay safe, everyone. +Stay safe, y’all. diff --git a/Scripts/fastlane/metadata/en-GB/release_notes.txt b/Scripts/fastlane/metadata/en-GB/release_notes.txt index f54d1c0f4878..7e01f0f114af 100644 --- a/Scripts/fastlane/metadata/en-GB/release_notes.txt +++ b/Scripts/fastlane/metadata/en-GB/release_notes.txt @@ -1,15 +1,11 @@ -New blocks! Welcome to the Latest Posts block, for highlighting your new stuff in posts and pages. +Site Creation has been streamlined by removing some intermediate steps. Pick the kind of site you want, enter a domain name, and you’re done. -When you create a new page, we’ll show you a few templates - pick one, and we’ll add all the necessary blocks to your page to get you started quickly. +The block editor got more improvements: the Cover block is now available; there are alignment options in the Heading block; and a dropdown toolbar makes alignment easier for Heading, Paragraph, Image, MediaText blocks. And we removed the dimming effect on unselected blocks. -We fixed up a few block editor issues; no more crashes when pasting in HTML content or missing Quote block borders in Dark Mode. +The layout of the Notifications screen caused text to get lost under the casing of some models of mobile phone. That’s fixed! -If you scheduled a post to publish in the past, it would display as “scheduled” instead of “published”. That’s fixed, along with an issue where dates displayed incorrectly if your site’s time zone wasn’t the same as your device’s time zone. +Sometimes you’d try to @-mention someone in a comment, and the app would suggest names but then not let you select one — that’s also fixed. We also took care of a few other issues that caused the app to crash when you tried to access the Site Pages screen or use the Quick Action buttons on iPads to get to your blog posts. And speaking of pages and posts, previews are now bigger for iPads running iOS 13. -We fixed some UI inconsistencies in Dark Mode and with the Reader toolbar. +We like our Dark Mode like we like our coffee: really dark, and without any bugs floating around in it. There were some glitches in Light and Dark Modes, and things sometimes behaved oddly when you switched from one to the other. There are far fewer of those glitches now. -You can use @-mentions when writing comments in fullscreen mode. - -Sometimes Signup and Login Magic Link emails would go to your spam folder, so we added a note to the confirmation screen suggesting that folks check their spam/junk folders if they don’t see the email promptly. - -Stay safe, everyone. +Stay safe, y’all. diff --git a/Scripts/fastlane/metadata/en-US/release_notes.txt b/Scripts/fastlane/metadata/en-US/release_notes.txt index 58dbb9e71d34..eff9d4af8fc8 100644 --- a/Scripts/fastlane/metadata/en-US/release_notes.txt +++ b/Scripts/fastlane/metadata/en-US/release_notes.txt @@ -1,15 +1,11 @@ -New blocks! Welcome the Latest Posts block, for highlighting your new stuff in posts and pages. +Site Creation has been streamlined by removing some intermediate steps. Pick the kind of site you want, enter a domain name, and you’re done. -When you create a new page, we’ll show you a few templates — pick one, and we’ll add all the necessary blocks to your page to get you started quickly. +The Block editor got more improvements: The Cover block is now available; there are alignment options in the Heading block; and a dropdown toolbar makes alignment easier for Heading, Paragraph, Image, MediaText blocks. And we removed the dimming effect on unselected blocks. -We fixed up a few block editor issues; no more crashes when pasting in HTML content or missing Quote block borders in Dark Mode. +The layout of the Notifications screen caused text to get lost under the casing of some models of mobile phone. That’s fixed! -If you scheduled a post to publish in the past, it would display as “scheduled” instead of “published.” That’s fixed, along with an issue where dates displayed incorrectly if your site’s timezone wasn’t the same as your device’s timezone. +Sometimes you’d try to @-mention someone in a comment, and the app would suggest names but then not let you select one — that’s also fixed. We also took care of a few other issues that caused the app to crash when you tried to access the Site Pages screen or use the Quick Action buttons on iPads to get to your blog posts. And speaking of pages and posts, previews are now bigger for iPads running iOS 13. -We fixed some UI inconsistencies in Dark Mode and with the Reader toolbar. +We like our Dark Mode like we like our coffee: really dark, and without any bugs floating around in it. There were some glitches in Light and Dark Modes, and things sometimes behaved oddly when you switched from one to the other. There are far fewer of those glitches now. -You can use @-mentions when writing comments in fullscreen mode. - -Sometimes Signup and Login Magic Link emails would go to your spam folder, so we added a note to the confirmation screen suggesting that folks check their spam/junk folders if they don’t see the email promptly. - -Stay safe, everyone. +Stay safe, y’all. diff --git a/Scripts/fastlane/metadata/es-ES/release_notes.txt b/Scripts/fastlane/metadata/es-ES/release_notes.txt index 8e054fa25b44..d3956dd6587f 100644 --- a/Scripts/fastlane/metadata/es-ES/release_notes.txt +++ b/Scripts/fastlane/metadata/es-ES/release_notes.txt @@ -1,15 +1,11 @@ -¡Nuevos bloques! Bienvenido al bloque de últimas entradas, para resaltar tus nuevas cosas en las entradas y páginas. +La creación del sitio se ha simplificado al eliminar algunos pasos intermedios. Elige el tipo de sitio que quieres, introduce un nombre de dominio y listo. -Cuando crees una nueva página, te mostraremos unas cuantas plantillas — elige una y añadiremos todos los bloques necesarios a tu página para que puedas empezar rápidamente. +El editor de bloques tiene más mejoras: Ahora está disponible el bloque portada; hay opciones de alineación en el bloque del encabezado; y una barra de herramientas desplegable hace más fácil la alineación de los bloques de encabezado, párrafo, imagen y medios y texto. Y hemos eliminado el efecto de atenuación en los bloques no seleccionados. -Hemos corregido algunos problemas del editor de bloques; ya no se producen más conflictos al pegar contenido HTML o la falta de los bordes del bloque de citas en el modo oscuro. +El diseño de la pantalla de avisos hacía que el texto se perdiera bajo la carcasa de algunos modelos de teléfono móvil. ¡Eso está corregido! -Si habías programado la publicación de una entrada en el pasado, se mostraba como «programada» en lugar de «publicada». Eso está corregido, junto con un problema por el que las fechas se mostraban incorrectamente si la zona horaria de tu sitio no era la misma que la de tu dispositivo. +A veces, intentabas @mencionar a alguien en un comentario y la aplicación sugería nombres, pero no te dejaba seleccionar uno — eso también está corregido. También nos hemos ocupado de otros problemas que hacían que la aplicación se bloqueara cuando intentabas acceder a la pantalla de las páginas del sitio o usar los botones de acción rápida en los iPad para acceder a tus entradas del blog. Y, hablando de páginas y entradas, ahora las vistas previas son más grandes para los iPad que ejecutan iOS 13. -Hemos corregido algunas inconsistencias de la interfaz de usuario en el modo oscuro y con la barra de herramientas del lector. +Nos gusta nuestro modo oscuro, como nos gusta nuestro café: muy oscuro y sin bichos flotando en él. Había algunos fallos en los modos claro y oscuro y, algunas veces, las cosas se comportaban de forma extraña cuando te cambiabas de uno a otro. Ahora hay muchos menos de esos fallos. -Puedes usar @-menciones cuando escribas comentarios en modo de pantalla completa. - -A veces, los correos electrónicos de registro y de acceso de Magic Link iban a tu carpeta de spam, por lo que hemos añadido una nota en la pantalla de confirmación sugiriendo que la gente compruebe sus carpetas de spam/basura si no ven el correo electrónico rápidamente. - -Ten cuidado, todos. +Manteneos todos a salvo. diff --git a/Scripts/fastlane/metadata/fr-FR/release_notes.txt b/Scripts/fastlane/metadata/fr-FR/release_notes.txt index 977e23d4e57f..48b6bf6c7d96 100644 --- a/Scripts/fastlane/metadata/fr-FR/release_notes.txt +++ b/Scripts/fastlane/metadata/fr-FR/release_notes.txt @@ -1,15 +1,11 @@ -De nouveaux blocs ! Bienvenue au bloc « Derniers articles » pour mettre en évidence vos nouvelles infos dans les articles et les pages. +Certaines étapes de la création de site ont été supprimées afin de rendre le processus plus fluide. Choisissez simplement le type de site qui vous convient et saisissez un nom de domaine. C’est tout ! -Lorsque vous créerez une nouvelle page, nous vous afficherons quelques modèles. Choisissez-en un et nous ajouterons tous les blocs nécessaires à votre page pour vous permettre de démarrer plus rapidement. +L’éditeur de blocs a été encore amélioré : Le bloc de couverture est désormais disponible, des options d’alignement sont proposées dans le bloc de titre et une barre d’outils déroulante facilite l’alignement des blocs de titre, de paragraphe, d’image ainsi que de multimédia et texte. Nous avons également supprimé l’effet d’assombrissement sur les blocs désélectionnés. -Nous avons corrigé quelques problèmes de l’éditeur de blocs ; plus de plantage lors d’un collage de contenu HTML ou de bordures manquantes de bloc de citation en mode foncé. +Sur certains modèles de téléphones portables, en raison de la disposition de l’écran Notifications, le texte se retrouvait masqué par la coque du téléphone. Le problème a été résolu. -Si vous planifiez un article à publier dans le passé, il s’affichera comme « planifié » à la place de « publié ». C’est corrigé ainsi qu’un problème où les dates ne s’affichaient pas correctement si le fuseau horaire de votre site n’était pas le même que celui de votre appareil. +Parfois, lorsque vous tentiez d’ajouter une @-mention pour identifier quelqu’un dans un commentaire, l’application suggérait des noms, mais il n’était pas possible de les sélectionner. Ce problème a également été résolu. Nous avons également résolu d’autres petits problèmes qui provoquaient le plantage de l’application lors des tentatives d’accès aux articles de votre blog via l’écran Pages du site ou l’utilisation des boutons d’action rapide sur les iPads. Et puisque nous parlons de pages et d’articles, les aperçus sont désormais plus grands sur les iPads exécutant iOS 13. -Nous avons corrigé quelques incohérences visuelles en mode foncé et avec la barre d’outil du Lecteur. +Pour nous, le mode sombre, c’est comme le bon café, il doit être bien noir et uniforme. Les modes sombre et clair se comportaient parfois bizarrement lorsque vous passiez de l’un à l’autre, c’est désormais beaucoup plus rare. -Vous pouvez utiliser les mentions « @ » quand vous écrivez des commentaires en plein écran. - -Parfois les e-mails d’inscription et de connexion par lien magique allaient dans les indésirables, nous avons ajouté une note à l’écran de confirmation vous suggérant de vérifier vos indésirables si vous ne voyez pas cet e-mail rapidement. - -Portez-vous bien ! +Prenez tous soin de vous. diff --git a/Scripts/fastlane/metadata/id/release_notes.txt b/Scripts/fastlane/metadata/id/release_notes.txt index c1845206dade..80713c665e4c 100644 --- a/Scripts/fastlane/metadata/id/release_notes.txt +++ b/Scripts/fastlane/metadata/id/release_notes.txt @@ -1,15 +1,11 @@ -Blok baru! Sambut blok Pos Terbaru, untuk menyoroti item baru Anda di pos dan halaman. +Pembuatan Situs telah disederhanakan dengan menghapus beberapa langkah yang tidak efisien. Pilih jenis situs yang Anda inginkan, masukkan nama domain, dan selesai. -Saat Anda membuat halaman baru, kami akan menampilkan beberapa template — pilih satu, dan kami akan menambahkan semua blok yang dibutuhkan ke halaman Anda agar Anda dapat memulai halaman dengan cepat. +Penyunting Blok mendapatkan penyempurnaan: Blok Sampul kini tersedia; ada opsi perataan di blok Heading; dan bilah peralatan tarik turun memudahkan perataan untuk blok Heading, Paragraf, Gambar, dan MediaText. Kami meniadakan efek peredupan pada blok yang tidak dipilih. -Kami memperbaiki beberapa masalah penyunting blok, tak ada lagi crash saat menempel di konten HTML ataupun tepi blok Kutipan yang tidak muncul di Mode Gelap. +Tata letak layar Pemberitahuan menyebabkan teks hilang ke bawah casing sebagian model ponsel. Sudah kami perbaiki! -Jika Anda menjadwalkan pos untuk dipublikasikan di masa lalu, itu akan ditampilkan sebagai “dijadwalkan” bukan “dipublikasikan”. Telah diperbaiki, bersama dengan masalah tanggal yang tidak ditampilkan secara tepat jika zona waktu situs Anda tidak sama dengan zona waktu perangkat. +Terkadang Anda ingin @-menyebut seseorang pada sebuah komentar, dan aplikasi akan menyarankan beberapa nama tetapi tidak dapat dipilih — kami juga telah memperbaikinya. Kami juga memperbaiki beberapa masalah lain yang menyebabkan aplikasi tertutup paksa saat Anda mencoba mengakses layar Halaman Situs atau menggunakan tombol Tindakan Cepat di iPad untuk membuka pos blog. Untuk halaman dan pos, pratinjau kini lebih besar untuk iPad yang menjalankan iOS 13. -Kami memperbaiki beberapa UI yang tidak konsisten di Mode Gelap dan dengan bilah peralatan Pembaca. +Seperti halnya kopi kesukaan kita, Mode Gelap itu seharusnya: hitam pekat dan bisa dinikmati tanpa gangguan. Ada beberapa gangguan pada Mode Gelap dan Terang, dan terkadang aplikasi menjadi aneh saat Anda beralih dari satu mode ke mode lain. Gangguan tersebut kini jauh berkurang. -Anda dapat menggunakan @-sebut saat menulis komentar di mode layar penuh. - -Terkadang email Tautan Ajaib Daftar dan Login akan masuk ke folder spam, sehingga kami menambahkan catatan ke layar konfirmasi untuk menyarankan pengguna agar memeriksa folder spam/sampah mereka jika tidak kunjung menemukan emailnya. - -Selalu jaga kesehatan, semuanya. +Tetaplah aman, kawan. diff --git a/Scripts/fastlane/metadata/it/release_notes.txt b/Scripts/fastlane/metadata/it/release_notes.txt index bd9079c8b462..4ec0ab7a67e0 100644 --- a/Scripts/fastlane/metadata/it/release_notes.txt +++ b/Scripts/fastlane/metadata/it/release_notes.txt @@ -1,15 +1,11 @@ -Nuovi blocchi. Dai il benvenuto al blocco Ultimi articoli, per mettere in evidenza nuovi elementi su articoli e pagine. +La creazione del sito è stata semplificata rimuovendo alcuni passaggi intermedi. Scegli il genere di sito che vuoi, inserisci il nome di dominio ed è fatta. -Quando crei una nuova pagina, ti mostreremo alcuni modelli: scegline uno e noi aggiungeremo tutti i blocchi necessari alla tua pagina per farti iniziare velocemente. +L'editor a blocchi è stato ulteriormente migliorato: è ora disponibile il blocco Copertina, sono presenti opzioni di allineamento nel blocco Titolo e una barra degli strumenti del menu a discesa rende più semplice gli allineamento per i blocchi Titolo, Paragrafo, Immagine e Media e testo. Abbiamo rimosso l'effetto di attenuazione sui blocchi non selezionati. -Abbiamo risolto alcuni problemi relativi all'editor a blocchi; non si verificano più arresti anomali quando si incollano i contenuti HTML o quando mancano i bordi del blocco Citazione in Dark Mode. +Il layout della schermata Notifiche causava la perdita di testo sotto il case di alcuni modelli di telefoni cellulari. Il problema è stato risolto. -Quando in passato pianificavi la pubblicazione di un articolo, veniva visualizzato come "pianificato" anziché "pubblicato". Questo problema è stato risolto, insieme a un altro che causava la visualizzazione errata delle date venivano visualizzate se il fuso orario del sito non era lo stesso del fuso orario del dispositivo. +Alcune volte hai provato a citare qualcuno tramite la @ in un commento e l'app ti ha suggerito dei nomi ma non te ne ha fatto selezionare uno. Questo problema è stato risolto. Ci siamo inoltre occuparti di alcuni altri problemi a causa dei quali l'app si arrestava in modo anomalo quando provavi ad accedere alla schermata Pagine del sito o a usare i pulsanti Azione rapida sull'iPad per ottenere gli articoli del tuo blog. Inoltre, parlando di pagine e articoli, le anteprime sono ora più grandi su iPad su cui è in esecuzione iOS 13. -Abbiamo corretto alcune incoerenze nella UI in Dark Mode e nella barra degli strumenti del Reader. +Amiamo la nostra Dark Mode come il nostro caffè: molto scura e senza alcun bug che la circonda. Erano stati riscontrati alcuni problemi tecnici per le modalità Light Mode e Dark Mode e a volte venivano riscontrati strani comportamenti quando si passava dall'una all'altra. Ora sono presenti molti meno problemi. -Puoi usare i riferimenti @ quando scrivi commenti in modalità schermo intero. - -A volte le e-mail di Accedi e Accesso tramite link finivano nella cartella dello spam, quindi abbiamo aggiunto una nota alla schermata di conferma in cui viene suggerito di controllare le cartelle di spam/posta indesiderata se non visualizzano subito le e-mail. - -State tutti al sicuro. +State al sicuro. Tutti. diff --git a/Scripts/fastlane/metadata/ja/release_notes.txt b/Scripts/fastlane/metadata/ja/release_notes.txt index 6b47a02f1d61..a52a71db41f1 100644 --- a/Scripts/fastlane/metadata/ja/release_notes.txt +++ b/Scripts/fastlane/metadata/ja/release_notes.txt @@ -1,15 +1,11 @@ -新しいブロックが登場 !最新の投稿ブロックを使用すると、投稿やページの最新情報を強調表示できます。 +中間的な段階をいくつか削除することで、サイト作成のプロセスを合理化しました。サイトの種類を選び、ドメイン名を入力すれば完了です。 -新しいページを作成すると、いくつかのテンプレートが表示されます。テンプレートを1つ選択すると、必要なすべてのブロックがページに追加され、すぐに使えるようになります。 +ブロックエディターがさらに改良されました。カバーブロックが利用できるようになり、見出しブロックのドロップダウンのツールバーオプションでで見出し、段落、画像、メディアテキストブロックの整列が簡単にできるようになりました。また、選択されていないブロックの調光効果を削除しました。 -ブロックエディターのいくつかの問題を修正しました。HTML コンテンツに貼り付けたとき、またはダークモードで引用ブロックの境界線が表示されなくなったときに、クラッシュすることがなくなりました。 +通知画面のレイアウトにより、携帯端末の一部のモデルでテキストが失われる原因となっていました。これを修正しました。 -投稿を過去に公開するように予約した場合、「公開済み」ではなく「予約済み」と表示されることがありました。この問題は、サイトのタイムゾーンがデバイスのタイムゾーンと異なる場合に日付が正しく表示されない問題とあわせて修正されました。 +コメントで誰かに @ 付きメンションをしようとすると、アプリが名前を提案しても選択できないことがありましたが、これも修正しました。また、サイトページ画面にアクセスしようとしたり、iPad のクイックアクションボタンを使ってブログ記事にアクセスしようとするとアプリがクラッシュしてしまう問題も修正しました。iOS 13の iPad ではページと投稿のプレビューが大きくなりました。 -ダークモードと Reader ツールバーで統一されていなかった一部の UI を修正しました。 +ライトモードとダークモードにいくつか不具合があり、切り替えたときに奇妙な動作をすることがありました。今では、そのような不具合ははるかに少なくなりました。 -フルスクリーンモードでコメントを書くときに、@- メンションを使用できます。 - -サインアップとログインのマジックリンクメールがスパムフォルダーに送信される場合があるため、すぐにメールが表示されない場合は、スパムメールや迷惑メールのフォルダーを確認するように促すメッセージを確認画面に追加しました。 - -身の安全を守りましょう。 +それでは皆さん、お体にお気をつけてお過ごしください ! diff --git a/Scripts/fastlane/metadata/nl-NL/release_notes.txt b/Scripts/fastlane/metadata/nl-NL/release_notes.txt index 261cb057ff6e..14d44627e65e 100644 --- a/Scripts/fastlane/metadata/nl-NL/release_notes.txt +++ b/Scripts/fastlane/metadata/nl-NL/release_notes.txt @@ -1,15 +1,11 @@ -Nieuwe blokken! Welkom bij het blok Nieuwste berichten, waar je je nieuwe content kunt laten opvallen in berichten en op pagina's. +Het aanmaken van een site is gestroomlijnd door een aantal tussenliggende stappen te verwijderen. Kies het soort site waar je naar op zoek bent, voer een domeinnaam in en je bent klaar. -Wanneer je een nieuwe pagina aanmaakt, laten we je een paar sjablonen zien. Kies er een en wij voegen dan alle benodigde blokken toe aan je pagina zodat je snel aan de slag kunt gaan. +De blokeditor bevat nu nog meer verbeteringen: Het Omslagblok is nu beschikbaar, er zijn uitlijningsopties voor het Titelblok en een vervolgkeuzebalk maakt het eenvoudiger om de blokken Titel, Paragraaf, Afbeelding en MediaTekst uit te lijnen. We hebben ook het dim-effect op niet-geselecteerde blokken verwijderd. -We hebben een paar problemen met de blok-editor verholpen. De app zou nu niet meer moeten crashen wanneer je HTML-content plakt en er zullen geen randen van citaatblokken meer ontbreken in de Nachtmodus. +De opmaak van het scherm Meldingen leidde ertoe dat tekst verloren kon raken onder de behuizing van sommige modellen mobiele telefoons. Dat is nu opgelost! -Als je voorheen berichten inplande voor publicatie, werden ze weergegeven als 'gepland' in plaats van als 'gepubliceerd'. Dat probleem is opgelost, net als het probleem waarbij data onjuist werden weergegeven als de tijdzone van je site niet hetzelfde was als de tijdzone van je apparaat. +Soms probeerde je iemand aan te spreken via een @-melding in de reactie en gaf de app je een aantal suggesties, maar kon je geen naam kiezen. Dat probleem is nu ook verholpen. We hebben ook een paar andere problemen aangepakt die zorgden dat de app crashte wanneer je toegang probeerde te krijgen tot het scherm Sitepagina's of wanneer je probeerde de knoppen voor snelle acties te gebruiken op een iPad om je blogberichten te openen. En over pagina's en berichten gesproken: voorbeeldweergaven zijn nu groter voor iPads waarop iOS 13 wordt uitgevoerd. -We hebben UI-inconsistenties opgemerkt in de Nachtmodus en met de Reader-werkbalk. - -Je kunt @-verwijzingen gebruiken wanneer je reacties schrijft in de weergave op volledig scherm. - -Soms werden e-mails om aan te melden en in te loggen via magische links naar je map met ongewenste e-mail verplaatst. Daarom hebben we een opmerking aan het bevestigingsscherm toegevoegd waarin staat dat je je mappen Spam/Ongewenste e-mail moet controleren als je de e-mail niet snel ontvangt. +We willen de nachtmodus echt zoals we onze koffie willen: heel donker en zonder dat er bugs in ronddrijven. Er zijn nog wat glitches aanwezig in de dag- en nachtmodus, wat soms tot vreemde problemen leidde wanneer je overschakelde van de ene op de andere. Er zijn nu een hoop minder glitches. Blijf veilig allemaal. diff --git a/Scripts/fastlane/metadata/pt-BR/release_notes.txt b/Scripts/fastlane/metadata/pt-BR/release_notes.txt deleted file mode 100644 index 91fe6d4056fd..000000000000 --- a/Scripts/fastlane/metadata/pt-BR/release_notes.txt +++ /dev/null @@ -1,15 +0,0 @@ -Novos blocos! Desejem boas-vindas para o bloco "Posts recentes", que mostra seu conteúdo recente em posts e páginas. - -Ao criar uma nova página, mostraremos alguns modelos. Escolha um modelo e os blocos necessários serão adicionados à página automaticamente para agilizar o processo de criação. - -Corrigimos alguns erros no editor de blocos. Você não verá mais travamentos ao colar conteúdo HTML ou falta de bordas no bloco de Citação no modo escuro. - -Ao agendar um post para ser publicado no passado, ele era exibido como agendado ao invés de publicado. Corrigimos esse comportamento e também um erro na exibição de datas quando o fuso horário do site não é o mesmo do dispositivo. - -Corrigimos inconsistências na interface no modo escuro e na barra do Leitor. - -Agora você pode usar @-menções ao digitar comentários no modo de tela cheia. - -Como os e-mails de inscrição e login podem ir para sua pasta de spam, adicionamos uma nota na tela de confirmação para lembrar nossos usuários de verificarem a pasta de spam caso o e-mail não seja exibido na pasta principal. - -Continuem em segurança e lavem as mãos :-) diff --git a/Scripts/fastlane/metadata/ru/release_notes.txt b/Scripts/fastlane/metadata/ru/release_notes.txt index 612b30bef9e8..496851bf7b62 100644 --- a/Scripts/fastlane/metadata/ru/release_notes.txt +++ b/Scripts/fastlane/metadata/ru/release_notes.txt @@ -1,13 +1,9 @@ -Новые блоки! Блок последних записей, чтобы выделить ваши свежие записи и страницы. +Создание сайта было упрощено путем удаления некоторых промежуточных шагов. Выберите тип сайта, который вы хотите, введите доменное имя, и все готово. -При создании новой страницы можно выбрать шаблон и вы получите страничку со всеми нужными блоками, чтобы быстро её наполнить. +Редактор блоков получил больше улучшений: теперь доступен блок Cover; в блоке заголовка есть варианты выравнивания; а выпадающая панель инструментов облегчает выравнивание для блоков «Заголовок», «Абзац», «Изображение», «Медиа+Текст». И мы убрали эффект затемнения на невыбранных блоках. -Исправлены проблемы с границами цитат в тёмном режиме и падение при вставке HTML-содержимого. +Макет экрана уведомлений приводил к потере текста под корпусом некоторых моделей мобильных телефонов. Это исправлено! -Мы исправили статус публикации для записей, если вы устанавливаете время публикации в прошлом, а также при несовпадении часового пояса сайта и устройства. +Иногда вы пытаетесь @-упомянуть кого-то в комментарии, и приложение предлагает имена, но не позволяет выбрать одно - это тоже исправлено. Мы также позаботились о некоторых других проблемах, которые приводили к сбою приложения при попытке доступа к экрану «Страницы сайта» или использования кнопок быстрого действия на iPad для доступа к сообщениям в блоге. Что касается страниц и записей, то размер превью теперь больше для iPad под iOS 13. -Исправлены некоторые проблемы интерфейса в темном режиме с панелью чтива. - -Теперь вы можете использовать @-упоминания в полноэкранном режиме ответа на комментарии. - -Иногда письма с магической ссылкой для входа или сброса пароля попадают в спам, мы добавили совет посмотреть там. +Нам нравится наш Темный режим, как и наш кофе: очень темный. В светлых и темных режимах были некоторые сбои, и иногда все происходило странно, когда вы переключались с одного на другой. Сейчас таких глюков гораздо меньше. diff --git a/Scripts/fastlane/metadata/sv/release_notes.txt b/Scripts/fastlane/metadata/sv/release_notes.txt index 932dccb2bbbb..cd739b5904e7 100644 --- a/Scripts/fastlane/metadata/sv/release_notes.txt +++ b/Scripts/fastlane/metadata/sv/release_notes.txt @@ -1,15 +1,11 @@ -Nya block! Låt oss presentera blocket för de senaste inläggen, som låter dig lyfta fram dina nyheter bland inlägg och sidor. +Det har nu blivit mycket smidigare att skapa en ny webbplats genom att några mellanliggande steg har tagits bort. Välj vilken typ av webbplats du vill ha och skriv in ett domännamn, så är det klar. -När du skapar en ny sida visar vi några mallar. När du väljer någon av dem lägger vi in de block som behövs på sidan för att du ska komma igång snabbt. +Blockredigeraren har förbättrats på flera sätt: Nu har du tillgång till blocket ”Överlägg”, I rubrikblocket kan du välja textjustering, och en verktygslist med rullgardinsmeny underlättar textjustering för block av typerna rubrik, stycke, bild och mediatext. Dessutom har vi tagit bort nedtoningseffekten på omarkerade block. -Vi har löst några problem med blockredigeraren: inga fler programkrascher när du klistrar in HTML-innehåll. Och citatblockets kanter försvinner inte längre, ens om du använder mörkt läge. +På vissa mobiler gjorde layouten av aviseringsskärmen att text kunde bli oåtkomlig – nu är det rättat! -Om du schemalade ett inlägg för publicering i förfluten tid visades det tidigare som ”schemalagt” i stället för ”publicerat”. Det är nu rättat, liksom ett annat problem där datum kunde visas på fel sätt om din webbplats och din telefon var inställda på olika tidszoner. +Ibland kanske du försökte att @-nämna någon i en kommentar, appen föreslog olika namn men det gick inte att välja något av dem. Det är också fixat nu. Vidare har vi tagit hand om några problem som kunde få appen att krascha när du försökte öppna skärmen med webbplatsens sidor eller använda snabbåtgärder på iPad för att komma till dina blogginlägg. På tal om sidor och inlägg – nu är förhandsvisningen i ett större format för iPad som kör iOS 13. -Vi har rättat några inkonsekventa saker i användargränssnittet i mörkt läge och med verktygslisten i läsaren. +Vi vill att vårt Dark Mode (mörkt läge) ska vara som kaffet vi dricker: svart som natten och utan diverse programfel. Det fanns olika missar i funktionen för ljust och mörkt läge. Ibland hände konstiga saker vid växling mellan lägena. Nu är missarna inte lika många längre. -När du skriver kommentarer i helskärmsläge kan du @-nämna personer. - -Ibland händer det att e-post med länkar för registrering och magisk inloggning hamnar i skräpfoldern, så på bekräftelsesidan har vi lagt till en påminnelse om att kolla i mapparna för spam och reklam om de inte hittar meddelandet direkt. - -Ta hand om er. +Var försiktiga och ta hand om er. diff --git a/Scripts/fastlane/metadata/tr/release_notes.txt b/Scripts/fastlane/metadata/tr/release_notes.txt index 241c2cff823c..efd313f6a6ea 100644 --- a/Scripts/fastlane/metadata/tr/release_notes.txt +++ b/Scripts/fastlane/metadata/tr/release_notes.txt @@ -1,15 +1,11 @@ -Yeni bloklar! Güncel yazılar bloğuna hoş geldin diyoruz, yeni yazı ve sayfalarınızı vurgulamak için kullanabilirsiniz. +Site Oluşturma, bazı ara adımlar kaldırılarak kolaylaştırıldı. İstediğiniz site türünü seçin, bir alan adı girin ve işiniz bitti. -Yeni bir sayfa oluşturduğunuzda, bir kaç şablon gösteriyoruz — birini seçin, ve gerekli tüm blokları sayfanıza ekleyerek işe başlamanızı sağlayalım. +Blok düzenleyicide daha fazla iyileştirme yapıldı: Kapak bloğu artık kullanılabilir; Başlık bloğunda hizalama seçenekleri vardır; ve bir açılır araç çubuğu Başlık, Paragraf, Resim, MediaText blokları için hizalamayı kolaylaştırır. Ve seçilmemiş bloklar üzerindeki karartma efektini kaldırdık. -Bir kaç blok düzenleyici sorununu düzelttik; HTML içerik yapıştırırken çökmeler ya da koyu modda alıntı bloğunun çerçevesinin olmaması gibi problemlere son. +Bildirimler ekranının düzeni, bazı cep telefonu modellerinin gövdesi altında metnin kaybolmasına neden oldu. Bu düzeltildi! -Eğer geçmişte bir yazı zamanladıysanız "zamanlanmış" yerine "yayımlanmış" olarak gözüküyordu. Bu düzeltildi, beraberinde sitenin zaman diliminin cihazınızın zaman dilimiyle aynı olmadığı durumlarda ortaya çıkan bir sorun da düzeltildi. +Bazen bir yorumda bir kişiyi @ belirtmeye çalışırsınız ve uygulama adları önerir, ancak bir tane seçmenize izin vermez - bu da düzeltildi. Ayrıca, Site Sayfaları ekranına erişmeye çalıştığınızda veya blog yayınlarınıza ulaşmak için iPad'lerde Hızlı İşlem düğmelerini kullandığınızda uygulamanın kilitlenmesine neden olan diğer birkaç sorunu da hallettik. Sayfalardan ve yayınlardan bahsetmişken, iOS 13 çalıştıran iPad'ler için önizlemeler artık daha büyük. -Koyu modda ve okuyucu araç çubuğundaki bazı kullanıcı arabirimi uyumsuzluklarını giderdik. +Karanlık modumuzu kahvemizi sevdiğimiz gibi seviyoruz: gerçekten karanlık ve içinde yüzen herhangi bir hata olmadan. Aydınlık ve karanlık modlarında bazı aksaklıklar vardı ve bazen birinden diğerine geçtiğinizde işler garip bir şekilde davrandı. Şu anda bu hatalardan çok daha azı var. -Tam ekran modunda yorum yazarken @-anmalarını kullanabilirsiniz. - -Bazen üyelik ve giriş sihirli bağlantı e-postaları istenmeyen postalar arasına gidebiliyor, biz de onaylama ekranına kullanıcıların istenmeyen/önemsiz posta kutularını kontrol etmelerini hatırlatır bir açıklama ekledik. - -Herkese sağlık ve güvenlik. +Hepiniz güvende olun. diff --git a/Scripts/fastlane/metadata/zh-Hans/release_notes.txt b/Scripts/fastlane/metadata/zh-Hans/release_notes.txt index 3d1fa74a3eef..b064c5441862 100644 --- a/Scripts/fastlane/metadata/zh-Hans/release_notes.txt +++ b/Scripts/fastlane/metadata/zh-Hans/release_notes.txt @@ -1,15 +1,11 @@ -新区块!欢迎使用“最新文章”区块,突出显示文章和页面中的新内容。 +站点创建流程实现简化,去掉了一些中间步骤。选择您需要的站点类型,输入域名,即可大功告成。 -当您创建新页面时,我们将展示几个模板供您选择,然后我们将为您的页面添加所有必要的区块,帮助您快速入门。 +区块编辑器得到了更多改进:现已推出区块编辑器;在“标题”区块中添加了对齐选项;添加了下拉工具栏,便于用户在“标题”、“段落”、“图像”和“MediaText”区块中更轻松地执行对齐操作。而且,我们去除了未选定区块上的暗淡效果。 -我们修复了一些区块编辑器问题;在 HTML 内容中进行粘贴或“暗黑模式”中缺失“引用”区块边框时,不再发生崩溃。 +“通知”屏幕的布局曾导致文本因某些型号的手机外壳的遮挡而无法显示。此问题已解决! -如果您在过去安排了要发布的文章,它将显示为“已安排”而不是“已发布”。此问题已修复,同时还修复了另一个问题,即如果站点时区与设备时区不同,则日期显示错误。 +有时,如果您尝试在评论中使用 @ 提及某人,应用程序会提供建议姓名,但之后您却无法选择 - 这个问题也已得到修复。我们还解决了如下一些问题:当您尝试访问“站点页面”屏幕或在 iPad 上使用“快速操作”按钮访问博客文章时,导致应用程序崩溃的问题。说到页面和文章,在运行 iOS 13 的 iPad 上,预览画面现在增大了。 -我们修复了“暗黑模式”与“阅读器”工具栏中一些 UI 不一致的问题。 +我们对深色模式的喜爱就如对咖啡的喜爱一样:颜色至深,而且其中不漂浮任何杂物。浅色模式和深色模式下曾有一些小问题,当您从一种模式切换到另一种模式时,某些地方表现得很奇怪。现在,这些小问题已经少之又少。 -您在全屏模式下撰写评论时可以使用 @-mentions。 - -有时,注册和免密码登录链接电子邮件会进入垃圾邮件文件夹,所以我们在确认屏幕上添加了一个备注,建议大家如果没有及时看到电子邮件,则检查他们的垃圾邮件/垃圾文件夹。 - -请大家注意安全。 +请大家确保安全。 diff --git a/Scripts/fastlane/metadata/zh-Hant/release_notes.txt b/Scripts/fastlane/metadata/zh-Hant/release_notes.txt index 0fab13d49234..940d8951f815 100644 --- a/Scripts/fastlane/metadata/zh-Hant/release_notes.txt +++ b/Scripts/fastlane/metadata/zh-Hant/release_notes.txt @@ -1,15 +1,11 @@ -新增區塊!歡迎使用最新文章區塊,藉此突顯你的文章和頁面中的最新內容。 +我們已移除一些中間步驟,簡化網站建立程序。挑選你想要的網站並輸入網域名稱,就大功告成了。 -當你建立新頁面時,我們會顯示幾個範本。選擇一個範本後,我們會為頁面新增所有必要區塊,讓你快速開始建立頁面。 +區塊編輯器也推出更多改善功能:你現在能夠使用封面區塊;標題區塊提供對齊選項;標題、段落、圖片、媒體文字區塊的下拉式工具列可讓對齊變得更輕鬆。我們也移除了未選取區塊的變暗效果。 -我們已修復幾個區塊編輯器問題;現在貼上 HTML 內容時不會再當機,深色模式也不會再導致引文區塊邊線消失。 +「通知」畫面的版面形式,會導致部分手機型號文字大小寫混亂的情況。此問題已獲得修正! -若你過去曾排程文章發佈時間,系統會將文章顯示為「已排程」而非「已發佈」。我們已修復此問題,以及當你的網站時區與裝置時區不同時,會導致日期顯示錯誤的問題。 +你有時會在留言中嘗試使用 @ 提及某人,應用程式雖會建議標註的姓名卻不讓你選擇,此問題也已獲得修正。我們也已解決其他幾個問題,這些問題過去會在你嘗試存取「網站頁面」畫面或使用 iPad 的「快速動作」按鈕時,導致應用程式當機。至於頁面和文章,執行 iOS 13 版本的 iPad 現可享有更大的預覽畫面。 -我們已修復部分 UI 在深色模式以及與閱讀器工具列不一致的問題。 +我們喜歡「深色模式」,就如同對咖啡的喜愛一般:顏色深沉,運作不會受任何錯誤干擾。「淺色模式」和「深色模式」過去會出現一些故障,互相切換有時也無法正常運作。現在這些故障情況已大幅減少了。 -在全螢幕模式撰寫留言時,你可以使用 @ 標記他人。 - -有時註冊和登入神奇連結電子郵件會傳送到你的垃圾郵件資料夾,因此我們已在確認畫面新增備註,建議使用者若沒有很快地收到電子郵件,應檢查垃圾郵件資料夾。 - -請各位注意安全。 +各位,請保持安全。 diff --git a/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift b/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift new file mode 100644 index 000000000000..b19b15960aaf --- /dev/null +++ b/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift @@ -0,0 +1,18 @@ +import Foundation + +extension AbstractPost { + static let passwordProtectedLabel = NSLocalizedString("Password protected", comment: "Privacy setting for posts set to 'Password protected'. Should be the same as in core WP.") + static let privateLabel = NSLocalizedString("Private", comment: "Privacy setting for posts set to 'Private'. Should be the same as in core WP.") + static let publicLabel = NSLocalizedString("Public", comment: "Privacy setting for posts set to 'Public' (default). Should be the same as in core WP.") + + /// A title describing the status. Ie.: "Public" or "Private" or "Password protected" + @objc var titleForVisibility: String { + if password != nil { + return AbstractPost.passwordProtectedLabel + } else if status == .publishPrivate { + return AbstractPost.privateLabel + } + + return AbstractPost.publicLabel + } +} diff --git a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift index f67f0b77202c..4bdda7f03178 100644 --- a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift +++ b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift @@ -7,6 +7,13 @@ import Foundation case editorCreatedPage case createSheetShown + // Prepublishing Nudges + case editorPostPublishTap + case editorPostScheduled + case editorPostVisibilityChanged + case editorPostTagsAdded + case editorPostPublishNowTapped + /// A String that represents the event var value: String { switch self { @@ -18,6 +25,16 @@ import Foundation return "editor_page_created" case .createSheetShown: return "create_sheet_shown" + case .editorPostPublishTap: + return "editor_post_publish_tapped" + case .editorPostScheduled: + return "editor_post_scheduled" + case .editorPostVisibilityChanged: + return "editor_post_visibility_changed" + case .editorPostTagsAdded: + return "editor_post_tags_added" + case .editorPostPublishNowTapped: + return "editor_post_publish_now_tapped" } } diff --git a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift index d33b3c20d4ab..53efda46b936 100644 --- a/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift +++ b/WordPress/Classes/Utility/Bottom Sheet/BottomSheetViewController.swift @@ -5,7 +5,6 @@ class BottomSheetViewController: UIViewController { static let gripHeight: CGFloat = 5 static let cornerRadius: CGFloat = 8 static let buttonSpacing: CGFloat = 8 - static let additionalSafeAreaInsetsRegular: UIEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0) static let minimumWidth: CGFloat = 300 enum Header { @@ -27,16 +26,24 @@ class BottomSheetViewController: UIViewController { } } + private var customHeaderSpacing: CGFloat? + + /// Additional safe are insets for regular horizontal size class + var additionalSafeAreaInsetsRegular: UIEdgeInsets = .zero + private weak var childViewController: DrawerPresentableViewController? - init(childViewController: DrawerPresentableViewController) { + init(childViewController: DrawerPresentableViewController, + customHeaderSpacing: CGFloat? = nil) { self.childViewController = childViewController + self.customHeaderSpacing = customHeaderSpacing super.init(nibName: nil, bundle: nil) } - func show(from presenting: UIViewController, sourceView: UIView? = nil) { + func show(from presenting: UIViewController, sourceView: UIView? = nil, arrowDirections: UIPopoverArrowDirection = .any) { if UIDevice.isPad() { modalPresentationStyle = .popover + popoverPresentationController?.permittedArrowDirections = arrowDirections popoverPresentationController?.sourceView = sourceView ?? UIView() popoverPresentationController?.sourceRect = sourceView?.bounds ?? .zero } else { @@ -58,6 +65,8 @@ class BottomSheetViewController: UIViewController { return button }() + private var stackView: UIStackView! + @objc func buttonPressed() { dismiss(animated: true, completion: nil) } @@ -84,12 +93,12 @@ class BottomSheetViewController: UIViewController { addChild(childViewController) - let stackView = UIStackView(arrangedSubviews: [ + stackView = UIStackView(arrangedSubviews: [ gripButton, childViewController.view ]) - stackView.setCustomSpacing(Constants.Header.spacing, after: gripButton) + stackView.setCustomSpacing(customHeaderSpacing ?? Constants.Header.spacing, after: gripButton) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical @@ -107,21 +116,30 @@ class BottomSheetViewController: UIViewController { refreshForTraits() } + override var preferredContentSize: CGSize { + set { + childViewController?.preferredContentSize = newValue + } + get { + return childViewController?.preferredContentSize ?? super.preferredContentSize + } + } + + override func accessibilityPerformEscape() -> Bool { + dismiss(animated: true, completion: nil) + return true + } + private func refreshForTraits() { if presentingViewController?.traitCollection.horizontalSizeClass == .regular && presentingViewController?.traitCollection.verticalSizeClass != .compact { gripButton.isHidden = true - additionalSafeAreaInsets = Constants.additionalSafeAreaInsetsRegular + additionalSafeAreaInsets = additionalSafeAreaInsetsRegular } else { gripButton.isHidden = false additionalSafeAreaInsets = .zero } } - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - return preferredContentSize = CGSize(width: Constants.minimumWidth, height: view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height) - } - @objc func keyboardWillShow(_ notification: NSNotification) { self.presentedVC?.transition(to: .expanded) } @@ -147,6 +165,10 @@ extension BottomSheetViewController: UIViewControllerTransitioningDelegate { // MARK: - DrawerDelegate extension BottomSheetViewController: DrawerPresentable { + var allowsUserTransition: Bool { + return childViewController?.allowsUserTransition ?? true + } + var compactWidth: DrawerWidth { childViewController?.compactWidth ?? .percentage(0.66) } diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index ad0e225bb5c3..b59a6bd67be0 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -4,7 +4,6 @@ enum FeatureFlag: Int, CaseIterable { case jetpackDisconnect case debugMenu - case postReblogging case unifiedAuth case quickActions case meMove @@ -23,8 +22,6 @@ enum FeatureFlag: Int, CaseIterable { case .debugMenu: return BuildConfiguration.current ~= [.localDeveloper, .a8cBranchTest] - case .postReblogging: - return true case .unifiedAuth: return BuildConfiguration.current == .localDeveloper case .quickActions: @@ -57,8 +54,6 @@ extension FeatureFlag: OverrideableFlag { return "Jetpack disconnect" case .debugMenu: return "Debug menu" - case .postReblogging: - return "Post Reblogging" case .unifiedAuth: return "Unified Auth" case .quickActions: @@ -78,6 +73,8 @@ extension FeatureFlag: OverrideableFlag { return false case .floatingCreateButton: return false + case .newReaderNavigation: + return false default: return true } diff --git a/WordPress/Classes/Utility/Media/GIFPlaybackStrategy.swift b/WordPress/Classes/Utility/Media/GIFPlaybackStrategy.swift index 68f93387316f..f78baad37d0a 100644 --- a/WordPress/Classes/Utility/Media/GIFPlaybackStrategy.swift +++ b/WordPress/Classes/Utility/Media/GIFPlaybackStrategy.swift @@ -54,18 +54,18 @@ extension GIFPlaybackStrategy { class SmallGIFPlaybackStrategy: GIFPlaybackStrategy { var maxSize = 8_000_000 // in MB - var frameBufferCount = 25 + var frameBufferCount = 50 var gifStrategy: GIFStrategy = .smallGIFs } class MediumGIFPlaybackStrategy: GIFPlaybackStrategy { var maxSize = 20_000_000 // in MB - var frameBufferCount = 50 + var frameBufferCount = 150 var gifStrategy: GIFStrategy = .mediumGIFs } class LargeGIFPlaybackStrategy: GIFPlaybackStrategy { var maxSize = 50_000_000 // in MB - var frameBufferCount = 60 + var frameBufferCount = 300 var gifStrategy: GIFStrategy = .largeGIFs } diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 8326b364d7bb..df4d67cce11d 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -2438,14 +2438,16 @@ extension AztecPostViewController { } } - private func insertImageAttachment(with url: URL = Constants.placeholderMediaLink) -> ImageAttachment { + private func insertImageAttachment(with url: URL = Constants.placeholderMediaLink, caption: String? = nil) -> ImageAttachment { let attachment = richTextView.replaceWithImage(at: self.richTextView.selectedRange, sourceURL: url, placeHolderImage: Assets.defaultMissingImage) attachment.size = .full if url.isGif { attachment.badgeTitle = Constants.mediaGIFBadgeTitle } - + if let caption = caption { + richTextView.replaceCaption(for: attachment, with: NSAttributedString(string: caption)) + } return attachment } @@ -2519,7 +2521,7 @@ extension AztecPostViewController { } switch media.mediaType { case .image: - let attachment = insertImageAttachment(with: remoteURL) + let attachment = insertImageAttachment(with: remoteURL, caption: media.caption) attachment.alt = media.alt WPAppAnalytics.track(.editorAddedPhotoViaWPMediaLibrary, withProperties: WPAppAnalytics.properties(for: media, selectionMethod: mediaSelectionMethod), with: post) case .video: @@ -2549,7 +2551,7 @@ extension AztecPostViewController { } var attachment: MediaAttachment? if media.mediaType == .image { - attachment = insertImageAttachment(with: tempMediaURL) + attachment = insertImageAttachment(with: tempMediaURL, caption: media.caption) } else if media.mediaType == .video, let remoteURLStr = media.remoteURL, let remoteURL = URL(string: remoteURLStr) { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m index 1536bdabbbff..6413c9c968fb 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m @@ -350,7 +350,7 @@ - (void)viewWillAppear:(BOOL)animated self.restorableSelectedIndexPath = nil; } - self.navigationItem.title = self.blog.settings.name; + self.navigationItem.title = NSLocalizedString(@"My Site", @"Title of My Site tab"); [self.headerView setBlog:self.blog]; @@ -1644,7 +1644,6 @@ - (void)handleDataModelChange:(NSNotification *)note NSSet *updatedObjects = note.userInfo[NSUpdatedObjectsKey]; if ([updatedObjects containsObject:self.blog] || [updatedObjects containsObject:self.blog.settings]) { - self.navigationItem.title = self.blog.settings.name; [self configureTableViewData]; BOOL isQuickStartSectionShownAfter = [self findSectionIndexWithSections:self.tableSections category:BlogDetailsSectionCategoryQuickStart] != NSNotFound; diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/NewBlogDetailHeaderView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/NewBlogDetailHeaderView.swift index 114b271481ac..2483345efe45 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/NewBlogDetailHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/Detail Header/NewBlogDetailHeaderView.swift @@ -104,12 +104,6 @@ class NewBlogDetailHeaderView: UIView { stackView.setCustomSpacing(Constants.spacingBelowIcon, after: siteIconView) stackView.setCustomSpacing(Constants.spacingBelowTitle, after: titleLabel) - /// Constraints for larger widths with extra padding (iPhone portrait) - let extraPaddingSideConstraints = [ - buttonsStackView.trailingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: -Constants.buttonsSidePadding), - buttonsStackView.leadingAnchor.constraint(lessThanOrEqualTo: stackView.leadingAnchor, constant: Constants.buttonsSidePadding) - ] - /// Constraints for constrained widths (iPad portrait) let minimumPaddingSideConstraints = [ buttonsStackView.leadingAnchor.constraint(greaterThanOrEqualTo: stackView.leadingAnchor, constant: 0), @@ -119,21 +113,21 @@ class NewBlogDetailHeaderView: UIView { let bottomConstraint = buttonsStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.buttonsBottomPadding) bottomConstraint.priority = UILayoutPriority(999) // Allow to break so encapsulated height (on initial table view load) doesn't spew warnings - /// If we are able to attach to the safe area's leading edge, we should, otherwise it can break - let leadingSafeAreaConstraint = stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor) - leadingSafeAreaConstraint.priority = .defaultHigh + let widthConstraint = buttonsStackView.widthAnchor.constraint(equalToConstant: 320) + widthConstraint.priority = .defaultHigh let edgeConstraints = [ - leadingSafeAreaConstraint, - stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + stackView.trailingAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.trailingAnchor), stackView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: Constants.minimumSideSpacing), stackView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.interSectionSpacing), + stackView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor), buttonsStackView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: Constants.interSectionSpacing), buttonsStackView.centerXAnchor.constraint(equalTo: stackView.centerXAnchor), - bottomConstraint + bottomConstraint, + widthConstraint ] - NSLayoutConstraint.activate(extraPaddingSideConstraints + minimumPaddingSideConstraints + edgeConstraints) + NSLayoutConstraint.activate(minimumPaddingSideConstraints + edgeConstraints) } override init(frame: CGRect) { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift index e9bea690f9af..14fe50ac55cf 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaInserterHelper.swift @@ -29,7 +29,7 @@ class GutenbergMediaInserterHelper: NSObject { func insertFromSiteMediaLibrary(media: [Media], callback: @escaping MediaPickerDidPickMediaCallback) { let formattedMedia = media.map { item in - return MediaInfo(id: item.mediaID?.int32Value, url: item.remoteURL, type: item.mediaTypeString) + return MediaInfo(id: item.mediaID?.int32Value, url: item.remoteURL, type: item.mediaTypeString, caption: item.caption) } callback(formattedMedia) } diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergStockPhotos.swift b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergStockPhotos.swift index 19eee9b9e608..6ddebd0cbbc2 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergStockPhotos.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergStockPhotos.swift @@ -61,7 +61,7 @@ extension GutenbergStockPhotos: StockPhotosPickerDelegate { } let mediaInfo = assets.compactMap({ (asset) -> MediaInfo? in - guard let media = self.mediaInserter.insert(exportableAsset: asset, source: .giphy) else { + guard let media = self.mediaInserter.insert(exportableAsset: asset, source: .stockPhotos) else { return nil } let mediaUploadID = media.gutenbergUploadID @@ -75,7 +75,7 @@ extension GutenbergStockPhotos: StockPhotosPickerDelegate { /// - Parameter assets: Stock Media objects to append. func appendOnNewBlocks(assets: ArraySlice) { assets.forEach { - if let media = self.mediaInserter.insert(exportableAsset: $0, source: .giphy) { + if let media = self.mediaInserter.insert(exportableAsset: $0, source: .stockPhotos) { self.gutenberg.appendMedia(id: media.gutenbergUploadID, url: $0.URL, type: .image) } } diff --git a/WordPress/Classes/ViewRelated/Media/MediaLibraryViewController.swift b/WordPress/Classes/ViewRelated/Media/MediaLibraryViewController.swift index 9e0b8af96d13..079090752e84 100644 --- a/WordPress/Classes/ViewRelated/Media/MediaLibraryViewController.swift +++ b/WordPress/Classes/ViewRelated/Media/MediaLibraryViewController.swift @@ -595,6 +595,15 @@ extension MediaLibraryViewController: WPMediaPickerViewControllerDelegate { updateViewState(for: pickerDataSource.numberOfAssets()) } + + func mediaPickerController(_ picker: WPMediaPickerViewController, handleError error: Error) -> Bool { + let nserror = error as NSError + if let mediaLibrary = self.blog.media, !mediaLibrary.isEmpty { + let title = NSLocalizedString("Unable to Sync", comment: "Title of error prompt shown when a sync the user initiated fails.") + WPError.showNetworkingNotice(title: title, error: nserror) + } + return true + } } // MARK: - State restoration diff --git a/WordPress/Classes/ViewRelated/Plugins/PluginListViewController.swift b/WordPress/Classes/ViewRelated/Plugins/PluginListViewController.swift index 4e1ac9de6a67..19c82bcf8634 100644 --- a/WordPress/Classes/ViewRelated/Plugins/PluginListViewController.swift +++ b/WordPress/Classes/ViewRelated/Plugins/PluginListViewController.swift @@ -95,6 +95,12 @@ class PluginListViewController: UITableViewController, ImmuTablePresenter { case .replace: tableView.reloadData() case .selective(let changedRows): + let tableViewSectionsCount = tableViewModel.sections.count + guard tableViewSectionsCount > 0 else { + tableView.reloadData() + return + } + if tableView.numberOfRows(inSection: 0) == changedRows.count { let indexPaths = changedRows.map { IndexPath(row: $0, section: 0) } tableView.reloadRows(at: indexPaths, with: .none) diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift index eecb1b231bae..d82119dc73dd 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift @@ -55,8 +55,6 @@ extension PostEditor where Self: UIViewController { return } - let isPage = post is Page - let publishBlock = { [unowned self] in if action == .saveAsDraft { self.post.status = .draft @@ -97,22 +95,11 @@ extension PostEditor where Self: UIViewController { } } - let promoBlock = { [unowned self] in - UserDefaults.standard.asyncPromoWasDisplayed = true - - let controller = FancyAlertViewController.makeAsyncPostingAlertController(action: action, isPage: isPage, onConfirm: publishBlock) - controller.modalPresentationStyle = .custom - controller.transitioningDelegate = self - self.present(controller, animated: true, completion: nil) - } - - - if action.isAsync && - !UserDefaults.standard.asyncPromoWasDisplayed { - promoBlock() - } else if action.isAsync, + if action.isAsync, let postStatus = self.post.original?.status ?? self.post.status, ![.publish, .publishPrivate].contains(postStatus) { + WPAnalytics.track(.editorPostPublishTap) + // Only display confirmation alert for unpublished posts displayPublishConfirmationAlert(for: action, onPublish: publishBlock) } else { @@ -144,17 +131,15 @@ extension PostEditor where Self: UIViewController { /// - dismissWhenDone: if `true`, the VC will be dismissed if the user picks "Publish". /// fileprivate func displayPublishConfirmationAlert(for action: PostEditorAction, onPublish publishAction: @escaping () -> ()) { - let title = action.publishingActionQuestionLabel - let keepEditingTitle = NSLocalizedString("Keep Editing", comment: "Button shown when the author is asked for publishing confirmation.") - let publishTitle = action.publishActionLabel - let style: UIAlertController.Style = UIDevice.isPad() ? .alert : .actionSheet - let alertController = UIAlertController(title: title, message: nil, preferredStyle: style) - - alertController.addCancelActionWithTitle(keepEditingTitle) - alertController.addDefaultActionWithTitle(publishTitle) { _ in + // End editing to avoid issues with accessibility + view.endEditing(true) + + let prepublishing = PrepublishingViewController(post: post as! Post) { _ in publishAction() } - present(alertController, animated: true, completion: nil) + let prepublishingNavigationController = PrepublishingNavigationController(rootViewController: prepublishing) + let bottomSheet = BottomSheetViewController(childViewController: prepublishingNavigationController, customHeaderSpacing: 0) + bottomSheet.show(from: self, sourceView: navigationBarManager.publishButton) } private func trackPostSave(stat: WPAnalyticsStat) { diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m index f3fc6efd5a6f..3110b0c82d8b 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m @@ -671,7 +671,7 @@ - (UITableViewCell *)configureMetaPostMetaCellForIndexPath:(NSIndexPath *)indexP // Visibility cell = [self getWPTableViewDisclosureCell]; cell.textLabel.text = NSLocalizedString(@"Visibility", @"The visibility settings of the post. Should be the same as in core WP."); - cell.detailTextLabel.text = [self titleForVisibility]; + cell.detailTextLabel.text = [self.apost titleForVisibility]; cell.tag = PostSettingsRowVisibility; cell.accessibilityIdentifier = @"Visibility"; @@ -1034,59 +1034,11 @@ - (void)showPostStatusSelector - (void)showPostVisibilitySelector { - NSArray *titles = @[ - NSLocalizedString(@"Public", @"Privacy setting for posts set to 'Public' (default). Should be the same as in core WP."), - NSLocalizedString(@"Password protected", @"Privacy setting for posts set to 'Password protected'. Should be the same as in core WP."), - NSLocalizedString(@"Private", @"Privacy setting for posts set to 'Private'. Should be the same as in core WP.") - ]; - NSDictionary *visiblityDict = @{ - @"DefaultValue": NSLocalizedString(@"Public", @"Privacy setting for posts set to 'Public' (default). Should be the same as in core WP."), - @"Title" : NSLocalizedString(@"Visibility", nil), - @"Titles" : titles, - @"Values" : titles, - @"CurrentValue" : [self titleForVisibility]}; - SettingsSelectionViewController *vc = [[SettingsSelectionViewController alloc] initWithDictionary:visiblityDict]; - __weak SettingsSelectionViewController *weakVc = vc; - vc.onItemSelected = ^(NSString *visibility) { + PostVisibilitySelectorViewController *vc = [[PostVisibilitySelectorViewController alloc] init:self.apost]; + __weak PostVisibilitySelectorViewController *weakVc = vc; + vc.completion = ^(NSString *visibility) { + [WPAnalytics trackEvent:WPAnalyticsEventEditorPostVisibilityChanged properties:@{@"via": @"settings"}]; [weakVc dismiss]; - - NSAssert(self.apost != nil, @"The post should not be nil here."); - NSAssert(!self.apost.isFault, @"The post should not be a fault here here."); - NSAssert(self.apost.managedObjectContext != nil, @"The post's MOC should not be nil here."); - - if ([visibility isEqualToString:NSLocalizedString(@"Private", @"Post privacy status in the Post Editor/Settings area (compare with WP core translations).")]) { - self.apost.status = PostStatusPrivate; - self.apost.password = nil; - } else { - if ([self.apost.status isEqualToString:PostStatusPrivate]) { - if ([self.apost.original.status isEqualToString:PostStatusPrivate]) { - self.apost.status = PostStatusPublish; - } else { - // restore the original status - self.apost.status = self.apost.original.status; - } - } - if ([visibility isEqualToString:NSLocalizedString(@"Password protected", @"Post password protection in the Post Editor/Settings area (compare with WP core translations).")]) { - - NSString *password = @""; - - NSAssert(self.apost.original != nil, - @"We're expecting to have a reference to the original post here."); - NSAssert(!self.apost.original.isFault, - @"The original post should not be a fault here here."); - NSAssert(self.apost.original.managedObjectContext != nil, - @"The original post's MOC should not be nil here."); - - if (self.apost.original.password) { - // restore the original password - password = self.apost.original.password; - } - self.apost.password = password; - } else { - self.apost.password = nil; - } - } - [self.tableView reloadData]; }; [self.navigationController pushViewController:vc animated:YES]; @@ -1304,7 +1256,7 @@ - (void)showTagsPicker tagsPicker.onValueChanged = ^(NSString * _Nonnull value) { if (!value.isEmpty) { - [WPAnalytics track:WPAnalyticsStatPostSettingsTagsAdded]; + [WPAnalytics trackEvent:WPAnalyticsEventEditorPostTagsAdded properties:@{@"via": @"settings"}]; } self.post.tags = value; @@ -1330,17 +1282,6 @@ - (void)featuredImageFailedLoading:(NSIndexPath *)indexPath withError:(NSError * cell.textLabel.text = NSLocalizedString(@"Featured Image did not load", @""); } -- (NSString *)titleForVisibility -{ - if (self.apost.password) { - return NSLocalizedString(@"Password protected", @"Privacy setting for posts set to 'Password protected'. Should be the same as in core WP."); - } else if ([self.apost.status isEqualToString:PostStatusPrivate]) { - return NSLocalizedString(@"Private", @"Privacy setting for posts set to 'Private'. Should be the same as in core WP."); - } - - return NSLocalizedString(@"Public", @"Privacy setting for posts set to 'Public' (default). Should be the same as in core WP."); -} - - (NoResultsViewController *)noResultsView { if (!_noResultsView) { diff --git a/WordPress/Classes/ViewRelated/Post/PostTagPickerViewController.swift b/WordPress/Classes/ViewRelated/Post/PostTagPickerViewController.swift index 88bce076fad9..630a4417b786 100644 --- a/WordPress/Classes/ViewRelated/Post/PostTagPickerViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostTagPickerViewController.swift @@ -2,7 +2,7 @@ import Foundation import CocoaLumberjack import WordPressShared -class PostTagPickerViewController: UIViewController { +class PostTagPickerViewController: UIViewController, DrawerPresentable { private let originalTags: [String] @objc var onValueChanged: ((String) -> Void)? @objc let blog: Blog @@ -25,6 +25,7 @@ class PostTagPickerViewController: UIViewController { fileprivate let textView = UITextView() private let textViewContainer = UIView() fileprivate let tableView = UITableView(frame: .zero, style: .grouped) + private let descriptionLabel = UILabel() fileprivate var dataSource: PostTagPickerDataSource = LoadingDataSource() { didSet { tableView.dataSource = dataSource @@ -67,17 +68,28 @@ class PostTagPickerViewController: UIViewController { textViewContainer.addSubview(textView) view.addSubview(textViewContainer) + descriptionLabel.text = NSLocalizedString("Tags help tell readers what a post is about. Separate different tags with commas.", comment: "Label explaining why users might want to add tags.") + descriptionLabel.numberOfLines = 0 + WPStyleGuide.configureLabelForRegularFontStyle(descriptionLabel) + descriptionLabel.textColor = .textSubtle + view.addSubview(descriptionLabel) + textView.translatesAutoresizingMaskIntoConstraints = false textViewContainer.translatesAutoresizingMaskIntoConstraints = false tableView.translatesAutoresizingMaskIntoConstraints = false + descriptionLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ + descriptionLabel.topAnchor.constraint(equalTo: view.readableContentGuide.topAnchor, constant: 10), + descriptionLabel.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), + descriptionLabel.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor), + textView.topAnchor.constraint(equalTo: textViewContainer.topAnchor), textView.bottomAnchor.constraint(equalTo: textViewContainer.bottomAnchor), textView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), textView.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor), - textViewContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 35), + textViewContainer.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 10), textViewContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: -1), textViewContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 1), textViewContainer.bottomAnchor.constraint(equalTo: tableView.topAnchor), @@ -93,13 +105,24 @@ class PostTagPickerViewController: UIViewController { textViewContainer.layer.masksToBounds = false keyboardObserver.tableView = tableView + + let doneButton = UIBarButtonItem(title: NSLocalizedString("Done", comment: "Done button title"), style: .plain, target: self, action: #selector(doneButtonPressed)) + navigationItem.setRightBarButton(doneButton, animated: false) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + textView.becomeFirstResponder() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) updateSuggestions() - textView.becomeFirstResponder() loadTags() + + tableView.contentInset.bottom += descriptionLabel.frame.height + 20 + updateTableViewBottomInset() } override func viewWillDisappear(_ animated: Bool) { @@ -111,6 +134,8 @@ class PostTagPickerViewController: UIViewController { onValueChanged?(tags.joined(separator: ", ")) } WPError.dismissNetworkingNotice() + + textView.resignFirstResponder() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -120,9 +145,21 @@ class PostTagPickerViewController: UIViewController { } } + @objc func doneButtonPressed() { + navigationController?.popViewController(animated: true) + } + fileprivate func reloadTableData() { tableView.reloadData() } + + fileprivate func updateTableViewBottomInset() { + guard !UIDevice.isPad() else { + return + } + + tableView.contentInset.bottom += presentedVC?.yPosition ?? 0 + } } diff --git a/WordPress/Classes/ViewRelated/Post/PostVisibilitySelectorViewController.swift b/WordPress/Classes/ViewRelated/Post/PostVisibilitySelectorViewController.swift new file mode 100644 index 000000000000..39872c1f8488 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/PostVisibilitySelectorViewController.swift @@ -0,0 +1,85 @@ +import UIKit + +@objc class PostVisibilitySelectorViewController: SettingsSelectionViewController { + /// The post to change the visibility + private var post: AbstractPost! + + /// A completion block that is called after the user select an option + @objc var completion: ((String) -> Void)? + + // MARK: - Constructors + + @objc init(_ post: AbstractPost) { + self.post = post + + let titles: NSArray = [ + NSLocalizedString("Public", comment: "Privacy setting for posts set to 'Public' (default). Should be the same as in core WP."), + NSLocalizedString("Password protected", comment: "Privacy setting for posts set to 'Password protected'. Should be the same as in core WP."), + NSLocalizedString("Private", comment: "Privacy setting for posts set to 'Private'. Should be the same as in core WP.") + ] + + let visiblityDict: [AnyHashable: Any] = [ + "DefaultValue": NSLocalizedString("Public", comment: "Privacy setting for posts set to 'Public' (default). Should be the same as in core WP."), + "Title": NSLocalizedString("Visibility", comment: "Visibility label"), + "Titles": titles, + "Values": titles, + "CurrentValue": post.titleForVisibility + ] + + super.init(dictionary: visiblityDict) + + onItemSelected = { [weak self] visibility in + guard let visibility = visibility as? String, + !post.isFault, post.managedObjectContext != nil else { + return + } + + if visibility == AbstractPost.privateLabel { + post.status = .publishPrivate + post.password = nil + } else { + if post.status == .publishPrivate { + if post.original?.status == .publishPrivate { + post.status = .publish + } else { + // restore the original status + post.status = post.original?.status + } + } + + if visibility == AbstractPost.passwordProtectedLabel { + var password = "" + + assert(post.original != nil, + "We're expecting to have a reference to the original post here.") + assert(!post.original!.isFault, + "We're expecting to have a reference to the original post here.") + assert(post.original!.managedObjectContext != nil, + "The original post's MOC should not be nil here.") + + if let originalPassword = post.original?.password { + password = originalPassword + } + post.password = password + } else { + post.password = nil + } + } + + self?.completion?(visibility) + + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init!(style: UITableView.Style, andDictionary dictionary: [AnyHashable: Any]!) { + super.init(style: style, andDictionary: dictionary) + } + + override init(style: UITableView.Style) { + super.init(style: style) + } +} diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/Blog+Title.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/Blog+Title.swift new file mode 100644 index 000000000000..faa8ffeae08e --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/Blog+Title.swift @@ -0,0 +1,11 @@ +import Foundation + +extension Blog { + + /// The title of the blog + var title: String? { + let blogName = settings?.name + let title = blogName != nil && blogName?.isEmpty == false ? blogName : displayURL as String? + return title + } +} diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PasswordAlertController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PasswordAlertController.swift new file mode 100644 index 000000000000..daed4debdad7 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PasswordAlertController.swift @@ -0,0 +1,70 @@ +import UIKit +import Gridicons + +/// Display an Alert Controller that prompts for a password +class PasswordAlertController { + + var passwordField: UITextField! + + var onSubmit: ((String?) -> Void)? + + var onCancel: (() -> Void)? + + init(onSubmit: @escaping (String?) -> Void, onCancel: @escaping () -> Void) { + self.onSubmit = onSubmit + self.onCancel = onCancel + } + + /// Show the Alert Controller from a given view controller + func show(from viewController: UIViewController) { + let alertController = UIAlertController( + title: AbstractPost.passwordProtectedLabel, + message: Constants.passwordMessage, + preferredStyle: .alert + ) + + let submitAction = UIAlertAction(title: Constants.alertSubmit, style: .default) { _ in + self.onSubmit?(self.passwordField.text) + self.onSubmit = nil + self.onCancel = nil + alertController.dismiss(animated: true) + } + + let cancelAction = UIAlertAction(title: Constants.alertCancel, style: .cancel) { _ in + self.onCancel?() + self.onSubmit = nil + self.onCancel = nil + alertController.dismiss(animated: true) + } + + alertController.addTextField { textField in + self.passwordField = textField + textField.placeholder = Constants.postPassword + let button = UIButton() + textField.rightView = button + textField.rightViewMode = .always + self.togglePassword(button) + button.addTarget(self, action: #selector(self.togglePassword(_:)), for: .touchUpInside) + } + + alertController.addAction(submitAction) + alertController.addAction(cancelAction) + + viewController.present(alertController, animated: true, completion: nil) + } + + /// Toggle the UITextField isSecureTextEntry on/off + @objc func togglePassword(_ sender: UIButton) { + let isSecureTextEntry = !passwordField.isSecureTextEntry + passwordField.isSecureTextEntry = isSecureTextEntry + sender.setImage(isSecureTextEntry ? .gridicon(.visible) : .gridicon(.notVisible), for: .normal) + } + + private enum Constants { + static let alertSubmit = NSLocalizedString("OK", comment: "Submit button on prompt for user information.") + static let alertCancel = NSLocalizedString("Cancel", comment: "Cancel prompt for user information.") + static let postPassword = NSLocalizedString("Enter password", comment: "Placeholder of a field to type a password to protect the post.") + static let passwordMessage = NSLocalizedString("Enter a password to protect this post", comment: "Message explaining why the user might enter a password.") + } + +} diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.swift new file mode 100644 index 000000000000..a3a089459efb --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.swift @@ -0,0 +1,86 @@ +import UIKit +import Gridicons + +protocol PrepublishingHeaderViewDelegate: class { + func closeButtonTapped() +} + +class PrepublishingHeaderView: UIView, NibLoadable { + + @IBOutlet weak var blogImageView: UIImageView! + @IBOutlet weak var publishingToLabel: UILabel! + @IBOutlet weak var blogTitleLabel: UILabel! + @IBOutlet weak var closeButtonView: UIView! + @IBOutlet weak var leadingConstraint: NSLayoutConstraint! + @IBOutlet weak var closeButton: UIButton! + @IBOutlet weak var separator: UIView! + + weak var delegate: PrepublishingHeaderViewDelegate? + + func configure(_ blog: Blog) { + blogImageView.downloadSiteIcon(for: blog) + blogTitleLabel.text = blog.title + } + + // MARK: - Close button + + func toggleCloseButton(visible: Bool) { + closeButtonView.layer.opacity = visible ? 1 : 0 + closeButtonView.isHidden = visible ? false : true + leadingConstraint.constant = visible ? 0 : Constants.leftRightInset + layoutIfNeeded() + } + + @IBAction func closeButtonTapped(_ sender: Any) { + delegate?.closeButtonTapped() + } + + // MARK: - Style + + override func awakeFromNib() { + super.awakeFromNib() + configureBackButton() + configurePublishingToLabel() + configureBlogTitleLabel() + configureBlogImage() + configureSeparator() + } + + private func configureBackButton() { + closeButtonView.isHidden = true + closeButton.setImage(.gridicon(.cross, size: Constants.backButtonSize), for: .normal) + closeButton.accessibilityLabel = Constants.close + closeButton.accessibilityHint = Constants.doubleTapToDismiss + + // Only show close button for accessibility purposes + toggleCloseButton(visible: UIAccessibility.isVoiceOverRunning) + } + + private func configurePublishingToLabel() { + publishingToLabel.text = publishingToLabel.text?.uppercased() + publishingToLabel.font = WPStyleGuide.TableViewHeaderDetailView.titleFont + publishingToLabel.textColor = WPStyleGuide.TableViewHeaderDetailView.titleColor + } + + private func configureBlogImage() { + blogImageView.layer.cornerRadius = Constants.imageRadius + blogImageView.clipsToBounds = true + } + + private func configureBlogTitleLabel() { + WPStyleGuide.applyPostTitleStyle(blogTitleLabel) + } + + private func configureSeparator() { + WPStyleGuide.applyBorderStyle(separator) + } + + private enum Constants { + 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/PrepublishingHeaderView.xib b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.xib new file mode 100644 index 000000000000..08b7dc5d1dcc --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingHeaderView.xib @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift new file mode 100644 index 000000000000..6e516d9c5682 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing Nudge/PrepublishingNavigationController.swift @@ -0,0 +1,40 @@ +import UIKit + +class PrepublishingNavigationController: LightNavigationController { + override func viewDidLoad() { + super.viewDidLoad() + + // Set the height for iPad + if UIDevice.isPad() { + preferredContentSize = Constants.iPadPreferredContentSize + } + } + + private enum Constants { + static let height: CGFloat = 290 + static let iPadPreferredContentSize = CGSize(width: 300, height: 240) + } +} + + +// MARK: - DrawerPresentable + +extension PrepublishingNavigationController: DrawerPresentable { + var allowsUserTransition: Bool { + return false + } + + var expandedHeight: DrawerHeight { + return .topMargin(20) + } + + var collapsedHeight: DrawerHeight { + return .contentHeight(Constants.height) + } + + var scrollableView: UIScrollView? { + let scroll = visibleViewController?.view as? UIScrollView + + return scroll + } +} diff --git a/WordPress/Classes/ViewRelated/Post/PrepublishingViewController.swift b/WordPress/Classes/ViewRelated/Post/PrepublishingViewController.swift new file mode 100644 index 000000000000..863680330a0b --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/PrepublishingViewController.swift @@ -0,0 +1,312 @@ +import UIKit +import WordPressAuthenticator + +private struct PrepublishingOption { + let id: PrepublishingIdentifier + let title: String +} + +private enum PrepublishingIdentifier { + case schedule + case visibility + case tags +} + +class PrepublishingViewController: UITableViewController { + let post: Post + + lazy var header: PrepublishingHeaderView = { + let header = PrepublishingHeaderView.loadFromNib() + return header + }() + + private lazy var publishSettingsViewModel: PublishSettingsViewModel = { + return PublishSettingsViewModel(post: post) + }() + + private lazy var presentedVC: DrawerPresentationController? = { + return (navigationController as? PrepublishingNavigationController)?.presentedVC + }() + + private let completion: (AbstractPost) -> () + + private let options: [PrepublishingOption] = [ + PrepublishingOption(id: .schedule, title: Constants.publishLabel), + PrepublishingOption(id: .visibility, title: NSLocalizedString("Visibility", comment: "Label for Visibility")), + PrepublishingOption(id: .tags, title: NSLocalizedString("Tags", comment: "Label for Tags")) + ] + + let publishButton: NUXButton = { + let nuxButton = NUXButton() + nuxButton.isPrimary = true + + return nuxButton + }() + + init(post: Post, completion: @escaping (AbstractPost) -> ()) { + self.post = post + self.completion = completion + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "" + + header.delegate = self + header.configure(post.blog) + setupPublishButton() + setupFooterSeparator() + + announcePublishButton() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: animated) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + let isPresentingAViewController = navigationController?.viewControllers.count ?? 0 > 1 + if isPresentingAViewController { + navigationController?.setNavigationBarHidden(false, animated: animated) + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return header + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return Constants.headerHeight + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return options.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: WPTableViewCell = { + guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.reuseIdentifier) as? WPTableViewCell else { + return WPTableViewCell.init(style: .value1, reuseIdentifier: Constants.reuseIdentifier) + } + return cell + }() + + cell.preservesSuperviewLayoutMargins = false + cell.separatorInset = .zero + cell.layoutMargins = Constants.cellMargins + + cell.accessoryType = .disclosureIndicator + cell.textLabel?.text = options[indexPath.row].title + + switch options[indexPath.row].id { + case .tags: + configureTagCell(cell) + case .visibility: + configureVisibilityCell(cell) + case .schedule: + configureScheduleCell(cell) + } + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + switch options[indexPath.row].id { + case .tags: + didTapTagCell() + case .visibility: + didTapVisibilityCell() + case .schedule: + didTapSchedule(indexPath) + } + } + + private func reloadData() { + tableView.reloadData() + } + + // MARK: - Tags + + private func configureTagCell(_ cell: WPTableViewCell) { + cell.detailTextLabel?.text = post.tags + } + + private func didTapTagCell() { + let tagPickerViewController = PostTagPickerViewController(tags: post.tags ?? "", blog: post.blog) + + tagPickerViewController.onValueChanged = { [weak self] tags in + if !tags.isEmpty { + WPAnalytics.track(.editorPostTagsAdded, properties: Constants.analyticsDefaultProperty) + } + + self?.post.tags = tags + self?.reloadData() + } + + navigationController?.pushViewController(tagPickerViewController, animated: true) + } + + // MARK: - Visibility + + private func configureVisibilityCell(_ cell: WPTableViewCell) { + cell.detailTextLabel?.text = post.titleForVisibility + } + + private func didTapVisibilityCell() { + let visbilitySelectorViewController = PostVisibilitySelectorViewController(post) + + visbilitySelectorViewController.completion = { [weak self] option in + self?.reloadData() + + WPAnalytics.track(.editorPostVisibilityChanged, properties: Constants.analyticsDefaultProperty) + + // If tue user selects password protected, prompt for a password + if option == AbstractPost.passwordProtectedLabel { + self?.showPasswordAlert() + } else { + self?.navigationController?.popViewController(animated: true) + } + } + + navigationController?.pushViewController(visbilitySelectorViewController, animated: true) + } + + // MARK: - Schedule + + func configureScheduleCell(_ cell: WPTableViewCell) { + cell.textLabel?.text = post.hasFuturePublishDate() ? Constants.scheduledLabel : Constants.publishLabel + cell.detailTextLabel?.text = publishSettingsViewModel.detailString + } + + func didTapSchedule(_ indexPath: IndexPath) { + transitionIfVoiceOverDisabled(to: .hidden) + SchedulingCalendarViewController.present( + from: self, + sourceView: tableView.cellForRow(at: indexPath)?.contentView, + viewModel: publishSettingsViewModel, + updated: { [weak self] date in + WPAnalytics.track(.editorPostScheduled, properties: Constants.analyticsDefaultProperty) + self?.publishSettingsViewModel.setDate(date) + self?.reloadData() + self?.updatePublishButtonLabel() + }, + onDismiss: { [weak self] in + self?.reloadData() + self?.transitionIfVoiceOverDisabled(to: .collapsed) + } + ) + } + + // MARK: - Publish Button + + private func setupPublishButton() { + let footer = UIView(frame: Constants.footerFrame) + footer.addSubview(publishButton) + footer.pinSubviewToSafeArea(publishButton, insets: Constants.nuxButtonInsets) + publishButton.translatesAutoresizingMaskIntoConstraints = false + tableView.tableFooterView = footer + publishButton.addTarget(self, action: #selector(publish(_:)), for: .touchUpInside) + updatePublishButtonLabel() + } + + private func setupFooterSeparator() { + guard let footer = tableView.tableFooterView else { + return + } + + let separator = UIView() + separator.translatesAutoresizingMaskIntoConstraints = false + footer.addSubview(separator) + NSLayoutConstraint.activate([ + separator.topAnchor.constraint(equalTo: footer.topAnchor), + separator.leftAnchor.constraint(equalTo: footer.leftAnchor), + separator.rightAnchor.constraint(equalTo: footer.rightAnchor), + separator.heightAnchor.constraint(equalToConstant: 1) + ]) + WPStyleGuide.applyBorderStyle(separator) + } + + private func updatePublishButtonLabel() { + publishButton.setTitle(post.isScheduled() ? Constants.scheduleNow : Constants.publishNow, for: .normal) + } + + @objc func publish(_ sender: UIButton) { + navigationController?.dismiss(animated: true) { + WPAnalytics.track(.editorPostPublishNowTapped) + self.completion(self.post) + } + } + + // MARK: - Password Prompt + + private func showPasswordAlert() { + let passwordAlertController = PasswordAlertController(onSubmit: { [weak self] password in + guard let password = password, !password.isEmpty else { + self?.cancelPasswordProtectedPost() + return + } + + self?.post.password = password + self?.navigationController?.popViewController(animated: true) + }, onCancel: { [weak self] in + self?.cancelPasswordProtectedPost() + }) + + passwordAlertController.show(from: self) + } + + private func cancelPasswordProtectedPost() { + post.status = .publish + post.password = nil + reloadData() + } + + // MARK: - Accessibility + + private func announcePublishButton() { + DispatchQueue.main.asyncAfter(deadline: .now()) { + UIAccessibility.post(notification: .screenChanged, argument: self.publishButton) + } + } + + /// Only perform a transition if Voice Over is disabled + /// This avoids some unresponsiveness + private func transitionIfVoiceOverDisabled(to position: DrawerPosition) { + guard !UIAccessibility.isVoiceOverRunning else { + return + } + + presentedVC?.transition(to: position) + } + + private enum Constants { + static let reuseIdentifier = "wpTableViewCell" + static let nuxButtonInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + static let cellMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + static let footerFrame = CGRect(x: 0, y: 0, width: 100, height: 80) + static let publishNow = NSLocalizedString("Publish Now", comment: "Label for a button that publishes the post") + static let scheduleNow = NSLocalizedString("Schedule Now", comment: "Label for the button that schedules the post") + static let publishLabel = NSLocalizedString("Publish", comment: "Label for Publish") + static let scheduledLabel = NSLocalizedString("Scheduled for", comment: "Scheduled for [date]") + static let headerHeight: CGFloat = 70 + static let analyticsDefaultProperty = ["via": "prepublishing_nudges"] + } +} + +extension PrepublishingViewController: PrepublishingHeaderViewDelegate { + func closeButtonTapped() { + dismiss(animated: true) + } +} diff --git a/WordPress/Classes/ViewRelated/Post/Scheduling/PublishSettingsViewController.swift b/WordPress/Classes/ViewRelated/Post/Scheduling/PublishSettingsViewController.swift index 05614445f506..e75ef7508b0a 100644 --- a/WordPress/Classes/ViewRelated/Post/Scheduling/PublishSettingsViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Scheduling/PublishSettingsViewController.swift @@ -26,6 +26,14 @@ struct PublishSettingsViewModel { let timeZone: TimeZone let title: String? + var detailString: String { + if let date = date { + return dateTimeFormatter.string(from: date) + } else { + return NSLocalizedString("Immediately", comment: "Undated post time label") + } + } + private let post: AbstractPost let dateFormatter: DateFormatter @@ -150,15 +158,9 @@ private struct DateAndTimeRow: ImmuTableRow { let rows: [ImmuTableRow] = viewModel.cells.map { cell in switch cell { case .dateTime: - let detailString: String - if let date = viewModel.date { - detailString = viewModel.dateTimeFormatter.string(from: date) - } else { - detailString = NSLocalizedString("Immediately", comment: "Undated post time label") - } return DateAndTimeRow( title: NSLocalizedString("Date and Time", comment: "Date and Time"), - detail: detailString, + detail: viewModel.detailString, accessibilityIdentifier: "Date and Time Row", action: presenter.present(dateTimeCalendarViewController(with: viewModel)) ) @@ -194,10 +196,17 @@ private struct DateAndTimeRow: ImmuTableRow { return { [weak self] row in let schedulingCalendarViewController = SchedulingCalendarViewController() - schedulingCalendarViewController.coordinator = DateCoordinator(date: model.date, timeZone: model.timeZone, dateFormatter: model.dateFormatter, dateTimeFormatter: model.dateTimeFormatter) { [weak self] date in - self?.viewModel.setDate(date) - NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: ImmuTableViewController.modelChangedNotification), object: nil) - } + schedulingCalendarViewController.coordinator = DateCoordinator( + date: model.date, + timeZone: model.timeZone, + dateFormatter: model.dateFormatter, + dateTimeFormatter: model.dateTimeFormatter, + updated: { [weak self] date in + WPAnalytics.track(.editorPostScheduled, properties: ["via": "settings"]) + self?.viewModel.setDate(date) + NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: ImmuTableViewController.modelChangedNotification), object: nil) + } + ) return self?.calendarNavigationController(rootViewController: schedulingCalendarViewController) ?? UINavigationController() } diff --git a/WordPress/Classes/ViewRelated/Post/Scheduling/SchedulingCalendarViewController+PresentFrom.swift b/WordPress/Classes/ViewRelated/Post/Scheduling/SchedulingCalendarViewController+PresentFrom.swift new file mode 100644 index 000000000000..edc9112cb2bc --- /dev/null +++ b/WordPress/Classes/ViewRelated/Post/Scheduling/SchedulingCalendarViewController+PresentFrom.swift @@ -0,0 +1,46 @@ +import Foundation + +extension SchedulingCalendarViewController { + static func present(from viewController: UIViewController, sourceView: UIView?, viewModel: PublishSettingsViewModel, updated: @escaping (Date?) -> Void, onDismiss: @escaping () -> Void) { + let schedulingCalendarViewController = SchedulingCalendarViewController() + schedulingCalendarViewController.coordinator = DateCoordinator(date: viewModel.date, timeZone: viewModel.timeZone, dateFormatter: viewModel.dateFormatter, dateTimeFormatter: viewModel.dateTimeFormatter, updated: updated) + let vc = SchedulingLightNavigationController(rootViewController: schedulingCalendarViewController) + vc.onDismiss = onDismiss + + if UIDevice.isPad() { + vc.modalPresentationStyle = .popover + } else { + vc.modalPresentationStyle = .custom + vc.transitioningDelegate = schedulingCalendarViewController + } + + if let popoverController = vc.popoverPresentationController, + let sourceView = sourceView { + popoverController.sourceView = sourceView + popoverController.sourceRect = sourceView.frame + } + + viewController.present(vc, animated: true) + } +} + +extension SchedulingCalendarViewController: UIViewControllerTransitioningDelegate, UIAdaptivePresentationControllerDelegate { + func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + let presentationController = HalfScreenPresentationController(presentedViewController: presented, presenting: presenting) + presentationController.delegate = self + return presentationController + } + + func adaptivePresentationStyle(for: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { + return traitCollection.verticalSizeClass == .compact ? .overFullScreen : .none + } +} + +class SchedulingLightNavigationController: LightNavigationController { + var onDismiss: (() -> Void)? + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + onDismiss?() + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Filter/FilterProvider.swift b/WordPress/Classes/ViewRelated/Reader/Filter/FilterProvider.swift new file mode 100644 index 000000000000..a36cdfa849f5 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/Filter/FilterProvider.swift @@ -0,0 +1,165 @@ +import WordPressFlux + +class FilterProvider: Observable, FilterTabBarItem { + + enum State { + case loading + case ready([TableDataItem]) + case error(Error) + + var isReady: Bool { + switch self { + case .ready: + return true + case .error, .loading: + return false + } + } + } + + var title: String { + return titleFunc(state) + } + + var state: State = .loading { + didSet { + emitChange() + } + } + + var items: [TableDataItem] { + switch state { + case .loading, .error: + return [] + case .ready(let items): + return items + } + } + + let accessibilityIdentifier: String + + let cellClass: UITableViewCell.Type + let reuseIdentifier: String + + private let titleFunc: (State?) -> String + + let changeDispatcher = Dispatcher() + + init(title: @escaping (State?) -> String, + accessibilityIdentifier: String, + cellClass: UITableViewCell.Type, + reuseIdentifier: String, + provider: (@escaping (Result<[TableDataItem], Error>) -> Void) -> Void) { + + titleFunc = title + self.accessibilityIdentifier = accessibilityIdentifier + self.cellClass = cellClass + self.reuseIdentifier = reuseIdentifier + + provider() { [weak self] result in + switch result { + case .success(let items): + self?.state = .ready(items) + case .failure(let error): + self?.state = .error(error) + } + } + } +} + +extension ReaderSiteTopic { + + static func filterProvider() -> FilterProvider { + let titleFunction: (FilterProvider.State?) -> String = { state in + switch state { + case .loading, .error, .none: + return NSLocalizedString("Sites", comment: "Sites Filter Tab Title") + case .ready(let items): + return String(format: NSLocalizedString("Sites (%lu)", comment: "Sites Filter Tab Title with Count"), items.count) + } + } + return FilterProvider(title: titleFunction, + accessibilityIdentifier: "SitesFilterTab", + cellClass: SiteTableViewCell.self, + reuseIdentifier: "Sites", + provider: tableProvider) + } + + private static func tableProvider(completion: @escaping (Result<[TableDataItem], Error>) -> Void) { + fetchFollowedSites(completion: { result in + let itemResult = result.map { sites in + sites.map { topic in + return TableDataItem(topic: topic, configure: { cell in + cell.textLabel?.text = topic.title + cell.detailTextLabel?.text = topic.siteURL + }) + } + } + completion(itemResult) + }) + } + + private static func fetchFollowedSites(completion: @escaping (Result<[ReaderSiteTopic], Error>) -> Void) { + let siteService = ReaderTopicService(managedObjectContext: ContextManager.sharedInstance().mainContext) + + siteService.fetchFollowedSites(success: { + completion(.success(siteService.allSiteTopics().filter { !$0.isExternal })) + }, failure: { error in + let unknownRestAPIError = NSError(domain: WordPressComRestApiErrorDomain, code: -1, userInfo: nil) + completion(.failure(error ?? unknownRestAPIError)) + DDLogError("Could not sync sites: \(String(describing: error))") + }) + } +} + +extension ReaderTagTopic { + + static func filterProvider() -> FilterProvider { + let titleFunction: (FilterProvider.State?) -> String = { state in + switch state { + case .loading, .error, .none: + return NSLocalizedString("Tags", comment: "Tags Filter Tab Title") + case .ready(let items): + return String(format: NSLocalizedString("Tags (%lu)", comment: "Tags Filter Tab Title with Count"), items.count) + } + } + return FilterProvider(title: titleFunction, + accessibilityIdentifier: "TagsFilterTab", + cellClass: UITableViewCell.self, + reuseIdentifier: "Tags", + provider: tableProvider) + } + + private static func tableProvider(completion: @escaping (Result<[TableDataItem], Error>) -> Void) { + fetchFollowedTags(completion: { result in + let itemResult = result.map { tags in + tags.map { topic in + return TableDataItem(topic: topic, configure: { (cell) in + cell.textLabel?.text = topic.slug + }) + } + } + completion(itemResult) + }) + } + + private static var tagsFetchRequest: NSFetchRequest { + let fetchRequest = NSFetchRequest(entityName: "ReaderTagTopic") + fetchRequest.predicate = NSPredicate(format: "following == %@ AND showInMenu == YES AND type == 'tag'", + NSNumber(value: ReaderHelpers.isLoggedIn())) + fetchRequest.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare))] + return fetchRequest + } + + private static func fetchFollowedTags(completion: @escaping (Result<[ReaderTagTopic], Error>) -> Void) { + do { + guard let topics = try ContextManager.sharedInstance().mainContext.fetch(tagsFetchRequest) as? [ReaderTagTopic] else { + return + } + completion(.success(topics)) + } catch { + DDLogError("There was a problem fetching followed tags." + error.localizedDescription) + completion(.failure(error)) + } + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Filter/FilterSheetView.swift b/WordPress/Classes/ViewRelated/Reader/Filter/FilterSheetView.swift new file mode 100644 index 000000000000..720f4e9ee4f6 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/Filter/FilterSheetView.swift @@ -0,0 +1,146 @@ +import WordPressFlux + +class FilterSheetView: UIView { + + enum Constants { + enum Header { + static let spacing: CGFloat = 16 + static let insets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18) + static let title = NSLocalizedString("Following", comment: "Title for Reader Filter Sheet") + static let font = WPStyleGuide.fontForTextStyle(.headline) + } + } + + // MARK: View Setup + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.tableFooterView = UIView() // To hide the separators for empty cells + tableView.separatorStyle = .none + tableView.delegate = self + return tableView + }() + + lazy var ghostableTableView: UITableView = { + let tableView = UITableView() + tableView.allowsSelection = false + tableView.isScrollEnabled = false + tableView.separatorStyle = .none + return tableView + }() + + lazy var filterTabBar: FilterTabBar = { + let tabBar = FilterTabBar() + WPStyleGuide.configureFilterTabBar(tabBar) + tabBar.tabSizingStyle = .equalWidths + tabBar.addTarget(self, action: #selector(FilterSheetView.changedTab(_:)), for: .valueChanged) + return tabBar + }() + + lazy var headerLabelView: UIView = { + let labelView = UIView() + let label = UILabel() + label.font = Constants.Header.font + label.text = Constants.Header.title + label.translatesAutoresizingMaskIntoConstraints = false + labelView.addSubview(label) + labelView.pinSubviewToAllEdges(label, insets: Constants.Header.insets) + return labelView + }() + + lazy var stackView: UIStackView = { + let stack = UIStackView(arrangedSubviews: [ + headerLabelView, + filterTabBar, + tableView, + ghostableTableView + ]) + + stack.setCustomSpacing(Constants.Header.spacing, after: headerLabelView) + stack.axis = .vertical + stack.translatesAutoresizingMaskIntoConstraints = false + return stack + }() + + // MARK: Properties + + private var subscriptions: [Receipt]? + private var changedFilter: (ReaderAbstractTopic) -> Void + private var dataSource: FilterTableViewDataSource? { + didSet { + tableView.dataSource = dataSource + tableView.reloadData() + } + } + + private var selectedFilter: FilterProvider? { + set { + if let filter = newValue { + dataSource = FilterTableViewDataSource(data: filter.items, reuseIdentifier: filter.reuseIdentifier) + if filter.state.isReady == false { + tableView.isHidden = true + updateGhostableTableViewOptions(cellClass: filter.cellClass, identifier: filter.reuseIdentifier) + } else { + ghostableTableView.stopGhostAnimation() + ghostableTableView.isHidden = true + tableView.isHidden = false + } + } + } + get { + return filterTabBar.items[filterTabBar.selectedIndex] as? FilterProvider + } + } + + // MARK: Methods + + init(filters: [FilterProvider], changedFilter: @escaping (ReaderAbstractTopic) -> Void) { + self.changedFilter = changedFilter + super.init(frame: .zero) + + filterTabBar.items = filters + filters.forEach { filter in + tableView.register(filter.cellClass, forCellReuseIdentifier: filter.reuseIdentifier) + } + selectedFilter = filters.first + + addSubview(stackView) + pinSubviewToAllEdges(stackView) + + subscriptions = filters.map() { filter in + filter.onChange() { [weak self] in + if self?.selectedFilter?.accessibilityIdentifier == filter.accessibilityIdentifier { + self?.selectedFilter = filter + } + self?.filterTabBar.items = filters + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateGhostableTableViewOptions(cellClass: UITableViewCell.Type, identifier: String) { + ghostableTableView.register(cellClass, forCellReuseIdentifier: identifier) + let ghostOptions = GhostOptions(displaysSectionHeader: false, reuseIdentifier: identifier, rowsPerSection: [15]) + let style = GhostStyle(beatDuration: GhostStyle.Defaults.beatDuration, + beatStartColor: .placeholderElement, + beatEndColor: .placeholderElementFaded) + ghostableTableView.removeGhostContent() + ghostableTableView.isHidden = false + ghostableTableView.displayGhostContent(options: ghostOptions, style: style) + } + + @objc func changedTab(_ sender: FilterTabBar) { + selectedFilter = filterTabBar.items[sender.selectedIndex] as? FilterProvider + } +} + +extension FilterSheetView: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let topic = dataSource?.data[indexPath.row].topic { + changedFilter(topic) + } + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Filter/FilterSheetViewController.swift b/WordPress/Classes/ViewRelated/Reader/Filter/FilterSheetViewController.swift new file mode 100644 index 000000000000..368c81724973 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/Filter/FilterSheetViewController.swift @@ -0,0 +1,34 @@ +class FilterSheetViewController: UIViewController { + + private let filters: [FilterProvider] + private let changedFilter: (ReaderAbstractTopic) -> Void + + //TODO: Make changedFilter generic + init(filters: [FilterProvider], changedFilter: @escaping (ReaderAbstractTopic) -> Void) { + self.filters = filters + self.changedFilter = changedFilter + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = FilterSheetView(filters: filters, changedFilter: changedFilter) + } +} + +extension FilterSheetViewController: DrawerPresentable { + var scrollableView: UIScrollView? { + return (view as? FilterSheetView)?.tableView + } + + var collapsedHeight: DrawerHeight { + if traitCollection.verticalSizeClass == .compact { + return .maxHeight + } else { + return .contentHeight(0) + } + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Filter/FilterTableData.swift b/WordPress/Classes/ViewRelated/Reader/Filter/FilterTableData.swift new file mode 100644 index 000000000000..04d735fe26db --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/Filter/FilterTableData.swift @@ -0,0 +1,55 @@ +struct TableDataItem { + let topic: ReaderAbstractTopic + let configure: (UITableViewCell) -> Void +} + +class FilterTableViewDataSource: NSObject, UITableViewDataSource { + + let data: [TableDataItem] + private let reuseIdentifier: String + + init(data: [TableDataItem], reuseIdentifier: String) { + self.data = data + self.reuseIdentifier = reuseIdentifier + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return data.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let item = data[indexPath.row] + let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) + + item.configure(cell) + + return cell + } +} + +class SiteTableViewCell: UITableViewCell, GhostableView { + + enum Constants { + static let textLabelCharacterWidth = 40 // Number of characters in text label + static let detailLabelCharacterWidth = 80 // Number of characters in detail label + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) + detailTextLabel?.textColor = UIColor.systemGray + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func ghostAnimationWillStart() { + contentView.subviews.forEach { view in + view.isGhostableDisabled = true + } + textLabel?.text = String(repeating: " ", count: Constants.textLabelCharacterWidth) + textLabel?.isGhostableDisabled = false + detailTextLabel?.text = String(repeating: " ", count: Constants.detailLabelCharacterWidth) + detailTextLabel?.isGhostableDisabled = false + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/NewsCard.xib b/WordPress/Classes/ViewRelated/Reader/NewsCard.xib index f6b77d7f0bbe..dc037081c8a6 100644 --- a/WordPress/Classes/ViewRelated/Reader/NewsCard.xib +++ b/WordPress/Classes/ViewRelated/Reader/NewsCard.xib @@ -79,9 +79,9 @@ - + - + diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift index 08c1f4a06c9d..6fe15f64a1fc 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDetailViewController.swift @@ -557,13 +557,6 @@ open class ReaderDetailViewController: UIViewController, UIViewControllerRestora WPStyleGuide.applyReaderCardTagButtonStyle(tagButton) WPStyleGuide.applyReaderCardActionButtonStyle(commentButton) WPStyleGuide.applyReaderCardActionButtonStyle(likeButton) - if !FeatureFlag.postReblogging.enabled { - // this becomes redundant, as saveForLaterButton does not have a label anymore - // and applyReaderActionButtonStyle() is called by applyReaderSaveForLaterButtonStyle - // which in turn is called by configureSaveForLaterButton. Same considerations for - // reblog button - WPStyleGuide.applyReaderCardActionButtonStyle(saveForLaterButton) - } view.backgroundColor = .listBackground @@ -952,9 +945,7 @@ open class ReaderDetailViewController: UIViewController, UIViewControllerRestora likeButton.isEnabled = ReaderHelpers.isLoggedIn() // as by design spec, only display like counts let likeCount = post?.likeCount()?.intValue ?? 0 - let shortTitle = likeCount > 0 ? "\(likeCount)" : "" - - let title = FeatureFlag.postReblogging.enabled ? shortTitle : post?.likeCountForDisplay() + let title = likeCount > 0 ? "\(likeCount)" : "" let selected = post?.isLiked ?? false let likeImage = UIImage(named: "icon-reader-like") @@ -969,9 +960,6 @@ open class ReaderDetailViewController: UIViewController, UIViewControllerRestora /// Uses the configuration in WPStyleGuide for the reblog button fileprivate func configureReblogButton() { - guard FeatureFlag.postReblogging.enabled else { - return - } reblogButton.isHidden = false WPStyleGuide.applyReaderReblogActionButtonStyle(reblogButton, showTitle: false) } @@ -1052,12 +1040,7 @@ open class ReaderDetailViewController: UIViewController, UIViewControllerRestora fileprivate func configureSaveForLaterButton() { WPStyleGuide.applyReaderSaveForLaterButtonStyle(saveForLaterButton) - if FeatureFlag.postReblogging.enabled { - WPStyleGuide.applyReaderSaveForLaterButtonTitles(saveForLaterButton, showTitle: false) - } else { - WPStyleGuide.applyReaderSaveForLaterButtonTitles(saveForLaterButton) - } - + WPStyleGuide.applyReaderSaveForLaterButtonTitles(saveForLaterButton, showTitle: false) saveForLaterButton.isHidden = false saveForLaterButton.isSelected = post?.isSavedForLater ?? false @@ -1563,9 +1546,7 @@ extension ReaderDetailViewController: Accessible { prepareHeaderForVoiceOver() prepareContentForVoiceOver() prepareActionButtonsForVoiceOver() - if FeatureFlag.postReblogging.enabled { - prepareReblogForVoiceOver() - } + prepareReblogForVoiceOver() NotificationCenter.default.addObserver(self, selector: #selector(setBarsAsVisibleIfVoiceOverIsEnabled), diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesStreamHeader.swift b/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesStreamHeader.swift index c179ea29c474..8c0fc5e3af66 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesStreamHeader.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesStreamHeader.swift @@ -1,7 +1,7 @@ import Foundation import Gridicons import WordPressShared.WPStyleGuide - +// TODO: - READERNAV - This (and the related xib) might need to be removed once the new Reader Tab Navigation is stable @objc open class ReaderFollowedSitesStreamHeader: UIView, ReaderStreamHeader { @IBOutlet fileprivate weak var borderedView: UIView! @IBOutlet fileprivate weak var imageView: UIImageView! diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift index 11dcc36c96d0..3aef302adbbf 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPostCardCell.swift @@ -243,11 +243,7 @@ import Gridicons borderedView.layer.borderWidth = .hairlineBorderWidth WPStyleGuide.applyReaderSaveForLaterButtonStyle(saveForLaterButton) - - if FeatureFlag.postReblogging.enabled { - WPStyleGuide.applyReaderReblogActionButtonStyle(reblogActionButton) - } - + WPStyleGuide.applyReaderReblogActionButtonStyle(reblogActionButton) WPStyleGuide.applyReaderFollowButtonStyle(followButton) WPStyleGuide.applyReaderCardBlogNameStyle(blogNameLabel) WPStyleGuide.applyReaderCardBylineLabelStyle(bylineLabel) @@ -509,8 +505,7 @@ import Gridicons fileprivate var shouldShowReblogActionButton: Bool { // reblog button is hidden if there's no content - guard FeatureFlag.postReblogging.enabled, - let provider = contentProvider, + guard let provider = contentProvider, !provider.isPrivate(), loggedInActionVisibility.isEnabled else { return false @@ -532,12 +527,9 @@ import Gridicons let commentTitle = commentCount > 0 ? String(commentCount) : "" likeActionButton.setTitle(likeTitle, for: .normal) commentActionButton.setTitle(commentTitle, for: .normal) - if FeatureFlag.postReblogging.enabled { - WPStyleGuide.applyReaderSaveForLaterButtonTitles(saveForLaterButton, showTitle: false) - WPStyleGuide.applyReaderReblogActionButtonTitle(reblogActionButton, showTitle: false) - } else { - saveForLaterButton.setTitle("", for: .normal) - } + WPStyleGuide.applyReaderSaveForLaterButtonTitles(saveForLaterButton, showTitle: false) + WPStyleGuide.applyReaderReblogActionButtonTitle(reblogActionButton, showTitle: false) + } else { let likeTitle = WPStyleGuide.likeCountForDisplay(likeCount) let commentTitle = WPStyleGuide.commentCountForDisplay(commentCount) @@ -546,9 +538,7 @@ import Gridicons commentActionButton.setTitle(commentTitle, for: .normal) WPStyleGuide.applyReaderSaveForLaterButtonTitles(saveForLaterButton) - if FeatureFlag.postReblogging.enabled { - WPStyleGuide.applyReaderReblogActionButtonTitle(reblogActionButton) - } + WPStyleGuide.applyReaderReblogActionButtonTitle(reblogActionButton) } } @@ -671,9 +661,7 @@ extension ReaderPostCardCell: Accessible { prepareMenuForVoiceOver() prepareVisitForVoiceOver() prepareFollowButtonForVoiceOver() - if FeatureFlag.postReblogging.enabled { - prepareReblogForVoiceOver() - } + prepareReblogForVoiceOver() } private func prepareCardForVoiceOver() { diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift index 4db51db8efcb..82da023c5239 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPostCellActions.swift @@ -6,7 +6,20 @@ class ReaderPostCellActions: NSObject, ReaderPostCellDelegate { var imageRequestAuthToken: String? = nil var isLoggedIn: Bool = false - private let visibleConfirmation: Bool + var visibleConfirmation: Bool { + didSet { + saveForLaterAction?.visibleConfirmation = visibleConfirmation + } + } + + private weak var saveForLaterAction: ReaderSaveForLaterAction? + + // saved posts + /// Posts that have been removed but not yet discarded + // TODO: - READERNAV - Set this property as private once the old reader class ReaderSavedPostCellActions is removed + var removedPosts = ReaderSaveForLaterRemovedPosts() + + weak var savedPostsDelegate: ReaderSavedPostCellActionsDelegate? init(context: NSManagedObjectContext, origin: UIViewController, topic: ReaderAbstractTopic? = nil, visibleConfirmation: Bool = true) { self.context = context @@ -38,10 +51,18 @@ class ReaderPostCellActions: NSObject, ReaderPostCellDelegate { } func readerCell(_ cell: ReaderPostCardCell, saveActionForProvider provider: ReaderPostContentProvider) { - guard let post = provider as? ReaderPost else { - return + if let origin = origin as? ReaderStreamViewController, origin.isSavedPostsController { + if let post = provider as? ReaderPost { + removedPosts.add(post) + } + savedPostsDelegate?.willRemove(cell) + + } else { + guard let post = provider as? ReaderPost else { + return + } + toggleSavedForLater(for: post) } - toggleSavedForLater(for: post) } func readerCell(_ cell: ReaderPostCardCell, shareActionForProvider provider: ReaderPostContentProvider, fromView sender: UIView) { @@ -105,19 +126,26 @@ class ReaderPostCellActions: NSObject, ReaderPostCellDelegate { func toggleSavedForLater(for post: ReaderPost) { let actionOrigin: ReaderSaveForLaterOrigin + // TODO: - READERNAV - Update this check once the old reader is removed if origin is ReaderSavedPostsViewController { actionOrigin = .savedStream + } else if let origin = origin as? ReaderStreamViewController, origin.isSavedPostsController, FeatureFlag.newReaderNavigation.enabled { + actionOrigin = .savedStream + } else { actionOrigin = .otherStream } if !post.isSavedForLater { - if let origin = origin as? UIViewController & UIViewControllerTransitioningDelegate { + if let origin = origin as? ReaderStreamViewController, !origin.isSavedPostsController { FancyAlertViewController.presentReaderSavedPostsAlertControllerIfNecessary(from: origin) } } - ReaderSaveForLaterAction(visibleConfirmation: visibleConfirmation).execute(with: post, context: context, origin: actionOrigin) + let saveAction = ReaderSaveForLaterAction(visibleConfirmation: visibleConfirmation) + + saveAction.execute(with: post, context: context, origin: actionOrigin) + saveForLaterAction = saveAction } fileprivate func visitSiteForPost(_ post: ReaderPost) { @@ -171,3 +199,21 @@ enum ReaderActionsVisibility: Equatable { } } } + + +// MARK: - Saved Posts +extension ReaderPostCellActions { + + func postIsRemoved(_ post: ReaderPost) -> Bool { + return removedPosts.contains(post) + } + + func restoreUnsavedPost(_ post: ReaderPost) { + removedPosts.remove(post) + } + + func clearRemovedPosts() { + removedPosts.all().forEach({ toggleSavedForLater(for: $0) }) + removedPosts = ReaderSaveForLaterRemovedPosts() + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLater+Analytics.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLater+Analytics.swift index e8839c0864b7..26167760a239 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLater+Analytics.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLater+Analytics.swift @@ -32,7 +32,8 @@ enum ReaderSaveForLaterOrigin { } } - fileprivate var viewAllPostsValue: String { + // TODO: - READERNAV - Refactor this and ReaderStreamViewController+Helper once the old reader is removed + var viewAllPostsValue: String { switch self { case .savedStream: return "post_list_saved_post_notice" @@ -83,6 +84,10 @@ extension ReaderSavedPostsViewController { extension ReaderStreamViewController { func trackSavedPostNavigation() { - WPAppAnalytics.track(.readerSavedPostOpened, withProperties: [ readerSaveForLaterSourceKey: ReaderSaveForLaterOrigin.otherStream.openPostValue ]) + if FeatureFlag.newReaderNavigation.enabled, isSavedPostsController { + WPAppAnalytics.track(.readerSavedPostOpened, withProperties: [ readerSaveForLaterSourceKey: ReaderSaveForLaterOrigin.savedStream.openPostValue ]) + } else { + WPAppAnalytics.track(.readerSavedPostOpened, withProperties: [ readerSaveForLaterSourceKey: ReaderSaveForLaterOrigin.otherStream.openPostValue ]) + } } } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLaterAction.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLaterAction.swift index ea679a20596d..f8a2e015f551 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLaterAction.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSaveForLaterAction.swift @@ -11,7 +11,7 @@ final class ReaderSaveForLaterAction { static let removeFromSavedError = NSLocalizedString("Could not remove post from Saved for Later", comment: "Title of a prompt.") } - private let visibleConfirmation: Bool + var visibleConfirmation: Bool init(visibleConfirmation: Bool = false) { self.visibleConfirmation = visibleConfirmation diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostCellActions.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostCellActions.swift index 8bfbbacc0a73..bd6bf8282e61 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostCellActions.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostCellActions.swift @@ -5,31 +5,11 @@ protocol ReaderSavedPostCellActionsDelegate: class { /// Specialises ReaderPostCellActions to provide specific overrides for the ReaderSavedPostsViewController final class ReaderSavedPostCellActions: ReaderPostCellActions { - /// Posts that have been removed but not yet discarded - private var removedPosts = ReaderSaveForLaterRemovedPosts() - - weak var delegate: ReaderSavedPostCellActionsDelegate? override func readerCell(_ cell: ReaderPostCardCell, saveActionForProvider provider: ReaderPostContentProvider) { if let post = provider as? ReaderPost { removedPosts.add(post) } - delegate?.willRemove(cell) - } - - func postIsRemoved(_ post: ReaderPost) -> Bool { - return removedPosts.contains(post) - } - - func restoreUnsavedPost(_ post: ReaderPost) { - removedPosts.remove(post) - } - - func clearRemovedPosts() { - let allRemovedPosts = removedPosts.all() - for post in allRemovedPosts { - toggleSavedForLater(for: post) - } - removedPosts = ReaderSaveForLaterRemovedPosts() + savedPostsDelegate?.willRemove(cell) } } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostsViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostsViewController.swift index d0b0b2b6c876..68cec58d9e2b 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostsViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSavedPostsViewController.swift @@ -2,7 +2,7 @@ import UIKit import Gridicons import WordPressShared import WordPressUI - +// TODO: - READERNAV - Remove this file once the new reader is released and stable final class ReaderSavedPostsViewController: UITableViewController { private enum Strings { static let title = NSLocalizedString("Saved Posts", comment: "Title for list of posts saved for later") @@ -105,7 +105,7 @@ final class ReaderSavedPostsViewController: UITableViewController { @objc public func configurePostCardCell(_ cell: UITableViewCell, post: ReaderPost) { if postCellActions == nil { postCellActions = ReaderSavedPostCellActions(context: managedObjectContext(), origin: self, topic: post.topic, visibleConfirmation: false) - postCellActions?.delegate = self + postCellActions?.savedPostsDelegate = self } cellConfiguration.configurePostCardCell(cell, diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift index 04d4d724296c..e24b32c9081e 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift @@ -45,12 +45,8 @@ extension ReaderStreamViewController { } func headerForStream(_ topic: ReaderAbstractTopic) -> ReaderHeader? { - if ReaderHelpers.topicIsFreshlyPressed(topic) || ReaderHelpers.topicIsLiked(topic) { - // no header for these special lists - return nil - } - if ReaderHelpers.topicIsFollowing(topic) { + if ReaderHelpers.topicIsFollowing(topic), !FeatureFlag.newReaderNavigation.enabled { return Bundle.main.loadNibNamed("ReaderFollowedSitesStreamHeader", owner: nil, options: nil)!.first as! ReaderFollowedSitesStreamHeader } @@ -135,5 +131,62 @@ extension ReaderStreamViewController { message: NSLocalizedString("No posts have been made recently", comment: "A default message shown whe the reader can find no post to display") ) } +} + + +// MARK: - No Results for saved posts +extension ReaderStreamViewController { + + func configureNoResultsViewForSavedPosts() { + + let noResultsResponse = NoResultsResponse(title: NSLocalizedString("No Saved Posts", + comment: "Message displayed in Reader Saved Posts view if a user hasn't yet saved any posts."), + message: NSLocalizedString("Tap [bookmark-outline] to save a post to your list.", + comment: "A hint displayed in the Saved Posts section of the Reader. The '[bookmark-outline]' placeholder will be replaced by an icon at runtime – please leave that string intact.")) + + var messageText = NSMutableAttributedString(string: noResultsResponse.message) + + // Get attributed string styled for No Results so it gets the correct font attributes added to it. + // The font is used by the attributed string `replace(_:with:)` method below to correctly position the icon. + let styledText = resultsStatusView.applyMessageStyleTo(attributedString: messageText) + messageText = NSMutableAttributedString(attributedString: styledText) + + let icon = UIImage.gridicon(.bookmarkOutline, size: CGSize(width: 18, height: 18)) + messageText.replace("[bookmark-outline]", with: icon) + + resultsStatusView.configure(title: noResultsResponse.title, attributedSubtitle: messageText) + } +} + +// MARK: - Undo cell for saved posts +extension ReaderStreamViewController { + + private enum UndoCell { + static let nibName = "ReaderSavedPostUndoCell" + static let reuseIdentifier = "ReaderUndoCellReuseIdentifier" + static let height: CGFloat = 44 + } + + func setupUndoCell(_ tableView: UITableView) { + let nib = UINib(nibName: UndoCell.nibName, bundle: nil) + tableView.register(nib, forCellReuseIdentifier: UndoCell.reuseIdentifier) + } + + func undoCell(_ tableView: UITableView) -> ReaderSavedPostUndoCell { + return tableView.dequeueReusableCell(withIdentifier: UndoCell.reuseIdentifier) as! ReaderSavedPostUndoCell + } + + func configureUndoCell(_ cell: ReaderSavedPostUndoCell, with post: ReaderPost) { + cell.title.text = post.titleForDisplay() + cell.delegate = self + } +} + + +// MARK: - Tracks +extension ReaderStreamViewController { + func trackSavedListAccessed() { + WPAppAnalytics.track(.readerSavedListViewed, withProperties: ["source": ReaderSaveForLaterOrigin.readerMenu.viewAllPostsValue]) + } } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift index c8d45fe92c39..415a1d016ff1 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift @@ -30,8 +30,18 @@ import WordPressFlux return tableViewController.tableView } - private var syncHelper: WPContentSyncHelper! - private var resultsStatusView = NoResultsViewController.controller() + private var syncHelpers: [ReaderAbstractTopic: WPContentSyncHelper] = [:] + + private var syncHelper: WPContentSyncHelper? { + guard let topic = readerTopic else { + return nil + } + let currentHelper = syncHelpers[topic] ?? WPContentSyncHelper() + syncHelpers[topic] = currentHelper + return currentHelper + } + + private(set) var resultsStatusView = NoResultsViewController.controller() private lazy var footerView: PostListFooterView = { return tableConfiguration.footer() @@ -105,7 +115,11 @@ import WordPressFlux /// The topic can be nil while a site or tag topic is being fetched, hence, optional. @objc var readerTopic: ReaderAbstractTopic? { didSet { - oldValue?.inUse = false + if let oldValue = oldValue { + oldValue.inUse = false + syncHelpers[oldValue]?.delegate = nil + } + syncHelper?.delegate = self if let newTopic = readerTopic { newTopic.inUse = true @@ -115,6 +129,9 @@ import WordPressFlux if readerTopic != nil && readerTopic != oldValue { if didSetupView { configureControllerForTopic() + if let syncHelper = syncHelper, syncHelper.isSyncing, !isShowingResultStatusView { + displayLoadingViewIfNeeded() + } } // Discard the siteID (if there was one) now that we have a good topic siteID = nil @@ -123,6 +140,21 @@ import WordPressFlux } } + var isSavedPostsController: Bool = false { + willSet { + if isSavedPostsController && !newValue { + postCellActions?.clearRemovedPosts() + } + } + didSet { + if isSavedPostsController { + configureControllerForTopic(synchronize: false) + trackSavedListAccessed() + } + postCellActions?.visibleConfirmation = !isSavedPostsController + } + } + /// Facilitates sharing of a blog via `ReaderStreamViewController+Sharing.swift`. let sharingController = PostSharingController() @@ -135,6 +167,11 @@ import WordPressFlux /// - Returns: An instance of the controller /// @objc class func controllerWithTopic(_ topic: ReaderAbstractTopic) -> ReaderStreamViewController { + // if a default discover topic is provided, treat it as a site to retrieve the header + if ReaderHelpers.topicIsDiscover(topic) && FeatureFlag.newReaderNavigation.enabled { + return controllerWithSiteID(ReaderHelpers.discoverSiteID, isFeed: false) + } + let storyboard = UIStoryboard(name: "Reader", bundle: Bundle.main) let controller = storyboard.instantiateViewController(withIdentifier: "ReaderStreamViewController") as! ReaderStreamViewController controller.readerTopic = topic @@ -238,7 +275,6 @@ import WordPressFlux setupTableView() setupFooterView() setupContentHandler() - setupSyncHelper() setupResultsStatusView() observeNetworkStatus() @@ -375,6 +411,7 @@ import WordPressFlux add(tableViewController, asChildOf: self) layoutTableView() tableConfiguration.setup(tableView) + setupUndoCell(tableView) } @objc func configureRefreshControl() { @@ -403,11 +440,6 @@ import WordPressFlux content.initializeContent(tableView: tableView, delegate: self) } - private func setupSyncHelper() { - syncHelper = WPContentSyncHelper() - syncHelper.delegate = self - } - private func setupResultsStatusView() { resultsStatusView.delegate = self } @@ -439,12 +471,12 @@ import WordPressFlux } tableView.tableHeaderView = header - - // This feels somewhat hacky, but it is the only way I found to insert a stack view into the header without breaking the autolayout constraints. - header.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true - header.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true - header.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true - + if !FeatureFlag.newReaderNavigation.enabled { + // This feels somewhat hacky, but it is the only way I found to insert a stack view into the header without breaking the autolayout constraints. + header.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true + header.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true + header.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true + } tableView.tableHeaderView?.layoutIfNeeded() tableView.tableHeaderView = tableView.tableHeaderView } @@ -463,7 +495,6 @@ import WordPressFlux /// Configures the controller for the `readerTopic`. This should only be called /// once when the topic is set. private func configureControllerForTopic(synchronize: Bool = true) { - assert(readerTopic != nil, "A reader topic is required") assert(isViewLoaded, "The controller's view must be loaded before displaying the topic") // Enable the view now that we have a topic. @@ -487,7 +518,11 @@ import WordPressFlux hideResultsStatus() recentlyBlockedSitePostObjectIDs.removeAllObjects() updateAndPerformFetchRequest() - configureStreamHeader() + if readerTopic != nil { + configureStreamHeader() + } else { + tableView.tableHeaderView = nil + } tableView.setContentOffset(CGPoint.zero, animated: false) content.refresh() refreshTableViewHeaderLayout() @@ -498,10 +533,10 @@ import WordPressFlux bumpStats() - let count = content.contentCount - // Make sure we're showing the no results view if appropriate - if !syncHelper.isSyncing && count == 0 { + if let syncHelper = syncHelper, !syncHelper.isSyncing, content.isEmpty { + displayNoResultsView() + } else if isSavedPostsController, content.isEmpty { displayNoResultsView() } @@ -719,7 +754,7 @@ import WordPressFlux return } - syncHelper.syncContentWithUserInteraction(true) + syncHelper?.syncContentWithUserInteraction(true) } @@ -805,7 +840,7 @@ import WordPressFlux let lastSynced = topic.lastSynced ?? Date(timeIntervalSince1970: 0) let interval = Int( Date().timeIntervalSince(lastSynced)) if canSync() && (interval >= refreshInterval || topic.posts.count == 0) { - syncHelper.syncContentWithUserInteraction(false) + syncHelper?.syncContentWithUserInteraction(false) } else { handleConnectionError() } @@ -845,7 +880,7 @@ import WordPressFlux return } - if syncHelper.isSyncing { + if let syncHelper = syncHelper, syncHelper.isSyncing { let alertTitle = NSLocalizedString("Busy", comment: "Title of a prompt letting the user know that they must wait until the current aciton completes.") let alertMessage = NSLocalizedString("Please wait til the current fetch completes.", comment: "Asks the usre to wait until the currently running fetch request completes.") let cancelTitle = NSLocalizedString("OK", comment: "Title of a button that dismisses a prompt") @@ -859,7 +894,7 @@ import WordPressFlux } indexPathForGapMarker = indexPath syncIsFillingGap = true - syncHelper.syncContentWithUserInteraction(true) + syncHelper?.syncContentWithUserInteraction(true) } @@ -1057,7 +1092,9 @@ import WordPressFlux // avoids returning readerPosts that do not belong to a topic (e.g. those // loaded from a notification). We can do this by specifying that self // has to exist within an empty set. - let predicateForNilTopic = NSPredicate(format: "topic = NULL AND SELF in %@", []) + let predicateForNilTopic = isSavedPostsController ? + NSPredicate(format: "isSavedForLater == YES") : + NSPredicate(format: "topic = NULL AND SELF in %@", []) guard let topic = readerTopic else { return predicateForNilTopic @@ -1083,18 +1120,15 @@ import WordPressFlux private func configurePostCardCell(_ cell: UITableViewCell, post: ReaderPost) { - guard let topic = readerTopic else { - return - } - if postCellActions == nil { postCellActions = ReaderPostCellActions(context: managedObjectContext(), origin: self, topic: readerTopic) } postCellActions?.isLoggedIn = isLoggedIn + postCellActions?.savedPostsDelegate = self cellConfiguration.configurePostCardCell(cell, withPost: post, - topic: topic, + topic: readerTopic ?? post.topic, delegate: postCellActions, loggedInActionVisibility: .visible(enabled: isLoggedIn)) } @@ -1117,7 +1151,7 @@ import WordPressFlux let service = ReaderTopicService(managedObjectContext: topic.managedObjectContext!) service.toggleFollowing(forTag: topic, success: { [weak self] in - self?.syncHelper.syncContent() + self?.syncHelper?.syncContent() }, failure: { [weak self] (error: Error?) in generator.notificationOccurred(.error) self?.updateStreamHeaderIfNeeded() @@ -1145,7 +1179,7 @@ import WordPressFlux let service = ReaderTopicService(managedObjectContext: topic.managedObjectContext!) service.toggleFollowing(forSite: topic, success: { [weak self] in - self?.syncHelper.syncContent() + self?.syncHelper?.syncContent() if toFollow { self?.dispatchSubscribingNotificationNotice(with: siteTitle, siteID: siteID) } @@ -1316,7 +1350,7 @@ extension ReaderStreamViewController: WPTableViewHandlerDelegate { func tableViewHandlerDidRefreshTableViewPreservingOffset(_ tableViewHandler: WPTableViewHandler) { hideResultsStatus() if tableViewHandler.resultsController.fetchedObjects?.count == 0 { - if syncHelper.isSyncing { + if let syncHelper = syncHelper, syncHelper.isSyncing { return } displayNoResultsView() @@ -1385,6 +1419,12 @@ extension ReaderStreamViewController: WPTableViewHandlerDelegate { return cell } + if isSavedPostsController, postCellActions?.postIsRemoved(post) == true { + let cell = undoCell(tableView) + configureUndoCell(cell, with: post) + return cell + } + let cell = tableConfiguration.postCardCell(tableView) configurePostCardCell(cell, post: post) return cell @@ -1402,7 +1442,7 @@ extension ReaderStreamViewController: WPTableViewHandlerDelegate { // - there is more content, // - when we are not alrady syncing // - when we are not waiting for scrolling to end to cleanup and refresh the list - if syncHelper.hasMoreContent && !syncHelper.isSyncing && !cleanupAndRefreshAfterScrolling { + if let syncHelper = syncHelper, syncHelper.hasMoreContent && !syncHelper.isSyncing && !cleanupAndRefreshAfterScrolling { syncHelper.syncMoreContent() } } @@ -1468,7 +1508,7 @@ extension ReaderStreamViewController: WPTableViewHandlerDelegate { } - if post.isSavedForLater { + if post.isSavedForLater || isSavedPostsController { trackSavedPostNavigation() } @@ -1536,6 +1576,9 @@ private extension ReaderStreamViewController { // Its possible the topic was deleted before a sync could be completed, // so make certain its not nil. guard let topic = readerTopic else { + if isSavedPostsController { + displayNoResultsForSavedPosts() + } return } @@ -1569,8 +1612,14 @@ private extension ReaderStreamViewController { resultsStatusView.configure(title: title, buttonTitle: buttonTitle, subtitle: subtitle, image: imageName, accessoryView: accessoryView) } + private func displayNoResultsForSavedPosts() { + configureNoResultsViewForSavedPosts() + displayResultsStatus() + } + func displayResultsStatus() { resultsStatusView.removeFromView() + tableViewController.addChild(resultsStatusView) tableView.insertSubview(resultsStatusView.view, belowSubview: refreshControl) resultsStatusView.view.frame = tableView.frame resultsStatusView.didMove(toParent: tableViewController) @@ -1663,3 +1712,38 @@ extension ReaderStreamViewController: UIViewControllerTransitioningDelegate { return FancyAlertPresentationController(presentedViewController: presented, presenting: presenting) } } + + +// MARK: - Topic Injection +extension ReaderStreamViewController { + func setTopic(_ topic: ReaderAbstractTopic?) { + guard let actualTopic = topic, ReaderHelpers.topicIsDiscover(actualTopic) else { + readerTopic = topic + return + } + readerTopic = nil + isFeed = false + siteID = ReaderHelpers.discoverSiteID + } +} + + +// MARK: - Saved Posts Delegate +extension ReaderStreamViewController: ReaderSavedPostCellActionsDelegate { + func willRemove(_ cell: ReaderPostCardCell) { + if let cellIndex = tableView.indexPath(for: cell) { + tableView.reloadRows(at: [cellIndex], with: .fade) + } + } +} + +// MARK: - Undo +extension ReaderStreamViewController: ReaderPostUndoCellDelegate { + func readerCellWillUndo(_ cell: ReaderSavedPostUndoCell) { + if let cellIndex = tableView.indexPath(for: cell), + let post: ReaderPost = content.object(at: cellIndex) { + postCellActions?.restoreUnsavedPost(post) + tableView.reloadRows(at: [cellIndex], with: .fade) + } + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift index b36e92a9645a..af6a8594cb71 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabView.swift @@ -9,6 +9,7 @@ class ReaderTabView: UIView { private let resetFilterButton: UIButton private let settingsButton: UIButton private let verticalDivider: UIView + private let horizontalDivider: UIView private let containerView: UIView private let viewModel: ReaderTabViewModel @@ -21,6 +22,7 @@ class ReaderTabView: UIView { resetFilterButton = UIButton(type: .custom) settingsButton = UIButton(type: .custom) verticalDivider = UIView() + horizontalDivider = UIView() containerView = UIView() self.viewModel = viewModel @@ -49,9 +51,9 @@ extension ReaderTabView { setupButtonsView() setupFilterButton() setupResetFilterButton() - setupVerticalDivider() + setupVerticalDivider(verticalDivider) + setupHorizontalDivider(horizontalDivider) setupSettingsButton() - setupContainerView() activateConstraints() } @@ -61,6 +63,7 @@ extension ReaderTabView { addSubview(mainStackView) mainStackView.addArrangedSubview(tabBar) mainStackView.addArrangedSubview(buttonsStackView) + mainStackView.addArrangedSubview(horizontalDivider) mainStackView.addArrangedSubview(containerView) } @@ -74,6 +77,7 @@ extension ReaderTabView { return } self.populateTabBar(with: items) + self.addContentToContainerView() } } @@ -83,17 +87,16 @@ extension ReaderTabView { return } buttonsStackView.isHidden = tabItem.shouldHideButtonsView + horizontalDivider.isHidden = tabItem.shouldHideButtonsView } private func setupButtonsView() { buttonsStackView.translatesAutoresizingMaskIntoConstraints = false buttonsStackView.isLayoutMarginsRelativeArrangement = true buttonsStackView.axis = .horizontal - buttonsStackView.alignment = .center + buttonsStackView.alignment = .fill buttonsStackView.addArrangedSubview(filterButton) buttonsStackView.addArrangedSubview(resetFilterButton) - let spacer = UIView() - buttonsStackView.addArrangedSubview(spacer) buttonsStackView.addArrangedSubview(verticalDivider) buttonsStackView.addArrangedSubview(settingsButton) } @@ -103,6 +106,7 @@ extension ReaderTabView { filterButton.contentEdgeInsets = Appearance.filterButtonInsets filterButton.imageEdgeInsets = Appearance.filterButtonimageInsets filterButton.titleEdgeInsets = Appearance.filterButtonTitleInsets + filterButton.contentHorizontalAlignment = .leading filterButton.titleLabel?.font = Appearance.filterButtonFont WPStyleGuide.applyReaderFilterButtonStyle(filterButton) @@ -118,9 +122,23 @@ extension ReaderTabView { resetFilterButton.isHidden = true } - private func setupVerticalDivider() { - verticalDivider.translatesAutoresizingMaskIntoConstraints = false - verticalDivider.backgroundColor = .divider + private func setupVerticalDivider(_ divider: UIView) { + divider.translatesAutoresizingMaskIntoConstraints = false + let dividerView = UIView() + dividerView.translatesAutoresizingMaskIntoConstraints = false + dividerView.backgroundColor = Appearance.dividerColor + divider.addSubview(dividerView) + NSLayoutConstraint.activate([ + dividerView.centerYAnchor.constraint(equalTo: divider.centerYAnchor), + dividerView.heightAnchor.constraint(equalTo: divider.heightAnchor, multiplier: Appearance.verticalDividerHeightMultiplier), + dividerView.leadingAnchor.constraint(equalTo: divider.leadingAnchor), + dividerView.trailingAnchor.constraint(equalTo: divider.trailingAnchor) + ]) + } + + private func setupHorizontalDivider(_ divider: UIView) { + divider.translatesAutoresizingMaskIntoConstraints = false + divider.backgroundColor = Appearance.dividerColor } private func setupSettingsButton() { @@ -129,9 +147,21 @@ extension ReaderTabView { WPStyleGuide.applyReaderSettingsButtonStyle(settingsButton) } - private func setupContainerView() { + private func addContentToContainerView() { + guard let controller = self.next as? UIViewController, + let readerTabItem = tabBar.items[tabBar.selectedIndex] as? ReaderTabItem, + let childController = viewModel.makeChildViewController(with: readerTabItem) else { + return + } + containerView.translatesAutoresizingMaskIntoConstraints = false - containerView.backgroundColor = .lightGray + childController.view.translatesAutoresizingMaskIntoConstraints = false + + controller.addChild(childController) + + containerView.addSubview(childController.view) + containerView.pinSubviewToAllEdges(childController.view) + childController.didMove(toParent: controller) } private func activateConstraints() { @@ -139,9 +169,9 @@ extension ReaderTabView { NSLayoutConstraint.activate([ buttonsStackView.heightAnchor.constraint(equalToConstant: Appearance.barHeight), resetFilterButton.widthAnchor.constraint(equalToConstant: Appearance.resetButtonWidth), - verticalDivider.widthAnchor.constraint(equalToConstant: Appearance.verticalDividerWidth), - verticalDivider.heightAnchor.constraint(equalTo: buttonsStackView.heightAnchor, - multiplier: Appearance.verticalDividerHeightMultiplier), + verticalDivider.widthAnchor.constraint(equalToConstant: Appearance.dividerWidth), + horizontalDivider.heightAnchor.constraint(equalToConstant: Appearance.dividerWidth), + horizontalDivider.widthAnchor.constraint(equalTo: mainStackView.widthAnchor), settingsButton.widthAnchor.constraint(equalToConstant: Appearance.settingsButtonWidth) ]) } @@ -151,39 +181,47 @@ extension ReaderTabView { extension ReaderTabView { /// Tab bar @objc private func selectedTabDidChange(_ tabBar: FilterTabBar) { - // hide/show buttons view depending on the selected TabBarItem - guard let tabItems = tabBar.items as? [ReaderTabItem], - buttonsStackView.isHidden != tabItems[tabBar.selectedIndex].shouldHideButtonsView else { + guard let tabItems = tabBar.items as? [ReaderTabItem] else { return } + self.viewModel.showTab(for: tabBar.items[tabBar.selectedIndex]) + // hide/show buttons depending on the selected tab. Do not execute the animation if not necessary. + guard buttonsStackView.isHidden != tabItems[tabBar.selectedIndex].shouldHideButtonsView else { + return + } + UIView.animate(withDuration: Appearance.tabBarAnimationsDuration) { + let shouldHideButtons = tabItems[tabBar.selectedIndex].shouldHideButtonsView + self.buttonsStackView.isHidden = shouldHideButtons + self.horizontalDivider.isHidden = shouldHideButtons + } + } - UIView.animate(withDuration: Appearance.tabBarAnimationsDuration, - animations: { - self.buttonsStackView.isHidden = tabItems[tabBar.selectedIndex].shouldHideButtonsView - }, - completion: { finished in - if finished { - self.viewModel.navigateToTab(at: tabBar.selectedIndex) - } - }) + func switchToSavedPosts() { + guard let index = tabBar.items.firstIndex(where: { + $0.title == "Saved" + }) else { + return + } + tabBar.setSelectedIndex(index) + selectedTabDidChange(tabBar) } /// Filter button @objc private func didTapFilterButton() { - //TODO: - READERNAV - Remove. This test code is for UI prototyping only - guard filterButton.titleLabel?.text == "Filter" else { - return + /// Present from the image view to align to the left hand side + viewModel.presentFilter(from: filterButton.imageView ?? filterButton) { [weak self] title in + if let title = title { + self?.resetFilterButton.isHidden = false + self?.setFilterButtonTitle(title) + } } - setFilterButtonTitle("Phoebe's Photos") - resetFilterButton.isHidden = false - viewModel.presentFilter() } /// Reset filter button @objc private func didTapResetFilterButton() { setFilterButtonTitle(Appearance.defaultFilterButtonTitle) resetFilterButton.isHidden = true - viewModel.resetFilter() + viewModel.resetFilter(selectedItem: tabBar.items[tabBar.selectedIndex]) } @objc private func didTapSettingsButton() { @@ -191,7 +229,6 @@ extension ReaderTabView { } } - // MARK: - Appearance extension ReaderTabView { @@ -202,17 +239,17 @@ extension ReaderTabView { 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: .semibold) + 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: 0, bottom: 0, right: 0) + static let filterButtonimageInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0) static let filterButtonTitleInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) - static let resetButtonWidth: CGFloat = 32 static let resetButtonInsets = UIEdgeInsets(top: 1, left: -4, bottom: -1, right: 4) static let settingsButtonWidth: CGFloat = 56 - static let verticalDividerWidth: CGFloat = 1 + static let dividerWidth: CGFloat = .hairlineBorderWidth + static let dividerColor: UIColor = .lightGray static let verticalDividerHeightMultiplier: CGFloat = 0.6 } } diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift index 22a9467adc7e..1a618c41c344 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift @@ -7,7 +7,14 @@ class ReaderTabViewController: UIViewController { init(viewModel: ReaderTabViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) - self.title = NSLocalizedString("Reader", comment: "The default title of the Reader") + self.viewModel.filterTapped = { [weak self] (fromView, completion) in + guard let self = self else { return } + self.viewModel.presentFilter(from: self, sourceView: fromView, completion: { [weak self] title in + self?.dismiss(animated: true, completion: nil) + completion(title) + }) + } + title = NSLocalizedString("Reader", comment: "The default title of the Reader") setupSearchButton() } @@ -22,14 +29,26 @@ class ReaderTabViewController: UIViewController { } override func loadView() { - self.view = ReaderTabView(viewModel: viewModel) + view = ReaderTabView(viewModel: viewModel) } } -// MARK: - Actions +// MARK: - Search extension ReaderTabViewController { - /// Search button + @objc private func didTapSearchButton() { - viewModel.performSearch() + let searchController = ReaderSearchViewController.controller() + navigationController?.pushViewController(searchController, animated: true) + } +} + + +// MARK: - Tab Switching +extension ReaderTabViewController { + @objc func navigateToSavedPosts() { + guard let readerTabView = view as? ReaderTabView else { + return + } + readerTabView.switchToSavedPosts() } } diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewModel.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewModel.swift index 03322df12408..6b4c3fa6cf3b 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewModel.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewModel.swift @@ -1,30 +1,84 @@ class ReaderTabViewModel { - // TODO: - READERNAV - Methods to be implemented. Signature will likely change - func navigateToTab(at index: Int) { } + var tabSelectionCallback: ((ReaderAbstractTopic?) -> Void)? + var selectedFilter: ReaderAbstractTopic? + var filterTapped: ((UIView, @escaping (ReaderAbstractTopic?) -> Void) -> Void)? + + init() { + addNotificationsObservers() + } + + func showTab(for item: FilterTabBarItem) { + + guard let readerItem = item as? ReaderTabItem else { + return + } + + let topic = readerItem.topic + let selectedTopic: ReaderAbstractTopic? + if readerItem.shouldHideButtonsView == false { + selectedTopic = selectedFilter ?? topic + } else { + selectedTopic = topic + } + + tabSelectionCallback?(selectedTopic) + } + + // TODO: - READERNAV - Methods to be implemented. Signature will likely change func performSearch() { } - func presentFilter() { } + func presentFilter(from: UIViewController, sourceView: UIView, completion: @escaping (ReaderAbstractTopic?) -> Void) { + let viewController = makeFilterSheetViewController(completion: completion) + let bottomSheet = BottomSheetViewController(childViewController: viewController) + bottomSheet.additionalSafeAreaInsetsRegular = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) + bottomSheet.show(from: from, sourceView: sourceView, arrowDirections: .up) + } + + func presentFilter(from: UIView, completion: @escaping (String?) -> Void) { + filterTapped?(from, { [weak self] topic in + self?.selectedFilter = topic + if let topic = topic { + self?.tabSelectionCallback?(topic) + } + completion(topic?.title) + }) + } - func resetFilter() { } + func resetFilter(selectedItem: FilterTabBarItem) { + selectedFilter = nil + if let topic = (selectedItem as? ReaderTabItem)?.topic { + tabSelectionCallback?(topic) + } + } func presentSettings() { } } +// MARK: - Bottom Sheet + +extension ReaderTabViewModel { + private func makeFilterSheetViewController(completion: @escaping (ReaderAbstractTopic) -> Void) -> FilterSheetViewController { + return FilterSheetViewController(filters: + [ReaderSiteTopic.filterProvider(), + ReaderTagTopic.filterProvider()], + changedFilter: completion) + } +} + // MARK: - Tab Bar extension ReaderTabViewModel { /// Fetch request to extract reader menu topics from Core Data private var topicsFetchRequest: NSFetchRequest { - let fetchRequest = NSFetchRequest(entityName: "ReaderAbstractTopic") + let fetchRequest = NSFetchRequest(entityName: ReaderTopics.entityName) - fetchRequest.predicate = NSPredicate(format: "following == %@ AND showInMenu == YES AND type == 'default' OR type == 'list' OR type == 'team'", - NSNumber(value: ReaderHelpers.isLoggedIn())) + fetchRequest.predicate = NSPredicate(format: ReaderTopics.predicateFormat, NSNumber(value: ReaderHelpers.isLoggedIn())) - fetchRequest.sortDescriptors = [NSSortDescriptor(key: "type", ascending: true)] + fetchRequest.sortDescriptors = [NSSortDescriptor(key: ReaderTopics.sortByKey, ascending: true)] return fetchRequest } @@ -36,11 +90,10 @@ extension ReaderTabViewModel { return } - let tabItems = topics.map { ReaderTabItem(topic: $0) } - completion(ReaderHelpers.rearrange(items: tabItems)) + completion(ReaderHelpers.rearrange(items: topics.map { ReaderTabItem(topic: $0) })) } catch { - DDLogError("There was a problem fetching topics for the menu." + error.localizedDescription) + DDLogError(ReaderTopics.fetchRequestError + error.localizedDescription) completion(nil) } } @@ -53,8 +106,90 @@ extension ReaderTabViewModel { service.fetchReaderMenu(success: { [weak self] in self?.fetchTabBarItems(completion: completion) }, failure: { error in - DDLogError("Error syncing menu: \(String(describing: error))") + DDLogError(ReaderTopics.remoteFetchError + String(describing: error)) completion(nil) }) } + + private enum ReaderTopics { + static let predicateFormat = "following == %@ AND showInMenu == YES AND type == 'default' OR type == 'list' OR type == 'team'" + + static let entityName = "ReaderAbstractTopic" + static let sortByKey = "type" + + static let fetchRequestError = "There was a problem fetching topics for the menu. " + static let remoteFetchError = "Error syncing menu: " + } +} + + +// MARK: Reader Content +extension ReaderTabViewModel { + + func makeChildViewController(with item: ReaderTabItem) -> UIViewController? { + guard let topic = item.topic else { + return nil + } + let controller = ReaderStreamViewController.controllerWithTopic(topic) + + self.tabSelectionCallback = { [weak controller] topic in + controller?.setTopic(topic) + controller?.isSavedPostsController = (topic == nil) + } + return controller + } +} + + +// MARK: - Logout and Termination Cleanup +extension ReaderTabViewModel { + + private func addNotificationsObservers() { + NotificationCenter.default.addObserver(forName: UIApplication.willTerminateNotification, + object: nil, + queue: nil) { notification in + self.cleanupStaleContent(removeAllTopics: false) + self.unflagInUseContent() + } + + NotificationCenter.default.addObserver(forName: .WPAccountDefaultWordPressComAccountChanged, + object: nil, + queue: nil) { notification in + self.unflagInUseContent() + self.clearSavedPosts() + self.cleanupStaleContent(removeAllTopics: true) + self.clearSearchSuggestions() + } + } + + /// Clears the inUse flag from any topics or posts so marked. + private func unflagInUseContent() { + let context = ContextManager.sharedInstance().mainContext + ReaderPostService(managedObjectContext: context).clearInUseFlags() + ReaderTopicService(managedObjectContext: context).clearInUseFlags() + } + + /// Clean up topics that do not belong in the menu and posts that have no topic + /// This is merely a convenient place to perform this task. + private func cleanupStaleContent(removeAllTopics removeAll: Bool) { + let context = ContextManager.sharedInstance().mainContext + ReaderPostService(managedObjectContext: context).deletePostsWithNoTopic() + + if removeAll { + ReaderTopicService(managedObjectContext: context).deleteAllTopics() + } else { + ReaderTopicService(managedObjectContext: context).deleteNonMenuTopics() + } + } + + /// Clears all saved posts, so they can be deleted by cleanup methods. + private func clearSavedPosts() { + let context = ContextManager.sharedInstance().mainContext + ReaderPostService(managedObjectContext: context).clearSavedPostFlags() + } + + private func clearSearchSuggestions() { + let context = ContextManager.sharedInstance().mainContext + ReaderSearchSuggestionService(managedObjectContext: context).deleteAllSuggestions() + } } diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/Post Stats/PostStatsViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/Post Stats/PostStatsViewModel.swift index 393eec6b1b73..d5cfafcc60ae 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/Post Stats/PostStatsViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/Post Stats/PostStatsViewModel.swift @@ -127,7 +127,10 @@ private extension PostStatsViewModel { let overviewData = OverviewTabData(tabTitle: StatSection.periodOverviewViews.tabTitle, tabData: dayData.viewCount, difference: dayData.difference, - differencePercent: dayData.percentage) + differencePercent: dayData.percentage, + date: selectedDate, + period: .day + ) let chart = PostChart(postViews: lastTwoWeeks) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift index 256dbe75274b..e3b7474d2943 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/DrawerPresentationController.swift @@ -4,6 +4,7 @@ public enum DrawerPosition { case expanded case collapsed case closed + case hidden } public enum DrawerHeight { @@ -13,8 +14,11 @@ public enum DrawerHeight { // Height is based on the specified margin from the top of the screen case topMargin(CGFloat) - // Height will be equal to the the content height value + // Height will be equal to the the content height value. A height of 0 will use the calculated height. case contentHeight(CGFloat) + + // Height in the hidden state will be equal the screens height + case hidden } public enum DrawerWidth { @@ -164,6 +168,10 @@ public class DrawerPresentationController: FancyAlertPresentationController { /// Returns the current position of the drawer public var currentPosition: DrawerPosition = .collapsed + /// Returns the Y position of the drawer + public var yPosition: CGFloat? { + return presentedView?.frame.origin.y + } /// Animates between the drawer positions /// - Parameter position: The position to animate to @@ -184,6 +192,9 @@ public class DrawerPresentationController: FancyAlertPresentationController { case .collapsed: margin = collapsedYPosition + case .hidden: + margin = hiddenYPosition + default: margin = 0 } @@ -236,6 +247,10 @@ public class DrawerPresentationController: FancyAlertPresentationController { return topMargin(with: height) } + private var hiddenYPosition: CGFloat { + return topMargin(with: .hidden) + } + /// Calculates the Y position for the view based on a DrawerHeight enum /// - Parameter drawerHeight: The drawer height to calculate private func topMargin(with drawerHeight: DrawerHeight) -> CGFloat { @@ -250,6 +265,9 @@ public class DrawerPresentationController: FancyAlertPresentationController { case .maxHeight: topMargin = safeAreaInsets.top + + case .hidden: + topMargin = UIScreen.main.bounds.height } return topMargin @@ -387,7 +405,8 @@ extension DrawerPresentationController: UIGestureRecognizerDelegate { /// Shouldn't happen; should always have container & presented view when tapped guard let containerView = containerView, - let presentedView = presentedView + let presentedView = presentedView, + currentPosition != .hidden else { return false } @@ -407,11 +426,12 @@ private extension DrawerPresentationController { guard let scrollView = presentableViewController?.scrollableView, !scrollView.isScrolling, - let presentedView = self.presentedView + let presentedView = self.presentedView, + let presentingView = presentingViewController.view else { return } - let bottom = presentingViewController.view.safeAreaLayoutGuide.layoutFrame.origin.y + let bottom = presentingView.safeAreaInsets.bottom let margin = presentedView.frame.origin.y + bottom scrollView.contentInset.bottom = margin diff --git a/WordPress/Classes/ViewRelated/System/FancyAlertViewController+Async.swift b/WordPress/Classes/ViewRelated/System/FancyAlertViewController+Async.swift deleted file mode 100644 index b96817d4859e..000000000000 --- a/WordPress/Classes/ViewRelated/System/FancyAlertViewController+Async.swift +++ /dev/null @@ -1,111 +0,0 @@ -import UIKit -import SafariServices - -private protocol AlertStrings { - static var titleText: String { get } - static var bodyText: String { get } - static var confirmTitle: String { get } -} - -extension FancyAlertViewController { - - typealias ButtonConfig = FancyAlertViewController.Config.ButtonConfig - - private struct PublishPostStrings: AlertStrings { - static let titleText = NSLocalizedString("Publish with Confidence", comment: "Title of alert informing users about the async publishing feature.") - static let bodyText = NSLocalizedString("You can now leave the editor and your post will save and publish behind the scenes! Give it a try!", comment: "Body text of alert informing users about the async publishing feature.") - static let confirmTitle = NSLocalizedString("Publish Now", comment: "Publish button shown in alert informing users about the async publishing feature.") - } - - private struct SchedulePostStrings: AlertStrings { - static let titleText = NSLocalizedString("Schedule with Confidence", comment: "Title of alert informing users about the async publishing feature, when scheduling a post.") - static let bodyText = NSLocalizedString("You can now leave the editor and your post will save and schedule behind the scenes! Give it a try!", comment: "Body text of alert informing users about the async publishing feature, when scheduling a post.") - static let confirmTitle = NSLocalizedString("Schedule Now", comment: "Schedule button shown in alert informing users about the async publishing feature.") - } - - private struct PublishPageStrings: AlertStrings { - static let titleText = NSLocalizedString("Publish with Confidence", comment: "Title of alert informing users about the async publishing feature.") - static let bodyText = NSLocalizedString("You can now leave the editor and your page will save and publish behind the scenes! Give it a try!", comment: "Body text of alert informing users about the async publishing feature, when publishing a page.") - static let confirmTitle = NSLocalizedString("Publish Now", comment: "Publish button shown in alert informing users about the async publishing feature.") - } - - private struct SchedulePageStrings: AlertStrings { - static let titleText = NSLocalizedString("Schedule with Confidence", comment: "Title of alert informing users about the async publishing feature, when scheduling a page.") - static let bodyText = NSLocalizedString("You can now leave the editor and your page will save and schedule behind the scenes! Give it a try!", comment: "Body text of alert informing users about the async publishing feature, when scheduling a page.") - static let confirmTitle = NSLocalizedString("Schedule Now", comment: "Schedule button shown in alert informing users about the async publishing feature.") - } - - private struct GeneralStrings { - static let keepEditingTitle = NSLocalizedString("Keep Editing", comment: "Button title shown in alert informing users about the async publishing feature.") - static let moreHelpTitle = NSLocalizedString("How does it work?", comment: "Title of the more help button on alert helping users understand their site address") - } - - static func makeAsyncPostingAlertController(action: PostEditorAction, isPage: Bool, onConfirm: @escaping (() -> Void)) -> FancyAlertViewController { - let strings = self.strings(for: action, isPage: isPage) - - let confirmButton = ButtonConfig(strings.confirmTitle) { controller, _ in - controller.dismiss(animated: true, completion: { - onConfirm() - }) - } - - let dismissButton = ButtonConfig(GeneralStrings.keepEditingTitle) { controller, _ in - controller.dismiss(animated: true) - } - - let moreHelpButton = ButtonConfig(GeneralStrings.moreHelpTitle) { controller, _ in - guard let url = URL(string: "http://en.blog.wordpress.com/2018/04/23/ios-nine-point-eight/") else { - return - } - - let safariViewController = SFSafariViewController(url: url) - safariViewController.modalPresentationStyle = .pageSheet - controller.present(safariViewController, animated: true) - } - - let image = UIImage(named: "wp-illustration-easy-async") - - let config = FancyAlertViewController.Config(titleText: strings.titleText, - bodyText: strings.bodyText, - headerImage: image, - dividerPosition: .bottom, - defaultButton: confirmButton, - cancelButton: dismissButton, - moreInfoButton: moreHelpButton, - titleAccessoryButton: nil, - dismissAction: {}) - - let controller = FancyAlertViewController.controllerWithConfiguration(configuration: config) - return controller - } - - private static func strings(for action: PostEditorAction, isPage: Bool) -> AlertStrings.Type { - switch (action, isPage) { - case (.schedule, true): - return SchedulePageStrings.self - case (.schedule, false): - return SchedulePostStrings.self - case (_, true): - return PublishPageStrings.self - case (_, false): - return PublishPostStrings.self - } - } -} - -// MARK: - User Defaults - -extension UserDefaults { - private enum Keys: String { - case asyncPromoWasDisplayed = "AsyncPromoWasDisplayed" - } - - var asyncPromoWasDisplayed: Bool { - get { - return bool(forKey: Keys.asyncPromoWasDisplayed.rawValue) - } - set { - set(newValue, forKey: Keys.asyncPromoWasDisplayed.rawValue) - } - } -} diff --git a/WordPress/Classes/ViewRelated/System/FancyAlertViewController+SavedPosts.swift b/WordPress/Classes/ViewRelated/System/FancyAlertViewController+SavedPosts.swift index 334a314c32f2..26a8cf8e14a9 100644 --- a/WordPress/Classes/ViewRelated/System/FancyAlertViewController+SavedPosts.swift +++ b/WordPress/Classes/ViewRelated/System/FancyAlertViewController+SavedPosts.swift @@ -2,6 +2,8 @@ import UIKit import Gridicons extension FancyAlertViewController { + typealias ButtonConfig = FancyAlertViewController.Config.ButtonConfig + private struct Strings { static let titleText = NSLocalizedString("Save Posts for Later", comment: "Title of alert informing users about the Reader Save for Later feature.") static let bodyText = NSLocalizedString("Save this post, and come back to read it whenever you'd like. It will only be available on this device — saved posts don't sync to other devices.", comment: "Body text of alert informing users about the Reader Save for Later feature.") diff --git a/WordPress/Classes/ViewRelated/System/FilterTabBar.swift b/WordPress/Classes/ViewRelated/System/FilterTabBar.swift index cec035d0e663..1407c17d1218 100644 --- a/WordPress/Classes/ViewRelated/System/FilterTabBar.swift +++ b/WordPress/Classes/ViewRelated/System/FilterTabBar.swift @@ -231,7 +231,7 @@ class FilterTabBar: UIControl { ]) scrollView.addSubview(stackView) - + stackView.isLayoutMarginsRelativeArrangement = true // We will manually constrain the stack view to the content layout guide scrollView.contentInsetAdjustmentBehavior = .never @@ -314,7 +314,6 @@ class FilterTabBar: UIControl { let padding = (tabSizingStyle == .equalWidths) ? 0 : AppearanceMetrics.horizontalPadding stackViewEdgeConstraints = [ - scrollView.contentLayoutGuide.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: padding), stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -padding) ] @@ -457,7 +456,7 @@ class FilterTabBar: UIControl { private enum AppearanceMetrics { static let height: CGFloat = 46.0 - static let bottomDividerHeight: CGFloat = 0.5 + static let bottomDividerHeight: CGFloat = .hairlineBorderWidth static let selectionIndicatorHeight: CGFloat = 2.0 static let horizontalPadding: CGFloat = 0.0 static let buttonInsets = UIEdgeInsets(top: 14.0, left: 12.0, bottom: 14.0, right: 12.0) diff --git a/WordPress/Classes/ViewRelated/System/WPTabBarController.m b/WordPress/Classes/ViewRelated/System/WPTabBarController.m index b904f041fd28..6dcb6faf3e4a 100644 --- a/WordPress/Classes/ViewRelated/System/WPTabBarController.m +++ b/WordPress/Classes/ViewRelated/System/WPTabBarController.m @@ -61,6 +61,7 @@ @interface WPTabBarController () - + diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift index bb63e5ebead9..599b2e8d6d6c 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichContentView.swift @@ -16,10 +16,6 @@ import WordPressShared /// class WPRichContentView: UITextView { - /// Used to keep references to image attachments. - /// - var mediaArray = [RichMedia]() - /// Manages the layout and positioning of text attachments. /// @objc lazy var attachmentManager: WPTextAttachmentManager = { @@ -339,30 +335,22 @@ extension WPRichContentView: WPTextAttachmentManagerDelegate { attachment.maxSize = CGSize(width: finalSize.width, height: finalSize.height) } - let index = mediaArray.count - let indexPath = IndexPath(row: index, section: 1) weak var weakImage = image - image.loadImage(from: mediaHost, preferedSize: finalSize, indexPath: indexPath, onSuccess: { [weak self] indexPath in - guard - let richMedia = self?.mediaArray[indexPath.row], - let img = weakImage - else { + image.loadImage(from: mediaHost, preferedSize: finalSize, onSuccess: { [weak self] in + guard let img = weakImage else { return } - richMedia.attachment.maxSize = img.contentSize() + attachment.maxSize = img.contentSize() if isUsingTemporaryLayoutDimensions { self?.layoutAttachmentViews() } - }, onError: { (indexPath, error) in + }, onError: { (error) in DDLogError("\(String(describing: error))") }) - let media = RichMedia(image: image, attachment: attachment) - mediaArray.append(media) - return image } @@ -415,12 +403,13 @@ extension WPRichContentView: WPTextAttachmentManagerDelegate { /// - Returns: An NSRange optional. /// func attachmentRangeForRichTextImage(_ richTextImage: WPRichTextImage) -> NSRange? { - for item in mediaArray { - if item.image == richTextImage { - return rangeOfAttachment(item.attachment) - } + guard let match = attachmentManager.attachmentViews.first( where: { (_, value) -> Bool in + return value.view == richTextImage + }) else { + return nil } - return nil + + return rangeOfAttachment(match.key) } diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift index 07683433c471..a3abf568b502 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPRichTextImage.swift @@ -10,7 +10,7 @@ open class WPRichTextImage: UIControl, WPRichTextMediaAttachment { @objc fileprivate(set) var imageView: CachedAnimatedImageView fileprivate lazy var imageLoader: ImageLoader = { - let imageLoader = ImageLoader(imageView: imageView, gifStrategy: .smallGIFs) + let imageLoader = ImageLoader(imageView: imageView, gifStrategy: .largeGIFs) imageLoader.photonQuality = Constants.readerPhotonQuality return imageLoader }() @@ -73,25 +73,23 @@ open class WPRichTextImage: UIControl, WPRichTextMediaAttachment { /// - Parameters: /// - host: The host for the media. /// - preferedSize: The prefered size of the image to load. - /// - indexPath: The IndexPath where this view is located — returned as a param in success and error blocks. /// - onSuccess: A closure to be called if the image was loaded successfully. /// - onError: A closure to be called if there was an error loading the image. func loadImage(from host: MediaHost, preferedSize size: CGSize = .zero, - indexPath: IndexPath, - onSuccess: ((IndexPath) -> Void)?, - onError: ((IndexPath, Error?) -> Void)?) { + onSuccess: (() -> Void)?, + onError: ((Error?) -> Void)?) { guard let contentURL = self.contentURL else { - onError?(indexPath, nil) + onError?(nil) return } let successHandler: (() -> Void)? = { - onSuccess?(indexPath) + onSuccess?() } let errorHandler: ((Error?) -> Void)? = { error in - onError?(indexPath, error) + onError?(error) } imageLoader.loadImage(with: contentURL, from: host, preferredSize: size, placeholder: nil, success: successHandler, error: errorHandler) diff --git a/WordPress/Classes/ViewRelated/Views/WPRichText/WPTextAttachmentManager.swift b/WordPress/Classes/ViewRelated/Views/WPRichText/WPTextAttachmentManager.swift index 11bc5eea3398..28b0f1e76487 100644 --- a/WordPress/Classes/ViewRelated/Views/WPRichText/WPTextAttachmentManager.swift +++ b/WordPress/Classes/ViewRelated/Views/WPRichText/WPTextAttachmentManager.swift @@ -6,7 +6,7 @@ import UIKit /// @objc open class WPTextAttachmentManager: NSObject { @objc open var attachments = [WPTextAttachment]() - var attachmentViews = [String: WPTextAttachmentView]() + var attachmentViews = [WPTextAttachment: WPTextAttachmentView]() @objc open weak var delegate: WPTextAttachmentManagerDelegate? @objc fileprivate(set) open weak var textView: UITextView? @objc let layoutManager: NSLayoutManager @@ -40,7 +40,7 @@ import UIKit /// - Returns: A UIView optional /// @objc open func viewForAttachment(_ attachment: WPTextAttachment) -> UIView? { - return attachmentViews[attachment.identifier]?.view + return attachmentViews[attachment]?.view } @@ -88,7 +88,7 @@ import UIKit // Make sure attachments are correctly laid out. attachmentsAndRanges.forEach { (attachment, range) in - guard let attachmentView = attachmentViews[attachment.identifier] else { + guard let attachmentView = attachmentViews[attachment] else { return } @@ -125,7 +125,7 @@ import UIKit self.attachments.append(attachment) if let view = self.delegate?.attachmentManager(self, viewForAttachment: attachment) { - self.attachmentViews[attachment.identifier] = WPTextAttachmentView(view: view, identifier: attachment.identifier, exclusionPath: nil) + self.attachmentViews[attachment] = WPTextAttachmentView(view: view, identifier: attachment.identifier, exclusionPath: nil) self.textView?.addSubview(view) } }) diff --git a/WordPress/Resources/ar.lproj/Localizable.strings b/WordPress/Resources/ar.lproj/Localizable.strings index e34a4632114c..0523cc888b02 100644 Binary files a/WordPress/Resources/ar.lproj/Localizable.strings and b/WordPress/Resources/ar.lproj/Localizable.strings differ diff --git a/WordPress/Resources/bg.lproj/Localizable.strings b/WordPress/Resources/bg.lproj/Localizable.strings index a60463bb0d6f..735666372cde 100644 Binary files a/WordPress/Resources/bg.lproj/Localizable.strings and b/WordPress/Resources/bg.lproj/Localizable.strings differ diff --git a/WordPress/Resources/cs.lproj/Localizable.strings b/WordPress/Resources/cs.lproj/Localizable.strings index 579792fe1a6d..704fb2151b52 100644 Binary files a/WordPress/Resources/cs.lproj/Localizable.strings and b/WordPress/Resources/cs.lproj/Localizable.strings differ diff --git a/WordPress/Resources/cy.lproj/Localizable.strings b/WordPress/Resources/cy.lproj/Localizable.strings index af6533d1a602..b41b48ca336e 100644 Binary files a/WordPress/Resources/cy.lproj/Localizable.strings and b/WordPress/Resources/cy.lproj/Localizable.strings differ diff --git a/WordPress/Resources/da.lproj/Localizable.strings b/WordPress/Resources/da.lproj/Localizable.strings index fce4d8a96848..58124e87c68c 100644 Binary files a/WordPress/Resources/da.lproj/Localizable.strings and b/WordPress/Resources/da.lproj/Localizable.strings differ diff --git a/WordPress/Resources/de.lproj/Localizable.strings b/WordPress/Resources/de.lproj/Localizable.strings index ca7958b584b5..586b5c4cd507 100644 Binary files a/WordPress/Resources/de.lproj/Localizable.strings and b/WordPress/Resources/de.lproj/Localizable.strings differ diff --git a/WordPress/Resources/en-AU.lproj/Localizable.strings b/WordPress/Resources/en-AU.lproj/Localizable.strings index dc8a9c88b205..23587a397b16 100644 Binary files a/WordPress/Resources/en-AU.lproj/Localizable.strings and b/WordPress/Resources/en-AU.lproj/Localizable.strings differ diff --git a/WordPress/Resources/en-CA.lproj/Localizable.strings b/WordPress/Resources/en-CA.lproj/Localizable.strings index e48a145eb477..6e2eeaa6f0a5 100644 Binary files a/WordPress/Resources/en-CA.lproj/Localizable.strings and b/WordPress/Resources/en-CA.lproj/Localizable.strings differ diff --git a/WordPress/Resources/en-GB.lproj/Localizable.strings b/WordPress/Resources/en-GB.lproj/Localizable.strings index e4e401cddd1b..2e257fde5751 100644 Binary files a/WordPress/Resources/en-GB.lproj/Localizable.strings and b/WordPress/Resources/en-GB.lproj/Localizable.strings differ diff --git a/WordPress/Resources/en.lproj/Localizable.strings b/WordPress/Resources/en.lproj/Localizable.strings index 2c17f3743b8d..46069c7a054c 100644 Binary files a/WordPress/Resources/en.lproj/Localizable.strings and b/WordPress/Resources/en.lproj/Localizable.strings differ diff --git a/WordPress/Resources/es.lproj/Localizable.strings b/WordPress/Resources/es.lproj/Localizable.strings index 72f2502ed5a9..523c3c2ceb31 100644 Binary files a/WordPress/Resources/es.lproj/Localizable.strings and b/WordPress/Resources/es.lproj/Localizable.strings differ diff --git a/WordPress/Resources/fr.lproj/Localizable.strings b/WordPress/Resources/fr.lproj/Localizable.strings index 8d4f88e765d4..57d33389d910 100644 Binary files a/WordPress/Resources/fr.lproj/Localizable.strings and b/WordPress/Resources/fr.lproj/Localizable.strings differ diff --git a/WordPress/Resources/he.lproj/Localizable.strings b/WordPress/Resources/he.lproj/Localizable.strings index 1a38301daed2..7562de2149c9 100644 Binary files a/WordPress/Resources/he.lproj/Localizable.strings and b/WordPress/Resources/he.lproj/Localizable.strings differ diff --git a/WordPress/Resources/hr.lproj/Localizable.strings b/WordPress/Resources/hr.lproj/Localizable.strings index 8df232e7ef24..4c69d87bc954 100644 Binary files a/WordPress/Resources/hr.lproj/Localizable.strings and b/WordPress/Resources/hr.lproj/Localizable.strings differ diff --git a/WordPress/Resources/hu.lproj/Localizable.strings b/WordPress/Resources/hu.lproj/Localizable.strings index fef0b79abeb5..e86e0e28531b 100644 Binary files a/WordPress/Resources/hu.lproj/Localizable.strings and b/WordPress/Resources/hu.lproj/Localizable.strings differ diff --git a/WordPress/Resources/id.lproj/Localizable.strings b/WordPress/Resources/id.lproj/Localizable.strings index cd2c6f89a06d..19aba6babb2c 100644 Binary files a/WordPress/Resources/id.lproj/Localizable.strings and b/WordPress/Resources/id.lproj/Localizable.strings differ diff --git a/WordPress/Resources/is.lproj/Localizable.strings b/WordPress/Resources/is.lproj/Localizable.strings index c01444edd2f4..6ba3fea8ec92 100644 Binary files a/WordPress/Resources/is.lproj/Localizable.strings and b/WordPress/Resources/is.lproj/Localizable.strings differ diff --git a/WordPress/Resources/it.lproj/Localizable.strings b/WordPress/Resources/it.lproj/Localizable.strings index b7cb55605afe..8b6897301a85 100644 Binary files a/WordPress/Resources/it.lproj/Localizable.strings and b/WordPress/Resources/it.lproj/Localizable.strings differ diff --git a/WordPress/Resources/ja.lproj/Localizable.strings b/WordPress/Resources/ja.lproj/Localizable.strings index d712bd3dcc7a..388db5c66f22 100644 Binary files a/WordPress/Resources/ja.lproj/Localizable.strings and b/WordPress/Resources/ja.lproj/Localizable.strings differ diff --git a/WordPress/Resources/ko.lproj/Localizable.strings b/WordPress/Resources/ko.lproj/Localizable.strings index 09e47e483222..785ef89e157e 100644 Binary files a/WordPress/Resources/ko.lproj/Localizable.strings and b/WordPress/Resources/ko.lproj/Localizable.strings differ diff --git a/WordPress/Resources/nb.lproj/Localizable.strings b/WordPress/Resources/nb.lproj/Localizable.strings index 9bc2f5735a51..4d959b2c6bbf 100644 Binary files a/WordPress/Resources/nb.lproj/Localizable.strings and b/WordPress/Resources/nb.lproj/Localizable.strings differ diff --git a/WordPress/Resources/nl.lproj/Localizable.strings b/WordPress/Resources/nl.lproj/Localizable.strings index 8e4f911dc0c8..e0372004af6f 100644 Binary files a/WordPress/Resources/nl.lproj/Localizable.strings and b/WordPress/Resources/nl.lproj/Localizable.strings differ diff --git a/WordPress/Resources/pl.lproj/Localizable.strings b/WordPress/Resources/pl.lproj/Localizable.strings index 0194826a9fe3..3186323ed851 100644 Binary files a/WordPress/Resources/pl.lproj/Localizable.strings and b/WordPress/Resources/pl.lproj/Localizable.strings differ diff --git a/WordPress/Resources/pt-BR.lproj/Localizable.strings b/WordPress/Resources/pt-BR.lproj/Localizable.strings index 56be1815d79a..e7433b4af2f6 100644 Binary files a/WordPress/Resources/pt-BR.lproj/Localizable.strings and b/WordPress/Resources/pt-BR.lproj/Localizable.strings differ diff --git a/WordPress/Resources/pt.lproj/Localizable.strings b/WordPress/Resources/pt.lproj/Localizable.strings index 649dbc3ceb67..9dcd19c53442 100644 Binary files a/WordPress/Resources/pt.lproj/Localizable.strings and b/WordPress/Resources/pt.lproj/Localizable.strings differ diff --git a/WordPress/Resources/ro.lproj/Localizable.strings b/WordPress/Resources/ro.lproj/Localizable.strings index b32fef9fbfa9..302e9f35bc22 100644 Binary files a/WordPress/Resources/ro.lproj/Localizable.strings and b/WordPress/Resources/ro.lproj/Localizable.strings differ diff --git a/WordPress/Resources/ru.lproj/Localizable.strings b/WordPress/Resources/ru.lproj/Localizable.strings index ed79153395de..c53b9b41cacb 100644 Binary files a/WordPress/Resources/ru.lproj/Localizable.strings and b/WordPress/Resources/ru.lproj/Localizable.strings differ diff --git a/WordPress/Resources/sk.lproj/Localizable.strings b/WordPress/Resources/sk.lproj/Localizable.strings index e0ad50eeae43..6acdcf1895ed 100644 Binary files a/WordPress/Resources/sk.lproj/Localizable.strings and b/WordPress/Resources/sk.lproj/Localizable.strings differ diff --git a/WordPress/Resources/sq.lproj/Localizable.strings b/WordPress/Resources/sq.lproj/Localizable.strings index 981894365bec..4c773752091e 100644 Binary files a/WordPress/Resources/sq.lproj/Localizable.strings and b/WordPress/Resources/sq.lproj/Localizable.strings differ diff --git a/WordPress/Resources/sv.lproj/Localizable.strings b/WordPress/Resources/sv.lproj/Localizable.strings index 5251792f0901..b11bf6a0f1cd 100644 Binary files a/WordPress/Resources/sv.lproj/Localizable.strings and b/WordPress/Resources/sv.lproj/Localizable.strings differ diff --git a/WordPress/Resources/th.lproj/Localizable.strings b/WordPress/Resources/th.lproj/Localizable.strings index 34fcc317637a..b288323ffc1b 100644 Binary files a/WordPress/Resources/th.lproj/Localizable.strings and b/WordPress/Resources/th.lproj/Localizable.strings differ diff --git a/WordPress/Resources/tr.lproj/Localizable.strings b/WordPress/Resources/tr.lproj/Localizable.strings index 23eb66d74f57..a9db00de91b1 100644 Binary files a/WordPress/Resources/tr.lproj/Localizable.strings and b/WordPress/Resources/tr.lproj/Localizable.strings differ diff --git a/WordPress/Resources/zh-Hans.lproj/Localizable.strings b/WordPress/Resources/zh-Hans.lproj/Localizable.strings index cd9859429d63..0d96b021050a 100644 Binary files a/WordPress/Resources/zh-Hans.lproj/Localizable.strings and b/WordPress/Resources/zh-Hans.lproj/Localizable.strings differ diff --git a/WordPress/Resources/zh-Hant.lproj/Localizable.strings b/WordPress/Resources/zh-Hant.lproj/Localizable.strings index 673a6898dd11..7390a067f818 100644 Binary files a/WordPress/Resources/zh-Hant.lproj/Localizable.strings and b/WordPress/Resources/zh-Hant.lproj/Localizable.strings differ diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index e3477b3fb531..e8d0275efb1d 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -143,7 +143,6 @@ 1714F8D020E6DA8900226DCB /* RouteMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1714F8CF20E6DA8900226DCB /* RouteMatcher.swift */; }; 1715179220F4B2EB002C4A38 /* Routes+Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1715179120F4B2EB002C4A38 /* Routes+Stats.swift */; }; 1715179420F4B5CD002C4A38 /* MySitesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1715179320F4B5CD002C4A38 /* MySitesCoordinator.swift */; }; - 171909E4206CFFCD0054DF0B /* FancyAlertViewController+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171909E3206CFFCD0054DF0B /* FancyAlertViewController+Async.swift */; }; 171963401D378D5100898E8B /* SearchWrapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1719633F1D378D5100898E8B /* SearchWrapperView.swift */; }; 1724DDC81C60F1200099D273 /* PlanDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1724DDC71C60F1200099D273 /* PlanDetailViewController.swift */; }; 1724DDCC1C6121D00099D273 /* Plans.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1724DDCB1C6121D00099D273 /* Plans.storyboard */; }; @@ -1026,9 +1025,7 @@ 83418AAA11C9FA6E00ACF00C /* Comment.m in Sources */ = {isa = PBXBuildFile; fileRef = 83418AA911C9FA6E00ACF00C /* Comment.m */; }; 834CE7341256D0DE0046A4A3 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 834CE7331256D0DE0046A4A3 /* CFNetwork.framework */; }; 8350E49611D2C71E00A7B073 /* Media.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350E49511D2C71E00A7B073 /* Media.m */; }; - 8355D67E11D13EAD00A61362 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8355D67D11D13EAD00A61362 /* MobileCoreServices.framework */; }; 8355D7D911D260AA00A61362 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8355D7D811D260AA00A61362 /* CoreData.framework */; }; - 835E2403126E66E50085940B /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 835E2402126E66E50085940B /* AssetsLibrary.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 8370D10A11FA499A009D650F /* WPTableViewActivityCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8370D10911FA499A009D650F /* WPTableViewActivityCell.m */; }; 83F3E26011275E07004CD686 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83F3E25F11275E07004CD686 /* MapKit.framework */; }; 83F3E2D311276371004CD686 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83F3E2D211276371004CD686 /* CoreLocation.framework */; }; @@ -1043,10 +1040,19 @@ 85D239AE1AE5A5FC0074768D /* BlogSyncFacade.m in Sources */ = {isa = PBXBuildFile; fileRef = 85D239A21AE5A5FC0074768D /* BlogSyncFacade.m */; }; 85DA8C4418F3F29A0074C8A4 /* WPAnalyticsTrackerWPCom.m in Sources */ = {isa = PBXBuildFile; fileRef = 85DA8C4318F3F29A0074C8A4 /* WPAnalyticsTrackerWPCom.m */; }; 85F8E19D1B018698000859BB /* PushAuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F8E19C1B018698000859BB /* PushAuthenticationServiceTests.swift */; }; + 8B0570D3243781100021A436 /* SchedulingCalendarViewController+PresentFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B0570D2243781100021A436 /* SchedulingCalendarViewController+PresentFrom.swift */; }; 8B05D29123A9417E0063B9AA /* WPMediaEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B05D29023A9417E0063B9AA /* WPMediaEditor.swift */; }; 8B05D29323AA572A0063B9AA /* GutenbergMediaEditorImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B05D29223AA572A0063B9AA /* GutenbergMediaEditorImage.swift */; }; + 8B0732E7242B9C5200E7FBD3 /* PrepublishingHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8B0732E6242B9C5200E7FBD3 /* PrepublishingHeaderView.xib */; }; + 8B0732E9242BA1F000E7FBD3 /* PrepublishingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B0732E8242BA1F000E7FBD3 /* PrepublishingHeaderView.swift */; }; + 8B0732F0242BF7E800E7FBD3 /* Blog+Title.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B0732EE242BF6EA00E7FBD3 /* Blog+Title.swift */; }; + 8B0732F3242BF99B00E7FBD3 /* PrepublishingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B0732F1242BF97B00E7FBD3 /* PrepublishingNavigationController.swift */; }; 8B158B2A243CF07F00C66823 /* BottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B158B29243CF07F00C66823 /* BottomSheetViewController.swift */; }; + 8B1CF00F2433902700578582 /* PasswordAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1CF00E2433902700578582 /* PasswordAlertController.swift */; }; + 8B1CF0112433E61C00578582 /* AbstractPost+TitleForVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1CF0102433E61C00578582 /* AbstractPost+TitleForVisibility.swift */; }; + 8B260D7E2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B260D7D2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift */; }; 8B3DECAB2388506400A459C2 /* SentryStartupEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3DECAA2388506400A459C2 /* SentryStartupEvent.swift */; }; + 8B6BD55024293FBE00DB8F28 /* PrepublishingNudgesViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B6BD54F24293FBE00DB8F28 /* PrepublishingNudgesViewControllerTests.swift */; }; 8B6EA62323FDE50B004BA312 /* PostServiceUploadingList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B6EA62223FDE50B004BA312 /* PostServiceUploadingList.swift */; }; 8B7623382384373E00AB3EE7 /* PageListViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B7623372384373E00AB3EE7 /* PageListViewControllerTests.swift */; }; 8B821F3C240020E2006B697E /* PostServiceUploadingListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B821F3B240020E2006B697E /* PostServiceUploadingListTests.swift */; }; @@ -1054,6 +1060,7 @@ 8B8FE8582343955500F9AD2E /* PostAutoUploadMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B8FE8562343952B00F9AD2E /* PostAutoUploadMessages.swift */; }; 8B93856E22DC08060010BF02 /* PageListSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */; }; 8B939F4323832E5D00ACCB0F /* PostListViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B939F4223832E5D00ACCB0F /* PostListViewControllerTests.swift */; }; + 8BAD272C241FEF3300E9D105 /* PrepublishingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BAD272B241FEF3300E9D105 /* PrepublishingViewController.swift */; }; 8BAD53D6241922B900230F4B /* WPAnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BAD53D5241922B900230F4B /* WPAnalyticsEvent.swift */; }; 8BC12F72231FEBA1004DDA72 /* PostCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC12F71231FEBA1004DDA72 /* PostCoordinatorTests.swift */; }; 8BC12F7523201917004DDA72 /* PostService+MarkAsFailedAndDraftIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC12F732320181E004DDA72 /* PostService+MarkAsFailedAndDraftIfNeeded.swift */; }; @@ -1062,7 +1069,9 @@ 8BC6020D2390412000EFE3D0 /* NullBlogPropertySanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC6020C2390412000EFE3D0 /* NullBlogPropertySanitizerTests.swift */; }; 8BD36E022395CAEA00EFFF1C /* MediaEditorOperation+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD36E012395CAEA00EFFF1C /* MediaEditorOperation+Description.swift */; }; 8BD36E062395CC4400EFFF1C /* MediaEditorOperation+DescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD36E052395CC4400EFFF1C /* MediaEditorOperation+DescriptionTests.swift */; }; + 8BE69512243E674300FF492F /* PrepublishingHeaderViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE69511243E674300FF492F /* PrepublishingHeaderViewTests.swift */; }; 8BE7C84123466927006EDE70 /* I18n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE7C84023466927006EDE70 /* I18n.swift */; }; + 8BF5F78D2421677300BE49B7 /* BottomSheetViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF5F78C2421677300BE49B7 /* BottomSheetViewControllerTests.swift */; }; 8BFE36FD230F16580061EBA8 /* AbstractPost+fixLocalMediaURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFE36FC230F16580061EBA8 /* AbstractPost+fixLocalMediaURLs.swift */; }; 8BFE36FF230F1C850061EBA8 /* AbstractPost+fixLocalMediaURLsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFE36FE230F1C850061EBA8 /* AbstractPost+fixLocalMediaURLsTests.swift */; }; 91138455228373EB00FB02B7 /* GutenbergVideoUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91138454228373EB00FB02B7 /* GutenbergVideoUploadProcessor.swift */; }; @@ -2078,6 +2087,10 @@ F5E032E82408D537003AF350 /* BottomSheetPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E032E52408D537003AF350 /* BottomSheetPresentationController.swift */; }; F5E032EA2409C291003AF350 /* BottomSheetAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E032E92409C291003AF350 /* BottomSheetAnimationController.swift */; }; F5E032EC240D49FF003AF350 /* ActionSheetComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E032EB240D49FF003AF350 /* ActionSheetComponent.swift */; }; + F5E29036243E4F5F00C19CA5 /* FilterProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E29035243E4F5F00C19CA5 /* FilterProvider.swift */; }; + F5E29038243FAB0300C19CA5 /* FilterTableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E29037243FAB0300C19CA5 /* FilterTableData.swift */; }; + F5E63129243BC8190088229D /* FilterSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E63128243BC8190088229D /* FilterSheetView.swift */; }; + F5E6312B243BC83E0088229D /* FilterSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E6312A243BC83E0088229D /* FilterSheetViewController.swift */; }; F5EF481723ABCAD8004C3532 /* MainShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74337EDC20054D5500777997 /* MainShareViewController.swift */; }; F5EF481823ABCAE0004C3532 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 740C7C4E202F4CD6001C31B0 /* MainInterface.storyboard */; }; F928EDA3226140620030D451 /* WPCrashLoggingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F928EDA2226140620030D451 /* WPCrashLoggingProvider.swift */; }; @@ -2451,7 +2464,6 @@ 1714F8CF20E6DA8900226DCB /* RouteMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMatcher.swift; sourceTree = ""; }; 1715179120F4B2EB002C4A38 /* Routes+Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Stats.swift"; sourceTree = ""; }; 1715179320F4B5CD002C4A38 /* MySitesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySitesCoordinator.swift; sourceTree = ""; }; - 171909E3206CFFCD0054DF0B /* FancyAlertViewController+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FancyAlertViewController+Async.swift"; sourceTree = ""; }; 1719633F1D378D5100898E8B /* SearchWrapperView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchWrapperView.swift; sourceTree = ""; }; 1724DDC71C60F1200099D273 /* PlanDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlanDetailViewController.swift; sourceTree = ""; }; 1724DDCB1C6121D00099D273 /* Plans.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Plans.storyboard; sourceTree = ""; }; @@ -3429,10 +3441,19 @@ 85DA8C4318F3F29A0074C8A4 /* WPAnalyticsTrackerWPCom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WPAnalyticsTrackerWPCom.m; sourceTree = ""; }; 85ED98AA17DFB17200090D0B /* iTunesArtwork@2x */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTunesArtwork@2x"; sourceTree = ""; }; 85F8E19C1B018698000859BB /* PushAuthenticationServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushAuthenticationServiceTests.swift; sourceTree = ""; }; + 8B0570D2243781100021A436 /* SchedulingCalendarViewController+PresentFrom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SchedulingCalendarViewController+PresentFrom.swift"; sourceTree = ""; }; 8B05D29023A9417E0063B9AA /* WPMediaEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WPMediaEditor.swift; sourceTree = ""; }; 8B05D29223AA572A0063B9AA /* GutenbergMediaEditorImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergMediaEditorImage.swift; sourceTree = ""; }; + 8B0732E6242B9C5200E7FBD3 /* PrepublishingHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PrepublishingHeaderView.xib; sourceTree = ""; }; + 8B0732E8242BA1F000E7FBD3 /* PrepublishingHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepublishingHeaderView.swift; sourceTree = ""; }; + 8B0732EE242BF6EA00E7FBD3 /* Blog+Title.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+Title.swift"; sourceTree = ""; }; + 8B0732F1242BF97B00E7FBD3 /* PrepublishingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepublishingNavigationController.swift; sourceTree = ""; }; 8B158B29243CF07F00C66823 /* BottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetViewController.swift; sourceTree = ""; }; + 8B1CF00E2433902700578582 /* PasswordAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordAlertController.swift; sourceTree = ""; }; + 8B1CF0102433E61C00578582 /* AbstractPost+TitleForVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AbstractPost+TitleForVisibility.swift"; sourceTree = ""; }; + 8B260D7D2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostVisibilitySelectorViewController.swift; sourceTree = ""; }; 8B3DECAA2388506400A459C2 /* SentryStartupEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStartupEvent.swift; sourceTree = ""; }; + 8B6BD54F24293FBE00DB8F28 /* PrepublishingNudgesViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepublishingNudgesViewControllerTests.swift; sourceTree = ""; }; 8B6EA62223FDE50B004BA312 /* PostServiceUploadingList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostServiceUploadingList.swift; sourceTree = ""; }; 8B7623372384373E00AB3EE7 /* PageListViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageListViewControllerTests.swift; sourceTree = ""; }; 8B821F3B240020E2006B697E /* PostServiceUploadingListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostServiceUploadingListTests.swift; sourceTree = ""; }; @@ -3440,6 +3461,7 @@ 8B8FE8562343952B00F9AD2E /* PostAutoUploadMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostAutoUploadMessages.swift; sourceTree = ""; }; 8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageListSectionHeaderView.swift; sourceTree = ""; }; 8B939F4223832E5D00ACCB0F /* PostListViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListViewControllerTests.swift; sourceTree = ""; }; + 8BAD272B241FEF3300E9D105 /* PrepublishingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepublishingViewController.swift; sourceTree = ""; }; 8BAD53D5241922B900230F4B /* WPAnalyticsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WPAnalyticsEvent.swift; sourceTree = ""; }; 8BC12F71231FEBA1004DDA72 /* PostCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCoordinatorTests.swift; sourceTree = ""; }; 8BC12F732320181E004DDA72 /* PostService+MarkAsFailedAndDraftIfNeeded.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostService+MarkAsFailedAndDraftIfNeeded.swift"; sourceTree = ""; }; @@ -3448,7 +3470,9 @@ 8BC6020C2390412000EFE3D0 /* NullBlogPropertySanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NullBlogPropertySanitizerTests.swift; sourceTree = ""; }; 8BD36E012395CAEA00EFFF1C /* MediaEditorOperation+Description.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaEditorOperation+Description.swift"; sourceTree = ""; }; 8BD36E052395CC4400EFFF1C /* MediaEditorOperation+DescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaEditorOperation+DescriptionTests.swift"; sourceTree = ""; }; + 8BE69511243E674300FF492F /* PrepublishingHeaderViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PrepublishingHeaderViewTests.swift; path = WordPressTest/PrepublishingHeaderViewTests.swift; sourceTree = SOURCE_ROOT; }; 8BE7C84023466927006EDE70 /* I18n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = I18n.swift; sourceTree = ""; }; + 8BF5F78C2421677300BE49B7 /* BottomSheetViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetViewControllerTests.swift; sourceTree = ""; }; 8BFE36FC230F16580061EBA8 /* AbstractPost+fixLocalMediaURLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AbstractPost+fixLocalMediaURLs.swift"; sourceTree = ""; }; 8BFE36FE230F1C850061EBA8 /* AbstractPost+fixLocalMediaURLsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AbstractPost+fixLocalMediaURLsTests.swift"; sourceTree = ""; }; 8CE5BBD00FF1470AC4B88247 /* Pods_WordPressTodayWidget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WordPressTodayWidget.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -4668,6 +4692,10 @@ F5E032E52408D537003AF350 /* BottomSheetPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomSheetPresentationController.swift; sourceTree = ""; }; F5E032E92409C291003AF350 /* BottomSheetAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetAnimationController.swift; sourceTree = ""; }; F5E032EB240D49FF003AF350 /* ActionSheetComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetComponent.swift; sourceTree = ""; }; + F5E29035243E4F5F00C19CA5 /* FilterProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProvider.swift; sourceTree = ""; }; + F5E29037243FAB0300C19CA5 /* FilterTableData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTableData.swift; sourceTree = ""; }; + F5E63128243BC8190088229D /* FilterSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterSheetView.swift; sourceTree = ""; }; + F5E6312A243BC83E0088229D /* FilterSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterSheetViewController.swift; sourceTree = ""; }; F7E3CC306AECBBCB71D2E19C /* Pods_WordPressDraftActionExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WordPressDraftActionExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F928EDA2226140620030D451 /* WPCrashLoggingProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WPCrashLoggingProvider.swift; sourceTree = ""; }; F93735F022D534FE00A3C312 /* LoggingURLRedactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingURLRedactor.swift; sourceTree = ""; }; @@ -4798,7 +4826,6 @@ FF75933B1BE2423800814D3B /* Photos.framework in Frameworks */, 93E5285619A77BAC003A1A9C /* NotificationCenter.framework in Frameworks */, 93A3F7DE1843F6F00082FEEA /* CoreTelephony.framework in Frameworks */, - 8355D67E11D13EAD00A61362 /* MobileCoreServices.framework in Frameworks */, A01C542E0E24E88400D411F2 /* SystemConfiguration.framework in Frameworks */, 374CB16215B93C0800DD0EBC /* AudioToolbox.framework in Frameworks */, E10B3655158F2D7800419A93 /* CoreGraphics.framework in Frameworks */, @@ -4813,7 +4840,6 @@ 83F3E2D311276371004CD686 /* CoreLocation.framework in Frameworks */, 8355D7D911D260AA00A61362 /* CoreData.framework in Frameworks */, 834CE7341256D0DE0046A4A3 /* CFNetwork.framework in Frameworks */, - 835E2403126E66E50085940B /* AssetsLibrary.framework in Frameworks */, 83043E55126FA31400EC9953 /* MessageUI.framework in Frameworks */, FD3D6D2C1349F5D30061136A /* ImageIO.framework in Frameworks */, B5AA54D51A8E7510003BDD12 /* WebKit.framework in Frameworks */, @@ -5247,7 +5273,6 @@ isa = PBXGroup; children = ( 405B53FA1F83C369002E19BF /* FancyAlerts+VerificationPrompt.swift */, - 171909E3206CFFCD0054DF0B /* FancyAlertViewController+Async.swift */, 176BB87E20D0068500751DCE /* FancyAlertViewController+SavedPosts.swift */, 4319AADD2090F00C0025D68E /* FancyAlertViewController+NotificationPrimer.swift */, 43290CF3214F755400F6B398 /* FancyAlertViewController+QuickStart.swift */, @@ -5448,6 +5473,7 @@ E16273E21B2AD89A00088AF7 /* MIGRATIONS.md */, FF37F90822385C9F00AFA3DB /* RELEASE-NOTES.txt */, B565D41C3DB27630CD503F9A /* Pods */, + 8B260D7C2444FB980010F756 /* Recovered References */, ); name = CustomTemplate; sourceTree = ""; @@ -5580,6 +5606,7 @@ F15272FC243B27BC00C8DC7A /* AbstractPost+Local.swift */, E19B17B11E5C8F36007517C6 /* AbstractPost.swift */, 74729CAD205722E300D1394D /* AbstractPost+Searchable.swift */, + 8B1CF0102433E61C00578582 /* AbstractPost+TitleForVisibility.swift */, 5D42A3D8175E7452005CFF05 /* BasePost.h */, 5D42A3D9175E7452005CFF05 /* BasePost.m */, E19B17AD1E5C6944007517C6 /* BasePost.swift */, @@ -6314,8 +6341,10 @@ 59ECF8791CB705EB00E68F25 /* Posts */ = { isa = PBXGroup; children = ( + 8BE69514243E676C00FF492F /* Prepublishing Nudges */, 59ECF87A1CB7061D00E68F25 /* PostSharingControllerTests.swift */, F18B43771F849F580089B817 /* PostAttachmentTests.swift */, + 8B6BD54F24293FBE00DB8F28 /* PrepublishingNudgesViewControllerTests.swift */, ); name = Posts; sourceTree = ""; @@ -7337,6 +7366,7 @@ 852416D01A12ED2D0030700C /* Utility */ = { isa = PBXGroup; children = ( + 8BF5F78B2421676100BE49B7 /* Bottom Sheet */, F93735F422D53C1800A3C312 /* Logging */, 93B853211B44165B0064FE72 /* Analytics */, F198533B21ADAD0700DCDAE7 /* Editor */, @@ -7540,6 +7570,18 @@ name = Networking; sourceTree = ""; }; + 8B0732EA242BEF1900E7FBD3 /* Prepublishing Nudge */ = { + isa = PBXGroup; + children = ( + 8B0732E6242B9C5200E7FBD3 /* PrepublishingHeaderView.xib */, + 8B0732E8242BA1F000E7FBD3 /* PrepublishingHeaderView.swift */, + 8B0732EE242BF6EA00E7FBD3 /* Blog+Title.swift */, + 8B0732F1242BF97B00E7FBD3 /* PrepublishingNavigationController.swift */, + 8B1CF00E2433902700578582 /* PasswordAlertController.swift */, + ); + path = "Prepublishing Nudge"; + sourceTree = ""; + }; 8B158B28243CF05900C66823 /* Bottom Sheet */ = { isa = PBXGroup; children = ( @@ -7548,6 +7590,13 @@ path = "Bottom Sheet"; sourceTree = ""; }; + 8B260D7C2444FB980010F756 /* Recovered References */ = { + isa = PBXGroup; + children = ( + ); + name = "Recovered References"; + sourceTree = ""; + }; 8B7623352384372200AB3EE7 /* Pages */ = { isa = PBXGroup; children = ( @@ -7572,6 +7621,22 @@ name = Aztec; sourceTree = ""; }; + 8BE69514243E676C00FF492F /* Prepublishing Nudges */ = { + isa = PBXGroup; + children = ( + 8BE69511243E674300FF492F /* PrepublishingHeaderViewTests.swift */, + ); + name = "Prepublishing Nudges"; + sourceTree = ""; + }; + 8BF5F78B2421676100BE49B7 /* Bottom Sheet */ = { + isa = PBXGroup; + children = ( + 8BF5F78C2421677300BE49B7 /* BottomSheetViewControllerTests.swift */, + ); + path = "Bottom Sheet"; + sourceTree = ""; + }; 931D26FB19EDA0D000114F17 /* ALIterativeMigrator */ = { isa = PBXGroup; children = ( @@ -8332,6 +8397,7 @@ isa = PBXGroup; children = ( F5D0A64C23CC157100B20D27 /* Preview */, + 8B0732EA242BEF1900E7FBD3 /* Prepublishing Nudge */, F57402A5235FF71F00374346 /* Scheduling */, 4349B0A6218A2E810034118A /* Revisions */, 5D1EBF56187C9B95003393F8 /* Categories */, @@ -8358,8 +8424,10 @@ ACBAB5FC0E121C7300F38795 /* PostSettingsViewController.h */, ACBAB5FD0E121C7300F38795 /* PostSettingsViewController.m */, FFEECFFB2084DE2B009B8CDB /* PostSettingsViewController+FeaturedImageUpload.swift */, + 8B260D7D2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift */, 593F26601CAB00CA00F14073 /* PostSharingController.swift */, E155EC711E9B7DCE009D7F63 /* PostTagPickerViewController.swift */, + 8BAD272B241FEF3300E9D105 /* PrepublishingViewController.swift */, 5903AE1C19B60AB9009D5354 /* WPButtonForNavigationBar.h */, 5903AE1A19B60A98009D5354 /* WPButtonForNavigationBar.m */, FF0AAE0B1A16550D0089841D /* WPMediaProgressTableViewController.h */, @@ -9300,6 +9368,7 @@ CCB3A03814C8DD5100D43C3F /* Reader */ = { isa = PBXGroup; children = ( + F597768C243E1E140062BAD1 /* Filter */, 5D1D04731B7A50B100CDE646 /* Reader.storyboard */, 5D5A6E901B613C1800DAF819 /* Cards */, 5D08B8FD19647C0800D5B381 /* Controllers */, @@ -10078,6 +10147,7 @@ F59AAC0F235E430E00385EE6 /* ChosenValueRow.swift */, F5660D08235D1CDD00020B1E /* CalendarMonthView.swift */, F5660CFF235CE82100020B1E /* SchedulingCalendarViewController.swift */, + 8B0570D2243781100021A436 /* SchedulingCalendarViewController+PresentFrom.swift */, F5844B6A235EAF3D007C6557 /* HalfScreenPresentationController.swift */, F59AAC15235EA46D00385EE6 /* LightNavigationController.swift */, F57402A6235FF9C300374346 /* SchedulingDate+Helpers.swift */, @@ -10085,6 +10155,17 @@ path = Scheduling; sourceTree = ""; }; + F597768C243E1E140062BAD1 /* Filter */ = { + isa = PBXGroup; + children = ( + F5E63128243BC8190088229D /* FilterSheetView.swift */, + F5E6312A243BC83E0088229D /* FilterSheetViewController.swift */, + F5E29035243E4F5F00C19CA5 /* FilterProvider.swift */, + F5E29037243FAB0300C19CA5 /* FilterTableData.swift */, + ); + path = Filter; + sourceTree = ""; + }; F5D0A64C23CC157100B20D27 /* Preview */ = { isa = PBXGroup; children = ( @@ -10642,6 +10723,7 @@ 17E3634522C417F0000E0C79 /* jetpack_green_icon_20pt@2x.png in Resources */, 17E363A422C41DBA000E0C79 /* hot_pink_icon_40pt@2x.png in Resources */, B5C66B701ACF06CA00F68370 /* NoteBlockHeaderTableViewCell.xib in Resources */, + 8B0732E7242B9C5200E7FBD3 /* PrepublishingHeaderView.xib in Resources */, 17DC4C3822C5E6910059CA11 /* open_source_dark_icon_83.5@2x.png in Resources */, 17DC4C3522C5E6910059CA11 /* open_source_dark_icon_29pt.png in Resources */, 5D6C4AF61B603CA3005E3C43 /* WPTableViewActivityCell.xib in Resources */, @@ -11825,6 +11907,7 @@ 313AE4A019E3F20400AAFABE /* CommentViewController.m in Sources */, 7E3E7A6620E44F200075D159 /* HeaderContentGroup.swift in Sources */, B5015C581D4FDBB300C9449E /* NotificationActionsService.swift in Sources */, + F5E63129243BC8190088229D /* FilterSheetView.swift in Sources */, A0E293F10E21027E00C6919C /* WPAddPostCategoryViewController.m in Sources */, E6BDEA731CE4824300682885 /* ReaderSearchTopic.swift in Sources */, 9A8ECE132254A3260043C8DA /* JetpackInstallError+Blocking.swift in Sources */, @@ -12064,6 +12147,7 @@ E166FA1B1BB0656B00374B5B /* PeopleCellViewModel.swift in Sources */, 73CE3E0E21F7F9D3007C9C85 /* TableViewOffsetCoordinator.swift in Sources */, 436D56292117312700CEAA33 /* RegisterDomainDetailsViewController.swift in Sources */, + F5E29036243E4F5F00C19CA5 /* FilterProvider.swift in Sources */, 027AC51D227896540033E56E /* DomainCreditEligibilityChecker.swift in Sources */, 83FEFC7611FF6C5A0078B462 /* SiteSettingsViewController.m in Sources */, 91DCE84421A6A7840062F134 /* PostEditor+BlogPicker.swift in Sources */, @@ -12084,12 +12168,14 @@ 74EFB5C8208674250070BD4E /* BlogListViewController+Activity.swift in Sources */, 738B9A5721B85CF20005062B /* TableDataCoordinator.swift in Sources */, B541276B1C0F7D610015CA80 /* SettingsMultiTextViewController.m in Sources */, + F5E6312B243BC83E0088229D /* FilterSheetViewController.swift in Sources */, E6F2788021BC1A4A008B4DB5 /* Plan.swift in Sources */, B5AC00681BE3C4E100F8E7C3 /* DiscussionSettingsViewController.swift in Sources */, D816C1F620E0896F00C4D82F /* TrashComment.swift in Sources */, 08AAD69F1CBEA47D002B2418 /* MenusService.m in Sources */, 9A4E216021F87AE200EFF212 /* QuickStartChecklistHeader.swift in Sources */, 98B52AE121F7AF4A006FF6B4 /* StatsDataHelper.swift in Sources */, + 8BAD272C241FEF3300E9D105 /* PrepublishingViewController.swift in Sources */, 9A22D9C0214A6BCA00BAEAF2 /* PageListTableViewHandler.swift in Sources */, 74729CA62056FE6000D1394D /* SearchableItemConvertable.swift in Sources */, FFE3B2C71B2E651400E9F1E0 /* WPAndDeviceMediaLibraryDataSource.m in Sources */, @@ -12102,6 +12188,7 @@ 40EE947F2213213F00CD264F /* PublicizeConnectionStatsRecordValue+CoreDataClass.swift in Sources */, F5660D03235CF73800020B1E /* SchedulingCalendarViewController.swift in Sources */, 1702BBE01CF3034E00766A33 /* DomainsService.swift in Sources */, + 8B1CF00F2433902700578582 /* PasswordAlertController.swift in Sources */, D817799420ABFDB300330998 /* ReaderPostCellActions.swift in Sources */, 402B2A7920ACD7690027C1DC /* ActivityStore.swift in Sources */, E62AFB6A1DC8E593007484FC /* NSAttributedString+WPRichText.swift in Sources */, @@ -12295,6 +12382,7 @@ E1E4CE0B1773C59B00430844 /* WPAvatarSource.m in Sources */, 983DBBAB22125DD500753988 /* StatsTableFooter.swift in Sources */, 85D239AE1AE5A5FC0074768D /* BlogSyncFacade.m in Sources */, + 8B0732F3242BF99B00E7FBD3 /* PrepublishingNavigationController.swift in Sources */, 5D97C2F315CAF8D8009B44DD /* UINavigationController+KeyboardFix.m in Sources */, D8B9B591204F658A003C6042 /* CommentsViewController+NetworkAware.swift in Sources */, 4034FDEA2007C42400153B87 /* ExpandableCell.swift in Sources */, @@ -12332,6 +12420,7 @@ 173BCE791CEB780800AE8817 /* Domain.swift in Sources */, 590E873B1CB8205700D1B734 /* PostListViewController.swift in Sources */, E15644EB1CE0E4C500D96E64 /* FeatureItemRow.swift in Sources */, + 8B0732E9242BA1F000E7FBD3 /* PrepublishingHeaderView.swift in Sources */, 0857C2791CE5375F0014AE99 /* MenuItemsVisualOrderingView.m in Sources */, D8212CB520AA68D5008E8AE8 /* ReaderSubscribingNotificationAction.swift in Sources */, FF8C54AD21F677260003ABCF /* GutenbergMediaInserterHelper.swift in Sources */, @@ -12414,6 +12503,7 @@ E6A2158E1D10627500DE5270 /* ReaderMenuViewModel.swift in Sources */, 73C8F06021BEED9100DDDF7E /* SiteAssemblyStep.swift in Sources */, 82C420761FE44BD900CFB15B /* SiteSettingsViewController+Swift.swift in Sources */, + F5E29038243FAB0300C19CA5 /* FilterTableData.swift in Sources */, E6A3384C1BB08E3F00371587 /* ReaderGapMarker.m in Sources */, D8281CF1212AB34C00D09098 /* NewsStats.swift in Sources */, E1CFC1571E0AC8FF001DF9E9 /* Pattern.swift in Sources */, @@ -12658,7 +12748,6 @@ CE46018B21139E8300F242B6 /* FooterTextContent.swift in Sources */, B5EEDB971C91F10400676B2B /* Blog+Interface.swift in Sources */, 5D839AA8187F0D6B00811F4A /* PostFeaturedImageCell.m in Sources */, - 171909E4206CFFCD0054DF0B /* FancyAlertViewController+Async.swift in Sources */, FFCB9F4B22A125BD0080A45F /* WPException.m in Sources */, 9A9E3FAD230E9DD000909BC4 /* StatsGhostCells.swift in Sources */, E14DF70620C922F200959BA9 /* NotificationCenter+ObserveOnce.swift in Sources */, @@ -12723,6 +12812,7 @@ 9A341E5721997A340036662E /* Blog+BlogAuthors.swift in Sources */, 7E3AB3DB20F52654001F33B6 /* ActivityContentStyles.swift in Sources */, 9A034CEB237DB8660047B41B /* StatsForegroundObservable.swift in Sources */, + 8B1CF0112433E61C00578582 /* AbstractPost+TitleForVisibility.swift in Sources */, E6431DE71C4E892900FD8D90 /* SharingViewController.m in Sources */, 7E4123C820F417EF00DF8486 /* FormattableActivity.swift in Sources */, 9A162F2B21C2A21A00FDC035 /* RevisionPreviewTextViewManager.swift in Sources */, @@ -12782,6 +12872,7 @@ D865722E2186F96D0023A99C /* SiteSegmentsWizardContent.swift in Sources */, E66969E01B9E648100EC9C00 /* ReaderTopicToReaderDefaultTopic37to38.swift in Sources */, F1DB8D2B2288C24500906E2F /* UploadsManager.swift in Sources */, + 8B0570D3243781100021A436 /* SchedulingCalendarViewController+PresentFrom.swift in Sources */, 82A062DE2017BCBA0084CE7C /* ActivityListSectionHeaderView.swift in Sources */, 73FEC871220B358500CEF791 /* WPAccount+RestApi.swift in Sources */, B5FF3BE71CAD881100C1D597 /* ImageCropOverlayView.swift in Sources */, @@ -12805,9 +12896,11 @@ E16FB7E11F8B5D7D0004DD9F /* WebViewControllerConfiguration.swift in Sources */, 98B3FA8421C05BDC00148DD4 /* ViewMoreRow.swift in Sources */, D816B8BE2112CC000052CE4D /* NewsItem.swift in Sources */, + 8B260D7E2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift in Sources */, B5416D011C17693B00006DD8 /* UIApplication+Helpers.m in Sources */, 731E88CA21C9A10B0055C014 /* ErrorStateView.swift in Sources */, 08D345501CD7F50900358E8C /* MenusSelectionDetailView.m in Sources */, + 8B0732F0242BF7E800E7FBD3 /* Blog+Title.swift in Sources */, 591AA5021CEF9BF20074934F /* Post+CoreDataProperties.swift in Sources */, E19B17AE1E5C6944007517C6 /* BasePost.swift in Sources */, 7E7947A9210BAC1D005BB851 /* NotificationContentRange.swift in Sources */, @@ -13188,6 +13281,7 @@ 748437EE1F1D4A7300E8DDAF /* RichContentFormatterTests.swift in Sources */, 8BE7C84123466927006EDE70 /* I18n.swift in Sources */, D88A649E208D82D2008AE9BC /* XCTestCase+Wait.swift in Sources */, + 8BF5F78D2421677300BE49B7 /* BottomSheetViewControllerTests.swift in Sources */, E18549DB230FBFEF003C620E /* BlogServiceDeduplicationTests.swift in Sources */, 400A2C8F2217AD7F000A8A59 /* ClicksStatsRecordValueTests.swift in Sources */, 7320C8BD2190C9FC0082FED5 /* UITextView+SummaryTests.swift in Sources */, @@ -13291,6 +13385,7 @@ 730354BA21C867E500CD18C2 /* SiteCreatorTests.swift in Sources */, B566EC751B83867800278395 /* NSMutableAttributedStringTests.swift in Sources */, E6B9B8AF1B94FA1C0001B92F /* ReaderStreamViewControllerTests.swift in Sources */, + 8BE69512243E674300FF492F /* PrepublishingHeaderViewTests.swift in Sources */, 40F50B82221310F000CBBB73 /* StatsTestCase.swift in Sources */, 02BE5CC02281B53F00E351BA /* RegisterDomainDetailsViewModelLoadingStateTests.swift in Sources */, 40EE948222132F5800CD264F /* PublicizeConectionStatsRecordValueTests.swift in Sources */, @@ -13316,6 +13411,7 @@ D88A64B0208DA093008AE9BC /* StockPhotosResultsPageTests.swift in Sources */, 0879FC161E9301DD00E1EFC8 /* MediaTests.swift in Sources */, B556EFCB1DCA374200728F93 /* DictionaryHelpersTests.swift in Sources */, + 8B6BD55024293FBE00DB8F28 /* PrepublishingNudgesViewControllerTests.swift in Sources */, 8BFE36FF230F1C850061EBA8 /* AbstractPost+fixLocalMediaURLsTests.swift in Sources */, 08A2AD791CCED2A800E84454 /* PostTagServiceTests.m in Sources */, F543AF5723A84E4D0022F595 /* PublishSettingsControllerTests.swift in Sources */, diff --git a/WordPress/WordPressShareExtension/ar.lproj/Localizable.strings b/WordPress/WordPressShareExtension/ar.lproj/Localizable.strings index 7ecb4a9a8c19..1b69e6da79f7 100644 Binary files a/WordPress/WordPressShareExtension/ar.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/ar.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/bg.lproj/Localizable.strings b/WordPress/WordPressShareExtension/bg.lproj/Localizable.strings index b43452f36f66..86f57de553eb 100644 Binary files a/WordPress/WordPressShareExtension/bg.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/bg.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/cs.lproj/Localizable.strings b/WordPress/WordPressShareExtension/cs.lproj/Localizable.strings index 5c3fb4c0e454..b113bd316f9d 100644 Binary files a/WordPress/WordPressShareExtension/cs.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/cs.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/cy.lproj/Localizable.strings b/WordPress/WordPressShareExtension/cy.lproj/Localizable.strings index 1ef9e16bdd8a..6a7201eac4aa 100644 Binary files a/WordPress/WordPressShareExtension/cy.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/cy.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/da.lproj/Localizable.strings b/WordPress/WordPressShareExtension/da.lproj/Localizable.strings index fd832ae798cd..f9d4c8cb3204 100644 Binary files a/WordPress/WordPressShareExtension/da.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/da.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/de.lproj/Localizable.strings b/WordPress/WordPressShareExtension/de.lproj/Localizable.strings index 0dda2885debe..52367c4e03db 100644 Binary files a/WordPress/WordPressShareExtension/de.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/de.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/en-AU.lproj/Localizable.strings b/WordPress/WordPressShareExtension/en-AU.lproj/Localizable.strings index 26063925815d..45e64f2469c0 100644 Binary files a/WordPress/WordPressShareExtension/en-AU.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/en-AU.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/en-CA.lproj/Localizable.strings b/WordPress/WordPressShareExtension/en-CA.lproj/Localizable.strings index 9924c517fd40..738358d2ce44 100644 Binary files a/WordPress/WordPressShareExtension/en-CA.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/en-CA.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/en-GB.lproj/Localizable.strings b/WordPress/WordPressShareExtension/en-GB.lproj/Localizable.strings index 26063925815d..45e64f2469c0 100644 Binary files a/WordPress/WordPressShareExtension/en-GB.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/en-GB.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/es.lproj/Localizable.strings b/WordPress/WordPressShareExtension/es.lproj/Localizable.strings index 57add7d0aac7..ec8dedb04122 100644 Binary files a/WordPress/WordPressShareExtension/es.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/es.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/fr.lproj/Localizable.strings b/WordPress/WordPressShareExtension/fr.lproj/Localizable.strings index d7f466525ae3..297991b00573 100644 Binary files a/WordPress/WordPressShareExtension/fr.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/fr.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/he.lproj/Localizable.strings b/WordPress/WordPressShareExtension/he.lproj/Localizable.strings index 25fea4439e89..0c2014853a30 100644 Binary files a/WordPress/WordPressShareExtension/he.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/he.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/hr.lproj/Localizable.strings b/WordPress/WordPressShareExtension/hr.lproj/Localizable.strings index 5c984dd8a6c6..bc62a556879e 100644 Binary files a/WordPress/WordPressShareExtension/hr.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/hr.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/hu.lproj/Localizable.strings b/WordPress/WordPressShareExtension/hu.lproj/Localizable.strings index fa9568146241..8eec513f6c12 100644 Binary files a/WordPress/WordPressShareExtension/hu.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/hu.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/id.lproj/Localizable.strings b/WordPress/WordPressShareExtension/id.lproj/Localizable.strings index 912ef275f7a3..47617a48c20f 100644 Binary files a/WordPress/WordPressShareExtension/id.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/id.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/is.lproj/Localizable.strings b/WordPress/WordPressShareExtension/is.lproj/Localizable.strings index 06938a97dbb4..f3bb2eb130ad 100644 Binary files a/WordPress/WordPressShareExtension/is.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/is.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/it.lproj/Localizable.strings b/WordPress/WordPressShareExtension/it.lproj/Localizable.strings index dafcc517c982..d1bcc96e7461 100644 Binary files a/WordPress/WordPressShareExtension/it.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/it.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/ja.lproj/Localizable.strings b/WordPress/WordPressShareExtension/ja.lproj/Localizable.strings index 8c56541dc347..3eb594f2938f 100644 Binary files a/WordPress/WordPressShareExtension/ja.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/ja.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/ko.lproj/Localizable.strings b/WordPress/WordPressShareExtension/ko.lproj/Localizable.strings index 26dd6f205f66..854aa33d4bfb 100644 Binary files a/WordPress/WordPressShareExtension/ko.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/ko.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/nb.lproj/Localizable.strings b/WordPress/WordPressShareExtension/nb.lproj/Localizable.strings index b3d1b757a91c..b6c2120cdb92 100644 Binary files a/WordPress/WordPressShareExtension/nb.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/nb.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/nl.lproj/Localizable.strings b/WordPress/WordPressShareExtension/nl.lproj/Localizable.strings index 3e6e9e08b509..f7566945be28 100644 Binary files a/WordPress/WordPressShareExtension/nl.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/nl.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/pl.lproj/Localizable.strings b/WordPress/WordPressShareExtension/pl.lproj/Localizable.strings index 03c3d0d72104..6d16500469bb 100644 Binary files a/WordPress/WordPressShareExtension/pl.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/pl.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/pt-BR.lproj/Localizable.strings b/WordPress/WordPressShareExtension/pt-BR.lproj/Localizable.strings index cc0357bed8db..11ab4124e698 100644 Binary files a/WordPress/WordPressShareExtension/pt-BR.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/pt-BR.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/pt.lproj/Localizable.strings b/WordPress/WordPressShareExtension/pt.lproj/Localizable.strings index 707b909161a8..c4f1d5818bc6 100644 Binary files a/WordPress/WordPressShareExtension/pt.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/pt.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/ro.lproj/Localizable.strings b/WordPress/WordPressShareExtension/ro.lproj/Localizable.strings index ac89018393a0..640f60915e7f 100644 Binary files a/WordPress/WordPressShareExtension/ro.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/ro.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/ru.lproj/Localizable.strings b/WordPress/WordPressShareExtension/ru.lproj/Localizable.strings index 9c939b926e02..a2c7c2fb4b2e 100644 Binary files a/WordPress/WordPressShareExtension/ru.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/ru.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/sk.lproj/Localizable.strings b/WordPress/WordPressShareExtension/sk.lproj/Localizable.strings index 3fd6413ddb64..93619336cb03 100644 Binary files a/WordPress/WordPressShareExtension/sk.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/sk.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/sq.lproj/Localizable.strings b/WordPress/WordPressShareExtension/sq.lproj/Localizable.strings index bab3de132c98..eb8d78456f7d 100644 Binary files a/WordPress/WordPressShareExtension/sq.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/sq.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/sv.lproj/Localizable.strings b/WordPress/WordPressShareExtension/sv.lproj/Localizable.strings index 070abdc28ea6..c4b44e0f1f2a 100644 Binary files a/WordPress/WordPressShareExtension/sv.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/sv.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/th.lproj/Localizable.strings b/WordPress/WordPressShareExtension/th.lproj/Localizable.strings index 4d31f165159e..7341bb9073cf 100644 Binary files a/WordPress/WordPressShareExtension/th.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/th.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/tr.lproj/Localizable.strings b/WordPress/WordPressShareExtension/tr.lproj/Localizable.strings index ce0b3f2c2313..01d16874031e 100644 Binary files a/WordPress/WordPressShareExtension/tr.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/tr.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/zh-Hans.lproj/Localizable.strings b/WordPress/WordPressShareExtension/zh-Hans.lproj/Localizable.strings index df8f5fd7cf36..cc51049ab511 100644 Binary files a/WordPress/WordPressShareExtension/zh-Hans.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/zh-Hans.lproj/Localizable.strings differ diff --git a/WordPress/WordPressShareExtension/zh-Hant.lproj/Localizable.strings b/WordPress/WordPressShareExtension/zh-Hant.lproj/Localizable.strings index d09d840866d2..06c02cea30f6 100644 Binary files a/WordPress/WordPressShareExtension/zh-Hant.lproj/Localizable.strings and b/WordPress/WordPressShareExtension/zh-Hant.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTest/Bottom Sheet/BottomSheetViewControllerTests.swift b/WordPress/WordPressTest/Bottom Sheet/BottomSheetViewControllerTests.swift new file mode 100644 index 000000000000..307fb760a1df --- /dev/null +++ b/WordPress/WordPressTest/Bottom Sheet/BottomSheetViewControllerTests.swift @@ -0,0 +1,33 @@ +import XCTest +import Nimble + +@testable import WordPress + +class BottomSheetViewControllerTests: XCTestCase { + + /// - Add the given ViewController as a child View Controller + /// + func testAddTheGivenViewControllerAsAChildViewController() { + let viewController = BottomSheetPresentableViewController() + let bottomSheet = BottomSheetViewController(childViewController: viewController) + + bottomSheet.viewDidLoad() + + expect(bottomSheet.children).to(contain(viewController)) + } + + /// - Add the given ViewController view to the subviews of the Bottom Sheet + /// + func testAddGivenVCViewToTheBottomSheetSubviews() { + let viewController = BottomSheetPresentableViewController() + let bottomSheet = BottomSheetViewController(childViewController: viewController) + + bottomSheet.viewDidLoad() + + expect(bottomSheet.view.subviews.flatMap { $0.subviews }).to(contain(viewController.view)) + } +} + +private class BottomSheetPresentableViewController: UIViewController, DrawerPresentable { + var initialHeight: CGFloat = 0 +} diff --git a/WordPress/WordPressTest/PrepublishingHeaderViewTests.swift b/WordPress/WordPressTest/PrepublishingHeaderViewTests.swift new file mode 100644 index 000000000000..47833f7abefe --- /dev/null +++ b/WordPress/WordPressTest/PrepublishingHeaderViewTests.swift @@ -0,0 +1,26 @@ +import UIKit +import Nimble + +@testable import WordPress + +class PrepublishingHeaderViewTests: XCTestCase { + + func testShareControllerCreated() { + let prepublishingHeaderView = PrepublishingHeaderView.loadFromNib() + let delegateMock = PrepublishingHeaderViewDelegateMock() + prepublishingHeaderView.delegate = delegateMock + + prepublishingHeaderView.closeButton.sendActions(for: .touchUpInside) + + expect(delegateMock.didCallCloseButtonTapped).to(beTrue()) + } + +} + +class PrepublishingHeaderViewDelegateMock: PrepublishingHeaderViewDelegate { + var didCallCloseButtonTapped = false + + func closeButtonTapped() { + didCallCloseButtonTapped = true + } +} diff --git a/WordPress/WordPressTest/PrepublishingNudgesViewControllerTests.swift b/WordPress/WordPressTest/PrepublishingNudgesViewControllerTests.swift new file mode 100644 index 000000000000..6772ad74fda7 --- /dev/null +++ b/WordPress/WordPressTest/PrepublishingNudgesViewControllerTests.swift @@ -0,0 +1,32 @@ +import XCTest +import Nimble + +@testable import WordPress + +class PrepublishingNudgesViewControllerTests: XCTestCase { + + override class func setUp() { + super.setUp() + + /// We need that in order to initialize the Authenticator, otherwise this test crashes + /// This is because we're using the NUXButton. Ideally, that component should be extracted + WordPressAuthenticationManager().initializeWordPressAuthenticator() + } + + /// Call the completion block when the "Publish" button is pressed + /// + func testCallCompletionBlockWhenButtonTapped() { + let post = PostBuilder().build() + var returnedPost: AbstractPost? + let prepublishingViewController = PrepublishingViewController(post: post) { post in + returnedPost = post + } + _ = UINavigationController(rootViewController: prepublishingViewController) + prepublishingViewController.viewDidLoad() + + prepublishingViewController.publishButton.sendActions(for: .touchUpInside) + + expect(returnedPost).toEventually(equal(post)) + } + +} diff --git a/WordPress/WordPressTest/ReaderDetailViewControllerTests.swift b/WordPress/WordPressTest/ReaderDetailViewControllerTests.swift index 284a5e567482..3ba0df521089 100644 --- a/WordPress/WordPressTest/ReaderDetailViewControllerTests.swift +++ b/WordPress/WordPressTest/ReaderDetailViewControllerTests.swift @@ -28,9 +28,6 @@ class ReaderDetailViewControllerTests: XCTestCase { } func testReblogButtonMatchesExpectation() { - guard FeatureFlag.postReblogging.enabled else { - return - } XCTAssertEqual(readerDetailViewController?.getReblogButtonForTesting().accessibilityLabel, TestConstants.reblogLabel, "Incorrect accessibility label: Reblog button") } } diff --git a/WordPress/WordPressTest/ReaderPostCardCellTests.swift b/WordPress/WordPressTest/ReaderPostCardCellTests.swift index 836ee8991be2..106947b4b723 100644 --- a/WordPress/WordPressTest/ReaderPostCardCellTests.swift +++ b/WordPress/WordPressTest/ReaderPostCardCellTests.swift @@ -197,16 +197,10 @@ final class ReaderPostCardCellTests: XCTestCase { } func testReblogActionButtonMatchesExpectation() { - guard FeatureFlag.postReblogging.enabled else { - return - } XCTAssertEqual(cell?.getReblogButtonForTesting().accessibilityLabel, TestConstants.reblogLabel, "Incorrect accessibility label: Reblog button") } func testReblogButtonIsVisible() { - guard FeatureFlag.postReblogging.enabled else { - return - } guard let button = cell?.getReblogButtonForTesting() else { XCTFail("Reblog button not found.") return @@ -215,9 +209,6 @@ final class ReaderPostCardCellTests: XCTestCase { } func testReblogButtonVisibleWithNoLoggedInUser() { - guard FeatureFlag.postReblogging.enabled else { - return - } cell?.loggedInActionVisibility = .visible(enabled: false) cell?.configureCell(mock!) diff --git a/WordPress/WordPressTodayWidget/ar.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/ar.lproj/Localizable.strings index df91d52eaea1..b4621bbc4087 100644 Binary files a/WordPress/WordPressTodayWidget/ar.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/ar.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/bg.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/bg.lproj/Localizable.strings index eed61e6f57bb..44ca9fd0ab5a 100644 Binary files a/WordPress/WordPressTodayWidget/bg.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/bg.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/cs.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/cs.lproj/Localizable.strings index 31214acd90a4..a095ba28f455 100644 Binary files a/WordPress/WordPressTodayWidget/cs.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/cs.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/cy.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/cy.lproj/Localizable.strings index b7b06ab059ee..6f9b22d2ee63 100644 Binary files a/WordPress/WordPressTodayWidget/cy.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/cy.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/da.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/da.lproj/Localizable.strings index 8f5ec76bae66..d9f7e0f78c76 100644 Binary files a/WordPress/WordPressTodayWidget/da.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/da.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/de.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/de.lproj/Localizable.strings index 3eb30fb3133a..f60729a9b2bc 100644 Binary files a/WordPress/WordPressTodayWidget/de.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/de.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/en-AU.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/en-AU.lproj/Localizable.strings index 8dfa67aabec1..4efbd313b4ef 100644 Binary files a/WordPress/WordPressTodayWidget/en-AU.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/en-AU.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/en-CA.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/en-CA.lproj/Localizable.strings index 8dfa67aabec1..4efbd313b4ef 100644 Binary files a/WordPress/WordPressTodayWidget/en-CA.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/en-CA.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/en-GB.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/en-GB.lproj/Localizable.strings index 8dfa67aabec1..4efbd313b4ef 100644 Binary files a/WordPress/WordPressTodayWidget/en-GB.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/en-GB.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/es.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/es.lproj/Localizable.strings index 1bc4d5e9572b..70fed2c8b20f 100644 Binary files a/WordPress/WordPressTodayWidget/es.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/es.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/fr.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/fr.lproj/Localizable.strings index 3ab7ff98dbee..d8891ac1e5f5 100644 Binary files a/WordPress/WordPressTodayWidget/fr.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/fr.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/he.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/he.lproj/Localizable.strings index 025e1c793976..c772a3477e7f 100644 Binary files a/WordPress/WordPressTodayWidget/he.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/he.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/hr.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/hr.lproj/Localizable.strings index 2e2e01b30df4..4f94d15e7c21 100644 Binary files a/WordPress/WordPressTodayWidget/hr.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/hr.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/hu.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/hu.lproj/Localizable.strings index 6ad18e82fa83..849d1189d84a 100644 Binary files a/WordPress/WordPressTodayWidget/hu.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/hu.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/id.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/id.lproj/Localizable.strings index 2fbd4aa7b6ac..71ce71bc40fa 100644 Binary files a/WordPress/WordPressTodayWidget/id.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/id.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/is.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/is.lproj/Localizable.strings index 5f38153784bb..f05f504acff9 100644 Binary files a/WordPress/WordPressTodayWidget/is.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/is.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/it.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/it.lproj/Localizable.strings index 7136fa8cfd6c..a17ebe994225 100644 Binary files a/WordPress/WordPressTodayWidget/it.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/it.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/ja.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/ja.lproj/Localizable.strings index df9454acd0d2..ea092a196289 100644 Binary files a/WordPress/WordPressTodayWidget/ja.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/ja.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/ko.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/ko.lproj/Localizable.strings index 1a88de85e3d4..8636e5b04371 100644 Binary files a/WordPress/WordPressTodayWidget/ko.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/ko.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/nb.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/nb.lproj/Localizable.strings index 8b932f477d08..243585dab2e0 100644 Binary files a/WordPress/WordPressTodayWidget/nb.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/nb.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/nl.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/nl.lproj/Localizable.strings index cc1cc089bef7..aacad4e21599 100644 Binary files a/WordPress/WordPressTodayWidget/nl.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/nl.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/pl.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/pl.lproj/Localizable.strings index b9c5473886ed..deff52b9ec36 100644 Binary files a/WordPress/WordPressTodayWidget/pl.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/pl.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/pt-BR.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/pt-BR.lproj/Localizable.strings index 747808369b63..9ef3289dd91e 100644 Binary files a/WordPress/WordPressTodayWidget/pt-BR.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/pt-BR.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/pt.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/pt.lproj/Localizable.strings index 04af9d38cb14..a964662b9512 100644 Binary files a/WordPress/WordPressTodayWidget/pt.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/pt.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/ro.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/ro.lproj/Localizable.strings index 27741762e07e..ea42e9c79b7a 100644 Binary files a/WordPress/WordPressTodayWidget/ro.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/ro.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/ru.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/ru.lproj/Localizable.strings index c5a9761f4b68..933cf1e1ee54 100644 Binary files a/WordPress/WordPressTodayWidget/ru.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/ru.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/sk.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/sk.lproj/Localizable.strings index c8268f249281..d5cddaccdc71 100644 Binary files a/WordPress/WordPressTodayWidget/sk.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/sk.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/sq.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/sq.lproj/Localizable.strings index 291c832c978c..63c8b8e95b81 100644 Binary files a/WordPress/WordPressTodayWidget/sq.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/sq.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/sv.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/sv.lproj/Localizable.strings index 4c2cee7a5407..49d8f854a45b 100644 Binary files a/WordPress/WordPressTodayWidget/sv.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/sv.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/th.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/th.lproj/Localizable.strings index 281edf75e573..e75a46436af9 100644 Binary files a/WordPress/WordPressTodayWidget/th.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/th.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/tr.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/tr.lproj/Localizable.strings index aca6f9abe924..c54ab86696bc 100644 Binary files a/WordPress/WordPressTodayWidget/tr.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/tr.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/zh-Hans.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/zh-Hans.lproj/Localizable.strings index 4522cc09f057..3a40399c7502 100644 Binary files a/WordPress/WordPressTodayWidget/zh-Hans.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/zh-Hans.lproj/Localizable.strings differ diff --git a/WordPress/WordPressTodayWidget/zh-Hant.lproj/Localizable.strings b/WordPress/WordPressTodayWidget/zh-Hant.lproj/Localizable.strings index c42f8181afba..a67002eec395 100644 Binary files a/WordPress/WordPressTodayWidget/zh-Hant.lproj/Localizable.strings and b/WordPress/WordPressTodayWidget/zh-Hant.lproj/Localizable.strings differ diff --git a/WordPress/WordPressUITests/Screens/Editor/AztecEditorScreen.swift b/WordPress/WordPressUITests/Screens/Editor/AztecEditorScreen.swift index faaf7c4fafd2..eb65711cacea 100644 --- a/WordPress/WordPressUITests/Screens/Editor/AztecEditorScreen.swift +++ b/WordPress/WordPressUITests/Screens/Editor/AztecEditorScreen.swift @@ -19,6 +19,7 @@ class AztecEditorScreen: BaseScreen { let editorCloseButton = XCUIApplication().navigationBars["Azctec Editor Navigation Bar"].buttons["Close"] let publishButton = XCUIApplication().buttons["Publish"] + let publishNowButton = XCUIApplication().buttons["Publish Now"] let moreButton = XCUIApplication().buttons["more_post_options"] let uploadProgressBar = XCUIApplication().progressIndicators["Progress"] @@ -262,6 +263,7 @@ class AztecEditorScreen: BaseScreen { func publish() -> EditorNoticeComponent { publishButton.tap() + confirmPublish() return EditorNoticeComponent(withNotice: "Post published", andAction: "View") @@ -271,11 +273,7 @@ class AztecEditorScreen: BaseScreen { if FancyAlertComponent.isLoaded() { FancyAlertComponent().acceptAlert() } else { - if isIpad { - app.alerts.buttons["Publish"].tap() - } else { - app.sheets.buttons["Publish"].tap() - } + publishNowButton.tap() } } diff --git a/WordPress/WordPressUITests/Screens/Editor/BlockEditorScreen.swift b/WordPress/WordPressUITests/Screens/Editor/BlockEditorScreen.swift index 3d2dcece3881..3fb00537545e 100644 --- a/WordPress/WordPressUITests/Screens/Editor/BlockEditorScreen.swift +++ b/WordPress/WordPressUITests/Screens/Editor/BlockEditorScreen.swift @@ -7,6 +7,7 @@ class BlockEditorScreen: BaseScreen { let editorNavBar = XCUIApplication().navigationBars["Gutenberg Editor Navigation Bar"] let editorCloseButton = XCUIApplication().navigationBars["Gutenberg Editor Navigation Bar"].buttons["Close"] let publishButton = XCUIApplication().buttons["Publish"] + let publishNowButton = XCUIApplication().buttons["Publish Now"] let moreButton = XCUIApplication().buttons["more_post_options"] // Editor area @@ -133,11 +134,7 @@ class BlockEditorScreen: BaseScreen { if FancyAlertComponent.isLoaded() { FancyAlertComponent().acceptAlert() } else { - if isIpad { - app.alerts.buttons["Publish"].tap() - } else { - app.sheets.buttons["Publish"].tap() - } + publishNowButton.tap() } } diff --git a/config/Version.internal.xcconfig b/config/Version.internal.xcconfig index 35885f89d5da..74779b40db91 100644 --- a/config/Version.internal.xcconfig +++ b/config/Version.internal.xcconfig @@ -1,4 +1,4 @@ -VERSION_SHORT=14.6 +VERSION_SHORT=14.7 // Internal long version example: VERSION_LONG=9.9.0.20180423 -VERSION_LONG=14.6.0.20200406 +VERSION_LONG=14.7.0.20200420 diff --git a/config/Version.public.xcconfig b/config/Version.public.xcconfig index 0c85c8995b22..30006f446a52 100644 --- a/config/Version.public.xcconfig +++ b/config/Version.public.xcconfig @@ -1,4 +1,4 @@ -VERSION_SHORT=14.6 +VERSION_SHORT=14.7 // Public long version example: VERSION_LONG=9.9.0.0 -VERSION_LONG=14.6.0.0 +VERSION_LONG=14.7.0.0