diff --git a/.buildkite/commands/checkout-release-branch.sh b/.buildkite/commands/checkout-release-branch.sh new file mode 100755 index 000000000..29bbde406 --- /dev/null +++ b/.buildkite/commands/checkout-release-branch.sh @@ -0,0 +1,14 @@ +#!/bin/bash -eu + +# We expect BUILDKITE_RELEASE_VERSION to be as an environment variable, e.g. by the automation that triggers the build on Buildkite. +# It must use the `BUILDKITE_` prefix to be passed to the agent due to how `hostmgr` works, in case this runs on a Mac agents. +if [[ -z "${BUILDKITE_RELEASE_VERSION}" ]]; then + echo "BUILDKITE_RELEASE_VERSION is not set." + exit 1 +fi + +# Buildkite, by default, checks out a specific commit. +# For many release actions, we need to be on a release branch instead. +BRANCH_NAME="release/${BUILDKITE_RELEASE_VERSION}" +git fetch origin "$BRANCH_NAME" +git checkout "$BRANCH_NAME" diff --git a/.buildkite/commands/configure-git-for-release-management.sh b/.buildkite/commands/configure-git-for-release-management.sh new file mode 100755 index 000000000..6aeae2b3b --- /dev/null +++ b/.buildkite/commands/configure-git-for-release-management.sh @@ -0,0 +1,8 @@ +#!/bin/bash -eu + +# The Git command line client is not configured in Buildkite. +# At the moment, steps that need Git access can configure it on deman using this script. +# Later on, we should be able to configure it on the agent instead. +add_host_to_ssh_known_hosts github.com +git config --global user.email "mobile+wpmobilebot@automattic.com" +git config --global user.name "Automattic Release Bot" diff --git a/.buildkite/commands/release-build.sh b/.buildkite/commands/release-build.sh index 1b7c2c3d0..94de0e420 100755 --- a/.buildkite/commands/release-build.sh +++ b/.buildkite/commands/release-build.sh @@ -9,9 +9,5 @@ install_cocoapods echo "--- :closed_lock_with_key: Installing Secrets" bundle exec fastlane run configure_apply -echo "--- :hammer_and_wrench: Build and Upload to App Store Connect" -bundle exec fastlane build_and_upload_to_app_store_connect \ - skip_confirm:true \ - skip_prechecks:true \ - create_release:true \ - beta_release:${1:-true} # use first call param, default to true for safety +echo "--- :hammer_and_wrench: Build for App Store Connect" +bundle exec fastlane build_for_app_store_connect diff --git a/.buildkite/commands/release-upload.sh b/.buildkite/commands/release-upload.sh new file mode 100755 index 000000000..bbee6c04d --- /dev/null +++ b/.buildkite/commands/release-upload.sh @@ -0,0 +1,19 @@ +#!/bin/bash -eu + +echo "--- :arrow_down: Downloading Artifacts" +ARTIFACTS_DIR='build/results' +STEP=testflight_build +buildkite-agent artifact download "$ARTIFACTS_DIR/*.ipa" . --step $STEP +buildkite-agent artifact download "$ARTIFACTS_DIR/*.zip" . --step $STEP + +echo "--- :rubygems: Setting up Gems" +install_gems + +echo "--- :closed_lock_with_key: Installing Secrets" +bundle exec fastlane run configure_apply + +echo "--- :hammer_and_wrench: Upload to App Store Connect" +bundle exec fastlane upload_to_app_store_connect \ + skip_prechecks:true \ + create_release:true \ + "beta_release:${1:-true}" # use first call param, default to true for safety diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 9acc5b91b..5e4fb659f 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,11 +1,13 @@ -# Nodes with values to reuse in the pipeline. -common_params: - # Common plugin settings to use with the `plugins` key. - - &common_plugins - - automattic/a8c-ci-toolkit#3.0.1 - # Common environment values to use with the `env` key. - - &common_env - IMAGE_ID: xcode-15.1 +# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json +--- + +# Variables used in this pipeline are defined in `shared-pipeline-vars`, which is `source`'d before calling `buildkite-agent pipeline upload` + +agents: + queue: mac + +env: + IMAGE_ID: $IMAGE_ID # This is the default pipeline – it will build and test the app steps: @@ -15,18 +17,34 @@ steps: ################# - label: "🔬 Build and Test" command: ".buildkite/commands/build-and-test.sh" - env: *common_env - plugins: *common_plugins + plugins: [$CI_TOOLKIT_PLUGIN] artifact_paths: - "build/results/*" + ################# + # Linters + ################# + - label: "☢️ Danger - PR Check" + command: danger + key: danger + if: "build.pull_request.id != null" + retry: + manual: + permit_on_passed: true + agents: + queue: "linter" + + - label: ":swift: SwiftLint" + command: swiftlint + agents: + queue: "linter" + ################# # Create Installable Build ################# - label: "🛠 Installable Build" command: ".buildkite/commands/build-prototype.sh" - env: *common_env - plugins: *common_plugins + plugins: [$CI_TOOLKIT_PLUGIN] if: "build.pull_request.id != null || build.pull_request.draft" artifact_paths: - "build/results/*" @@ -36,7 +54,6 @@ steps: ################# - label: "🔬 UI Test (Full)" command: ".buildkite/commands/build-and-ui-test.sh SimplenoteUITests 'iPhone SE (3rd generation)'" - env: *common_env - plugins: *common_plugins + plugins: [$CI_TOOLKIT_PLUGIN] artifact_paths: - "build/results/*" diff --git a/.buildkite/release-build.yml b/.buildkite/release-build.yml index 085d1bcf6..5631eff49 100644 --- a/.buildkite/release-build.yml +++ b/.buildkite/release-build.yml @@ -1,20 +1,22 @@ -# This pipeline is meant to be run via the Buildkite API, and is only used for release builds - -# Nodes with values to reuse in the pipeline. -common_params: - # Common plugin settings to use with the `plugins` key. - - &common_plugins - - automattic/a8c-ci-toolkit#3.0.1 - # Common environment values to use with the `env` key. - - &common_env - IMAGE_ID: xcode-15.1 +env: + IMAGE_ID: $IMAGE_ID +agents: + queue: mac steps: + - label: ":testflight: Build Simplenote iOS for App Store Connect" + key: testflight_build + command: .buildkite/commands/release-build.sh + priority: 1 + plugins: [$CI_TOOLKIT_PLUGIN] + artifact_paths: + - build/results/*.zip + - build/results/*.ipa - - label: ":testflight: Simplenote iOS Release Build (App Store Connect)" - command: ".buildkite/commands/release-build.sh" + - label: ":testflight: Upload Simplenote iOS to App Store Connect" + depends_on: testflight_build + command: .buildkite/commands/release-upload.sh $BETA_RELEASE priority: 1 - env: *common_env - plugins: *common_plugins + plugins: [$CI_TOOLKIT_PLUGIN] notify: - slack: "#build-and-ship" diff --git a/.buildkite/release-pipelines/complete-code-freeze.yml b/.buildkite/release-pipelines/complete-code-freeze.yml new file mode 100644 index 000000000..4a5990cf1 --- /dev/null +++ b/.buildkite/release-pipelines/complete-code-freeze.yml @@ -0,0 +1,36 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json +--- + +# Variables used in this pipeline are defined in `shared-pipeline-vars`, which is `source`'d before calling `buildkite-agent pipeline upload` + +env: + IMAGE_ID: $IMAGE_ID + +# The code freeze completion needs to run on macOS because it uses genstrings under the hood. +agents: + queue: mac + +steps: + - label: Complete Code Freeze + key: complete_code_freeze + plugins: [$CI_TOOLKIT_PLUGIN] + command: | + echo '--- :git: Configure Git for Release Management' + .buildkite/commands/configure-git-for-release-management.sh + + echo '--- :git: Checkout release branch' + .buildkite/commands/checkout-release-branch.sh + + echo '--- :ruby: Set up Ruby Tools' + install_gems + + echo '--- :closed_lock_with_key: Access secrets' + bundle exec fastlane run configure_apply + + echo '--- :shipit: Complete code freeze' + bundle exec fastlane complete_code_freeze skip_confirm:true + retry: + manual: + # If failed, we prefer retrying via ReleaseV2 rather than Buildkite. + # Rationale: ReleaseV2 is the source of truth for the process and track links to the various builds. + allowed: false diff --git a/.buildkite/release-pipelines/new-beta-release.yml b/.buildkite/release-pipelines/new-beta-release.yml new file mode 100644 index 000000000..b4c37a32b --- /dev/null +++ b/.buildkite/release-pipelines/new-beta-release.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json +--- + +agents: + queue: mac + +# The new beta workflow needs to run on macOS because it uses SwiftGen under the hood. +# +# Notice that SwiftGen should work Linux, but we haven't tested it yet. +# One thing at a time... +env: + IMAGE_ID: $IMAGE_ID + +steps: + - label: New Beta Release + plugins: [$CI_TOOLKIT_PLUGIN] + command: | + echo '--- :git: Configure Git for Release Management' + .buildkite/commands/configure-git-for-release-management.sh + + echo '--- :git: Checkout Release Branch' + .buildkite/commands/checkout-release-branch.sh + + echo '--- :ruby: Set up Ruby Tools' + install_gems + + echo '--- :closed_lock_with_key: Access Secrets' + bundle exec fastlane run configure_apply + + echo '--- :shipit: New Beta Release' + bundle exec fastlane new_beta_release skip_confirm:true + retry: + manual: + # If those jobs fail, one should always prefer re-triggering a new build from ReleaseV2 rather than retrying the individual job from Buildkite + allowed: false diff --git a/.buildkite/release-pipelines/start-code-freeze.yml b/.buildkite/release-pipelines/start-code-freeze.yml new file mode 100644 index 000000000..49fabdaf4 --- /dev/null +++ b/.buildkite/release-pipelines/start-code-freeze.yml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json +--- + +# Variables used in this pipeline are defined in `shared-pipeline-vars`, which is `source`'d before calling `buildkite-agent pipeline upload` + +steps: + - label: Start Code Freeze + plugins: + - $CI_TOOLKIT_PLUGIN + agents: + queue: tumblr-metal + command: | + echo '--- :robot_face: Use bot for Git operations' + source use-bot-for-git wpmobilebot + + echo '--- :ruby: Setup Ruby Tools' + install_gems + + echo '--- :shipit: Start code freeze' + bundle exec fastlane start_code_freeze skip_confirm:true + retry: + manual: + # If failed, we prefer retrying via ReleaseV2 rather than Buildkite. + # Rationale: ReleaseV2 is the source of truth for the process and track links to the various builds. + allowed: false diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars new file mode 100755 index 000000000..3c7429977 --- /dev/null +++ b/.buildkite/shared-pipeline-vars @@ -0,0 +1,11 @@ +#!/bin/sh + +# This file is `source`'d before calling `buildkite-agent pipeline upload`, and can be used +# to set up some variables that will be interpolated in the `.yml` pipeline before uploading it. + +# The ~> modifier is not currently used, but we check for it just in case +XCODE_VERSION=$(sed -E -n 's/^(~> )?(.*)/xcode-\2/p' .xcode-version) +export CI_TOOLKIT_PLUGIN_VERSION="3.4.2" + +export IMAGE_ID="$XCODE_VERSION" +export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#$CI_TOOLKIT_PLUGIN_VERSION" diff --git a/.bundle/config b/.bundle/config index bd4454d51..7a906f438 100644 --- a/.bundle/config +++ b/.bundle/config @@ -1,4 +1,5 @@ --- +BUNDLE_BUNDLE_SPECIFIC_PLATFORM: "false" BUNDLE_PATH: "vendor/bundle" BUNDLE_RETRY: "3" BUNDLE_JOBS: "3" diff --git a/.configure b/.configure index 7f10aae64..5498e1b3e 100644 --- a/.configure +++ b/.configure @@ -18,11 +18,6 @@ "destination": "~/.configure/simplenote-ios/secrets/google_cloud_keys.json", "encrypt": true }, - { - "file": "iOS/simplenote/project.env", - "destination": "~/.configure/simplenote-ios/secrets/project.env", - "encrypt": true - }, { "file": "iOS/app_store_connect_fastlane_api_key.json", "destination": "~/.configure/simplenote-ios/secrets/app_store_connect_fastlane_api_key.json", diff --git a/.github/workflows/run-danger.yml b/.github/workflows/run-danger.yml new file mode 100644 index 000000000..874acdde2 --- /dev/null +++ b/.github/workflows/run-danger.yml @@ -0,0 +1,17 @@ +name: ☢️ Trigger Danger On Buildkite + +on: + pull_request: + types: [labeled, unlabeled, milestoned, demilestoned, ready_for_review] + +jobs: + dangermattic: + if: ${{ (github.event.pull_request.draft == false) }} + uses: Automattic/dangermattic/.github/workflows/reusable-retry-buildkite-step-on-events.yml@v1.1.2 + with: + org-slug: "automattic" + pipeline-slug: "simplenote-ios" + retry-step-key: "danger" + build-commit-sha: "${{ github.event.pull_request.head.sha }}" + secrets: + buildkite-api-token: ${{ secrets.TRIGGER_BK_BUILD_TOKEN }} diff --git a/.hound.yml b/.hound.yml deleted file mode 100644 index 0d5250000..000000000 --- a/.hound.yml +++ /dev/null @@ -1,7 +0,0 @@ -fail_on_violations: true - -rubocop: - enabled: false - -swiftlint: - config_file: .swiftlint.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..b4e39abfd --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,29 @@ +inherit_from: .rubocop_todo.yml + +AllCops: + Exclude: + - DerivedData/**/* + - Pods/**/* + - vendor/**/* + NewCops: enable + SuggestExtensions: + rubocop-rake: false + +Metrics/MethodLength: + Max: 30 + +Layout/LineLength: + Max: 180 + +Metrics/BlockLength: + Exclude: &xfiles + - fastlane/Fastfile + - fastlane/lanes/*.rb + - Rakefile + +Style/HashSyntax: + EnforcedShorthandSyntax: never +# Used by UI test account methods. See https://github.com/Automattic/simplenote-ios/pull/1275 for more details. +Style/GlobalVars: + AllowedVariables: + - $used_test_account_index diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..3a2a1e13e --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,40 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2024-02-20 18:18:24 UTC using RuboCop version 1.60.2. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. +# URISchemes: http, https +Layout/LineLength: + Exclude: + - 'fastlane/Fastfile' + +# Offense count: 6 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Exclude: + - 'fastlane/Fastfile' + +# Offense count: 2 +Lint/NonLocalExitFromIterator: + Exclude: + - 'Scripts/update-translations.rb' + +# Offense count: 1 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to +Naming/MethodParameterName: + Exclude: + - 'Scripts/update-translations.rb' + +# Offense count: 9 +# Configuration parameters: AllowedVariables. +Style/GlobalVars: + Exclude: + - 'fastlane/Fastfile' diff --git a/.swiftlint.yml b/.swiftlint.yml index 604062415..6d386548e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,5 @@ +swiftlint_version: 0.54.0 + # Project configuration excluded: - Pods @@ -15,6 +17,10 @@ only_rules: # if,for,while,do statements shouldn't wrap their conditionals in parentheses. - control_statement + - discarded_notification_center_observer + + - duplicate_imports + # Arguments can be omitted when matching enums with associated types if they # are not used. - empty_enum_arguments @@ -36,8 +42,9 @@ only_rules: - trailing_semicolon # Lines should not have trailing whitespace. - - trailing_whitespace + # - trailing_whitespace + # - vertical_whitespace - custom_rules # Rules configuration diff --git a/.xcode-version b/.xcode-version index 6dfe8b129..232a7fc1a 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -14.3.1 +15.4 diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 000000000..5deb44319 --- /dev/null +++ b/Dangerfile @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +github.dismiss_out_of_range_messages + +# `files: []` forces rubocop to scan all files, not just the ones modified in the PR +rubocop.lint(files: [], force_exclusion: true, inline_comment: true, fail_on_inline_comment: true, include_cop_names: true) + +manifest_pr_checker.check_all_manifest_lock_updated + +podfile_checker.check_podfile_does_not_have_branch_references + +ios_release_checker.check_core_data_model_changed +ios_release_checker.check_release_notes_and_app_store_strings + +# skip remaining checks if we're in a release-process PR +if github.pr_labels.include?('Releases') + message('This PR has the `Releases` label: some checks will be skipped.') + return +end + +common_release_checker.check_internal_release_notes_changed(report_type: :message) + +ios_release_checker.check_modified_translations_on_release_branch + +view_changes_checker.check + +pr_size_checker.check_diff_size(max_size: 500) + +# skip remaining checks if the PR is still a Draft +if github.pr_draft? + message('This PR is still a Draft: some checks will be skipped.') + return +end + +labels_checker.check( + do_not_merge_labels: ['[Status] DO NOT MERGE'], + required_labels: [//], + required_labels_error: 'PR requires at least one label.' +) + +# runs the milestone check if this is not a WIP feature and the PR is against the main branch or the release branch +milestone_checker.check_milestone_due_date(days_before_due: 4) if (github_utils.main_branch? || github_utils.release_branch?) && !github_utils.wip_feature? diff --git a/Gemfile b/Gemfile index 565b1b481..166d46262 100644 --- a/Gemfile +++ b/Gemfile @@ -4,15 +4,12 @@ source 'https://rubygems.org' # 1.14.0 fixes a bug that broke compatibility with Ruby 3 gem 'cocoapods', '~> 1.14' +gem 'danger-dangermattic', '~> 1.0' gem 'fastlane', '~> 2' -gem 'fastlane-plugin-appcenter', '~> 1.11' +gem 'fastlane-plugin-appcenter', '~> 2.1.2' gem 'fastlane-plugin-sentry', '~> 1.6' -gem 'fastlane-plugin-wpmreleasetoolkit', '~> 9.1' -gem 'rubocop', '~> 1.38' +gem 'fastlane-plugin-wpmreleasetoolkit', '~> 12.0' group :screenshots, optional: true do gem 'rmagick', '~> 3.2.0' end - -plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index c8611365f..c134643c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,11 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (7.1.1) + activesupport (7.1.3.4) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -13,43 +15,47 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.15) + artifactory (3.0.17) ast (2.4.2) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.842.0) - aws-sdk-core (3.185.1) - aws-eventstream (~> 1, >= 1.0.2) + aws-eventstream (1.3.0) + aws-partitions (1.958.0) + aws-sdk-core (3.201.3) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.72.0) - aws-sdk-core (~> 3, >= 3.184.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.136.0) - aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - base64 (0.1.1) - bigdecimal (3.1.4) - buildkit (1.5.0) + base64 (0.2.0) + bigdecimal (3.1.8) + buildkit (1.6.0) sawyer (>= 0.6) chroma (0.2.0) claide (1.1.0) - cocoapods (1.14.1) + claide-plugins (0.9.2) + cork + nap + open4 (~> 1.3) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.1) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.6.0, < 2.0) @@ -62,7 +68,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.1) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -73,7 +79,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (2.0) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -85,22 +91,44 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.2) + concurrent-ruby (1.3.3) connection_pool (2.4.1) + cork (0.3.0) + colored2 (~> 3.1) + danger (9.5.0) + claide (~> 1.0) + claide-plugins (>= 0.9.2) + colored2 (~> 3.1) + cork (~> 0.1) + faraday (>= 0.9.0, < 3.0) + faraday-http-cache (~> 2.0) + git (~> 1.13) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.0) + octokit (>= 4.0) + terminal-table (>= 1, < 4) + danger-dangermattic (1.1.2) + danger (~> 9.4) + danger-plugin-api (~> 1.0) + danger-rubocop (~> 0.13) + rubocop (~> 1.63) + danger-plugin-api (1.0.0) + danger (> 2.0) + danger-rubocop (0.13.0) + danger + rubocop (~> 1.0) declarative (0.0.20) diffy (3.4.2) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) dotenv (2.8.1) - drb (2.1.1) - ruby2_keywords + drb (2.2.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.104.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -119,25 +147,27 @@ GEM faraday-em_http (1.0.0) faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) + faraday-http-cache (2.5.1) + faraday (>= 0.8) faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.7) - fastlane (2.216.0) + fastimage (2.3.1) + fastlane (2.222.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -149,6 +179,7 @@ GEM gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) google-cloud-storage (~> 1.31) highline (~> 2.0) http-cookie (~> 1.0.5) @@ -157,10 +188,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (~> 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -169,11 +200,11 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-appcenter (1.11.1) - fastlane-plugin-sentry (1.14.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-appcenter (2.1.2) + fastlane-plugin-sentry (1.22.0) os (~> 1.1, >= 1.1.4) - fastlane-plugin-wpmreleasetoolkit (9.1.0) + fastlane-plugin-wpmreleasetoolkit (12.0.0) activesupport (>= 6.1.7.1) buildkit (~> 1.5) chroma (= 0.2.0) @@ -194,12 +225,12 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - git (1.18.0) + git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) - google-apis-androidpublisher_v3 (0.51.0) + google-apis-androidpublisher_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.1) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -207,24 +238,23 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick google-apis-iamcredentials_v1 (0.17.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-playcustomapp_v1 (0.13.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.19.0) - google-apis-core (>= 0.9.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) - google-cloud-storage (1.44.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.19.0) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -235,78 +265,91 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) java-properties (0.3.0) jmespath (1.6.2) - json (2.6.3) - jwt (2.7.1) - mini_magick (4.12.0) + json (2.7.2) + jwt (2.8.2) + base64 + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.3) + mini_magick (4.13.2) mini_mime (1.1.5) - minitest (5.20.0) + minitest (5.24.1) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) - mutex_m (0.1.2) + multipart-post (2.4.1) + mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - nokogiri (1.15.4-arm64-darwin) + nkf (0.2.0) + nokogiri (1.16.7-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) octokit (6.1.1) faraday (>= 1, < 3) sawyer (~> 0.9) + open4 (1.3.4) options (2.3.2) - optparse (0.1.1) + optparse (0.5.0) os (1.1.4) - parallel (1.23.0) - parser (3.1.2.1) + parallel (1.26.3) + parser (3.3.4.2) ast (~> 2.4.1) - plist (3.7.0) - progress_bar (1.3.3) - highline (>= 1.6, < 3) + racc + plist (3.7.1) + progress_bar (1.3.4) + highline (>= 1.6) options (~> 2.3.0) public_suffix (4.0.7) - racc (1.7.1) + racc (1.8.1) rainbow (3.1.1) - rake (13.0.6) - rake-compiler (1.2.5) + rake (13.2.1) + rake-compiler (1.2.7) rake rchardet (1.8.0) - regexp_parser (2.6.0) + regexp_parser (2.9.2) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.6) + rexml (3.2.9) + strscan rmagick (3.2.0) rouge (2.0.7) - rubocop (1.38.0) + rubocop (1.65.1) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.1.2.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) + regexp_parser (>= 2.4, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.23.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.23.0) - parser (>= 3.1.1.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.32.1) + parser (>= 3.3.1.0) ruby-macho (2.5.1) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - security (0.1.3) - signet (0.18.0) + security (0.1.5) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -314,26 +357,23 @@ GEM simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) unicode-display_width (2.5.0) - webrick (1.8.1) word_wrap (1.0.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -349,15 +389,16 @@ PLATFORMS arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 + x86_64-linux DEPENDENCIES cocoapods (~> 1.14) + danger-dangermattic (~> 1.0) fastlane (~> 2) - fastlane-plugin-appcenter (~> 1.11) + fastlane-plugin-appcenter (~> 2.1.2) fastlane-plugin-sentry (~> 1.6) - fastlane-plugin-wpmreleasetoolkit (~> 9.1) + fastlane-plugin-wpmreleasetoolkit (~> 12.0) rmagick (~> 3.2.0) - rubocop (~> 1.38) BUNDLED WITH - 2.4.13 + 2.4.21 diff --git a/Podfile b/Podfile index ef7e409e8..d6548d74f 100644 --- a/Podfile +++ b/Podfile @@ -1,8 +1,8 @@ +# frozen_string_literal: true + source 'https://cdn.cocoapods.org/' -unless ['BUNDLE_BIN_PATH', 'BUNDLE_GEMFILE'].any? { |k| ENV.key?(k) } - raise 'Please run CocoaPods via `bundle exec`' -end +raise 'Please run CocoaPods via `bundle exec`' unless %w[BUNDLE_BIN_PATH BUNDLE_GEMFILE].any? { |k| ENV.key?(k) } inhibit_all_warnings! use_frameworks! @@ -13,7 +13,6 @@ workspace 'Simplenote.xcworkspace' # Main # abstract_target 'Automattic' do - # Main Target # target 'Simplenote' do @@ -44,6 +43,19 @@ abstract_target 'Automattic' do end end +## Tools +## =================== +## + +def swiftlint_version + require 'yaml' + + YAML.load_file('.swiftlint.yml')['swiftlint_version'] +end + +abstract_target 'Tools' do + pod 'SwiftLint', swiftlint_version +end # Post Install # diff --git a/Podfile.lock b/Podfile.lock index e590a298d..bf8d40e2c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -21,6 +21,7 @@ PODS: - Simperium/SocketTrust (1.9.0) - Simperium/SPReachability (1.9.0) - Simperium/SSKeychain (1.9.0) + - SwiftLint (0.54.0) - WordPress-Ratings-iOS (0.0.2) - ZIPFoundation (0.9.15) @@ -29,6 +30,7 @@ DEPENDENCIES: - AppCenter/Distribute (~> 4.4.3) - Gridicons (~> 0.18) - Simperium (= 1.9.0) + - SwiftLint (= 0.54.0) - WordPress-Ratings-iOS (= 0.0.2) - ZIPFoundation (~> 0.9.9) @@ -37,6 +39,7 @@ SPEC REPOS: - AppCenter - Gridicons - Simperium + - SwiftLint - WordPress-Ratings-iOS - ZIPFoundation @@ -44,9 +47,10 @@ SPEC CHECKSUMS: AppCenter: 3fd04aa1b166e16fdb03ec81dabe488aece83fbd Gridicons: dc92efbe5fd60111d2e8ea051d84a60cca552abc Simperium: 45d828d68aad71f3449371346f270013943eff78 + SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211 WordPress-Ratings-iOS: 9f83dbba6e728c5121b1fd21b5683cf2fd120646 ZIPFoundation: 063163dc828bf699c5be160eb4f58f676322d94f -PODFILE CHECKSUM: 0663a4b2a1777f0957be7a40d3b2f7bf92a7fb64 +PODFILE CHECKSUM: 6fc187e283b4fb7f9f4a0ef73a27632724c90845 -COCOAPODS: 1.14.1 +COCOAPODS: 1.15.2 diff --git a/README.md b/README.md index 94f4554e9..f73b2862d 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,8 @@ Third party libraries and resources managed by CocoaPods will be installed by th #### SwiftLint -We use [SwiftLint](https://github.com/realm/SwiftLint) to enforce a common style for Swift code. The app should build and work without it, but if you plan to write code, you are encouraged to install it. No commit should have lint warnings or errors. - -You can set up a Git [pre-commit hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) to run SwiftLint automatically when committing by running: - -`rake git:install_hooks` - -This is the recommended way to include SwiftLint in your workflow, as it catches lint issues locally before your code makes its way to Github. - -Alternately, a SwiftLint scheme is exposed within the project; Xcode will show a warning if you don't have SwiftLint installed. - -SwiftLint is integrated directly into the Xcode project, so lint errors appear as warnings after you build the project - -If your code has any style violations, you can try to automatically correct them by running: - -`rake lint:autocorrect` - -Otherwise you have to fix them manually. +We use [SwiftLint](https://github.com/realm/SwiftLint) to enforce a common style for Swift code. If you plan to write code, SwiftLint is going to be installed when you run `bundle exec pod install` and SwiftLint will run during the build. +No commit should have lint warnings or errors. ### Open Xcode diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index c705c09d6..317d019a0 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,30 @@ -4.51 +4.55 +----- + + +4.54 +----- +- Updated icons to work with new iOS 18 styles +- Add fall back login with username and password option to login +- Updated link to privacy notice for California users + +4.53 +----- +- Under the hood improvements + +4.52 ----- +- Extended support for iOS Shortcuts +- Fixed issue where notes selection was lost when app backgrounded +- Fixed an issue can activate the faceid switch without a passcode +- Magic Link Login Support +- Login UI has been overhauled +4.51 +----- +- Fixed issue where note cell appearance changes on press down instead of when the cell is selected +- Added ability to enable and disable indexing notes in Spotlight +- Fixed an issue where using the new note widget with the lock screen could create empty notes while the app was locked 4.50 ----- diff --git a/Rakefile b/Rakefile index e27dc8665..f040dc5ff 100644 --- a/Rakefile +++ b/Rakefile @@ -1,61 +1,67 @@ -SWIFTLINT_VERSION="0.41.0" -XCODE_WORKSPACE="Simplenote.xcworkspace" -XCODE_SCHEME="Simplenote" -XCODE_CONFIGURATION="Debug" +# frozen_string_literal: true +require 'English' require 'fileutils' require 'tmpdir' require 'rake/clean' require 'yaml' require 'digest' -PROJECT_DIR = File.expand_path(File.dirname(__FILE__)) + +# Constants +SWIFTLINT_VERSION = '0.41.0' +PROJECT_DIR = __dir__ +XCODE_WORKSPACE = 'Simplenote.xcworkspace' +XCODE_SCHEME = 'Simplenote' +XCODE_CONFIGURATION = 'Debug' +LOCAL_PATH = 'vendor/bundle' task default: %w[test] -desc "Install required dependencies" -task :dependencies => %w[dependencies:check] +desc 'Install required dependencies' +task dependencies: %w[dependencies:check] namespace :dependencies do - task :check => %w[bundler:check bundle:check credentials:apply pod:check lint:check] + task check: %w[bundler:check bundle:check credentials:apply pod:check] namespace :bundler do task :check do - unless command?("bundler") - Rake::Task["dependencies:bundler:install"].invoke - end + Rake::Task['dependencies:bundler:install'].invoke unless command?('bundler') end task :install do - puts "Bundler not found in PATH, installing to vendor" + puts 'Bundler not found in PATH, installing to vendor' ENV['GEM_HOME'] = File.join(PROJECT_DIR, 'vendor', 'gems') - ENV['PATH'] = File.join(PROJECT_DIR, 'vendor', 'gems', 'bin') + ":#{ENV['PATH']}" - sh "gem install bundler" unless command?("bundler") + ENV['PATH'] = File.join(PROJECT_DIR, 'vendor', 'gems', 'bin') + ":#{ENV.fetch('PATH', nil)}" + sh 'gem install bundler' unless command?('bundler') end - CLOBBER << "vendor/gems" + CLOBBER << 'vendor/gems' end namespace :bundle do task :check do - sh "bundle check --path=${BUNDLE_PATH:-vendor/bundle} > /dev/null", verbose: false do |ok, res| + sh "bundle config set --local path #{LOCAL_PATH} > /dev/null", verbose: false + sh 'bundle check > /dev/null', verbose: false do |ok, _res| next if ok + # bundle check exits with a non zero code if install is needed - dependency_failed("Bundler") - Rake::Task["dependencies:bundle:install"].invoke + dependency_failed('Bundler') + Rake::Task['dependencies:bundle:install'].invoke end end task :install do - fold("install.bundler") do - sh "bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}" + fold('install.bundler') do + sh 'bundle install --jobs=3 --retry=3' end end - CLOBBER << "vendor/bundle" - CLOBBER << ".bundle" + CLOBBER << 'vendor/bundle' + CLOBBER << '.bundle' end namespace :credentials do task :apply do next unless Dir.exist?(File.join(Dir.home, '.mobile-secrets/.git')) || ENV.key?('CONFIGURE_ENCRYPTION_KEY') + sh('FASTLANE_SKIP_UPDATE_CHECK=1 FASTLANE_ENV_PRINTER=1 bundle exec fastlane run configure_apply force:true') end end @@ -63,137 +69,89 @@ namespace :dependencies do namespace :pod do task :check do unless podfile_locked? && lockfiles_match? - dependency_failed("CocoaPods") - Rake::Task["dependencies:pod:install"].invoke + dependency_failed('CocoaPods') + Rake::Task['dependencies:pod:install'].invoke end end task :install do - fold("install.cocoapds") do + fold('install.cocoapds') do pod %w[install] end end task :clean do - fold("clean.cocoapds") do + fold('clean.cocoapds') do FileUtils.rm_rf('Pods') end end - CLOBBER << "Pods" + CLOBBER << 'Pods' end - - namespace :lint do - - task :check do - if swiftlint_needs_install - dependency_failed("SwiftLint") - Rake::Task["dependencies:lint:install"].invoke - end - end - - task :install do - fold("install.swiftlint") do - puts "Installing SwiftLint #{SWIFTLINT_VERSION} into #{swiftlint_path}" - Dir.mktmpdir do |tmpdir| - # Try first using a binary release - zipfile = "#{tmpdir}/swiftlint-#{SWIFTLINT_VERSION}.zip" - sh "curl --fail --location -o #{zipfile} https://github.com/realm/SwiftLint/releases/download/#{SWIFTLINT_VERSION}/portable_swiftlint.zip || true" - if File.exist?(zipfile) - extracted_dir = "#{tmpdir}/swiftlint-#{SWIFTLINT_VERSION}" - sh "unzip #{zipfile} -d #{extracted_dir}" - FileUtils.mkdir_p("#{swiftlint_path}/bin") - FileUtils.cp("#{extracted_dir}/swiftlint", "#{swiftlint_path}/bin/swiftlint") - else - sh "git clone --quiet https://github.com/realm/SwiftLint.git #{tmpdir}" - Dir.chdir(tmpdir) do - sh "git checkout --quiet #{SWIFTLINT_VERSION}" - sh "git submodule --quiet update --init --recursive" - FileUtils.remove_entry_secure(swiftlint_path) if Dir.exist?(swiftlint_path) - FileUtils.mkdir_p(swiftlint_path) - sh "make prefix_install PREFIX='#{swiftlint_path}'" - end - end - end - end - end - CLOBBER << "vendor/swiftlint" - end - end -CLOBBER << "vendor" +CLOBBER << 'vendor' desc "Build #{XCODE_SCHEME}" -task :build => [:dependencies] do +task build: [:dependencies] do xcodebuild(:build) end desc "Profile build #{XCODE_SCHEME}" -task :buildprofile => [:dependencies] do - ENV["verbose"] = "1" +task buildprofile: [:dependencies] do + ENV['verbose'] = '1' xcodebuild(:build, "OTHER_SWIFT_FLAGS='-Xfrontend -debug-time-compilation -Xfrontend -debug-time-expression-type-checking'") end -task :timed_build => [:clean] do +task timed_build: [:clean] do require 'benchmark' time = Benchmark.measure do - Rake::Task["build"].invoke + Rake::Task['build'].invoke end puts "CPU Time: #{time.total}" puts "Wall Time: #{time.real}" end -desc "Run test suite" -task :test => [:dependencies] do +desc 'Run test suite' +task test: [:dependencies] do xcodebuild(:build, :test) end -desc "Remove any temporary products" +desc 'Remove any temporary products' task :clean do xcodebuild(:clean) end -desc "Checks the source for style errors" -task :lint => %w[dependencies:lint:check] do - swiftlint %w[lint --quiet] -end - -namespace :lint do - desc "Automatically corrects style errors where possible" - task :autocorrect => %w[dependencies:lint:check] do - swiftlint %w[autocorrect] - end -end - namespace :git do - hooks = %w[pre-commit post-checkout post-merge] + hooks = %w[post-checkout post-merge] - desc "Install git hooks" + desc 'Install git hooks' task :install_hooks do hooks.each do |hook| target = hook_target(hook) source = hook_source(hook) backup = hook_backup(hook) - next if File.symlink?(target) and File.readlink(target) == source - next if File.file?(target) and File.identical?(target, source) + next if File.symlink?(target) && (File.readlink(target) == source) + next if File.file?(target) && File.identical?(target, source) + if File.exist?(target) puts "Existing hook for #{hook}. Creating backup at #{target} -> #{backup}" - FileUtils.mv(target, backup, :force => true) + FileUtils.mv(target, backup, force: true) end FileUtils.ln_s(source, target) puts "Installed #{hook} hook" end end - desc "Uninstall git hooks" + desc 'Uninstall git hooks' task :uninstall_hooks do hooks.each do |hook| target = hook_target(hook) source = hook_source(hook) backup = hook_backup(hook) - next unless File.symlink?(target) and File.readlink(target) == source + next unless File.symlink?(target) && (File.readlink(target) == source) + puts "Removing hook for #{hook}" File.unlink(target) if File.exist?(backup) @@ -217,14 +175,6 @@ namespace :git do end namespace :git do - task :pre_commit => %[dependencies:lint:check] do - begin - swiftlint %w[lint --quiet --strict] - rescue - exit $?.exitstatus - end - end - task :post_merge do check_dependencies_hook end @@ -234,19 +184,14 @@ namespace :git do end end -desc "Open the project in Xcode" -task :xcode => [:dependencies] do +desc 'Open the project in Xcode' +task xcode: [:dependencies] do sh "open #{XCODE_WORKSPACE}" end -def fold(label, &block) - puts "travis_fold:start:#{label}" if is_travis? +def fold(label) + puts "--- #{label}" if ENV['BUILDKITE'] yield - puts "travis_fold:end:#{label}" if is_travis? -end - -def is_travis? - return ENV["TRAVIS"] != nil end def pod(args) @@ -259,41 +204,22 @@ def lockfiles_match? end def podfile_locked? - podfile_checksum = Digest::SHA1.file("Podfile") - lockfile_checksum = YAML.load(File.read("Podfile.lock"))["PODFILE CHECKSUM"] + podfile_checksum = Digest::SHA1.file('Podfile') + lockfile_checksum = YAML.load_file('Podfile.lock')['PODFILE CHECKSUM'] podfile_checksum == lockfile_checksum end -def swiftlint_path - "#{PROJECT_DIR}/vendor/swiftlint" -end - -def swiftlint(args) - args = [swiftlint_bin] + args - sh(*args) -end - -def swiftlint_bin - "#{swiftlint_path}/bin/swiftlint" -end - -def swiftlint_needs_install - return true unless File.exist?(swiftlint_bin) - installed_version = `"#{swiftlint_bin}" version`.chomp - return (installed_version != SWIFTLINT_VERSION) -end - def xcodebuild(*build_cmds) - cmd = "xcodebuild" + cmd = 'xcodebuild' cmd += " -destination 'platform=iOS Simulator,name=iPhone 6s'" - cmd += " -sdk iphonesimulator" + cmd += ' -sdk iphonesimulator' cmd += " -workspace #{XCODE_WORKSPACE}" cmd += " -scheme #{XCODE_SCHEME}" cmd += " -configuration #{xcode_configuration}" - cmd += " " - cmd += build_cmds.map(&:to_s).join(" ") - cmd += " | bundle exec xcpretty -f `bundle exec xcpretty-travis-formatter` && exit ${PIPESTATUS[0]}" unless ENV['verbose'] + cmd += ' ' + cmd += build_cmds.map(&:to_s).join(' ') + cmd += ' | bundle exec xcpretty -f `bundle exec xcpretty-travis-formatter` && exit ${PIPESTATUS[0]}' unless ENV['verbose'] sh(cmd) end @@ -304,22 +230,23 @@ end def command?(command) system("which #{command} > /dev/null 2>&1") end + def dependency_failed(component) msg = "#{component} dependencies missing or outdated. " if ENV['DRY_RUN'] - msg += "Run rake dependencies to install them." - fail msg + msg += 'Run rake dependencies to install them.' + raise msg else - msg += "Installing..." + msg += 'Installing...' puts msg end end def check_dependencies_hook - ENV['DRY_RUN'] = "1" + ENV['DRY_RUN'] = '1' begin Rake::Task['dependencies'].invoke - rescue Exception => e + rescue StandardError => e puts e.message exit 1 end diff --git a/Scripts/extract_release_notes.rb b/Scripts/extract_release_notes.rb deleted file mode 100644 index c2e5649cf..000000000 --- a/Scripts/extract_release_notes.rb +++ /dev/null @@ -1,90 +0,0 @@ -# Parses the release notes file to extract the current version information and -# puts it to STDOUT. -# -# To update the release notes for localization: -# -# ruby ./this_script >| Simplenote/Resources/release_notes.txt -# -# To generate the App Store Connect release message: -# -# ruby ./this_script | pbcopy -# -# To generate the GitHub and App Center release message: -# -# ruby ./this_script -k | pbcopy - -GITHUB_URL = 'https://github.com/Automattic/simplenote-ios' - -RELEASE_NOTES_FILE = 'RELEASE-NOTES.txt' -NOTES = File.read(RELEASE_NOTES_FILE) -lines = NOTES.lines - -def replace_pr_number_with_markdown_link(string) - string.gsub(/\#\d*$/) do |pr_number| - "[#{pr_number}](#{GITHUB_URL}/pull/#{pr_number.gsub('#', '')})" - end -end - -# This is a very bare bone option parsing. It does the job for this simple use -# case, but it should not be built upon. -# -# If you plan to add more options, please consider using a gem to manage them -# properly. -mode = ARGV[0] == '-k' ? :keep_pr_links : :strip_pr_links - -# Format: -# -# 1.23 -# ----- -# -# 1.22 -# ----- -# - something #123 -# - something #234 -# -# 1.21 -# ----- -# - something something #345 - -# Skip the first three lines: the next version header -lines = lines[3...] - -# Isolate the current version by looking for the first new line -release_lines = [] - -# Find the start of the releases by looking for the line with the '-----' -# sequence. This accounts for the edge case in which more new lines make it -# into the release notes file than expected. -index = 0 -until lines[index].start_with? '-----' - index += 1 -end - -lines[(index+1)...].each do |line| - break if line.strip == '' - release_lines.push line -end - -formatted_lines = release_lines. - map { |l| l.gsub(/- /, '- ') } - -case mode -when :strip_pr_links - formatted_lines = formatted_lines. - map { |l| l.gsub(/ \#\d*$/, '') } -when :keep_pr_links - formatted_lines = formatted_lines. - # The PR "links" are not actually links, but PR "ids". On GitHub, they'll - # be automatically parsed into links to the corresponding PR, but outside - # GitHub, such as in our internal posts or on App Center, they won't. - # - # It's probably best to update the convention in writing the release notes - # but in the meantime let's compensate with more automation. - map { |l| replace_pr_number_with_markdown_link(l) } -end - -# It would be good to either add overriding of the file where the parsed -# release notes should go. I haven't done it yet because I'm using this script -# also to generate the text for the release notes on GitHub, where I want to -# keep the PR links. See info on the usage a the start of the file. -puts formatted_lines diff --git a/Scripts/localize.py b/Scripts/localize.py deleted file mode 100755 index 659a7d381..000000000 --- a/Scripts/localize.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Localize.py - Incremental localization on XCode projects -# João Moreno 2009 -# http://joaomoreno.com/ - -# Modified by Steve Streeting 2010 https://www.stevestreeting.com -# Changes -# - Use .strings files encoded as UTF-8 -# This is useful because Mercurial and Git treat UTF-16 as binary and can't -# diff/merge them. For use on iPhone you can run an iconv script during build to -# convert back to UTF-16 (Mac OS X will happily use UTF-8 .strings files). -# - Clean up .old and .new files once we're done - -from sys import argv -from codecs import open -from re import compile -from copy import copy -import os - -re_translation = compile(r'^"(.+)" = "(.+)";$') -re_comment_single = compile(r'^/\*.*\*/$') -re_comment_start = compile(r'^/\*.*$') -re_comment_end = compile(r'^.*\*/$') - -class LocalizedString(): - def __init__(self, comments, translation): - self.comments, self.translation = comments, translation - self.key, self.value = re_translation.match(self.translation).groups() - - def __unicode__(self): - return u'%s%s\n' % (u''.join(self.comments), self.translation) - -class LocalizedFile(): - def __init__(self, fname=None, auto_read=False): - self.fname = fname - self.strings = [] - self.strings_d = {} - - if auto_read: - self.read_from_file(fname) - - def read_from_file(self, fname=None): - fname = self.fname if fname == None else fname - try: - f = open(fname, encoding='utf_8', mode='r') - except: - print 'File %s does not exist.' % fname - exit(-1) - - line = f.readline() - while line: - comments = [line] - - if not re_comment_single.match(line): - while line and not re_comment_end.match(line): - line = f.readline() - comments.append(line) - - line = f.readline() - if line and re_translation.match(line): - translation = line - else: - raise Exception('invalid file') - - line = f.readline() - while line and line == u'\n': - line = f.readline() - - string = LocalizedString(comments, translation) - self.strings.append(string) - self.strings_d[string.key] = string - - f.close() - - def save_to_file(self, fname=None): - fname = self.fname if fname == None else fname - try: - f = open(fname, encoding='utf_8', mode='w') - except: - print 'Couldn\'t open file %s.' % fname - exit(-1) - - for string in self.strings: - f.write(string.__unicode__()) - - f.close() - - def merge_with(self, new): - merged = LocalizedFile() - - for string in new.strings: - if self.strings_d.has_key(string.key): - new_string = copy(self.strings_d[string.key]) - new_string.comments = string.comments - string = new_string - - merged.strings.append(string) - merged.strings_d[string.key] = string - - return merged - -def merge(merged_fname, old_fname, new_fname): - try: - old = LocalizedFile(old_fname, auto_read=True) - new = LocalizedFile(new_fname, auto_read=True) - merged = old.merge_with(new) - merged.save_to_file(merged_fname) - except: - print 'Error: input files have invalid format.' - - -STRINGS_FILE = 'Localizable.strings' - -def localize(path): - - language = os.path.join(path, "en.lproj") - original = merged = language + os.path.sep + STRINGS_FILE - - old = original + '.old' - new = original + '.new' - - # Using with `-print0` and `xargs -0` to account for spaces in the paths `find` might return - genstrings_cmd = 'find ./Simplenote ./SimplenoteShare ./SimplenoteWidgets \( -name "*.m" -o -name "*.swift" \) -print0 | xargs -0 genstrings -q -o "%s"' - - if os.path.isfile(original): - os.rename(original, old) - os.system(genstrings_cmd % language) - os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (original, new)) - merge(merged, old, new) - else: - os.system(genstrings_cmd % language) - os.rename(original, old) - os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (old, original)) - - if os.path.isfile(old): - os.remove(old) - if os.path.isfile(new): - os.remove(new) - -if __name__ == '__main__': - basedir = os.getcwd() - path = os.path.join(basedir, 'Simplenote') - localize(path) diff --git a/Scripts/swiftlint.sh b/Scripts/swiftlint.sh deleted file mode 100755 index 2e8098e02..000000000 --- a/Scripts/swiftlint.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -# -# Runs SwiftLint on the whole workspace. -# -# This does not run in Continuous Integration. -# - -# Abort if we are running in CI -# See https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables -if [ "$CI" = true ] ; then - echo "warning: skipping SwiftLint build phase because running on CI." - exit 0 -fi - -# Get the directory of this file. -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -# Temporarily move to the root directory so that SwiftLint can correctly -# find the paths returned from the `git` commands below. -pushd $DIR/../ > /dev/null - -# Paths relative to the root directory -SWIFTLINT="./vendor/swiftlint/bin/swiftlint" -CONFIG_FILE=".swiftlint.yml" -if ! which $SWIFTLINT >/dev/null; then - echo "error: SwiftLint is not installed. Install by running `rake dependencies`." - exit 1 -fi - -# Run SwiftLint on the modified files. -# -# The `|| true` at the end is to stop `grep` from returning a non-zero exit if there -# are no matches. Xcode's build will fail if we don't do this. -# -MODIFIED_FILES=`git diff --name-only --diff-filter=d HEAD | grep -G "\.swift$" || true` -echo $MODIFIED_FILES | xargs $SWIFTLINT --config $CONFIG_FILE --quiet -MODIFIED_FILES_LINT_RESULT=$? - -# Run SwiftLint on the added files -ADDED_FILES=`git ls-files --others --exclude-standard | grep -G "\.swift$" || true` -echo $ADDED_FILES | xargs $SWIFTLINT --config $CONFIG_FILE --quiet -ADDED_FILES_LINT_RESULT=$? - -# Restore the previous directory -popd > /dev/null - -# Exit with non-zero if SwiftLint found a serious violation in the linted files. -# -# This stops Xcode from complaining about "...did not return a nonzero exit code...". -# -if [ $MODIFIED_FILES_LINT_RESULT -ne 0 ] || [ $ADDED_FILES_LINT_RESULT -ne 0 ] ; then - exit 1 -fi diff --git a/Scripts/update-translations.rb b/Scripts/update-translations.rb index 8576e863f..2a3b11a30 100755 --- a/Scripts/update-translations.rb +++ b/Scripts/update-translations.rb @@ -1,8 +1,8 @@ #!/usr/bin/env ruby -# encoding: utf-8 +# frozen_string_literal: true # Supported languages: -# ar,cy,zh-Hans,zh-Hant,nl,fa,fr,de,el,he,id,ko,pt,ru,es,sv,tr,ja,it +# ar,cy,zh-Hans,zh-Hant,nl,fa,fr,de,el,he,id,ko,pt,ru,es,sv,tr,ja,it # * Arabic # * Welsh # * Chinese (China) [zh-Hans] @@ -25,11 +25,11 @@ require 'json' if Dir.pwd =~ /Scripts/ - puts "Must run script from root folder" + puts 'Must run script from root folder' exit end -ALL_LANGS={ +ALL_LANGS = { 'ar' => 'ar', # Arabic 'cy' => 'cy', # Welsh 'de' => 'de', # German @@ -48,35 +48,35 @@ 'sv' => 'sv', # Swedish 'tr' => 'tr', # Turkish 'zh-cn' => 'zh-Hans-CN', # Chinese (China) - 'zh-tw' => 'zh-Hant-TW', # Chinese (Taiwan) -} + 'zh-tw' => 'zh-Hant-TW' # Chinese (Taiwan) +}.freeze def copy_header(target_file, trans_strings) trans_strings.each_line do |line| - if (!line.start_with?("/*")) + unless line.start_with?('/*') target_file.write("\n") return - end + end target_file.write(line) end end def copy_comment(f, trans_strings, value) - prev_line="" + prev_line = '' trans_strings.each_line do |line| if line.include?(value) f.write(prev_line) - return + return end - prev_line=line + prev_line = line end end langs = {} -if ARGV.count > 0 - for key in ARGV - unless local = ALL_LANGS[key] +if ARGV.count.positive? + ARGV.each do |key| + unless (local = ALL_LANGS[key]) puts "Unknown language #{key}" exit 1 end @@ -86,11 +86,11 @@ def copy_comment(f, trans_strings, value) langs = ALL_LANGS end -langs.each do |code,local| +langs.each do |code, local| lang_dir = File.join('Simplenote', "#{local}.lproj") puts "Updating #{code}" system "mkdir -p #{lang_dir}" - + # Backup the current file system "if [ -e #{lang_dir}/Localizable.strings ]; then cp #{lang_dir}/Localizable.strings #{lang_dir}/Localizable.strings.bak; fi" diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 95d1ed3ac..fe39dec84 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -124,7 +124,6 @@ A61471B925D70C190065D849 /* PopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61471B725D70C190065D849 /* PopoverViewController.swift */; }; A628BEB625ECD97900121B64 /* SignupVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A628BEB425ECD97900121B64 /* SignupVerificationViewController.swift */; }; A628BEB725ECD97900121B64 /* SignupVerificationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A628BEB525ECD97900121B64 /* SignupVerificationViewController.xib */; }; - A628BEC325ED703A00121B64 /* RemoteConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A628BEC225ED703A00121B64 /* RemoteConstants.swift */; }; A6344AD9255952C70072FA07 /* NoteContentPreviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6344AD8255952C70072FA07 /* NoteContentPreviewTests.swift */; }; A645FA70254C5E69008A1519 /* NoteContentHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A645FA6F254C5E69008A1519 /* NoteContentHelperTests.swift */; }; A64DE6E9255D1C9F001D0526 /* ContentSlice.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64DE6E8255D1C9F001D0526 /* ContentSlice.swift */; }; @@ -172,13 +171,8 @@ A6C0DFE825C18E3300B9BE39 /* NoteEditorTagListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A6C0DFE625C18E3300B9BE39 /* NoteEditorTagListViewController.xib */; }; A6C2721825AF0C1E00593731 /* TagListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C2721725AF0C1E00593731 /* TagListViewCell.swift */; }; A6C3D8B7256687970042F584 /* SPObjectManager+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C3D8B6256687970042F584 /* SPObjectManager+Simplenote.swift */; }; - A6C7648025E9131C00A39067 /* SignupRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C7647F25E9131C00A39067 /* SignupRemote.swift */; }; - A6CC0B0725B8287F00F12A85 /* AccountRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CC0B0625B8287F00F12A85 /* AccountRemote.swift */; }; A6CC0B1025B83FE400F12A85 /* AccountVerificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CC0B0F25B83FE400F12A85 /* AccountVerificationControllerTests.swift */; }; A6CC0B1F25B840A400F12A85 /* MockAccountVerificationRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CC0B1E25B840A400F12A85 /* MockAccountVerificationRemote.swift */; }; - A6CC0B2D25B84FF200F12A85 /* AccountVerificationRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CC0B2C25B84FF200F12A85 /* AccountVerificationRemoteTests.swift */; }; - A6CC0B3625B8502800F12A85 /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CC0B3525B8502800F12A85 /* MockURLSession.swift */; }; - A6CC0B4425B8505700F12A85 /* MockURLSessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CC0B4325B8505700F12A85 /* MockURLSessionDataTask.swift */; }; A6CDD9C825F0163D00E0BC4D /* MagicLinkAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CDD9C725F0163D00E0BC4D /* MagicLinkAuthenticator.swift */; }; A6CDF900256B9CB900CF2F27 /* ViewSpinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CDF8FF256B9CB900CF2F27 /* ViewSpinner.swift */; }; A6D5AE6525483F8A00326C76 /* NSTextStorage+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AE6425483F8A00326C76 /* NSTextStorage+Simplenote.swift */; }; @@ -237,11 +231,12 @@ B513F2B62319A8F40021CFA4 /* SPNoteTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B513F2B52319A8F40021CFA4 /* SPNoteTableViewCell.xib */; }; B513F2B82319A9A40021CFA4 /* UITableViewCell+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B513F2B72319A9A40021CFA4 /* UITableViewCell+Simplenote.swift */; }; B513FB2422EF6A4B00B178AC /* SPUserInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = B513FB2322EF6A4B00B178AC /* SPUserInterface.swift */; }; - B514678D291AC7790062736E /* UIAlertController+Sustainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514678C291AC7790062736E /* UIAlertController+Sustainer.swift */; }; B5185CFB23427F2F0060145A /* SearchDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5185CFA23427F2F0060145A /* SearchDisplayController.swift */; }; B518899E1E0D5EF800E71B83 /* SPContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518899D1E0D5EF800E71B83 /* SPContactsManager.swift */; }; B51889A01E0D5F2200E71B83 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B518899F1E0D5F2200E71B83 /* Contacts.framework */; }; B51889A41E0DB01E00E71B83 /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B51889A31E0DB01E00E71B83 /* ContactsUI.framework */; }; + B51D445B2C52CB4000F296A7 /* SimplenoteEndpoints in Frameworks */ = {isa = PBXBuildFile; productRef = B51D445A2C52CB4000F296A7 /* SimplenoteEndpoints */; }; + B51D445E2C52CDA800F296A7 /* SPUser+UserProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D445D2C52CDA800F296A7 /* SPUser+UserProtocol.swift */; }; B51F6DD12460C3EE0074DDD9 /* AuthenticationValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F6DD02460C3EE0074DDD9 /* AuthenticationValidator.swift */; }; B51F6DD32460D12B0074DDD9 /* AuthenticationValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F6DD22460D12B0074DDD9 /* AuthenticationValidatorTests.swift */; }; B51F6DD52460D7540074DDD9 /* NSPredicate+Email.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F6DD42460D7540074DDD9 /* NSPredicate+Email.swift */; }; @@ -296,7 +291,10 @@ B5476BC123D8E5D0000E7723 /* String+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5476BC023D8E5D0000E7723 /* String+Simplenote.swift */; }; B549E249290B3BDB0072C3E8 /* Preferences+IAP.swift in Sources */ = {isa = PBXBuildFile; fileRef = B549E248290B3BDB0072C3E8 /* Preferences+IAP.swift */; }; B549E24B290B3DD70072C3E8 /* StoreConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B549E24A290B3DD70072C3E8 /* StoreConstants.swift */; }; + B54A11C22C135EA2002AC8AA /* UIButton+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A11C12C135EA2002AC8AA /* UIButton+Simplenote.swift */; }; + B54A11C42C136225002AC8AA /* MagicLinkRequestedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A11C32C136225002AC8AA /* MagicLinkRequestedView.swift */; }; B54B04D82407169500401FBB /* SPAppDelegate+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54B04D72407169500401FBB /* SPAppDelegate+Extensions.swift */; }; + B54C1B552C4EE350001E9E18 /* UIAlertController+Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54C1B542C4EE350001E9E18 /* UIAlertController+Auth.swift */; }; B54D9C572909BA2600D0E0EC /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C562909BA2600D0E0EC /* StoreManager.swift */; }; B54D9C592909CC4400D0E0EC /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C582909CC4400D0E0EC /* StoreProduct.swift */; }; B55051821A4328B9002A1093 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B55051811A4328B9002A1093 /* LocalAuthentication.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -325,6 +323,7 @@ B56AA00022AEDD06003F85CB /* PlainTextExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56A9FFF22AEDD06003F85CB /* PlainTextExtractor.swift */; }; B56AA00622B03CBB003F85CB /* Extractors.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56AA00522B03CBB003F85CB /* Extractors.swift */; }; B56B357F1AC3565600B9F365 /* UITextView+Simplenote.m in Sources */ = {isa = PBXBuildFile; fileRef = B56B357D1AC3565600B9F365 /* UITextView+Simplenote.m */; }; + B56BAF612C2DE317005065C9 /* MagicLinkInvalidView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56BAF602C2DE317005065C9 /* MagicLinkInvalidView.swift */; }; B56C8CA2234CEAF100FE55F4 /* TagListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B56C8CA1234CEAF100FE55F4 /* TagListViewController.xib */; }; B56E763422BD394C00C5AA47 /* UIImage+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E763322BD394C00C5AA47 /* UIImage+Simplenote.swift */; }; B56E763622BD565700C5AA47 /* UIView+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E763522BD565700C5AA47 /* UIView+Simplenote.swift */; }; @@ -380,6 +379,7 @@ B5C2EDD4255ACB4900C09B32 /* InterlinkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C2EDD1255ACB4800C09B32 /* InterlinkViewController.swift */; }; B5C2EDF0255AFB6C00C09B32 /* PassthruView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C2EDEF255AFB6C00C09B32 /* PassthruView.swift */; }; B5C2EDFE255B19D300C09B32 /* NSAttributedString+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C2EDFD255B19D300C09B32 /* NSAttributedString+Simplenote.swift */; }; + B5C99A512C45B94600728813 /* AuthenticationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C99A502C45B94600728813 /* AuthenticationMode.swift */; }; B5C9F71E193E75FE00FD2491 /* SPDebugViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C9F71C193E75FE00FD2491 /* SPDebugViewController.m */; }; B5CBEF4022D3AD92009DBE67 /* MigrationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CBEF3F22D3AD92009DBE67 /* MigrationsHandler.swift */; }; B5CBEF4222D3B419009DBE67 /* Bundle+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CBEF4122D3B419009DBE67 /* Bundle+Simplenote.swift */; }; @@ -411,8 +411,8 @@ B5EB1EF61C204EBD0080A1B3 /* SimplenoteShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = B5EB1EEC1C204EBD0080A1B3 /* SimplenoteShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; B5EB1EFE1C205A800080A1B3 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5EB1EFD1C205A800080A1B3 /* Icons.xcassets */; }; B5EB1EFF1C205A800080A1B3 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5EB1EFD1C205A800080A1B3 /* Icons.xcassets */; }; - B5F232B629084336006D8570 /* SustainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F232B529084336006D8570 /* SustainerView.swift */; }; - B5F232BA29084526006D8570 /* SustainerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5F232B929084526006D8570 /* SustainerView.xib */; }; + B5F232B629084336006D8570 /* BannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F232B529084336006D8570 /* BannerView.swift */; }; + B5F232BA29084526006D8570 /* BannerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5F232B929084526006D8570 /* BannerView.xib */; }; B5F3000223F4502C007A7C59 /* SPSortBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5F3000123F4502C007A7C59 /* SPSortBar.xib */; }; B5F3FFFF23F44679007A7C59 /* SPSortBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F3FFFE23F44679007A7C59 /* SPSortBar.swift */; }; BA02A59226C0AE92005FF36E /* NoteListTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA02A59126C0AE92005FF36E /* NoteListTable.swift */; }; @@ -421,8 +421,9 @@ BA0890A526BB9B680035CA48 /* NoteListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0890A426BB9B680035CA48 /* NoteListRow.swift */; }; BA0890A726BB9BE20035CA48 /* ListWidgetHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0890A626BB9BE20035CA48 /* ListWidgetHeaderView.swift */; }; BA0890A926BB9BF80035CA48 /* NewNoteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0890A826BB9BF80035CA48 /* NewNoteButton.swift */; }; + BA0AF10D2BE996600050EEBD /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59560DF251A46D500A06788 /* KeychainManager.swift */; }; + BA0AF10E2BE996630050EEBD /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD30471FC4CFA2008D0B78 /* KeychainPasswordItem.swift */; }; BA0ED16E26D708AC002533B6 /* Color+Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0ED16D26D708AC002533B6 /* Color+Widgets.swift */; }; - BA0F5E0426B62A8B0098C605 /* RemoteError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0F5E0326B62A8B0098C605 /* RemoteError.swift */; }; BA113E4D269E860500F3E3B4 /* markdown-light.css in Resources */ = {isa = PBXBuildFile; fileRef = BA113E4C269E860500F3E3B4 /* markdown-light.css */; }; BA122DBA265EF402003D3BC5 /* SimplenoteConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5095DC824632E3300812711 /* SimplenoteConstants.swift */; }; BA122DBB265EF40E003D3BC5 /* UIColor+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B575736B232D454300443C2E /* UIColor+Helpers.swift */; }; @@ -435,13 +436,25 @@ BA122DCA265F2E2D003D3BC5 /* UIColorSimplenoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA122DC9265F2E2D003D3BC5 /* UIColorSimplenoteTests.swift */; }; BA12B06F26B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA12B06C26B0D0150026F31D /* SPManagedObject+Widget.swift */; }; BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA12B06C26B0D0150026F31D /* SPManagedObject+Widget.swift */; }; - BA18532826488DBC00D9A347 /* SignupRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA18532726488DBC00D9A347 /* SignupRemoteTests.swift */; }; BA2015BB2B57384F005E59AA /* AutomatticTracks in Frameworks */ = {isa = PBXBuildFile; productRef = BA2015BA2B57384F005E59AA /* AutomatticTracks */; }; + BA289B5C2BE4371A000E6794 /* ListWidgetIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */; }; + BA289B5F2BE43728000E6794 /* NoteWidgetIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */; }; + BA289B602BE43816000E6794 /* NoteWidgetIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */; }; + BA289B612BE43825000E6794 /* ListWidgetIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */; }; + BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */; }; + BA289B722BE45A39000E6794 /* IntentsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B702BE45A39000E6794 /* IntentsConstants.swift */; }; + BA289B732BE45A3C000E6794 /* IntentsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B702BE45A39000E6794 /* IntentsConstants.swift */; }; + BA289B762BE45BBB000E6794 /* OpenNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */; }; + BA289B782BE45BFB000E6794 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B550F93022BA65CD00091939 /* ActivityType.swift */; }; BA2D82C6261522F100A1695B /* PublishNoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2D82C5261522F100A1695B /* PublishNoticePresenter.swift */; }; - BA3101A926C8C76A00C95F93 /* ListWidgetIntent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA7071E426BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; BA32A90F26B7469F00727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; BA32A91926B746A200727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; BA32A91A26B746A300727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; + BA34B04C2BEAE9E800580E15 /* Uploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFFFE622AA9DB100B968CD /* Uploader.swift */; }; + BA34B04D2BEAE9FE00580E15 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = B59314D61A486B3800B651ED /* SPConstants.m */; }; + BA34B04E2BEAEB4100580E15 /* NSURLSessionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AD7AB01D1C82D3009725FB /* NSURLSessionConfiguration+Extensions.swift */; }; + BA34B0572BEC216B00580E15 /* IntentNote+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA34B0562BEC216B00580E15 /* IntentNote+Helpers.swift */; }; + BA3580902BE95BE100CE1590 /* AppendNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */; }; BA3856CD2681715700F388CC /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3856CC2681715700F388CC /* CoreDataManager.swift */; }; BA3CE86226D5DF3800EFF9EB /* WidgetTag+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3CE86126D5DF3800EFF9EB /* WidgetTag+Helpers.swift */; }; BA3CE8D926D5E21C00EFF9EB /* WidgetTag+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3CE86126D5DF3800EFF9EB /* WidgetTag+Helpers.swift */; }; @@ -450,23 +463,27 @@ BA4499B325ED8AB0000C563E /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4499B225ED8AB0000C563E /* NoticeView.swift */; }; BA4499BC25ED95D0000C563E /* Notice.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4499BB25ED95D0000C563E /* Notice.swift */; }; BA4499C525ED95E5000C563E /* NoticeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4499C425ED95E5000C563E /* NoticeAction.swift */; }; + BA4A01902C1CCEC100EEE567 /* RecoveryArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4A018F2C1CCEC100EEE567 /* RecoveryArchiver.swift */; }; BA4C6CFC264C744300B723A7 /* SeparatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6CFB264C744300B723A7 /* SeparatorsView.swift */; }; BA5249FD26DF0BC600DAC945 /* WidgetWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5249FC26DF0BC600DAC945 /* WidgetWarningView.swift */; }; BA524A0226DF1AE800DAC945 /* WidgetsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA524A0126DF1AE800DAC945 /* WidgetsState.swift */; }; BA55124E2600210B00D8F882 /* TimerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA55124D2600210B00D8F882 /* TimerFactory.swift */; }; BA55B05A25F067DF0042582B /* NoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA55B05925F067DF0042582B /* NoticePresenter.swift */; }; BA55B06325F068650042582B /* NoticeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA55B06225F068650042582B /* NoticeController.swift */; }; - BA5768E3269A803F008B510E /* Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5768E2269A803F008B510E /* Remote.swift */; }; BA5768EC269BE4D0008B510E /* AccountDeletionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5768EB269BE4D0008B510E /* AccountDeletionController.swift */; }; BA5C1C0725BF9D6C006E3820 /* SPDragBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5C1C0625BF9D6C006E3820 /* SPDragBar.swift */; }; - BA5E5B7D264A148C00D0EE19 /* URLRequest+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5E5B7C264A148C00D0EE19 /* URLRequest+Simplenote.swift */; }; BA608EF526BB6E0200A9D94E /* ListWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA608EF426BB6E0200A9D94E /* ListWidget.swift */; }; BA608EF726BB6E7400A9D94E /* ListWidgetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA608EF626BB6E7400A9D94E /* ListWidgetProvider.swift */; }; BA608EF926BB6F4C00A9D94E /* ListWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA608EF826BB6F4C00A9D94E /* ListWidgetView.swift */; }; + BA69A5192BE0127C0096E50F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = BA69A5182BE0127C0096E50F /* PrivacyInfo.xcprivacy */; }; + BA69A51B2BE015640096E50F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = BA69A51A2BE015640096E50F /* PrivacyInfo.xcprivacy */; }; + BA69A51D2BE015BF0096E50F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = BA69A51C2BE015BF0096E50F /* PrivacyInfo.xcprivacy */; }; + BA6D7B8B2BE588F0006AE368 /* IntentsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA6D7B8A2BE588F0006AE368 /* IntentsError.swift */; }; BA6DA19126DB5F1B000464C8 /* URLComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA6DA19026DB5F1B000464C8 /* URLComponents.swift */; }; BA6DA19226DB5F1B000464C8 /* URLComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA6DA19026DB5F1B000464C8 /* URLComponents.swift */; }; BA7071E526BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA7071E426BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition */; }; BA7071E626BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA7071E426BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition */; }; + BA7240222C1BA1210088EC11 /* RecoveryUnarchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7240212C1BA1210088EC11 /* RecoveryUnarchiver.swift */; }; BA75D8A326C084E900883FFA /* Text+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA75D87D26C0843600883FFA /* Text+Simplenote.swift */; }; BA75D8AE26C0862E00883FFA /* View+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA75D8AD26C0862E00883FFA /* View+Simplenote.swift */; }; BA75D8BA26C0865C00883FFA /* Filling.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA75D8B926C0865C00883FFA /* Filling.swift */; }; @@ -476,8 +493,8 @@ BA86616326B35CF000466746 /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F920265A2294661C0061B1DE /* BuildConfiguration.swift */; }; BA86620E26B3A73D00466746 /* UserDefaults+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DF734322A56E2800602CE7 /* UserDefaults+Simplenote.swift */; }; BA86621826B3A73D00466746 /* UserDefaults+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DF734322A56E2800602CE7 /* UserDefaults+Simplenote.swift */; }; - BA86622326B3AE4A00466746 /* WidgetResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA86622226B3AE4A00466746 /* WidgetResultsController.swift */; }; - BA86622426B3AE4A00466746 /* WidgetResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA86622226B3AE4A00466746 /* WidgetResultsController.swift */; }; + BA86622326B3AE4A00466746 /* ExtensionResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA86622226B3AE4A00466746 /* ExtensionResultsController.swift */; }; + BA86622426B3AE4A00466746 /* ExtensionResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA86622226B3AE4A00466746 /* ExtensionResultsController.swift */; }; BA86624926B3B05100466746 /* SimplenoteConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5095DC824632E3300812711 /* SimplenoteConstants.swift */; }; BA86625326B3B14800466746 /* SortMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DF734522A5713600602CE7 /* SortMode.swift */; }; BA86625D26B3B14900466746 /* SortMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DF734522A5713600602CE7 /* SortMode.swift */; }; @@ -491,6 +508,13 @@ BA8FC2A5267AC7470082962E /* SharedStorageMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FC2A4267AC7470082962E /* SharedStorageMigrator.swift */; }; BA9B19F926A8EF3200692366 /* SpinnerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9B19F826A8EF3200692366 /* SpinnerViewController.swift */; }; BA9B59022685549F00DAD1ED /* StorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9B59012685549F00DAD1ED /* StorageSettings.swift */; }; + BA9C7EC92BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7EC82BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift */; }; + BA9C7ECB2BED7F7B007A8460 /* FindNoteWithTagIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7ECA2BED7F7B007A8460 /* FindNoteWithTagIntentHandler.swift */; }; + BA9C7ECD2BED813B007A8460 /* IntentTag+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7ECC2BED813B007A8460 /* IntentTag+Helpers.swift */; }; + BA9C7ED02BEE9BA7007A8460 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7ECF2BEE9BA7007A8460 /* ShortcutIntents.intentdefinition */; }; + BA9C7ED12BEE9BA7007A8460 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7ECF2BEE9BA7007A8460 /* ShortcutIntents.intentdefinition */; }; + BA9C7EFB2BF2CC3E007A8460 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */; }; + BA9C7EFC2BF2CCAE007A8460 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */; }; BAA4856925D5E40900F3BDB9 /* SearchQuery+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA4856825D5E40900F3BDB9 /* SearchQuery+Simplenote.swift */; }; BAA59E79269F9FE30068BD3D /* Date+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA59E78269F9FE30068BD3D /* Date+Simplenote.swift */; }; BAA63C3325EEDA83001589D7 /* NoteLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA63C3225EEDA83001589D7 /* NoteLinkTests.swift */; }; @@ -505,7 +529,6 @@ BAB5762426703C8100B0C56F /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BAB5762326703C8100B0C56F /* Intents.framework */; }; BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB5762626703C8200B0C56F /* IntentHandler.swift */; }; BAB5762B26703C8200B0C56F /* SimplenoteIntents.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = BAB5762226703C8100B0C56F /* SimplenoteIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - BAB5764626703D0000B0C56F /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB5762626703C8200B0C56F /* IntentHandler.swift */; }; BAB5765B26703D0600B0C56F /* NoteWidgetIntent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BAB5760A26703C0C00B0C56F /* NoteWidgetIntent.intentdefinition */; }; BAB5765C26703D0600B0C56F /* NoteWidgetIntent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BAB5760A26703C0C00B0C56F /* NoteWidgetIntent.intentdefinition */; }; BAB5768726703EA000B0C56F /* NoteWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB5767B26703E8100B0C56F /* NoteWidgetView.swift */; }; @@ -516,9 +539,11 @@ BAB6C04426BA49F3007495C4 /* ContentSlice.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64DE6E8255D1C9F001D0526 /* ContentSlice.swift */; }; BAB6C04526BA4A04007495C4 /* String+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5476BC023D8E5D0000E7723 /* String+Simplenote.swift */; }; BAB6C04726BA4CAF007495C4 /* WidgetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB6C04626BA4CAF007495C4 /* WidgetController.swift */; }; + BAB898D32BEC404200E238B8 /* CreateNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */; }; BABFFF2226CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; }; BABFFF2326CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; }; BABFFF2426CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; }; + BAD0F1ED2BED49C200E73E45 /* FindNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */; }; BAE08626261282D1009D40CD /* Note+Publish.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE08625261282D1009D40CD /* Note+Publish.swift */; }; BAE63CAF26E05312002BF81A /* NSString+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59188C124005B8A0069EF96 /* NSString+Simplenote.swift */; }; BAE63CB026E05313002BF81A /* NSString+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59188C124005B8A0069EF96 /* NSString+Simplenote.swift */; }; @@ -553,9 +578,8 @@ BAFA93FA265DE8920009DCFB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E29ADD4D17848E8500E55842 /* Images.xcassets */; }; BAFDBBD926B88BD200119615 /* Date+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFDBBD726B88BD200119615 /* Date+Simplenote.swift */; }; BAFDBBDA26B88BD200119615 /* Date+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFDBBD726B88BD200119615 /* Date+Simplenote.swift */; }; - BAFDBBE426B88F1300119615 /* NoteWidgetIntent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BAB5760A26703C0C00B0C56F /* NoteWidgetIntent.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; - BAFFCEA626DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFCEA526DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift */; }; - BAFFCEA726DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFCEA526DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift */; }; + BAFFCEA626DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFCEA526DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift */; }; + BAFFCEA726DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFCEA526DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift */; }; D435D38FCA5863E8447D5C2C /* Pods_Automattic_SimplenoteShare.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 183BAB896957F07FDCAB6DF5 /* Pods_Automattic_SimplenoteShare.framework */; }; D82BFE4B2624A8AB003DFA32 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82BFE4A2624A8AB003DFA32 /* Settings.swift */; }; D82BFE542624A8C5003DFA32 /* SimplenoteUISmokeTestsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82BFE532624A8C5003DFA32 /* SimplenoteUISmokeTestsSettings.swift */; }; @@ -725,11 +749,11 @@ 375D24AF21E01131007AB25A /* html_smartypants.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = html_smartypants.c; sourceTree = ""; }; 375D24B021E01131007AB25A /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; 3762530520F54FFB00C1F239 /* SPAboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SPAboutViewController.swift; path = Classes/SPAboutViewController.swift; sourceTree = ""; }; - 37CD04B91A60538B00C4A8E0 /* ko */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; - 37CD04BA1A6053CC00C4A8E0 /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - 37CD04BB1A6053E900C4A8E0 /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; - 37CD04BC1A60540500C4A8E0 /* id */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; - 37CD04BD1A6054E900C4A8E0 /* ru */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 37CD04B91A60538B00C4A8E0 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + 37CD04BA1A6053CC00C4A8E0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 37CD04BB1A6053E900C4A8E0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 37CD04BC1A60540500C4A8E0 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 37CD04BD1A6054E900C4A8E0 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 37E10E7620B3368700864E43 /* WPAuthHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WPAuthHandler.m; path = Classes/WPAuthHandler.m; sourceTree = ""; }; 37E10E7920B3477800864E43 /* WPAuthHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WPAuthHandler.h; path = Classes/WPAuthHandler.h; sourceTree = ""; }; 37E55A6621BF2B1800F14241 /* SPTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPTextAttachment.swift; sourceTree = ""; }; @@ -744,6 +768,7 @@ 3FA60139242C5AAD0068FC52 /* SimplenoteScreenshots.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimplenoteScreenshots.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3FA6013B242C5AAE0068FC52 /* SimplenoteScreenshots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplenoteScreenshots.swift; sourceTree = ""; }; 3FA6013D242C5AAE0068FC52 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3FD478242C572EE10071B8B9 /* Project.Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Project.Release.xcconfig; sourceTree = ""; }; 3FF314D226FC47720012E68E /* XCUIApplication+Assertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+Assertions.swift"; sourceTree = ""; }; 3FFBDBEB26FBF132008AD052 /* UITestsFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UITestsFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3FFBDBED26FBF132008AD052 /* UITestsFoundation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UITestsFoundation.h; sourceTree = ""; }; @@ -796,7 +821,6 @@ A61471B725D70C190065D849 /* PopoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PopoverViewController.swift; path = Classes/PopoverViewController.swift; sourceTree = ""; }; A628BEB425ECD97900121B64 /* SignupVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SignupVerificationViewController.swift; path = Classes/SignupVerificationViewController.swift; sourceTree = ""; }; A628BEB525ECD97900121B64 /* SignupVerificationViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = SignupVerificationViewController.xib; path = Classes/SignupVerificationViewController.xib; sourceTree = ""; }; - A628BEC225ED703A00121B64 /* RemoteConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RemoteConstants.swift; path = Classes/RemoteConstants.swift; sourceTree = ""; }; A6344AD8255952C70072FA07 /* NoteContentPreviewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContentPreviewTests.swift; sourceTree = ""; }; A645FA6F254C5E69008A1519 /* NoteContentHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContentHelperTests.swift; sourceTree = ""; }; A64DE6E8255D1C9F001D0526 /* ContentSlice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContentSlice.swift; path = Classes/ContentSlice.swift; sourceTree = ""; }; @@ -844,13 +868,8 @@ A6C0DFE625C18E3300B9BE39 /* NoteEditorTagListViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NoteEditorTagListViewController.xib; sourceTree = ""; }; A6C2721725AF0C1E00593731 /* TagListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagListViewCell.swift; sourceTree = ""; }; A6C3D8B6256687970042F584 /* SPObjectManager+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SPObjectManager+Simplenote.swift"; path = "Classes/SPObjectManager+Simplenote.swift"; sourceTree = ""; }; - A6C7647F25E9131C00A39067 /* SignupRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SignupRemote.swift; path = Classes/SignupRemote.swift; sourceTree = ""; }; - A6CC0B0625B8287F00F12A85 /* AccountRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRemote.swift; sourceTree = ""; }; A6CC0B0F25B83FE400F12A85 /* AccountVerificationControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationControllerTests.swift; sourceTree = ""; }; A6CC0B1E25B840A400F12A85 /* MockAccountVerificationRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAccountVerificationRemote.swift; sourceTree = ""; }; - A6CC0B2C25B84FF200F12A85 /* AccountVerificationRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationRemoteTests.swift; sourceTree = ""; }; - A6CC0B3525B8502800F12A85 /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = ""; }; - A6CC0B4325B8505700F12A85 /* MockURLSessionDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSessionDataTask.swift; sourceTree = ""; }; A6CDD9C725F0163D00E0BC4D /* MagicLinkAuthenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MagicLinkAuthenticator.swift; path = Classes/MagicLinkAuthenticator.swift; sourceTree = ""; }; A6CDF8FF256B9CB900CF2F27 /* ViewSpinner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewSpinner.swift; path = Classes/ViewSpinner.swift; sourceTree = ""; }; A6D5AE6425483F8A00326C76 /* NSTextStorage+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextStorage+Simplenote.swift"; sourceTree = ""; }; @@ -907,11 +926,11 @@ B513F2B52319A8F40021CFA4 /* SPNoteTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = SPNoteTableViewCell.xib; path = Classes/SPNoteTableViewCell.xib; sourceTree = ""; }; B513F2B72319A9A40021CFA4 /* UITableViewCell+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UITableViewCell+Simplenote.swift"; path = "Classes/UITableViewCell+Simplenote.swift"; sourceTree = ""; }; B513FB2322EF6A4B00B178AC /* SPUserInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SPUserInterface.swift; path = Classes/SPUserInterface.swift; sourceTree = ""; }; - B514678C291AC7790062736E /* UIAlertController+Sustainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Sustainer.swift"; sourceTree = ""; }; B5185CFA23427F2F0060145A /* SearchDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchDisplayController.swift; path = Classes/SearchDisplayController.swift; sourceTree = ""; }; B518899D1E0D5EF800E71B83 /* SPContactsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SPContactsManager.swift; path = Classes/SPContactsManager.swift; sourceTree = ""; }; B518899F1E0D5F2200E71B83 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; B51889A31E0DB01E00E71B83 /* ContactsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ContactsUI.framework; path = System/Library/Frameworks/ContactsUI.framework; sourceTree = SDKROOT; }; + B51D445D2C52CDA800F296A7 /* SPUser+UserProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPUser+UserProtocol.swift"; sourceTree = ""; }; B51F6DD02460C3EE0074DDD9 /* AuthenticationValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationValidator.swift; sourceTree = ""; }; B51F6DD22460D12B0074DDD9 /* AuthenticationValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationValidatorTests.swift; sourceTree = ""; }; B51F6DD42460D7540074DDD9 /* NSPredicate+Email.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPredicate+Email.swift"; sourceTree = ""; }; @@ -964,7 +983,10 @@ B5476BC023D8E5D0000E7723 /* String+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "String+Simplenote.swift"; path = "Classes/String+Simplenote.swift"; sourceTree = ""; }; B549E248290B3BDB0072C3E8 /* Preferences+IAP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+IAP.swift"; sourceTree = ""; }; B549E24A290B3DD70072C3E8 /* StoreConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreConstants.swift; sourceTree = ""; }; + B54A11C12C135EA2002AC8AA /* UIButton+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Simplenote.swift"; sourceTree = ""; }; + B54A11C32C136225002AC8AA /* MagicLinkRequestedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkRequestedView.swift; sourceTree = ""; }; B54B04D72407169500401FBB /* SPAppDelegate+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPAppDelegate+Extensions.swift"; sourceTree = ""; }; + B54C1B542C4EE350001E9E18 /* UIAlertController+Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Auth.swift"; sourceTree = ""; }; B54D9C542909B21700D0E0EC /* Simplenote 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Simplenote 6.xcdatamodel"; sourceTree = ""; }; B54D9C562909BA2600D0E0EC /* StoreManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreManager.swift; sourceTree = ""; }; B54D9C582909CC4400D0E0EC /* StoreProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProduct.swift; sourceTree = ""; }; @@ -975,16 +997,16 @@ B550F93722BA814D00091939 /* NSUserActivity+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSUserActivity+Shortcuts.swift"; path = "Classes/NSUserActivity+Shortcuts.swift"; sourceTree = ""; }; B5526335238881EE009AB3B2 /* Simplenote 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Simplenote 5.xcdatamodel"; sourceTree = ""; }; B552AB8624B8E75E00E5E115 /* SPNoteEditorViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SPNoteEditorViewController+Extensions.swift"; path = "Classes/SPNoteEditorViewController+Extensions.swift"; sourceTree = ""; }; - B5536B861B8E46AC00FF190A /* cy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Localizable.strings; sourceTree = ""; }; - B5536B871B8E47B400FF190A /* de */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - B5536B881B8E490200FF190A /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; - B5536B891B8E498900FF190A /* tr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; - B5536B8A1B8E49C600FF190A /* ar */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; - B5536B8B1B8E4A1900FF190A /* zh-Hant-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = ""; }; - B5536B8C1B8E4A5200FF190A /* el */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; - B5536B8D1B8E4A7D00FF190A /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; - B5536B8E1B8E4AAC00FF190A /* nl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - B5536B8F1B8E4B8A00FF190A /* zh-Hans-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-Hans-CN"; path = "zh-Hans-CN.lproj/Localizable.strings"; sourceTree = ""; }; + B5536B861B8E46AC00FF190A /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Localizable.strings; sourceTree = ""; }; + B5536B871B8E47B400FF190A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + B5536B881B8E490200FF190A /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + B5536B891B8E498900FF190A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + B5536B8A1B8E49C600FF190A /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; + B5536B8B1B8E4A1900FF190A /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = ""; }; + B5536B8C1B8E4A5200FF190A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; + B5536B8D1B8E4A7D00FF190A /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; + B5536B8E1B8E4AAC00FF190A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + B5536B8F1B8E4B8A00FF190A /* zh-Hans-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans-CN"; path = "zh-Hans-CN.lproj/Localizable.strings"; sourceTree = ""; }; B55AC57922D27B9100D8CEB2 /* OptionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionsTests.swift; sourceTree = ""; }; B55E428B22A1A4550018C0CE /* SPSortOrderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SPSortOrderViewController.swift; path = Classes/SPSortOrderViewController.swift; sourceTree = ""; }; B5651239243BC063001EFE77 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; @@ -1006,6 +1028,7 @@ B56AA00522B03CBB003F85CB /* Extractors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extractors.swift; sourceTree = ""; }; B56B357C1AC3565600B9F365 /* UITextView+Simplenote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UITextView+Simplenote.h"; path = "Classes/UITextView+Simplenote.h"; sourceTree = ""; }; B56B357D1AC3565600B9F365 /* UITextView+Simplenote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UITextView+Simplenote.m"; path = "Classes/UITextView+Simplenote.m"; sourceTree = ""; }; + B56BAF602C2DE317005065C9 /* MagicLinkInvalidView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkInvalidView.swift; sourceTree = ""; }; B56C8CA1234CEAF100FE55F4 /* TagListViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = TagListViewController.xib; path = Classes/TagListViewController.xib; sourceTree = ""; }; B56E763322BD394C00C5AA47 /* UIImage+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIImage+Simplenote.swift"; path = "Classes/UIImage+Simplenote.swift"; sourceTree = ""; }; B56E763522BD565700C5AA47 /* UIView+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIView+Simplenote.swift"; path = "Classes/UIView+Simplenote.swift"; sourceTree = ""; }; @@ -1069,6 +1092,7 @@ B5C2EDD1255ACB4800C09B32 /* InterlinkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InterlinkViewController.swift; path = Classes/InterlinkViewController.swift; sourceTree = ""; }; B5C2EDEF255AFB6C00C09B32 /* PassthruView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PassthruView.swift; path = Classes/PassthruView.swift; sourceTree = ""; }; B5C2EDFD255B19D300C09B32 /* NSAttributedString+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSAttributedString+Simplenote.swift"; path = "Classes/NSAttributedString+Simplenote.swift"; sourceTree = ""; }; + B5C99A502C45B94600728813 /* AuthenticationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationMode.swift; sourceTree = ""; }; B5C9F71B193E75FE00FD2491 /* SPDebugViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPDebugViewController.h; path = Classes/SPDebugViewController.h; sourceTree = ""; }; B5C9F71C193E75FE00FD2491 /* SPDebugViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPDebugViewController.m; path = Classes/SPDebugViewController.m; sourceTree = ""; }; B5CA3E0D22B1503700AC0D47 /* RELEASE-NOTES.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "RELEASE-NOTES.txt"; sourceTree = ""; }; @@ -1108,8 +1132,8 @@ B5EB1EF11C204EBD0080A1B3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; B5EB1EF31C204EBD0080A1B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B5EB1EFD1C205A800080A1B3 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Icons.xcassets; path = ../Icons.xcassets; sourceTree = ""; }; - B5F232B529084336006D8570 /* SustainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SustainerView.swift; sourceTree = ""; }; - B5F232B929084526006D8570 /* SustainerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SustainerView.xib; sourceTree = ""; }; + B5F232B529084336006D8570 /* BannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerView.swift; sourceTree = ""; }; + B5F232B929084526006D8570 /* BannerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BannerView.xib; sourceTree = ""; }; B5F3000123F4502C007A7C59 /* SPSortBar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = SPSortBar.xib; path = Classes/SPSortBar.xib; sourceTree = ""; }; B5F3FFFE23F44679007A7C59 /* SPSortBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SPSortBar.swift; path = Classes/SPSortBar.swift; sourceTree = ""; }; BA02A59126C0AE92005FF36E /* NoteListTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteListTable.swift; sourceTree = ""; }; @@ -1118,14 +1142,21 @@ BA0890A626BB9BE20035CA48 /* ListWidgetHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWidgetHeaderView.swift; sourceTree = ""; }; BA0890A826BB9BF80035CA48 /* NewNoteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteButton.swift; sourceTree = ""; }; BA0ED16D26D708AC002533B6 /* Color+Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Widgets.swift"; sourceTree = ""; }; - BA0F5E0326B62A8B0098C605 /* RemoteError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteError.swift; sourceTree = ""; }; BA113E4C269E860500F3E3B4 /* markdown-light.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = "markdown-light.css"; sourceTree = ""; }; BA122DC5265EF90C003D3BC5 /* NewNoteWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteWidgetView.swift; sourceTree = ""; }; BA122DC9265F2E2D003D3BC5 /* UIColorSimplenoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorSimplenoteTests.swift; sourceTree = ""; }; BA12B06C26B0D0150026F31D /* SPManagedObject+Widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SPManagedObject+Widget.swift"; sourceTree = ""; }; - BA18532726488DBC00D9A347 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.swift; sourceTree = ""; }; + BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Simplenote 7.xcdatamodel"; sourceTree = ""; }; + BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWidgetIntentHandler.swift; sourceTree = ""; }; + BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteWidgetIntentHandler.swift; sourceTree = ""; }; + BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenNewNoteIntentHandler.swift; sourceTree = ""; }; + BA289B702BE45A39000E6794 /* IntentsConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentsConstants.swift; sourceTree = ""; }; + BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenNoteIntentHandler.swift; sourceTree = ""; }; BA2D82C5261522F100A1695B /* PublishNoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishNoticePresenter.swift; sourceTree = ""; }; BA32A90E26B7469F00727247 /* WidgetError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetError.swift; sourceTree = ""; }; + BA34B04F2BEAEF4800580E15 /* SimplenoteIntentsRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SimplenoteIntentsRelease.entitlements; sourceTree = ""; }; + BA34B0562BEC216B00580E15 /* IntentNote+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntentNote+Helpers.swift"; sourceTree = ""; }; + BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppendNoteIntentHandler.swift; sourceTree = ""; }; BA3856CC2681715700F388CC /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; BA3CE86126D5DF3800EFF9EB /* WidgetTag+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WidgetTag+Helpers.swift"; sourceTree = ""; }; BA3FB8CE25FEA0C500EA9A1B /* NoticeControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeControllerTests.swift; sourceTree = ""; }; @@ -1133,21 +1164,25 @@ BA4499B225ED8AB0000C563E /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = ""; }; BA4499BB25ED95D0000C563E /* Notice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notice.swift; sourceTree = ""; }; BA4499C425ED95E5000C563E /* NoticeAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeAction.swift; sourceTree = ""; }; + BA4A018F2C1CCEC100EEE567 /* RecoveryArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryArchiver.swift; sourceTree = ""; }; BA4C6CFB264C744300B723A7 /* SeparatorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorsView.swift; sourceTree = ""; }; BA5249FC26DF0BC600DAC945 /* WidgetWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetWarningView.swift; sourceTree = ""; }; BA524A0126DF1AE800DAC945 /* WidgetsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsState.swift; sourceTree = ""; }; BA55124D2600210B00D8F882 /* TimerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerFactory.swift; sourceTree = ""; }; BA55B05925F067DF0042582B /* NoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticePresenter.swift; sourceTree = ""; }; BA55B06225F068650042582B /* NoticeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeController.swift; sourceTree = ""; }; - BA5768E2269A803F008B510E /* Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Remote.swift; sourceTree = ""; }; BA5768EB269BE4D0008B510E /* AccountDeletionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionController.swift; sourceTree = ""; }; BA5C1C0625BF9D6C006E3820 /* SPDragBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPDragBar.swift; sourceTree = ""; }; - BA5E5B7C264A148C00D0EE19 /* URLRequest+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+Simplenote.swift"; sourceTree = ""; }; BA608EF426BB6E0200A9D94E /* ListWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWidget.swift; sourceTree = ""; }; BA608EF626BB6E7400A9D94E /* ListWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWidgetProvider.swift; sourceTree = ""; }; BA608EF826BB6F4C00A9D94E /* ListWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWidgetView.swift; sourceTree = ""; }; + BA69A5182BE0127C0096E50F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + BA69A51A2BE015640096E50F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + BA69A51C2BE015BF0096E50F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + BA6D7B8A2BE588F0006AE368 /* IntentsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentsError.swift; sourceTree = ""; }; BA6DA19026DB5F1B000464C8 /* URLComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponents.swift; sourceTree = ""; }; BA7071E426BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ListWidgetIntent.intentdefinition; sourceTree = ""; }; + BA7240212C1BA1210088EC11 /* RecoveryUnarchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryUnarchiver.swift; sourceTree = ""; }; BA75D87D26C0843600883FFA /* Text+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+Simplenote.swift"; sourceTree = ""; }; BA75D8AD26C0862E00883FFA /* View+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Simplenote.swift"; sourceTree = ""; }; BA75D8B926C0865C00883FFA /* Filling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filling.swift; sourceTree = ""; }; @@ -1157,11 +1192,36 @@ BA8661D626B3A08700466746 /* SimplenoteIntents-Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimplenoteIntents-Debug.entitlements"; sourceTree = ""; }; BA8661D726B3A0A000466746 /* SimplenoteIntents-Release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimplenoteIntents-Release.entitlements"; sourceTree = ""; }; BA8661D826B3A0B000466746 /* SimplenoteIntents-DistributionInternal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimplenoteIntents-DistributionInternal.entitlements"; sourceTree = ""; }; - BA86622226B3AE4A00466746 /* WidgetResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetResultsController.swift; sourceTree = ""; }; + BA86622226B3AE4A00466746 /* ExtensionResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionResultsController.swift; sourceTree = ""; }; BA88765026B79324001C9C9E /* DemoContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoContent.swift; sourceTree = ""; }; BA8FC2A4267AC7470082962E /* SharedStorageMigrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedStorageMigrator.swift; sourceTree = ""; }; BA9B19F826A8EF3200692366 /* SpinnerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerViewController.swift; sourceTree = ""; }; BA9B59012685549F00DAD1ED /* StorageSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSettings.swift; sourceTree = ""; }; + BA9C7EC82BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyNoteContentIntentHandler.swift; sourceTree = ""; }; + BA9C7ECA2BED7F7B007A8460 /* FindNoteWithTagIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNoteWithTagIntentHandler.swift; sourceTree = ""; }; + BA9C7ECC2BED813B007A8460 /* IntentTag+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntentTag+Helpers.swift"; sourceTree = ""; }; + BA9C7ECE2BEE9BA7007A8460 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/ShortcutIntents.intentdefinition; sourceTree = ""; }; + BA9C7ED32BEE9BF3007A8460 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7ED52BEE9BF8007A8460 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7ED72BEE9BFC007A8460 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7ED92BEE9BFE007A8460 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EDB2BEE9C00007A8460 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EDD2BEE9C04007A8460 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EDF2BEE9C07007A8460 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EE12BEE9C0C007A8460 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EE32BEE9C0F007A8460 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EE52BEE9C11007A8460 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EE72BEE9C13007A8460 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EE92BEE9C19007A8460 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/ShortcutIntents.strings"; sourceTree = ""; }; + BA9C7EEB2BEE9C22007A8460 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EED2BEE9C24007A8460 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EEF2BEE9C38007A8460 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EF12BEE9C3A007A8460 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EF32BEE9C3C007A8460 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EF52BEE9C3D007A8460 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/ShortcutIntents.strings; sourceTree = ""; }; + BA9C7EF72BEE9C3E007A8460 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/ShortcutIntents.strings"; sourceTree = ""; }; + BA9C7EF92BEE9C3F007A8460 /* zh-Hans-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans-CN"; path = "zh-Hans-CN.lproj/ShortcutIntents.strings"; sourceTree = ""; }; + BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; BAA4856825D5E40900F3BDB9 /* SearchQuery+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchQuery+Simplenote.swift"; sourceTree = ""; }; BAA59E78269F9FE30068BD3D /* Date+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Simplenote.swift"; sourceTree = ""; }; BAA63C3225EEDA83001589D7 /* NoteLinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteLinkTests.swift; sourceTree = ""; }; @@ -1181,7 +1241,9 @@ BAB5769226703F0E00B0C56F /* NoteWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteWidgetProvider.swift; sourceTree = ""; }; BAB576BD2670512C00B0C56F /* NoteWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteWidget.swift; sourceTree = ""; }; BAB6C04626BA4CAF007495C4 /* WidgetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetController.swift; sourceTree = ""; }; + BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewNoteIntentHandler.swift; sourceTree = ""; }; BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDefaults.swift; sourceTree = ""; }; + BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNoteIntentHandler.swift; sourceTree = ""; }; BAE08625261282D1009D40CD /* Note+Publish.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Note+Publish.swift"; sourceTree = ""; }; BAF4A96E26DB085D00C51C1D /* NSURLComponents+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSURLComponents+Simplenote.swift"; sourceTree = ""; }; BAF8D42226AE10F100CA9383 /* Tag+Widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Tag+Widget.swift"; sourceTree = ""; }; @@ -1197,7 +1259,7 @@ BAFA93EE265DE3B90009DCFB /* NewNoteWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteWidget.swift; sourceTree = ""; }; BAFA93F0265DE45D0009DCFB /* NewNoteWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteWidgetProvider.swift; sourceTree = ""; }; BAFDBBD726B88BD200119615 /* Date+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Simplenote.swift"; sourceTree = ""; }; - BAFFCEA526DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetCoreDataWrapper.swift; sourceTree = ""; }; + BAFFCEA526DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionCoreDataWrapper.swift; sourceTree = ""; }; C07B32800E3C783BDF105B3A /* Pods-Automattic-SimplenoteShare.distribution alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Automattic-SimplenoteShare.distribution alpha.xcconfig"; path = "Pods/Target Support Files/Pods-Automattic-SimplenoteShare/Pods-Automattic-SimplenoteShare.distribution alpha.xcconfig"; sourceTree = ""; }; D82BFE4A2624A8AB003DFA32 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; D82BFE532624A8C5003DFA32 /* SimplenoteUISmokeTestsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplenoteUISmokeTestsSettings.swift; sourceTree = ""; }; @@ -1229,9 +1291,9 @@ E219EAF617C5322400179B40 /* SPInteractiveTextStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPInteractiveTextStorage.m; path = Classes/SPInteractiveTextStorage.m; sourceTree = ""; }; E21F57B717C1244E001F02D3 /* SPEditorTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPEditorTextView.h; path = Classes/SPEditorTextView.h; sourceTree = ""; }; E21F57B817C1244E001F02D3 /* SPEditorTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPEditorTextView.m; path = Classes/SPEditorTextView.m; sourceTree = ""; }; - E225E23E180DB1DF005A71A0 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; - E225E23F180DB4DD005A71A0 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; - E225E240180DB61A005A71A0 /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + E225E23E180DB1DF005A71A0 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + E225E23F180DB4DD005A71A0 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + E225E240180DB61A005A71A0 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; E22A17CE17A2E1CE00383575 /* NSManagedObjectContext+CoreDataExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSManagedObjectContext+CoreDataExtensions.h"; path = "Classes/NSManagedObjectContext+CoreDataExtensions.h"; sourceTree = ""; }; E22A17CF17A2E1CE00383575 /* NSManagedObjectContext+CoreDataExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSManagedObjectContext+CoreDataExtensions.m"; path = "Classes/NSManagedObjectContext+CoreDataExtensions.m"; sourceTree = ""; }; E22A17D117A2E2A000383575 /* SPObjectManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPObjectManager.h; path = Simplenote/Classes/SPObjectManager.h; sourceTree = SOURCE_ROOT; }; @@ -1255,7 +1317,7 @@ E26AC90F179DA25F00C4BFB6 /* SPSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPSettingsViewController.m; path = Classes/SPSettingsViewController.m; sourceTree = ""; }; E277B5FF17A063020095CD24 /* SPTagEntryField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPTagEntryField.h; path = Classes/SPTagEntryField.h; sourceTree = ""; }; E277B60017A063020095CD24 /* SPTagEntryField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPTagEntryField.m; path = Classes/SPTagEntryField.m; sourceTree = ""; }; - E280453D180DAE0200670073 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + E280453D180DAE0200670073 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E283709817D7B46300AB562D /* SPAcitivitySafari.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPAcitivitySafari.h; path = Classes/SPAcitivitySafari.h; sourceTree = ""; }; E283709917D7B46300AB562D /* SPAcitivitySafari.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPAcitivitySafari.m; path = Classes/SPAcitivitySafari.m; sourceTree = ""; }; E28A760D178CBE6D008659DE /* SPNoteEditorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPNoteEditorViewController.h; path = Classes/SPNoteEditorViewController.h; sourceTree = ""; }; @@ -1337,6 +1399,7 @@ 46A3C9BE17DFA81A002865AE /* UIKit.framework in Frameworks */, 46A3C9BF17DFA81A002865AE /* Foundation.framework in Frameworks */, 4352BA0867E0E416EB5FF6B9 /* Pods_Automattic_Simplenote.framework in Frameworks */, + B51D445B2C52CB4000F296A7 /* SimplenoteEndpoints in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1396,6 +1459,7 @@ BA8661D526B3A00B00466746 /* SimplenoteIntents-DistributionAlpha.entitlements */, BA8661D826B3A0B000466746 /* SimplenoteIntents-DistributionInternal.entitlements */, BA8661D726B3A0A000466746 /* SimplenoteIntents-Release.entitlements */, + BA69A51C2BE015BF0096E50F /* PrivacyInfo.xcprivacy */, ); path = "Supporting Files"; sourceTree = ""; @@ -1568,6 +1632,7 @@ 8C573B0D22CD1AD6005BC6F5 /* Version.Public.xcconfig */, 8C573B0A22CD1A61005BC6F5 /* Simplenote.debug.xcconfig */, 8C573B0B22CD1A6F005BC6F5 /* Simplenote.release.xcconfig */, + 3FD478242C572EE10071B8B9 /* Project.Release.xcconfig */, ); path = config; sourceTree = ""; @@ -1655,7 +1720,6 @@ children = ( A6CC0B1D25B8408900F12A85 /* Helpers */, A6CC0B0F25B83FE400F12A85 /* AccountVerificationControllerTests.swift */, - A6CC0B2C25B84FF200F12A85 /* AccountVerificationRemoteTests.swift */, ); path = Verification; sourceTree = ""; @@ -1668,15 +1732,6 @@ path = Helpers; sourceTree = ""; }; - A6CC0B3425B8501B00F12A85 /* Network */ = { - isa = PBXGroup; - children = ( - A6CC0B3525B8502800F12A85 /* MockURLSession.swift */, - A6CC0B4325B8505700F12A85 /* MockURLSessionDataTask.swift */, - ); - path = Network; - sourceTree = ""; - }; A6E1E79C24BDDF30008A44BC /* Card */ = { isa = PBXGroup; children = ( @@ -1762,11 +1817,18 @@ B52F35D022F3254800724793 /* Theme.swift */, B5AB169B22FB2DF300B4EBA5 /* UIKitConstants.swift */, B5095DC824632E3300812711 /* SimplenoteConstants.swift */, - A628BEC225ED703A00121B64 /* RemoteConstants.swift */, ); name = Settings; sourceTree = ""; }; + B51D445C2C52CD8D00F296A7 /* Authentication */ = { + isa = PBXGroup; + children = ( + B51D445D2C52CDA800F296A7 /* SPUser+UserProtocol.swift */, + ); + name = Authentication; + sourceTree = ""; + }; B51F6DCF2460C3D80074DDD9 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -1883,7 +1945,6 @@ B54D9C562909BA2600D0E0EC /* StoreManager.swift */, B549E24A290B3DD70072C3E8 /* StoreConstants.swift */, B54D9C582909CC4400D0E0EC /* StoreProduct.swift */, - B514678C291AC7790062736E /* UIAlertController+Sustainer.swift */, B5BADB7C2909D78B00275B29 /* TestConfiguration.storekit */, ); name = Store; @@ -1917,7 +1978,6 @@ A6344AD8255952C70072FA07 /* NoteContentPreviewTests.swift */, A694ABB225D1687200CC3A2D /* NoteScrollPositionCacheTests.swift */, BAB0179A260AD591007A9CC3 /* PublishControllerTests.swift */, - BA18532726488DBC00D9A347 /* SignupRemoteTests.swift */, ); name = Tools; sourceTree = ""; @@ -1952,6 +2012,7 @@ B50F479E1D1D77FC00822748 /* UIAlertController+Helpers.swift */, B5757367232BED0700443C2E /* UIBezierPath+Simplenote.swift */, B570D2D82360F16F006E1C85 /* UIBlurEffect+Simplenote.swift */, + B54A11C12C135EA2002AC8AA /* UIButton+Simplenote.swift */, B575736B232D454300443C2E /* UIColor+Helpers.swift */, B58BF1FA23D78ADF00515B50 /* UIContextualAction+Simplenote.swift */, B5BA5B072551FB3C002AAD43 /* UIDevice+Simplenote.swift */, @@ -1984,7 +2045,6 @@ A6C0DFB425C1581D00B9BE39 /* UIScrollView+Simplenote.swift */, A6A8968D25D5779D00A7B390 /* FileManager+Simplenote.swift */, BAE08625261282D1009D40CD /* Note+Publish.swift */, - BA5E5B7C264A148C00D0EE19 /* URLRequest+Simplenote.swift */, BAFDBBD726B88BD200119615 /* Date+Simplenote.swift */, BAB27BD526BA425C00AE4ACC /* Color+Simplenote.swift */, BAA59E78269F9FE30068BD3D /* Date+Simplenote.swift */, @@ -2001,11 +2061,15 @@ B56A695622F9CD1500B90398 /* SPOnboardingViewController.xib */, B56A695D22F9D53300B90398 /* SPAuthError.swift */, B56A695C22F9D53300B90398 /* SPAuthHandler.swift */, + B5C99A502C45B94600728813 /* AuthenticationMode.swift */, + B54C1B542C4EE350001E9E18 /* UIAlertController+Auth.swift */, B56A695F22F9D53400B90398 /* SPAuthViewController.swift */, B56A695E22F9D53300B90398 /* SPAuthViewController.xib */, B5AB169722FA124F00B4EBA5 /* SPSheetController.swift */, B5AB169922FA128000B4EBA5 /* SPSheetController.xib */, A6CDD9C725F0163D00E0BC4D /* MagicLinkAuthenticator.swift */, + B54A11C32C136225002AC8AA /* MagicLinkRequestedView.swift */, + B56BAF602C2DE317005065C9 /* MagicLinkInvalidView.swift */, A628BEB425ECD97900121B64 /* SignupVerificationViewController.swift */, A628BEB525ECD97900121B64 /* SignupVerificationViewController.xib */, ); @@ -2098,7 +2162,6 @@ B5B85BA6235173D900AD5221 /* Helpers */ = { isa = PBXGroup; children = ( - A6CC0B3425B8501B00F12A85 /* Network */, B5421F3B23CE7A14004DDC19 /* Constants.swift */, B5421F3D23CE7A1C004DDC19 /* MockupStorage.swift */, B5421F3E23CE7A1C004DDC19 /* MockupStorage+Sample.swift */, @@ -2134,7 +2197,6 @@ B5BE053E1AB751FD002417BF /* Tools */ = { isa = PBXGroup; children = ( - BA57692A269D2103008B510E /* Remotes */, B51F6DCF2460C3D80074DDD9 /* Onboarding */, B5CBEF3F22D3AD92009DBE67 /* MigrationsHandler.swift */, B5D3FCCF201F96AC00A813B7 /* StatusChecker.h */, @@ -2169,6 +2231,7 @@ BA3856CC2681715700F388CC /* CoreDataManager.swift */, BA9B59012685549F00DAD1ED /* StorageSettings.swift */, BA32A90E26B7469F00727247 /* WidgetError.swift */, + BA7240212C1BA1210088EC11 /* RecoveryUnarchiver.swift */, ); name = Tools; sourceTree = ""; @@ -2209,6 +2272,7 @@ children = ( B5CFFFE822AA9DD700B968CD /* Note.swift */, B5CFFFE622AA9DB100B968CD /* Uploader.swift */, + BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */, ); path = Simperium; sourceTree = ""; @@ -2253,8 +2317,8 @@ B5F232B429084317006D8570 /* Sustainer */ = { isa = PBXGroup; children = ( - B5F232B529084336006D8570 /* SustainerView.swift */, - B5F232B929084526006D8570 /* SustainerView.xib */, + B5F232B529084336006D8570 /* BannerView.swift */, + B5F232B929084526006D8570 /* BannerView.xib */, ); name = Sustainer; sourceTree = ""; @@ -2303,6 +2367,31 @@ path = Providers; sourceTree = ""; }; + BA289B622BE43949000E6794 /* Intent Handlers */ = { + isa = PBXGroup; + children = ( + BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */, + BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */, + BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */, + BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */, + BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */, + BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */, + BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */, + BA9C7EC82BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift */, + BA9C7ECA2BED7F7B007A8460 /* FindNoteWithTagIntentHandler.swift */, + ); + path = "Intent Handlers"; + sourceTree = ""; + }; + BA34B0552BEC214800580E15 /* ResolutionResults */ = { + isa = PBXGroup; + children = ( + BA34B0562BEC216B00580E15 /* IntentNote+Helpers.swift */, + BA9C7ECC2BED813B007A8460 /* IntentTag+Helpers.swift */, + ); + path = ResolutionResults; + sourceTree = ""; + }; BA3FB8CD25FEA09F00EA9A1B /* Notice */ = { isa = PBXGroup; children = ( @@ -2320,15 +2409,12 @@ name = Notices; sourceTree = ""; }; - BA57692A269D2103008B510E /* Remotes */ = { + BA4A018E2C1CCEB300EEE567 /* Tools */ = { isa = PBXGroup; children = ( - A6C7647F25E9131C00A39067 /* SignupRemote.swift */, - A6CC0B0625B8287F00F12A85 /* AccountRemote.swift */, - BA5768E2269A803F008B510E /* Remote.swift */, - BA0F5E0326B62A8B0098C605 /* RemoteError.swift */, + BA4A018F2C1CCEC100EEE567 /* RecoveryArchiver.swift */, ); - name = Remotes; + path = Tools; sourceTree = ""; }; BA75D87C26C0842000883FFA /* Extensions */ = { @@ -2359,6 +2445,7 @@ 241709C7266829CD00F6E2B1 /* SimplenoteWidgetsExtension-DistributionAlpha.entitlements */, 241709D126682AED00F6E2B1 /* SimplenoteWidgetsExtension-DistributionInternal.entitlements */, 241709BB266827A800F6E2B1 /* SimplenoteWidgetsExtension-Release.entitlements */, + BA69A51A2BE015640096E50F /* PrivacyInfo.xcprivacy */, ); path = "Supporting Files"; sourceTree = ""; @@ -2366,8 +2453,14 @@ BAB5762526703C8200B0C56F /* SimplenoteIntents */ = { isa = PBXGroup; children = ( + BA4A018E2C1CCEB300EEE567 /* Tools */, + BA34B04F2BEAEF4800580E15 /* SimplenoteIntentsRelease.entitlements */, 092FD78026D7BB72006BE8E2 /* Supporting Files */, BAB5762626703C8200B0C56F /* IntentHandler.swift */, + BA6D7B8A2BE588F0006AE368 /* IntentsError.swift */, + BA289B702BE45A39000E6794 /* IntentsConstants.swift */, + BA289B622BE43949000E6794 /* Intent Handlers */, + BA34B0552BEC214800580E15 /* ResolutionResults */, BAF8D4AD26AE136D00CA9383 /* Simplenote-Intents-Bridging-Header.h */, ); path = SimplenoteIntents; @@ -2386,11 +2479,11 @@ BAF8D4CB26AE142C00CA9383 /* Tools */ = { isa = PBXGroup; children = ( - BA86622226B3AE4A00466746 /* WidgetResultsController.swift */, + BA86622226B3AE4A00466746 /* ExtensionResultsController.swift */, BA88765026B79324001C9C9E /* DemoContent.swift */, BAADC8A326C634DB004CAAA9 /* WidgetConstants.swift */, BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */, - BAFFCEA526DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift */, + BAFFCEA526DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift */, BA524A0126DF1AE800DAC945 /* WidgetsState.swift */, ); path = Tools; @@ -2575,6 +2668,7 @@ E29ADD4117848E8500E55842 /* Simplenote */ = { isa = PBXGroup; children = ( + B51D445C2C52CD8D00F296A7 /* Authentication */, 467D9C7A1788D10400785EF3 /* Categories */, A690032F253D85C40087D0D2 /* Controllers */, B5C7BF92230C592B000DEC91 /* Credentials */, @@ -2610,6 +2704,7 @@ B5250A6B22B922F900AE7797 /* Simplenote-Internal.entitlements */, E29ADD4717848E8500E55842 /* main.m */, E29ADD4917848E8500E55842 /* Simplenote-Prefix.pch */, + BA9C7ECF2BEE9BA7007A8460 /* ShortcutIntents.intentdefinition */, ); path = "Supporting Files"; sourceTree = ""; @@ -2695,6 +2790,7 @@ B5767F9022FDF2B900052D81 /* LaunchScreen.storyboard */, E280453E180DAE0200670073 /* Localizable.strings */, 467D9C5C1788A4FB00785EF3 /* Simplenote.xcdatamodeld */, + BA69A5182BE0127C0096E50F /* PrivacyInfo.xcprivacy */, ); path = Resources; sourceTree = ""; @@ -2786,6 +2882,7 @@ B50D2FB524E6DFAC00163FC3 /* SimplenoteSearch */, B59CACFB2541E05400958330 /* SimplenoteInterlinks */, BA2015BA2B57384F005E59AA /* AutomatticTracks */, + B51D445A2C52CB4000F296A7 /* SimplenoteEndpoints */, ); productName = Simplenote; productReference = 46A3C9D717DFA81A002865AE /* Simplenote.app */; @@ -2904,7 +3001,7 @@ BuildIndependentTargetsInParallel = YES; CLASSPREFIX = SP; LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1520; + LastUpgradeCheck = 1530; ORGANIZATIONNAME = Automattic; TargetAttributes = { 3F1BB4AD243199FF006D1A04 = { @@ -3005,6 +3102,7 @@ 3F762E8D2677F19C0088CD45 /* XCRemoteSwiftPackageReference "XCUITestHelpers" */, 3F4BA07C26CDF295000619B1 /* XCRemoteSwiftPackageReference "ScreenObject" */, BA2015B92B573761005E59AA /* XCRemoteSwiftPackageReference "Automattic-Tracks-iOS" */, + B51D44592C52CB4000F296A7 /* XCRemoteSwiftPackageReference "SimplenoteEndpoints-Swift" */, ); productRefGroup = E29ADD3917848E8500E55842 /* Products */; projectDirPath = ""; @@ -3051,6 +3149,7 @@ A6C0DFE825C18E3300B9BE39 /* NoteEditorTagListViewController.xib in Resources */, A628BEB725ECD97900121B64 /* SignupVerificationViewController.xib in Resources */, B56A695822F9CD1500B90398 /* SPOnboardingViewController.xib in Resources */, + BA69A5192BE0127C0096E50F /* PrivacyInfo.xcprivacy in Resources */, A61471B825D70C190065D849 /* PopoverViewController.xib in Resources */, A6C0589524AD2B8F006BC572 /* SPNoteHistoryViewController.xib in Resources */, A6A648A525AC3AC00074A094 /* AccountVerificationViewController.xib in Resources */, @@ -3066,7 +3165,7 @@ A69F861D25408085005140F2 /* NoteInformationViewController.xib in Resources */, BA4499AA25ED8821000C563E /* NoticeView.xib in Resources */, BA113E4D269E860500F3E3B4 /* markdown-light.css in Resources */, - B5F232BA29084526006D8570 /* SustainerView.xib in Resources */, + B5F232BA29084526006D8570 /* BannerView.xib in Resources */, B513F2B62319A8F40021CFA4 /* SPNoteTableViewCell.xib in Resources */, B5078A001C1F5595009F097A /* markdown-dark.css in Resources */, B502311325129525002C3CDA /* SPDiagnosticsViewController.xib in Resources */, @@ -3101,6 +3200,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BA69A51D2BE015BF0096E50F /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3108,6 +3208,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BA69A51B2BE015640096E50F /* PrivacyInfo.xcprivacy in Resources */, BAFA93E1265DCFCD0009DCFB /* Assets.xcassets in Resources */, BAFA93FA265DE8920009DCFB /* Images.xcassets in Resources */, ); @@ -3230,7 +3331,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$SRCROOT/Scripts/swiftlint.sh\"\n"; + shellScript = "[ $CI ] && exit 0\n./Pods/SwiftLint/swiftlint\n"; }; B553F5A11D10474600F7E397 /* Delete Frameworks folder (temporary fix for CocoaPods) */ = { isa = PBXShellScriptBuildPhase; @@ -3331,12 +3432,13 @@ E215C793180B115C00AD36B5 /* SPNavigationController.m in Sources */, B5DF734422A56E2800602CE7 /* UserDefaults+Simplenote.swift in Sources */, A6DB2C1525BF5D6E00437B99 /* SPMarkdownPreviewViewController+Extensions.swift in Sources */, + B56BAF612C2DE317005065C9 /* MagicLinkInvalidView.swift in Sources */, B56A696022F9D53400B90398 /* SPAuthHandler.swift in Sources */, A6E1E78924B5196C008A44BC /* SPNoteHistoryControllerDelegate.swift in Sources */, B5B9AB4522EE8AF0001CB0AD /* UIImageName.swift in Sources */, BA2D82C6261522F100A1695B /* PublishNoticePresenter.swift in Sources */, A64DE6E9255D1C9F001D0526 /* ContentSlice.swift in Sources */, - B5F232B629084336006D8570 /* SustainerView.swift in Sources */, + B5F232B629084336006D8570 /* BannerView.swift in Sources */, B5A6846D23CE84D400398BBC /* NotesListController.swift in Sources */, B52BB74822CFBD540042C162 /* UIActivityViewController+Simplenote.swift in Sources */, B5BA5B1C25520ECF002AAD43 /* UIKeyboardAppearance+Simplenote.swift in Sources */, @@ -3353,12 +3455,15 @@ B543C7E323CF76EA00003A80 /* NotesListFilter.swift in Sources */, 46A3C96717DFA81A002865AE /* NSManagedObjectContext+CoreDataExtensions.m in Sources */, A6C0DFA725C0992D00B9BE39 /* NoteScrollPositionCache.swift in Sources */, + BA7240222C1BA1210088EC11 /* RecoveryUnarchiver.swift in Sources */, B59314D81A486B3800B651ED /* SPConstants.m in Sources */, 375D24B421E01131007AB25A /* escape.c in Sources */, B50F47A61D1D791B00822748 /* NSURL+Extensions.swift in Sources */, B52BB74E22CFD1660042C162 /* SimplenoteActivityItemSource.swift in Sources */, B537730F252E14C600BC78C5 /* OptionsViewController.swift in Sources */, B56A696722F9D55F00B90398 /* UIView+Animations.swift in Sources */, + B54A11C22C135EA2002AC8AA /* UIButton+Simplenote.swift in Sources */, + BA9C7ED02BEE9BA7007A8460 /* ShortcutIntents.intentdefinition in Sources */, BA3856CD2681715700F388CC /* CoreDataManager.swift in Sources */, A60DF30825A44F0F00FDADF3 /* PinLockRemoveController.swift in Sources */, A694ABAB25D1549D00CC3A2D /* FileStorage.swift in Sources */, @@ -3409,7 +3514,6 @@ B54D9C592909CC4400D0E0EC /* StoreProduct.swift in Sources */, A6C0589924AD47CB006BC572 /* SPSnappingSlider.swift in Sources */, B550F93122BA65CD00091939 /* ActivityType.swift in Sources */, - BA3101A926C8C76A00C95F93 /* ListWidgetIntent.intentdefinition in Sources */, 46A3C97817DFA81A002865AE /* PersonTag.m in Sources */, B5BFAEBB21519DCA00918DC8 /* SPPrivacyViewController.swift in Sources */, A6C0DFE725C18E3300B9BE39 /* NoteEditorTagListViewController.swift in Sources */, @@ -3429,12 +3533,10 @@ A66E41E1256D0F44000C6FCB /* RoundedButton.swift in Sources */, F920265B2294661C0061B1DE /* BuildConfiguration.swift in Sources */, B59188C0240059950069EF96 /* NSRegularExpression+Simplenote.swift in Sources */, - B514678D291AC7790062736E /* UIAlertController+Sustainer.swift in Sources */, A68ABCA824ABFB5300715EBD /* SPShadowView.swift in Sources */, B56E763622BD565700C5AA47 /* UIView+Simplenote.swift in Sources */, B5E4082D235DE41A00D4E1DF /* UIColor+Studio.swift in Sources */, A6CDF900256B9CB900CF2F27 /* ViewSpinner.swift in Sources */, - BA5E5B7D264A148C00D0EE19 /* URLRequest+Simplenote.swift in Sources */, 375D24B721E01131007AB25A /* stack.c in Sources */, 373AD30821C4739500A4EA89 /* NSMutableAttributedString+Styling.m in Sources */, B524AE112352CC7900EA11D4 /* UIScreen+Simplenote.swift in Sources */, @@ -3457,7 +3559,6 @@ B5757368232BED0700443C2E /* UIBezierPath+Simplenote.swift in Sources */, B5476BB723D89AF7000E7723 /* SPSectionHeaderView.swift in Sources */, B5377349252E2A4200BC78C5 /* Value1TableViewCell.swift in Sources */, - BA0F5E0426B62A8B0098C605 /* RemoteError.swift in Sources */, B53C5A5A230330CD00DA2143 /* SPNoteListViewController+Extensions.swift in Sources */, A6C0DFB525C1581D00B9BE39 /* UIScrollView+Simplenote.swift in Sources */, 375D24B621E01131007AB25A /* html_blocks.c in Sources */, @@ -3510,6 +3611,7 @@ 46A3C98F17DFA81A002865AE /* SPNoteListViewController.m in Sources */, B5BD6AF51BF66093004ECE33 /* SPMarkdownPreviewViewController.m in Sources */, B5F3FFFF23F44679007A7C59 /* SPSortBar.swift in Sources */, + B54C1B552C4EE350001E9E18 /* UIAlertController+Auth.swift in Sources */, BA4499C525ED95E5000C563E /* NoticeAction.swift in Sources */, A6E02778256E9487002054DF /* PinLockSetupController.swift in Sources */, B513F2B42319A8D50021CFA4 /* SPNoteTableViewCell.swift in Sources */, @@ -3524,11 +3626,10 @@ A6C3D8B7256687970042F584 /* SPObjectManager+Simplenote.swift in Sources */, 46A3C99817DFA81A002865AE /* SPEntryListCell.m in Sources */, BAB017722609456D007A9CC3 /* PublishController.swift in Sources */, + BA289B732BE45A3C000E6794 /* IntentsConstants.swift in Sources */, BAF4A96F26DB085D00C51C1D /* NSURLComponents+Simplenote.swift in Sources */, B5E951E124FEDAD4004B10B8 /* UIPasteboard+Note.swift in Sources */, - A6C7648025E9131C00A39067 /* SignupRemote.swift in Sources */, A681C73C2541AC8D00F369C2 /* HuggableTableView.swift in Sources */, - BAFDBBE426B88F1300119615 /* NoteWidgetIntent.intentdefinition in Sources */, 46A3C99917DFA81A002865AE /* SPTransitionController.m in Sources */, B56A695B22F9CD4E00B90398 /* UINavigationBar+Simplenote.swift in Sources */, 46A3C99B17DFA81A002865AE /* Tag.m in Sources */, @@ -3547,6 +3648,7 @@ B5AEC38423FAC5D600D24221 /* DateFormatter+Simplenote.swift in Sources */, B5DF734622A5713600602CE7 /* SortMode.swift in Sources */, BA55B05A25F067DF0042582B /* NoticePresenter.swift in Sources */, + B51D445E2C52CDA800F296A7 /* SPUser+UserProtocol.swift in Sources */, 46A3C99D17DFA81A002865AE /* SPAddCollaboratorsViewController.m in Sources */, B52646AA22D3E04C00EBF299 /* UIViewController+Simplenote.swift in Sources */, B55E428C22A1A4550018C0CE /* SPSortOrderViewController.swift in Sources */, @@ -3556,7 +3658,6 @@ E215C79E180B228800AD36B5 /* SPTextField.m in Sources */, A6DE79CE2552E6CC00BC69C6 /* TagListViewController.swift in Sources */, B513F2B82319A9A40021CFA4 /* UITableViewCell+Simplenote.swift in Sources */, - A628BEC325ED703A00121B64 /* RemoteConstants.swift in Sources */, B51F6DD52460D7540074DDD9 /* NSPredicate+Email.swift in Sources */, B5C2EDF0255AFB6C00C09B32 /* PassthruView.swift in Sources */, A6F4882325A8889E0050CFA8 /* UITextField+Tag.swift in Sources */, @@ -3592,6 +3693,7 @@ B5CBEF4222D3B419009DBE67 /* Bundle+Simplenote.swift in Sources */, B575736C232D454300443C2E /* UIColor+Helpers.swift in Sources */, 37E55A6721BF2B1800F14241 /* SPTextAttachment.swift in Sources */, + B54A11C42C136225002AC8AA /* MagicLinkRequestedView.swift in Sources */, BA8FC2A5267AC7470082962E /* SharedStorageMigrator.swift in Sources */, A69F850F253EC2B2005140F2 /* SPCardConfigurable.swift in Sources */, B5BE05541AB75C3B002417BF /* Settings.m in Sources */, @@ -3604,7 +3706,7 @@ A6BF0E352567B29B008DE8E0 /* RoundedCrossButton.swift in Sources */, 375D24B121E01131007AB25A /* html5_blocks.c in Sources */, B504D4DE23D2014200AEED27 /* IndexPath+Simplenote.swift in Sources */, - A6CC0B0725B8287F00F12A85 /* AccountRemote.swift in Sources */, + B5C99A512C45B94600728813 /* AuthenticationMode.swift in Sources */, A61471B925D70C190065D849 /* PopoverViewController.swift in Sources */, B536988D25646C9400817E30 /* CGRect+Simplenote.swift in Sources */, B5E96B611BDE5ACA00D707F5 /* SPMarkdownParser.m in Sources */, @@ -3613,7 +3715,6 @@ B59560E0251A46D500A06788 /* KeychainManager.swift in Sources */, 46A3C9AF17DFA81A002865AE /* NSString+Condensing.m in Sources */, A6F487AF25A79D550050CFA8 /* BiometricAuthentication.swift in Sources */, - BA5768E3269A803F008B510E /* Remote.swift in Sources */, A6F4881325A884CE0050CFA8 /* UITextInput+Simplenote.swift in Sources */, B50F47A41D1D78EA00822748 /* SPRatingsHelper.m in Sources */, 371A8630213DF00E002E9120 /* SPPinLockManager.swift in Sources */, @@ -3628,8 +3729,6 @@ A6CC0B1025B83FE400F12A85 /* AccountVerificationControllerTests.swift in Sources */, A694ABB325D1687200CC3A2D /* NoteScrollPositionCacheTests.swift in Sources */, A6E6CE7525A5B4A3005A92DB /* MockPinLockManager.swift in Sources */, - A6CC0B4425B8505700F12A85 /* MockURLSessionDataTask.swift in Sources */, - A6CC0B3625B8502800F12A85 /* MockURLSession.swift in Sources */, B52F35D322F356F500724793 /* UserDefaults+Tests.swift in Sources */, B511AEE9255A0A5E005B2159 /* InterlinkResultsControllerTests.swift in Sources */, 374F5EF521BF057E00B57E8B /* NSMutableAttributedStringStylingTests.swift in Sources */, @@ -3642,13 +3741,11 @@ BAB017AB260ADDEB007A9CC3 /* MockTimerFactory.swift in Sources */, B55AC57A22D27B9100D8CEB2 /* OptionsTests.swift in Sources */, B543C7DF23CF6AB400003A80 /* NotesListControllerTests.swift in Sources */, - A6CC0B2D25B84FF200F12A85 /* AccountVerificationRemoteTests.swift in Sources */, B5421F4023CE7A1C004DDC19 /* MockupStorage+Sample.swift in Sources */, A6F4883225A891A70050CFA8 /* TagTextFieldInputValidatorTests.swift in Sources */, A6E6CE9125A5B970005A92DB /* PinLockRemoveControllerTests.swift in Sources */, B57401A025B7D9960058960E /* EmailVerificationTests.swift in Sources */, A6E6CEC925A6053A005A92DB /* MockApplication.swift in Sources */, - BA18532826488DBC00D9A347 /* SignupRemoteTests.swift in Sources */, BA3FB8CF25FEA0C500EA9A1B /* NoticeControllerTests.swift in Sources */, BAB32C63269E4190005C72B2 /* RemoteResult+TestHelpers.swift in Sources */, A645FA70254C5E69008A1519 /* NoteContentHelperTests.swift in Sources */, @@ -3695,6 +3792,7 @@ B52893B12360B7E300DF2571 /* UIColor+Studio.swift in Sources */, B5E196BC230F522D00F5658A /* SPCredentials.swift in Sources */, B56A9FFE22AEDCB0003F85CB /* URLExtractor.swift in Sources */, + BA9C7EFB2BF2CC3E007A8460 /* Downloader.swift in Sources */, B56315BE236BD9970066C151 /* UIKitConstants.swift in Sources */, 74F6638322BADD0300FA147E /* SharePresentationController.swift in Sources */, 74388F4822CFFABD001C5EC0 /* NSObject+Helpers.swift in Sources */, @@ -3708,33 +3806,55 @@ buildActionMask = 2147483647; files = ( BA86620E26B3A73D00466746 /* UserDefaults+Simplenote.swift in Sources */, + BAD0F1ED2BED49C200E73E45 /* FindNoteIntentHandler.swift in Sources */, BAF8D52826AE173D00CA9383 /* FileManager+Simplenote.swift in Sources */, + BA34B04D2BEAE9FE00580E15 /* SPConstants.m in Sources */, + BA289B5F2BE43728000E6794 /* NoteWidgetIntentHandler.swift in Sources */, BAF8D50026AE171400CA9383 /* CoreDataManager.swift in Sources */, + BA9C7EFC2BF2CCAE007A8460 /* Downloader.swift in Sources */, BAE63CB026E05313002BF81A /* NSString+Simplenote.swift in Sources */, BAE63CB226E05325002BF81A /* NSPredicate+Email.swift in Sources */, BAF8D51426AE172600CA9383 /* StorageSettings.swift in Sources */, + BA289B5C2BE4371A000E6794 /* ListWidgetIntentHandler.swift in Sources */, BA86616326B35CF000466746 /* BuildConfiguration.swift in Sources */, + BA34B04C2BEAE9E800580E15 /* Uploader.swift in Sources */, + BA289B722BE45A39000E6794 /* IntentsConstants.swift in Sources */, + BA34B0572BEC216B00580E15 /* IntentNote+Helpers.swift in Sources */, BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */, + BA9C7EC92BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift in Sources */, + BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, + BA9C7ED12BEE9BA7007A8460 /* ShortcutIntents.intentdefinition in Sources */, + BA0AF10D2BE996600050EEBD /* KeychainManager.swift in Sources */, BA86625D26B3B14900466746 /* SortMode.swift in Sources */, + BAB898D32BEC404200E238B8 /* CreateNewNoteIntentHandler.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAF4A9AA26DB138600C51C1D /* NoteContentHelper.swift in Sources */, + BA9C7ECB2BED7F7B007A8460 /* FindNoteWithTagIntentHandler.swift in Sources */, + BA6D7B8B2BE588F0006AE368 /* IntentsError.swift in Sources */, + BA289B782BE45BFB000E6794 /* ActivityType.swift in Sources */, + BA3580902BE95BE100CE1590 /* AppendNoteIntentHandler.swift in Sources */, BA7071E626BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition in Sources */, - BA86622426B3AE4A00466746 /* WidgetResultsController.swift in Sources */, + BA86622426B3AE4A00466746 /* ExtensionResultsController.swift in Sources */, BABFFF2426CF9094003A4C25 /* WidgetDefaults.swift in Sources */, + BA289B762BE45BBB000E6794 /* OpenNoteIntentHandler.swift in Sources */, + BA4A01902C1CCEC100EEE567 /* RecoveryArchiver.swift in Sources */, BAF8D46E26AE118800CA9383 /* SPCredentials.swift in Sources */, BAF8D5C526AE254100CA9383 /* Simplenote.xcdatamodeld in Sources */, BAB6C04426BA49F3007495C4 /* ContentSlice.swift in Sources */, BA86624926B3B05100466746 /* SimplenoteConstants.swift in Sources */, BAB5765C26703D0600B0C56F /* NoteWidgetIntent.intentdefinition in Sources */, + BA9C7ECD2BED813B007A8460 /* IntentTag+Helpers.swift in Sources */, BAB6C04526BA4A04007495C4 /* String+Simplenote.swift in Sources */, BA32A91926B746A200727247 /* WidgetError.swift in Sources */, BAF4A9BD26DB13B400C51C1D /* Note+Widget.swift in Sources */, BAFDBBDA26B88BD200119615 /* Date+Simplenote.swift in Sources */, BA3CE8D926D5E21C00EFF9EB /* WidgetTag+Helpers.swift in Sources */, BA86627226B3B1B700466746 /* NSSortDescriptor+Simplenote.swift in Sources */, + BA0AF10E2BE996630050EEBD /* KeychainPasswordItem.swift in Sources */, BAADC8A526C634DB004CAAA9 /* WidgetConstants.swift in Sources */, + BA34B04E2BEAEB4100580E15 /* NSURLSessionConfiguration+Extensions.swift in Sources */, BAF8D45526AE10FC00CA9383 /* Tag+Widget.swift in Sources */, - BAFFCEA726DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift in Sources */, + BAFFCEA726DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift in Sources */, BAF8D53C26AE175C00CA9383 /* Bundle+Simplenote.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3765,22 +3885,24 @@ BAB6C04226BA496F007495C4 /* String+Simplenote.swift in Sources */, BA122DBB265EF40E003D3BC5 /* UIColor+Helpers.swift in Sources */, BA75D8BA26C0865C00883FFA /* Filling.swift in Sources */, - BA86622326B3AE4A00466746 /* WidgetResultsController.swift in Sources */, + BA86622326B3AE4A00466746 /* ExtensionResultsController.swift in Sources */, BA0890A926BB9BF80035CA48 /* NewNoteButton.swift in Sources */, BA122DBA265EF402003D3BC5 /* SimplenoteConstants.swift in Sources */, BAF8D51E26AE173D00CA9383 /* FileManager+Simplenote.swift in Sources */, BA86615926B35CEF00466746 /* BuildConfiguration.swift in Sources */, BAF4A97926DB10BD00C51C1D /* NoteContentHelper.swift in Sources */, BAE63CB126E05325002BF81A /* NSPredicate+Email.swift in Sources */, - BAFFCEA626DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift in Sources */, + BAFFCEA626DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift in Sources */, BA122DBC265EF410003D3BC5 /* ColorStudio.swift in Sources */, BA608EF926BB6F4C00A9D94E /* ListWidgetView.swift in Sources */, BAE63CAF26E05312002BF81A /* NSString+Simplenote.swift in Sources */, BA86626726B3B1B600466746 /* NSSortDescriptor+Simplenote.swift in Sources */, + BA289B612BE43825000E6794 /* ListWidgetIntentHandler.swift in Sources */, BAF8D46426AE118800CA9383 /* SPCredentials.swift in Sources */, BA122DC1265EF4C5003D3BC5 /* UITraitCollection+Simplenote.swift in Sources */, BA12B06F26B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAFA93F4265DE4A70009DCFB /* NewNoteWidgetProvider.swift in Sources */, + BA289B602BE43816000E6794 /* NoteWidgetIntentHandler.swift in Sources */, BAB27BD726BA425C00AE4ACC /* Color+Simplenote.swift in Sources */, BAB5768726703EA000B0C56F /* NoteWidgetView.swift in Sources */, BAF8D44C26AE10FC00CA9383 /* Note+Widget.swift in Sources */, @@ -3799,7 +3921,6 @@ BA122DC2265EF4E5003D3BC5 /* SPUserInterface.swift in Sources */, BAB576A826703F4500B0C56F /* NoteWidgetProvider.swift in Sources */, BAF8D4F626AE171400CA9383 /* CoreDataManager.swift in Sources */, - BAB5764626703D0000B0C56F /* IntentHandler.swift in Sources */, BAB576C92670514500B0C56F /* NoteWidget.swift in Sources */, BA122DC7265EF92D003D3BC5 /* NewNoteWidgetView.swift in Sources */, ); @@ -3900,6 +4021,34 @@ name = MainInterface.storyboard; sourceTree = ""; }; + BA9C7ECF2BEE9BA7007A8460 /* ShortcutIntents.intentdefinition */ = { + isa = PBXVariantGroup; + children = ( + BA9C7ECE2BEE9BA7007A8460 /* Base */, + BA9C7ED32BEE9BF3007A8460 /* en */, + BA9C7ED52BEE9BF8007A8460 /* sv */, + BA9C7ED72BEE9BFC007A8460 /* ja */, + BA9C7ED92BEE9BFE007A8460 /* it */, + BA9C7EDB2BEE9C00007A8460 /* ko */, + BA9C7EDD2BEE9C04007A8460 /* fr */, + BA9C7EDF2BEE9C07007A8460 /* es */, + BA9C7EE12BEE9C0C007A8460 /* id */, + BA9C7EE32BEE9C0F007A8460 /* ru */, + BA9C7EE52BEE9C11007A8460 /* cy */, + BA9C7EE72BEE9C13007A8460 /* de */, + BA9C7EE92BEE9C19007A8460 /* pt-BR */, + BA9C7EEB2BEE9C22007A8460 /* tr */, + BA9C7EED2BEE9C24007A8460 /* ar */, + BA9C7EEF2BEE9C38007A8460 /* fa */, + BA9C7EF12BEE9C3A007A8460 /* he */, + BA9C7EF32BEE9C3C007A8460 /* el */, + BA9C7EF52BEE9C3D007A8460 /* nl */, + BA9C7EF72BEE9C3E007A8460 /* zh-Hant-TW */, + BA9C7EF92BEE9C3F007A8460 /* zh-Hans-CN */, + ); + name = ShortcutIntents.intentdefinition; + sourceTree = ""; + }; E280453E180DAE0200670073 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -4372,6 +4521,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Beta"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; CLANG_ENABLE_MODULES = YES; CLANG_WARN_STRICT_PROTOTYPES = NO; @@ -4382,6 +4532,7 @@ COPY_PHASE_STRIP = NO; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = PZYM8XX95Q; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PZYM8XX95Q; ENABLE_BITCODE = NO; ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -4412,6 +4563,7 @@ PRODUCT_NAME = Simplenote; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "Simplenote Development"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Simplenote Development"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG USE_VERBOSE_LOGGING $(SP_IOS_SDK)"; SWIFT_OBJC_BRIDGING_HEADER = "Simplenote/Simplenote-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -4426,6 +4578,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Beta"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; CLANG_ENABLE_MODULES = YES; CLANG_WARN_STRICT_PROTOTYPES = NO; @@ -4435,7 +4588,6 @@ CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = PZYM8XX95Q; ENABLE_BITCODE = NO; ENABLE_MODULE_VERIFIER = YES; ENABLE_TESTABILITY = YES; @@ -4466,7 +4618,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow; PRODUCT_NAME = Simplenote; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "Simplenote Release"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "RELEASE $(SP_IOS_SDK)"; SWIFT_OBJC_BRIDGING_HEADER = "Simplenote/Simplenote-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -4539,6 +4690,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Beta"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; CLANG_ENABLE_MODULES = YES; CLANG_WARN_STRICT_PROTOTYPES = NO; @@ -4735,6 +4887,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; CLANG_ENABLE_MODULES = YES; CLANG_WARN_STRICT_PROTOTYPES = NO; @@ -4744,7 +4897,6 @@ CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = PZYM8XX95Q; ENABLE_BITCODE = NO; ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -4774,7 +4926,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow; PRODUCT_NAME = Simplenote; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.codality.NotationalFlow"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "BUILD_APP_STORE $(SP_IOS_SDK)"; SWIFT_OBJC_BRIDGING_HEADER = "Simplenote/Simplenote-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -4846,6 +4997,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Beta"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; CLANG_ENABLE_MODULES = YES; CLANG_WARN_STRICT_PROTOTYPES = NO; @@ -5114,7 +5266,6 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PZYM8XX95Q; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -5135,7 +5286,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow.Share; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "Simplenote Release Share"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP_EXTENSION; SWIFT_OBJC_BRIDGING_HEADER = "SimplenoteShare/Simplenote-Share-Bridging-Header.h"; @@ -5196,7 +5346,6 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution: Automattic, Inc. (PZYM8XX95Q)"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PZYM8XX95Q; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -5217,7 +5366,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow.Share; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.codality.NotationalFlow.Share"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP_EXTENSION; SWIFT_OBJC_BRIDGING_HEADER = "SimplenoteShare/Simplenote-Share-Bridging-Header.h"; @@ -5235,11 +5383,12 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "SimplenoteIntents/Supporting Files/SimplenoteIntents-Debug.entitlements"; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = PZYM8XX95Q; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PZYM8XX95Q; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -5257,7 +5406,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow.Development.Intents; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "Simplenote Development Intents"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Simplenote Development Intents"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_EXTENSION"; SWIFT_OBJC_BRIDGING_HEADER = "SimplenoteIntents/Simplenote-Intents-Bridging-Header.h"; @@ -5276,13 +5426,12 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = "SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements"; + CODE_SIGN_ENTITLEMENTS = SimplenoteIntents/SimplenoteIntentsRelease.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PZYM8XX95Q; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "SimplenoteIntents/Supporting Files/Info.plist"; @@ -5296,7 +5445,6 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow.Intents; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.codality.NotationalFlow.Intents"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP_EXTENSION; SWIFT_OBJC_BRIDGING_HEADER = "SimplenoteIntents/Simplenote-Intents-Bridging-Header.h"; @@ -5396,7 +5544,6 @@ CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PZYM8XX95Q; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "SimplenoteIntents/Supporting Files/Info.plist"; @@ -5410,7 +5557,6 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow.Intents; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.codality.NotationalFlow.Intents"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP_EXTENSION; SWIFT_OBJC_BRIDGING_HEADER = "SimplenoteIntents/Simplenote-Intents-Bridging-Header.h"; @@ -5483,7 +5629,6 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 4.36.0.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PZYM8XX95Q; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SimplenoteWidgets/Info.plist; @@ -5498,7 +5643,6 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.codality.NotationalFlow.Widgets"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP_EXTENSION; SWIFT_OBJC_BRIDGING_HEADER = "SimplenoteWidgets/Simplenote-Widgets-Bridging-Header.h"; @@ -5609,7 +5753,6 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 4.36.0.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PZYM8XX95Q; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SimplenoteWidgets/Info.plist; @@ -5624,7 +5767,6 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.codality.NotationalFlow.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.codality.NotationalFlow.Widgets"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP_EXTENSION; SWIFT_OBJC_BRIDGING_HEADER = "SimplenoteWidgets/Simplenote-Widgets-Bridging-Header.h"; @@ -6082,6 +6224,14 @@ minimumVersion = 1.3.0; }; }; + B51D44592C52CB4000F296A7 /* XCRemoteSwiftPackageReference "SimplenoteEndpoints-Swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "git@github.com:Automattic/SimplenoteEndpoints-Swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; B59CACFA2541E05400958330 /* XCRemoteSwiftPackageReference "SimplenoteInterlinks-Swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "git@github.com:Automattic/SimplenoteInterlinks-Swift.git"; @@ -6103,7 +6253,7 @@ repositoryURL = "https://github.com/Automattic/Automattic-Tracks-iOS"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 3.2.0; + minimumVersion = 3.5.0; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -6139,6 +6289,11 @@ package = B50D2FB424E6DFAC00163FC3 /* XCRemoteSwiftPackageReference "SimplenoteSearch-Swift" */; productName = SimplenoteSearch; }; + B51D445A2C52CB4000F296A7 /* SimplenoteEndpoints */ = { + isa = XCSwiftPackageProductDependency; + package = B51D44592C52CB4000F296A7 /* XCRemoteSwiftPackageReference "SimplenoteEndpoints-Swift" */; + productName = SimplenoteEndpoints; + }; B59CACFB2541E05400958330 /* SimplenoteInterlinks */ = { isa = XCSwiftPackageProductDependency; package = B59CACFA2541E05400958330 /* XCRemoteSwiftPackageReference "SimplenoteInterlinks-Swift" */; @@ -6180,6 +6335,7 @@ 467D9C5C1788A4FB00785EF3 /* Simplenote.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */, B54D9C542909B21700D0E0EC /* Simplenote 6.xcdatamodel */, B5526335238881EE009AB3B2 /* Simplenote 5.xcdatamodel */, B594E60C21508170001577EE /* Simplenote 4.xcdatamodel */, @@ -6187,7 +6343,7 @@ 467D9C5D1788A4FB00785EF3 /* Simplenote 2.xcdatamodel */, 467D9C5E1788A4FB00785EF3 /* Simplenote.xcdatamodel */, ); - currentVersion = B54D9C542909B21700D0E0EC /* Simplenote 6.xcdatamodel */; + currentVersion = BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */; name = Simplenote.xcdatamodeld; path = ../Simplenote.xcdatamodeld; sourceTree = ""; diff --git a/Simplenote.xcodeproj/xcshareddata/xcschemes/Simplenote.xcscheme b/Simplenote.xcodeproj/xcshareddata/xcschemes/Simplenote.xcscheme index 3e62a3f76..964ae8c3f 100644 --- a/Simplenote.xcodeproj/xcshareddata/xcschemes/Simplenote.xcscheme +++ b/Simplenote.xcodeproj/xcshareddata/xcschemes/Simplenote.xcscheme @@ -1,6 +1,6 @@ ) -> Void) { - let request = verificationURLRequest(with: email) - - performDataTask(with: request, completion: completion) - } - - /// Send account deletion request for user - /// - func requestDelete(_ user: SPUser, completion: @escaping (_ result: Result) -> Void) { - let request = deleteRequest(with: user) - performDataTask(with: request, completion: completion) - } - - // MARK: URL Requests - - private func verificationURLRequest(with email: String) -> URLRequest { - let base64EncodedEmail = email.data(using: .utf8)!.base64EncodedString() - let verificationURL = URL(string: SimplenoteConstants.verificationURL)! - - var request = URLRequest(url: verificationURL.appendingPathComponent(base64EncodedEmail), - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: RemoteConstants.timeout) - request.httpMethod = RemoteConstants.Method.GET - - return request - } - - private func deleteRequest(with user: SPUser) -> URLRequest { - let url = URL(string: SimplenoteConstants.accountDeletionURL)! - - var request = URLRequest(url: url, - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: RemoteConstants.timeout) - request.httpMethod = RemoteConstants.Method.POST - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let body = [ - "username": user.email.lowercased(), - "token": user.authToken - ] - request.httpBody = try? JSONEncoder().encode(body) - - return request - } -} diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift new file mode 100644 index 000000000..9d025a02d --- /dev/null +++ b/Simplenote/AuthenticationMode.swift @@ -0,0 +1,199 @@ +import Foundation + + +// MARK: - State +// +struct AuthenticationState { + var username = String() + var password = String() + var code = String() +} + + +// MARK: - Authentication Elements +// +struct AuthenticationInputElements: OptionSet, Hashable { + let rawValue: UInt + + static let username = AuthenticationInputElements(rawValue: 1 << 0) + static let password = AuthenticationInputElements(rawValue: 1 << 1) + static let code = AuthenticationInputElements(rawValue: 1 << 2) + static let actionSeparator = AuthenticationInputElements(rawValue: 1 << 3) +} + + +// MARK: - Authentication Actions +// +enum AuthenticationActionName { + case primary + case secondary + case tertiary + case quaternary +} + +struct AuthenticationActionDescriptor { + let name: AuthenticationActionName + let selector: Selector + let text: String? + let attributedText: NSAttributedString? + + init(name: AuthenticationActionName, selector: Selector, text: String?, attributedText: NSAttributedString? = nil) { + self.name = name + self.selector = selector + self.text = text + self.attributedText = attributedText + } +} + + +// MARK: - AuthenticationMode: Signup / Login +// +struct AuthenticationMode { + let title: String + let header: String? + let inputElements: AuthenticationInputElements + let validationStyle: AuthenticationValidator.Style + let actions: [AuthenticationActionDescriptor] + + init(title: String, header: String? = nil, inputElements: AuthenticationInputElements, validationStyle: AuthenticationValidator.Style, actions: [AuthenticationActionDescriptor]) { + self.title = title + self.header = header + self.inputElements = inputElements + self.validationStyle = validationStyle + self.actions = actions + } +} + + +// MARK: - Public Properties +// +extension AuthenticationMode { + + func buildHeaderText(email: String) -> NSAttributedString? { + guard let header = header?.replacingOccurrences(of: "{{EMAIL}}", with: email) else { + return nil + } + + return NSMutableAttributedString(string: header, attributes: [ + .font: UIFont.preferredFont(for: .headline, weight: .regular) + ], highlighting: email, highlightAttributes: [ + .font: UIFont.preferredFont(for: .headline, weight: .bold) + ]) + } +} + + +// MARK: - Default Operation Modes +// +extension AuthenticationMode { + + /// Login with Password + /// + static func loginWithPassword(header: String? = nil, includeUsername: Bool = false) -> AuthenticationMode { + var inputElements: AuthenticationInputElements = includeUsername ? [.username, .password] : [.password] + + return .init(title: NSLocalizedString("Log In with Password", comment: "LogIn Interface Title"), + header: header, + inputElements: inputElements, + validationStyle: .legacy, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(SPAuthViewController.performLogInWithPassword), + text: NSLocalizedString("Log In", comment: "LogIn Action")), + AuthenticationActionDescriptor(name: .secondary, + selector: #selector(SPAuthViewController.presentPasswordReset), + text: NSLocalizedString("Forgot your password?", comment: "Password Reset Action")) + ]) + } + + /// Requests a Login Code + /// + static var requestLoginCode: AuthenticationMode { + return .init(title: NSLocalizedString("Log In", comment: "LogIn Interface Title"), + inputElements: [.username, .actionSeparator], + validationStyle: .legacy, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(SPAuthViewController.requestLogInCode), + text: NSLocalizedString("Log in with email", comment: "Sends the User an email with an Authentication Code")), + AuthenticationActionDescriptor(name: .secondary, selector: #selector(SPAuthViewController.presentUsernameAndPasswordInterface), text: nil, attributedText: Strings.usernameAndPasswordOption), + AuthenticationActionDescriptor(name: .tertiary, + selector: #selector(SPAuthViewController.performLogInWithWPCOM), + text: NSLocalizedString("Log in with WordPress.com", comment: "Password fallback Action")) + ]) + } + + /// Login with Code: Submit Code + Authenticate the user + /// + static var loginWithCode: AuthenticationMode { + return .init(title: NSLocalizedString("Enter Code", comment: "LogIn Interface Title"), + header: NSLocalizedString("We've sent a code to {{EMAIL}}. The code will be valid for a few minutes.", comment: "Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is!"), + inputElements: [.code, .actionSeparator], + validationStyle: .legacy, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(SPAuthViewController.performLogInWithCode), + text: NSLocalizedString("Log In", comment: "LogIn Interface Title")), + AuthenticationActionDescriptor(name: .quaternary, + selector: #selector(SPAuthViewController.presentPasswordInterface), + text: NSLocalizedString("Enter password", comment: "Enter Password fallback Action")), + ]) + } + + /// Signup: Contains all of the strings + delegate wirings, so that the AuthUI handles user account creation scenarios. + /// + static var signup: AuthenticationMode { + return .init(title: NSLocalizedString("Sign Up", comment: "SignUp Interface Title"), + inputElements: [.username], + validationStyle: .strong, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(SPAuthViewController.performSignUp), + text: NSLocalizedString("Sign Up", comment: "SignUp Action")), + AuthenticationActionDescriptor(name: .secondary, + selector: #selector(SPAuthViewController.presentTermsOfService), + text: nil, + attributedText: Strings.termsOfService) + ]) + } +} + + +// MARK: - String Constants +// +private enum Strings { + + /// Returns a formatted Secondary Action String for Signup + /// + static var termsOfService: NSAttributedString { + let output = NSMutableAttributedString(string: String(), attributes: [ + .font: UIFont.preferredFont(forTextStyle: .subheadline) + ]) + + let prefix = NSLocalizedString("By creating an account you agree to our", comment: "Terms of Service Legend *PREFIX*: printed in dark color") + let suffix = NSLocalizedString("Terms and Conditions", comment: "Terms of Service Legend *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue") + + output.append(string: prefix, foregroundColor: .simplenoteGray60Color) + output.append(string: " ") + output.append(string: suffix, foregroundColor: .simplenoteBlue60Color) + + return output + } + + /// Returns a formatted Secondary Action String for Optional Username and password login + /// + static var usernameAndPasswordOption: NSAttributedString { + let output = NSMutableAttributedString(string: String(), attributes: [ + .font: UIFont.preferredFont(forTextStyle: .subheadline) + ]) + + let prefix = NSLocalizedString("We'll email you a code to log in, or you can", comment: "Option to login with username and password *PREFIX*: printed in dark color") + let suffix = NSLocalizedString("log in manually.", comment: "Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue") + + output.append(string: prefix, foregroundColor: .simplenoteGray60Color) + output.append(string: " ") + output.append(string: suffix, foregroundColor: .simplenoteBlue60Color) + + return output + } +} diff --git a/Simplenote/AuthenticationValidator.swift b/Simplenote/AuthenticationValidator.swift index 4b3642e40..c9466bc93 100644 --- a/Simplenote/AuthenticationValidator.swift +++ b/Simplenote/AuthenticationValidator.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - AuthenticationValidator // struct AuthenticationValidator { @@ -12,7 +11,10 @@ struct AuthenticationValidator { /// Minimum Password Length: SignUp /// private let strongPasswordLength = UInt(8) - + + /// Login Code Length + /// + private let loginCodeLength = UInt(6) /// Returns the Validation Result for a given Username /// @@ -50,9 +52,16 @@ struct AuthenticationValidator { private func minimumPasswordLength(for style: Style) -> UInt { return (style == .legacy) ? legacyPasswordLength : strongPasswordLength } + + func performCodeValidation(code: String) -> Result { + if code.count >= loginCodeLength { + return .success + } + + return .codeTooShort + } } - // MARK: - Nested Types // extension AuthenticationValidator { @@ -68,10 +77,10 @@ extension AuthenticationValidator { case passwordMatchesUsername case passwordTooShort(length: UInt) case passwordContainsInvalidCharacter + case codeTooShort } } - // MARK: - Validation Results: String Conversion // extension AuthenticationValidator.Result: CustomStringConvertible { @@ -95,6 +104,8 @@ extension AuthenticationValidator.Result: CustomStringConvertible { case .passwordContainsInvalidCharacter: return NSLocalizedString("Password must not contain tabs nor newlines", comment: "Message displayed when a password contains a disallowed character") + case .codeTooShort: + return NSLocalizedString("Login Code is too short", comment: "Message displayed when a login code is too short") } } } diff --git a/Simplenote/SustainerView.swift b/Simplenote/BannerView.swift similarity index 87% rename from Simplenote/SustainerView.swift rename to Simplenote/BannerView.swift index 9f84c029f..ec98db8cc 100644 --- a/Simplenote/SustainerView.swift +++ b/Simplenote/BannerView.swift @@ -1,10 +1,9 @@ import Foundation import UIKit - -// MARK: - SustainerView +// MARK: - BannerView // -class SustainerView: UIView { +class BannerView: UIView { @IBOutlet private var backgroundView: UIView! @@ -30,12 +29,6 @@ class SustainerView: UIView { var onPress: (() -> Void)? - var isActiveSustainer = false { - didSet { - refreshInterface() - } - } - var preferredWidth: CGFloat? { didSet { guard let preferredWidth else { @@ -53,22 +46,22 @@ class SustainerView: UIView { refreshInterface() } - // MARK: - Actions @IBAction - func sustainerWasPresssed() { + func bannerWasPresssed() { onPress?() } } - // MARK: - Private API(s) // -private extension SustainerView { +private extension BannerView { - func refreshInterface() { - let style = isActiveSustainer ? Style.sustainer : Style.notSubscriber + func refreshInterface(with style: Style? = nil) { + guard let style else { + return + } titleLabel.text = style.title detailsLabel.text = style.details @@ -79,7 +72,6 @@ private extension SustainerView { } } - // MARK: - Style // private struct Style { @@ -89,8 +81,8 @@ private struct Style { let backgroundColor: UIColor } - private extension Style { + // Leaving these styles in cause we may want them back someday static var sustainer: Style { Style(title: NSLocalizedString("You are a Simplenote Sustainer", comment: "Current Sustainer Title"), details: NSLocalizedString("Thank you for your continued support", comment: "Current Sustainer Details"), @@ -106,7 +98,6 @@ private extension Style { } } - // MARK: - Metrics // private enum Metrics { diff --git a/Simplenote/SustainerView.xib b/Simplenote/BannerView.xib similarity index 94% rename from Simplenote/SustainerView.xib rename to Simplenote/BannerView.xib index f334fcbad..dc1d2af0e 100644 --- a/Simplenote/SustainerView.xib +++ b/Simplenote/BannerView.xib @@ -1,9 +1,9 @@ - + - + @@ -11,7 +11,12 @@ - + + + + + + @@ -24,13 +29,13 @@ - + - + - + @@ -38,7 +43,7 @@ - + - - - - - diff --git a/Simplenote/Classes/ActivityType.swift b/Simplenote/Classes/ActivityType.swift index 88a5cc8d2..b303b2969 100644 --- a/Simplenote/Classes/ActivityType.swift +++ b/Simplenote/Classes/ActivityType.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simplenote's User Activities // enum ActivityType: String { @@ -12,10 +11,12 @@ enum ActivityType: String { /// New Note Activity /// case newNote = "com.codality.NotationalFlow.newNote" + case newNoteShortcut = "OpenNewNoteIntent" /// Open a Note! /// case openNote = "com.codality.NotationalFlow.openNote" + case openNoteShortcut = "OpenNoteIntent" /// Open an Item that was indexed by Spotlight /// diff --git a/Simplenote/Classes/Bundle+Simplenote.swift b/Simplenote/Classes/Bundle+Simplenote.swift index cc8abbff2..3c9beacc4 100644 --- a/Simplenote/Classes/Bundle+Simplenote.swift +++ b/Simplenote/Classes/Bundle+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Bundle: Simplenote Methods // extension Bundle { @@ -33,7 +32,6 @@ extension Bundle { } } - // MARK: - Private Helpers // private extension Bundle { diff --git a/Simplenote/Classes/CGRect+Simplenote.swift b/Simplenote/Classes/CGRect+Simplenote.swift index 2d574c154..62148df79 100644 --- a/Simplenote/Classes/CGRect+Simplenote.swift +++ b/Simplenote/Classes/CGRect+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - CGRect Methods // extension CGRect { diff --git a/Simplenote/Classes/CSSearchable+Helpers.swift b/Simplenote/Classes/CSSearchable+Helpers.swift index ef12bd6a2..adb6f7433 100644 --- a/Simplenote/Classes/CSSearchable+Helpers.swift +++ b/Simplenote/Classes/CSSearchable+Helpers.swift @@ -32,8 +32,29 @@ extension CSSearchableItem { } extension CSSearchableIndex { + // MARK: - Index Notes + @objc + func indexSpotlightItems(in context: NSManagedObjectContext) { + guard Options.shared.indexNotesInSpotlight else { + return + } + + context.perform { + if let deleted = context.fetchObjects(forEntityName: "Note", with: NSPredicate(format: "deleted == YES")) as? [Note] { + self.deleteSearchableNotes(deleted) + } + + if let notes = context.fetchObjects(forEntityName: "Note", with: NSPredicate(format: "deleted == NO")) as? [Note] { + self.indexSearchableNotes(notes) + } + } + } @objc func indexSearchableNote(_ note: Note) { + guard Options.shared.indexNotesInSpotlight else { + return + } + let item = CSSearchableItem(note: note) indexSearchableItems([item]) { error in if let error = error { @@ -43,6 +64,10 @@ extension CSSearchableIndex { } @objc func indexSearchableNotes(_ notes: [Note]) { + guard Options.shared.indexNotesInSpotlight else { + return + } + let items = notes.map { return CSSearchableItem(note: $0) } @@ -69,5 +94,4 @@ extension CSSearchableIndex { } } } - } diff --git a/Simplenote/Classes/ColorStudio.swift b/Simplenote/Classes/ColorStudio.swift index 5c6d518df..5cb0cc500 100644 --- a/Simplenote/Classes/ColorStudio.swift +++ b/Simplenote/Classes/ColorStudio.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Color Studio Constants // Ref. https://github.com/Automattic/color-studio // diff --git a/Simplenote/Classes/DateFormatter+Simplenote.swift b/Simplenote/Classes/DateFormatter+Simplenote.swift index c97771d29..2d26c2791 100644 --- a/Simplenote/Classes/DateFormatter+Simplenote.swift +++ b/Simplenote/Classes/DateFormatter+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simplenote Extension // extension DateFormatter { diff --git a/Simplenote/Classes/EditorFactory.swift b/Simplenote/Classes/EditorFactory.swift index db0818718..f5a9eaaa7 100644 --- a/Simplenote/Classes/EditorFactory.swift +++ b/Simplenote/Classes/EditorFactory.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Editor Factory (!) // @objcMembers @@ -48,7 +47,6 @@ class EditorFactory: NSObject { } } - // MARK: - Constants // private struct Constants { diff --git a/Simplenote/Classes/FileManager+Simplenote.swift b/Simplenote/Classes/FileManager+Simplenote.swift index 95905de1e..782f685a7 100644 --- a/Simplenote/Classes/FileManager+Simplenote.swift +++ b/Simplenote/Classes/FileManager+Simplenote.swift @@ -19,4 +19,35 @@ extension FileManager { var sharedContainerURL: URL { containerURL(forSecurityApplicationGroupIdentifier: SimplenoteConstants.sharedGroupDomain)! } + + func recoveryDirectoryURL() -> URL? { + let dir = sharedContainerURL.appendingPathComponent(Constants.recoveryDir) + + do { + try createDirectoryIfNeeded(at: dir) + } catch { + NSLog("Could not create recovery directory because: $@", error.localizedDescription) + return nil + } + + return dir + } + + func createDirectoryIfNeeded(at url: URL, withIntermediateDirectories: Bool = true) throws { + if directoryExistsAtURL(url) { + return + } + + try createDirectory(at: url, withIntermediateDirectories: true) + } + + func directoryExistsAtURL(_ url: URL) -> Bool { + var isDir: ObjCBool = false + let exists = self.fileExists(atPath: url.path, isDirectory: &isDir) + return exists && isDir.boolValue + } +} + +private struct Constants { + static let recoveryDir = "Recovery" } diff --git a/Simplenote/Classes/IndexPath+Simplenote.swift b/Simplenote/Classes/IndexPath+Simplenote.swift index f373ea0f6..34cb5721b 100644 --- a/Simplenote/Classes/IndexPath+Simplenote.swift +++ b/Simplenote/Classes/IndexPath+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - IndexPath // extension IndexPath { diff --git a/Simplenote/Classes/InterlinkProcessor.swift b/Simplenote/Classes/InterlinkProcessor.swift index 4f6db1c0f..2580dcc2f 100644 --- a/Simplenote/Classes/InterlinkProcessor.swift +++ b/Simplenote/Classes/InterlinkProcessor.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - InterlinkProcessorDelegate // protocol InterlinkProcessorDelegate: NSObjectProtocol { @@ -10,7 +9,6 @@ protocol InterlinkProcessorDelegate: NSObjectProtocol { func interlinkProcessor(_ processor: InterlinkProcessor, insert text: String, in range: Range) } - // MARK: - InterlinkProcessor // class InterlinkProcessor: NSObject { @@ -77,7 +75,6 @@ class InterlinkProcessor: NSObject { } } - // MARK: - Presenting // private extension InterlinkProcessor { @@ -117,7 +114,6 @@ private extension InterlinkProcessor { } } - // MARK: - Scrolling // extension InterlinkProcessor { @@ -152,7 +148,6 @@ extension InterlinkProcessor { } } - // MARK: - State // private extension InterlinkProcessor { @@ -162,7 +157,6 @@ private extension InterlinkProcessor { } } - // MARK: - Settings // private enum Settings { diff --git a/Simplenote/Classes/InterlinkResultsController.swift b/Simplenote/Classes/InterlinkResultsController.swift index dbc7f1280..19933bbdd 100644 --- a/Simplenote/Classes/InterlinkResultsController.swift +++ b/Simplenote/Classes/InterlinkResultsController.swift @@ -1,7 +1,6 @@ import Foundation import SimplenoteFoundation - // MARK: - InterlinkResultsController // class InterlinkResultsController { @@ -14,7 +13,6 @@ class InterlinkResultsController { /// var maximumNumberOfResults = Settings.defaultMaximumResults - /// Designated Initializer /// init(viewContext: NSManagedObjectContext) { @@ -34,7 +32,6 @@ class InterlinkResultsController { } } - // MARK: - Private // private extension InterlinkResultsController { @@ -71,7 +68,6 @@ private extension InterlinkResultsController { } } - // MARK: - Settings! // private enum Settings { diff --git a/Simplenote/Classes/InterlinkViewController.swift b/Simplenote/Classes/InterlinkViewController.swift index c2910cf75..df41f31f3 100644 --- a/Simplenote/Classes/InterlinkViewController.swift +++ b/Simplenote/Classes/InterlinkViewController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - InterlinkViewController // class InterlinkViewController: UIViewController { @@ -25,7 +24,7 @@ class InterlinkViewController: UIViewController { var desiredHeight: CGFloat { return Metrics.maximumTableHeight } - + // MARK: - Overridden API(s) override func viewDidLoad() { @@ -35,7 +34,6 @@ class InterlinkViewController: UIViewController { } } - // MARK: - Initialization // private extension InterlinkViewController { @@ -56,7 +54,6 @@ private extension InterlinkViewController { } } - // MARK: - UITableViewDataSource // extension InterlinkViewController: UITableViewDataSource { @@ -83,7 +80,6 @@ extension InterlinkViewController: UITableViewDataSource { } } - // MARK: - UITableViewDelegate // extension InterlinkViewController: UITableViewDelegate { @@ -94,7 +90,6 @@ extension InterlinkViewController: UITableViewDelegate { } } - // MARK: - Private API(s) // private extension InterlinkViewController { @@ -108,7 +103,6 @@ private extension InterlinkViewController { } } - // MARK: - Metrics // private enum Metrics { diff --git a/Simplenote/Classes/KeyboardObservable.swift b/Simplenote/Classes/KeyboardObservable.swift index 07285baab..13a3cd2d4 100644 --- a/Simplenote/Classes/KeyboardObservable.swift +++ b/Simplenote/Classes/KeyboardObservable.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - /// Adopters of this protocol will recieve interactive keyboard-based notifications /// by implmenting the provided functions within. /// @@ -69,7 +68,6 @@ extension KeyboardObservable { } } - // MARK: - Notification + UIKeyboardInfo // private extension Notification { diff --git a/Simplenote/Classes/KeychainManager.swift b/Simplenote/Classes/KeychainManager.swift index 3fb3c3236..b3b2346c4 100644 --- a/Simplenote/Classes/KeychainManager.swift +++ b/Simplenote/Classes/KeychainManager.swift @@ -1,7 +1,5 @@ import Foundation - - // MARK: - KeychainManager // enum KeychainManager { @@ -22,8 +20,6 @@ enum KeychainManager { static var timestamp: String? } - - // MARK: - KeychainItemWrapper // @propertyWrapper @@ -64,7 +60,6 @@ struct KeychainItemWrapper { } } - // MARK: - Keychain Constants // enum SimplenoteKeychain { diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index bbd23d883..50046956e 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -1,29 +1,89 @@ import Foundation +import SimplenoteEndpoints + + +// MARK: - Notifications +// +extension NSNotification.Name { + static let magicLinkAuthWillStart = NSNotification.Name("magicLinkAuthWillStart") + static let magicLinkAuthDidSucceed = NSNotification.Name("magicLinkAuthDidSucceed") + static let magicLinkAuthDidFail = NSNotification.Name("magicLinkAuthDidFail") +} + // MARK: - MagicLinkAuthenticator // struct MagicLinkAuthenticator { let authenticator: SPAuthenticator - func handle(url: URL) { - guard url.host == Constants.host else { - return + func handle(url: URL) -> Bool { + guard AllowedHosts.all.contains(url.host) else { + return false } guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else { - return + return false + } + + if attemptLoginWithToken(queryItems: queryItems) { + return true } + return attemptLoginWithAuthCode(queryItems: queryItems) + } +} + +// MARK: - Private API(s) +// +private extension MagicLinkAuthenticator { + + @discardableResult + func attemptLoginWithToken(queryItems: [URLQueryItem]) -> Bool { guard let email = queryItems.base64DecodedValue(for: Constants.emailField), let token = queryItems.value(for: Constants.tokenField), !email.isEmpty, !token.isEmpty else { - return + return false } authenticator.authenticate(withUsername: email, token: token) + return true } -} + @discardableResult + func attemptLoginWithAuthCode(queryItems: [URLQueryItem]) -> Bool { + guard let email = queryItems.base64DecodedValue(for: Constants.emailField), + let authCode = queryItems.value(for: Constants.authCodeField), + !email.isEmpty, !authCode.isEmpty + else { + return false + } + + NSLog("[MagicLinkAuthenticator] Requesting SyncToken for \(email) and \(authCode)") + NotificationCenter.default.post(name: .magicLinkAuthWillStart, object: nil) + + Task { @MainActor in + do { + let remote = LoginRemote() + let confirmation = try await remote.requestLoginConfirmation(email: email, authCode: authCode) + + Task { @MainActor in + NSLog("[MagicLinkAuthenticator] Should auth with token \(confirmation.syncToken)") + authenticator.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) + + NotificationCenter.default.post(name: .magicLinkAuthDidSucceed, object: nil) + SPTracker.trackLoginLinkConfirmationSuccess() + } + + } catch { + NSLog("[MagicLinkAuthenticator] Magic Link TokenExchange Error: \(error)") + NotificationCenter.default.post(name: .magicLinkAuthDidFail, object: error) + SPTracker.trackLoginLinkConfirmationFailure() + } + } + + return true + } +} // MARK: - [URLQueryItem] Helper // @@ -42,11 +102,16 @@ private extension Array where Element == URLQueryItem { } } - // MARK: - Constants // +private struct AllowedHosts { + static let hostForSimplenoteSchema = "login" + static let hostForUniversalLinks = URL(string: SPCredentials.defaultEngineURL)!.host + static let all = [hostForSimplenoteSchema, hostForUniversalLinks] +} + private struct Constants { - static let host = "login" static let emailField = "email" static let tokenField = "token" + static let authCodeField = "auth_code" } diff --git a/Simplenote/Classes/MigrationsHandler.swift b/Simplenote/Classes/MigrationsHandler.swift index adaae6aa0..c78b9c792 100644 --- a/Simplenote/Classes/MigrationsHandler.swift +++ b/Simplenote/Classes/MigrationsHandler.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simplenote's Upgrade handling flows // class MigrationsHandler: NSObject { @@ -33,7 +32,6 @@ class MigrationsHandler: NSObject { } } - // MARK: - Private Methods // private extension MigrationsHandler { diff --git a/Simplenote/Classes/NSAttributedString+AuthError.swift b/Simplenote/Classes/NSAttributedString+AuthError.swift index 7e43a173c..c037668ad 100644 --- a/Simplenote/Classes/NSAttributedString+AuthError.swift +++ b/Simplenote/Classes/NSAttributedString+AuthError.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSAttributedString + AuthError Helpers // extension NSAttributedString { @@ -49,7 +48,6 @@ extension NSAttributedString { } } - // MARK: - Diagnostic Title(s) // private enum Title { @@ -58,7 +56,6 @@ private enum Title { static let response = NSLocalizedString("Response", comment: "Response Title") } - // MARK: - Text Styles // private enum Style { diff --git a/Simplenote/Classes/NSAttributedString+Simplenote.swift b/Simplenote/Classes/NSAttributedString+Simplenote.swift index a7a13aec6..1bb358653 100644 --- a/Simplenote/Classes/NSAttributedString+Simplenote.swift +++ b/Simplenote/Classes/NSAttributedString+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSMutableAttributedString Simplenote Methods // extension NSAttributedString { diff --git a/Simplenote/Classes/NSMutableAttributedString+Simplenote.swift b/Simplenote/Classes/NSMutableAttributedString+Simplenote.swift index 1b5cb2b6c..7e5327f0c 100644 --- a/Simplenote/Classes/NSMutableAttributedString+Simplenote.swift +++ b/Simplenote/Classes/NSMutableAttributedString+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - NSMutableAttributedString Methods // extension NSMutableAttributedString { diff --git a/Simplenote/Classes/NSMutableParagraphStyle+Simplenote.swift b/Simplenote/Classes/NSMutableParagraphStyle+Simplenote.swift index 1d5eafa8a..fd4c431ea 100644 --- a/Simplenote/Classes/NSMutableParagraphStyle+Simplenote.swift +++ b/Simplenote/Classes/NSMutableParagraphStyle+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSMutableParagraphStyle // extension NSMutableParagraphStyle { diff --git a/Simplenote/Classes/NSRegularExpression+Simplenote.swift b/Simplenote/Classes/NSRegularExpression+Simplenote.swift index 4fa1981e8..49501f7ba 100644 --- a/Simplenote/Classes/NSRegularExpression+Simplenote.swift +++ b/Simplenote/Classes/NSRegularExpression+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSRegularExpression (Checklists) // extension NSRegularExpression { @@ -19,7 +18,6 @@ extension NSRegularExpression { try! NSRegularExpression(pattern: "\\s*(-[ \t]+\\[[xX\\s]?\\])", options: .anchorsMatchLines) }() - /// Both our Checklist regexes look like this: `"^\\s*(EXPRESSION)"` /// This will produce two resulting NSRange(s): a top level one, including the full match, and a "capture group" /// By requesting the Range for `EXPRESSION` we'd be able to track **exactly** the location of our list marker `- [ ]` (disregarding, thus, the leading space). diff --git a/Simplenote/Classes/NSSortDescriptor+Simplenote.swift b/Simplenote/Classes/NSSortDescriptor+Simplenote.swift index bd9a756d4..74f64de1b 100644 --- a/Simplenote/Classes/NSSortDescriptor+Simplenote.swift +++ b/Simplenote/Classes/NSSortDescriptor+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - SortMode List Methods // extension NSSortDescriptor { diff --git a/Simplenote/Classes/NSString+Simplenote.swift b/Simplenote/Classes/NSString+Simplenote.swift index 6ed0f4a6e..a2301ed3e 100644 --- a/Simplenote/Classes/NSString+Simplenote.swift +++ b/Simplenote/Classes/NSString+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSString Simplenote Helpers // extension NSString { diff --git a/Simplenote/Classes/NSURL+Extensions.swift b/Simplenote/Classes/NSURL+Extensions.swift index 48e0ac5bd..de264c410 100644 --- a/Simplenote/Classes/NSURL+Extensions.swift +++ b/Simplenote/Classes/NSURL+Extensions.swift @@ -1,6 +1,5 @@ import Foundation - /// This extension contains several helper-additions to Cocoa NSURL class. /// extension NSURL { diff --git a/Simplenote/Classes/NSURL+Links.swift b/Simplenote/Classes/NSURL+Links.swift index 0840ebb76..491e8c20b 100644 --- a/Simplenote/Classes/NSURL+Links.swift +++ b/Simplenote/Classes/NSURL+Links.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSURL + Interlink // extension NSURL { diff --git a/Simplenote/Classes/NSUserActivity+Shortcuts.swift b/Simplenote/Classes/NSUserActivity+Shortcuts.swift index efb9ed8f0..0cb027b0d 100644 --- a/Simplenote/Classes/NSUserActivity+Shortcuts.swift +++ b/Simplenote/Classes/NSUserActivity+Shortcuts.swift @@ -1,7 +1,6 @@ import Foundation import CoreSpotlight - // MARK: - Simplenote's NSUserActivities // extension NSUserActivity { diff --git a/Simplenote/Classes/NSUserActivity+Simplenote.swift b/Simplenote/Classes/NSUserActivity+Simplenote.swift index 18b50d549..109283911 100644 --- a/Simplenote/Classes/NSUserActivity+Simplenote.swift +++ b/Simplenote/Classes/NSUserActivity+Simplenote.swift @@ -3,8 +3,6 @@ import CoreSpotlight import Intents import MobileCoreServices - - // MARK: - NSUserActivity Convenience Methods // extension NSUserActivity { diff --git a/Simplenote/Classes/Note+Properties.swift b/Simplenote/Classes/Note+Properties.swift index 67378f5b3..e871ec6a3 100644 --- a/Simplenote/Classes/Note+Properties.swift +++ b/Simplenote/Classes/Note+Properties.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Note // extension Note { diff --git a/Simplenote/Classes/NotesListController.swift b/Simplenote/Classes/NotesListController.swift index a841f2732..53f828b37 100644 --- a/Simplenote/Classes/NotesListController.swift +++ b/Simplenote/Classes/NotesListController.swift @@ -2,7 +2,6 @@ import Foundation import SimplenoteFoundation import SimplenoteSearch - // MARK: - NotesListController // class NotesListController: NSObject { @@ -72,7 +71,6 @@ class NotesListController: NSObject { /// var onBatchChanges: ((_ sectionsChangeset: ResultsSectionsChangeset, _ rowsChangeset: ResultsObjectsChangeset) -> Void)? - /// Designated Initializer /// init(viewContext: NSManagedObjectContext) { @@ -83,7 +81,6 @@ class NotesListController: NSObject { } } - // MARK: - Public API // extension NotesListController { @@ -168,7 +165,6 @@ extension NotesListController { } } - // MARK: - Search API // extension NotesListController { @@ -202,7 +198,6 @@ extension NotesListController { } } - // MARK: - Convenience APIs // extension NotesListController { @@ -217,7 +212,6 @@ extension NotesListController { } } - // MARK: - Private API: ResultsController Refreshing // private extension NotesListController { @@ -239,7 +233,6 @@ private extension NotesListController { } } - // MARK: - Private API: Realtime Refreshing // private extension NotesListController { diff --git a/Simplenote/Classes/NotesListFilter.swift b/Simplenote/Classes/NotesListFilter.swift index bf1522765..43584ecd3 100644 --- a/Simplenote/Classes/NotesListFilter.swift +++ b/Simplenote/Classes/NotesListFilter.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NotesListFilter // enum NotesListFilter: Equatable { @@ -10,7 +9,6 @@ enum NotesListFilter: Equatable { case tag(name: String) } - // MARK: - NotesListFilter: Public API // extension NotesListFilter { @@ -36,7 +34,6 @@ extension NotesListFilter { } } - /// Filter's visible Title /// var title: String { diff --git a/Simplenote/Classes/NotesListSection.swift b/Simplenote/Classes/NotesListSection.swift index dcb3e5240..819cbae8b 100644 --- a/Simplenote/Classes/NotesListSection.swift +++ b/Simplenote/Classes/NotesListSection.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NotesListSection // struct NotesListSection { diff --git a/Simplenote/Classes/NotesListState.swift b/Simplenote/Classes/NotesListState.swift index 61f9a4375..5faee6aed 100644 --- a/Simplenote/Classes/NotesListState.swift +++ b/Simplenote/Classes/NotesListState.swift @@ -8,7 +8,6 @@ enum NotesListState: Equatable { case searching(query: SearchQuery) } - // MARK: - NotesListState: Public API // extension NotesListState { diff --git a/Simplenote/Classes/OptionsViewController.swift b/Simplenote/Classes/OptionsViewController.swift index a7c7a7a6b..c433b70ba 100644 --- a/Simplenote/Classes/OptionsViewController.swift +++ b/Simplenote/Classes/OptionsViewController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import SimplenoteFoundation - // MARK: - OptionsControllerDelegate // protocol OptionsControllerDelegate: AnyObject { @@ -11,7 +10,6 @@ protocol OptionsControllerDelegate: AnyObject { func optionsControllerDidPressTrash(_ sender: OptionsViewController) } - // MARK: - OptionsViewController // class OptionsViewController: UIViewController { @@ -77,7 +75,6 @@ class OptionsViewController: UIViewController { } } - // MARK: - Initialization // private extension OptionsViewController { @@ -113,7 +110,6 @@ private extension OptionsViewController { } } - // MARK: - EntityObserverDelegate // extension OptionsViewController: EntityObserverDelegate { @@ -128,7 +124,6 @@ extension OptionsViewController: EntityObserverDelegate { } } - // MARK: - UITableViewDelegate // extension OptionsViewController: UITableViewDelegate { @@ -143,7 +138,6 @@ extension OptionsViewController: UITableViewDelegate { } } - // MARK: - UITableViewDataSource // extension OptionsViewController: UITableViewDataSource { @@ -158,7 +152,6 @@ extension OptionsViewController: UITableViewDataSource { } } - // MARK: - Helper API(s) // private extension OptionsViewController { @@ -172,7 +165,6 @@ private extension OptionsViewController { } } - // MARK: - Building Cells // private extension OptionsViewController { @@ -288,7 +280,6 @@ private extension OptionsViewController { } } - // MARK: - Publishing // extension OptionsViewController { @@ -311,7 +302,6 @@ extension OptionsViewController { } - // MARK: - Action Handlers // private extension OptionsViewController { @@ -412,7 +402,6 @@ private extension OptionsViewController { } } - // MARK: - SPCollaboratorDelegate // extension OptionsViewController: SPCollaboratorDelegate { @@ -431,14 +420,12 @@ extension OptionsViewController: SPCollaboratorDelegate { } } - // MARK: - Section: Defines a TableView Section // private struct Section { let rows: [Row] } - // MARK: - TableView Rows // private enum Row { diff --git a/Simplenote/Classes/PassthruView.swift b/Simplenote/Classes/PassthruView.swift index c7e0dd146..8299aa0bd 100644 --- a/Simplenote/Classes/PassthruView.swift +++ b/Simplenote/Classes/PassthruView.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - PassthruView: Doesn't capture tap events performed over itself! // class PassthruView: UIView { diff --git a/Simplenote/Classes/PinLock/PinLockVerifyController.swift b/Simplenote/Classes/PinLock/PinLockVerifyController.swift index e97d8e1ad..87d46405c 100644 --- a/Simplenote/Classes/PinLock/PinLockVerifyController.swift +++ b/Simplenote/Classes/PinLock/PinLockVerifyController.swift @@ -18,6 +18,8 @@ final class PinLockVerifyController: PinLockBaseController, PinLockController { private let application: ApplicationStateProvider private weak var delegate: PinLockVerifyControllerDelegate? + private var onSuccesBlocks = [(() -> Void)]() + init(pinLockManager: SPPinLockManager = .shared, application: ApplicationStateProvider = UIApplication.shared, delegate: PinLockVerifyControllerDelegate) { @@ -37,6 +39,7 @@ final class PinLockVerifyController: PinLockBaseController, PinLockController { } delegate?.pinLockVerifyControllerDidComplete(self) + runOnSuccessBlocks() } func handleCancellation() { @@ -50,6 +53,21 @@ final class PinLockVerifyController: PinLockBaseController, PinLockController { func applicationDidBecomeActive() { verifyBiometry() } + + private func runOnSuccessBlocks() { + onSuccesBlocks.forEach({ + $0() + }) + removeSuccesBlocks() + } + + func addOnSuccesBlock(block: @escaping () -> Void) { + onSuccesBlocks.append(block) + } + + func removeSuccesBlocks() { + onSuccesBlocks.removeAll() + } } // MARK: - Biometry @@ -71,6 +89,7 @@ private extension PinLockVerifyController { if success { self.delegate?.pinLockVerifyControllerDidComplete(self) + self.runOnSuccessBlocks() } } } diff --git a/Simplenote/Classes/PinLock/PinLockViewController.swift b/Simplenote/Classes/PinLock/PinLockViewController.swift index 25c2ac776..124a6225f 100644 --- a/Simplenote/Classes/PinLock/PinLockViewController.swift +++ b/Simplenote/Classes/PinLock/PinLockViewController.swift @@ -14,7 +14,7 @@ class PinLockViewController: UIViewController { @IBOutlet private weak var headerStackView: UIStackView! @IBOutlet private var keypadButtons: [UIButton] = [] - private let controller: PinLockController + let controller: PinLockController private var inputValues: [Int] = [] { didSet { progressView.progress = inputValues.count diff --git a/Simplenote/Classes/PopoverViewController.swift b/Simplenote/Classes/PopoverViewController.swift index a4e3af352..4e7894a46 100644 --- a/Simplenote/Classes/PopoverViewController.swift +++ b/Simplenote/Classes/PopoverViewController.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - PopoverViewController // class PopoverViewController: UIViewController { @@ -43,7 +42,6 @@ class PopoverViewController: UIViewController { /// var onViewSizeChange: (() -> Void)? - init(viewController: UIViewController) { self.viewController = viewController super.init(nibName: nil, bundle: nil) @@ -72,7 +70,6 @@ class PopoverViewController: UIViewController { } } - // MARK: - Initialization // private extension PopoverViewController { @@ -108,7 +105,6 @@ private extension PopoverViewController { } } - // MARK: - Metrics // private enum Metrics { diff --git a/Simplenote/Classes/RemoteConstants.swift b/Simplenote/Classes/RemoteConstants.swift deleted file mode 100644 index 4a081c0c2..000000000 --- a/Simplenote/Classes/RemoteConstants.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -// MARK: Remote Constants -// -struct RemoteConstants { - struct Method { - static let GET = "GET" - static let POST = "POST" - } - - static let timeout: TimeInterval = 30 -} diff --git a/Simplenote/Classes/SPAboutViewController.swift b/Simplenote/Classes/SPAboutViewController.swift index b3ce35f12..e9739e734 100644 --- a/Simplenote/Classes/SPAboutViewController.swift +++ b/Simplenote/Classes/SPAboutViewController.swift @@ -1,6 +1,5 @@ import UIKit - class SPAboutViewController: UIViewController { private let tableView = HuggableTableView(frame: .zero, style: .grouped) @@ -31,7 +30,6 @@ class SPAboutViewController: UIViewController { } } - // MARK: - Configuration // private extension SPAboutViewController { @@ -212,7 +210,6 @@ private extension SPAboutViewController { } } - // MARK: - UITableViewDataSource Conformance // extension SPAboutViewController: UITableViewDataSource { @@ -249,7 +246,6 @@ extension SPAboutViewController: UITableViewDataSource { } } - // MARK: - UITableViewDelegate Conformance // extension SPAboutViewController: UITableViewDelegate { @@ -315,7 +311,6 @@ private extension SPAboutViewController { } } - // MARK: - Constants // private enum Constants { @@ -338,7 +333,7 @@ private enum Constants { static let termsString = NSLocalizedString("Terms of Service", comment: "Simplenote terms of service") static let termsURLString = "https://simplenote.com/terms/" static let californiaString = NSLocalizedString("Privacy Notice for California Users", comment: "Simplenote terms of service") - static let californiaURLString = "https://automattic.com/privacy/#california-consumer-privacy-act-ccpa" + static let californiaURLString = "https://automattic.com/privacy/#us-privacy-laws" static let titles: [String] = [ NSLocalizedString("Blog", comment: "The Simplenote blog"), diff --git a/Simplenote/Classes/SPAuthError.swift b/Simplenote/Classes/SPAuthError.swift index 8f3dbdf64..66c80ef8b 100644 --- a/Simplenote/Classes/SPAuthError.swift +++ b/Simplenote/Classes/SPAuthError.swift @@ -1,4 +1,5 @@ import Foundation +import SimplenoteEndpoints // MARK: - SPAuthError @@ -11,6 +12,9 @@ enum SPAuthError: Error { case compromisedPassword case unverifiedEmail case tooManyAttempts + case generic + case requestNotFound + case invalidCode case unknown(statusCode: Int, response: String?, error: Error?) } @@ -19,10 +23,28 @@ enum SPAuthError: Error { // extension SPAuthError { - /// Returns the SPAuthError matching a given Simperium Login Error Code + /// Returns an AuthError matching a RemoteError (Login) Counterpart + /// + init(loginRemoteError: RemoteError) { + self = SPAuthError(loginErrorCode: loginRemoteError.statusCode, response: loginRemoteError.response, error: loginRemoteError.networkError) + } + + /// Returns an AuthError matching a RemoteError (Signup) Counterpart + /// + init(signupRemoteError: RemoteError) { + self = SPAuthError(signupErrorCode: signupRemoteError.statusCode, response: signupRemoteError.response, error: signupRemoteError.networkError) + } + + /// Returns the AuthError matching a given Simperium Login Error Code /// init(loginErrorCode: Int, response: String?, error: Error?) { switch loginErrorCode { + case .zero: + self = .network + case 400 where response == Constants.requestNotFound: + self = .requestNotFound + case 400 where response == Constants.invalidCode: + self = .invalidCode case 401 where response == Constants.compromisedPassword: self = .compromisedPassword case 401: @@ -36,10 +58,12 @@ extension SPAuthError { } } - /// Returns the SPAuthError matching a given Simperium Signup Error Code + /// Returns the AuthError matching a given a Signup Error Code /// init(signupErrorCode: Int, response: String?, error: Error?) { switch signupErrorCode { + case .zero: + self = .network case 401: self = .signupBadCredentials case 409: @@ -92,7 +116,11 @@ extension SPAuthError { return NSLocalizedString("You must verify your email before being able to login.", comment: "Error for un verified email") case .tooManyAttempts: return NSLocalizedString("Too many login attempts. Try again later.", comment: "Error message for too many login attempts") - case .unknown: + case .requestNotFound: + return NSLocalizedString("Your authentication code has expired. Please request a new one", comment: "Error message for Invalid Login Code") + case .invalidCode: + return NSLocalizedString("The code you've entered is not correct. Please try again", comment: "Error message for Invalid Login Code") + default: return NSLocalizedString("We're having problems. Please try again soon.", comment: "Generic error") } } @@ -101,4 +129,6 @@ extension SPAuthError { private struct Constants { static let compromisedPassword = "compromised password" static let requiresVerification = "verification required" + static let requestNotFound = "request-not-found" + static let invalidCode = "invalid-code" } diff --git a/Simplenote/Classes/SPAuthHandler.swift b/Simplenote/Classes/SPAuthHandler.swift index 6f655d441..5d0fcdd9c 100644 --- a/Simplenote/Classes/SPAuthHandler.swift +++ b/Simplenote/Classes/SPAuthHandler.swift @@ -1,6 +1,6 @@ import Foundation import SafariServices - +import SimplenoteEndpoints // MARK: - SPAuthHandler // @@ -10,7 +10,6 @@ class SPAuthHandler { /// private let simperiumService: SPAuthenticator - /// Designated Initializer. /// /// - Parameter simperiumService: Reference to a valid SPAuthenticator instance. @@ -19,8 +18,6 @@ class SPAuthHandler { self.simperiumService = simperiumService } - - /// Authenticates against the Simperium Backend. /// /// - Note: Errors are mapped into SPAuthError Instances @@ -39,7 +36,6 @@ class SPAuthHandler { }) } - /// Validates a set of credentials against the Simperium Backend. /// /// - Note: This API is meant to be used to verify an unsecured set of credentials, before presenting the Reset Password UI. @@ -58,6 +54,31 @@ class SPAuthHandler { }) } + /// Requests an Authentication Email + /// + @MainActor + func requestLoginEmail(username: String) async throws { + let remote = LoginRemote() + do { + try await remote.requestLoginEmail(email: username) + } catch let remoteError as RemoteError { + throw SPAuthError(loginRemoteError: remoteError) + } + } + + /// Performs LogIn the User with an Authentication Code + /// + @MainActor + func loginWithCode(username: String, code: String) async throws { + let remote = LoginRemote() + do { + let confirmation = try await remote.requestLoginConfirmation(email: username, authCode: code.uppercased()) + simperiumService.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) + + } catch let remoteError as RemoteError { + throw SPAuthError(loginRemoteError: remoteError) + } + } /// Registers a new user in the Simperium Backend. /// @@ -68,25 +89,17 @@ class SPAuthHandler { /// - onCompletion: Closure to be executed on completion /// func signupWithCredentials(username: String, onCompletion: @escaping (SPAuthError?) -> Void) { - SignupRemote().signup(with: username) { (result) in + SignupRemote().requestSignup(email: username) { (result) in switch result { case .success: onCompletion(nil) - case .failure(let error): - onCompletion(self.authenticationError(for: error)) + case .failure(let remoteError): + let error = SPAuthError(signupRemoteError: remoteError) + onCompletion(error) } } } - private func authenticationError(for remoteError: RemoteError) -> SPAuthError { - switch remoteError { - case .network: - return SPAuthError.network - case .requestError(let statusCode, let error): - return SPAuthError(signupErrorCode: statusCode, response: error?.localizedDescription, error: error) - } - } - /// Presents the Password Reset (Web) Interface /// func presentPasswordReset(from sourceViewController: UIViewController, username: String) { diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index a293f0f16..e53ce56b8 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -1,6 +1,8 @@ import Foundation import UIKit import SafariServices +import SwiftUI +import SimplenoteEndpoints // MARK: - SPAuthViewController @@ -15,6 +17,14 @@ class SPAuthViewController: UIViewController { /// @IBOutlet private var stackView: UIStackView! + /// # Header: Container View + /// + @IBOutlet private var headerContainerView: UIView! + + /// # Header: Title Label + /// + @IBOutlet private var headerLabel: SPLabel! + /// # Email: Input Field /// @IBOutlet private var emailInputView: SPTextInputView! { @@ -65,14 +75,36 @@ class SPAuthViewController: UIViewController { passwordWarningLabel.isHidden = true } } + + /// # Code: Input Field + /// + @IBOutlet private var codeInputView: SPTextInputView! { + didSet { + codeInputView.placeholder = AuthenticationStrings.codePlaceholder + codeInputView.passwordRules = UITextInputPasswordRules(descriptor: SimplenoteConstants.passwordRules) + codeInputView.returnKeyType = .done + codeInputView.textColor = .simplenoteGray80Color + codeInputView.delegate = self + codeInputView.textContentType = .oneTimeCode + } + } + + /// # Code: Warning Label + /// + @IBOutlet private var codeWarningLabel: SPLabel! { + didSet { + codeWarningLabel.textInsets = AuthenticationConstants.warningInsets + codeWarningLabel.textColor = .simplenoteRed60Color + codeWarningLabel.isHidden = true + } + } /// # Primary Action: LogIn / SignUp /// @IBOutlet private var primaryActionButton: SPSquaredButton! { didSet { - primaryActionButton.setTitle(mode.primaryActionText, for: .normal) primaryActionButton.setTitleColor(.white, for: .normal) - primaryActionButton.addTarget(self, action: mode.primaryActionSelector, for: .touchUpInside) + primaryActionButton.accessibilityIdentifier = "Main Action" } } @@ -81,6 +113,7 @@ class SPAuthViewController: UIViewController { @IBOutlet private var primaryActionSpinner: UIActivityIndicatorView! { didSet { primaryActionSpinner.style = .medium + primaryActionSpinner.color = .white } } @@ -88,18 +121,9 @@ class SPAuthViewController: UIViewController { /// @IBOutlet private var secondaryActionButton: UIButton! { didSet { - if let title = mode.secondaryActionText { - secondaryActionButton.setTitle(title, for: .normal) - secondaryActionButton.setTitleColor(.simplenoteBlue60Color, for: .normal) - } - - if let attributedTitle = mode.secondaryActionAttributedText { - secondaryActionButton.setAttributedTitle(attributedTitle, for: .normal) - } - + secondaryActionButton.setTitleColor(.simplenoteBlue60Color, for: .normal) secondaryActionButton.titleLabel?.textAlignment = .center secondaryActionButton.titleLabel?.numberOfLines = 0 - secondaryActionButton.addTarget(self, action: mode.secondaryActionSelector, for: .touchUpInside) } } @@ -118,6 +142,52 @@ class SPAuthViewController: UIViewController { return button }() + /// # Actions Separator: Container + /// + @IBOutlet private var actionsSeparator: UIView! + + /// # Tertiary Separator: Label (Or) + /// + @IBOutlet private var actionsSeparatorLabel: UILabel! { + didSet { + actionsSeparatorLabel.text = AuthenticationStrings.separatorText + } + } + + /// # Tertiary Action: WPCOM SSO + /// + @IBOutlet private var tertiaryActionButton: SPSquaredButton! { + didSet { + tertiaryActionButton.setTitleColor(.white, for: .normal) + tertiaryActionButton.backgroundColor = .simplenoteWPBlue50Color + } + } + + /// # Tertiary Action: + /// + @IBOutlet private var quaternaryActionButton: SPSquaredButton! { + didSet { + quaternaryActionButton.setTitleColor(.black, for: .normal) + quaternaryActionButton.backgroundColor = .clear + quaternaryActionButton.layer.borderWidth = 1 + quaternaryActionButton.layer.borderColor = UIColor.black.cgColor + } + } + + /// # All of the Visible InputView(s) + /// + private var visibleInputViews: [SPTextInputView] { + [emailInputView, passwordInputView, codeInputView].filter { inputView in + inputView.isHidden == false + } + } + + /// # All of the Action Views + /// + private var allActionViews: [UIButton] { + [primaryActionButton, secondaryActionButton, tertiaryActionButton, quaternaryActionButton] + } + /// # Simperium's Authenticator Instance /// private let controller: SPAuthHandler @@ -129,61 +199,59 @@ class SPAuthViewController: UIViewController { /// # Indicates if we've got valid Credentials. Doesn't display any validation warnings onscreen /// private var isInputValid: Bool { - return performUsernameValidation() == .success && performPasswordValidation() == .success + performInputElementsValidation().values.allSatisfy { result in + result == .success + } } /// # Returns the EmailInputView's Text: When empty this getter returns an empty string, instead of nil /// private var email: String { - get { - return emailInputView.text ?? String() - } - set { - emailInputView.text = newValue - } + state.username } /// # Returns the PasswordInputView's Text: When empty this getter returns an empty string, instead of nil /// private var password: String { - get { - return passwordInputView.text ?? String() - } - set { - passwordInputView.text = newValue - } + state.password } /// Indicates if we must nuke the Password Field's contents whenever the App becomes active /// private var mustResetPasswordField = false - /// # Authentication Mode: Signup or Login + /// # Authentication Mode: Signup / Login with Password / Login with Link /// - let mode: AuthenticationMode + private let mode: AuthenticationMode + + /// # State: Allows us to preserve State, when dealing with a multi staged flow + /// + private var state: AuthenticationState { + didSet { + ensureStylesMatchValidationState() + ensureWarningsAreDismissedWhenNeeded() + } + } /// Indicates if the Extended Debug Mode is enabled /// var debugEnabled = false - - /// NSCodable Required Initializer /// required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - /// Designated Initializer /// - init(controller: SPAuthHandler, mode: AuthenticationMode = .login) { + init(controller: SPAuthHandler, mode: AuthenticationMode = .requestLoginCode, state: AuthenticationState = .init()) { self.controller = controller self.mode = mode + self.state = state super.init(nibName: nil, bundle: nil) } - // MARK: - Overridden Methods override func viewDidLoad() { @@ -191,7 +259,10 @@ class SPAuthViewController: UIViewController { setupNavigationController() startListeningToNotifications() - passwordInputView.isHidden = mode.isPasswordHidden + refreshHeaderView() + refreshInputViews() + refreshActionViews() + reloadInputViewsFromState() // hiding text from back button navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) @@ -209,11 +280,11 @@ class SPAuthViewController: UIViewController { // Note: running becomeFirstResponder in `viewWillAppear` has the weird side effect of breaking caret // repositioning in the Text Field. Seriously. // Ref. https://github.com/Automattic/simplenote-ios/issues/453 - self.emailInputView.becomeFirstResponder() + // + visibleInputViews.first?.becomeFirstResponder() } } - // MARK: - Actions // extension SPAuthViewController { @@ -225,7 +296,6 @@ extension SPAuthViewController { } } - // MARK: - Interface // private extension SPAuthViewController { @@ -246,6 +316,59 @@ private extension SPAuthViewController { navigationController?.setNavigationBarHidden(false, animated: true) } + func refreshHeaderView() { + let headerText = mode.buildHeaderText(email: email) + + headerLabel.attributedText = headerText + headerContainerView.isHidden = headerText == nil + } + + func refreshInputViews() { + let inputElements = mode.inputElements + + emailInputView.isHidden = !inputElements.contains(.username) + passwordInputView.isHidden = !inputElements.contains(.password) + codeInputView.isHidden = !inputElements.contains(.code) + actionsSeparator.isHidden = !inputElements.contains(.actionSeparator) + } + + func refreshActionViews() { + let viewMap: [AuthenticationActionName: UIButton] = [ + .primary: primaryActionButton, + .secondary: secondaryActionButton, + .tertiary: tertiaryActionButton, + .quaternary: quaternaryActionButton + ] + + for actionView in allActionViews { + actionView.isHidden = true + } + + for descriptor in mode.actions { + guard let actionView = viewMap[descriptor.name] else { + assertionFailure() + continue + } + + if let title = descriptor.text { + actionView.setTitle(title, for: .normal) + } + + if let attributedTitle = descriptor.attributedText { + actionView.setAttributedTitle(attributedTitle, for: .normal) + } + + actionView.addTarget(self, action: descriptor.selector, for: .touchUpInside) + actionView.isHidden = false + } + } + + func reloadInputViewsFromState() { + emailInputView.text = state.username + passwordInputView.text = state.password + codeInputView.text = state.code + } + func ensureStylesMatchValidationState() { primaryActionButton.backgroundColor = isInputValid ? .simplenoteBlue50Color : .simplenoteGray20Color } @@ -276,22 +399,26 @@ private extension SPAuthViewController { } } - // MARK: - Actions // -private extension SPAuthViewController { +extension SPAuthViewController { /// Whenever the input is Valid, we'll perform the Primary Action /// func performPrimaryActionIfPossible() { - guard isInputValid else { + guard ensureWarningsAreOnScreenWhenNeeded() else { return } - perform(mode.primaryActionSelector) - } + guard let primaryActionDescriptor = mode.actions.first(where: { $0.name == .primary}) else { + assertionFailure() + return + } - @IBAction func performLogIn() { + perform(primaryActionDescriptor.selector) + } + + @IBAction func performLogInWithPassword() { guard ensureWarningsAreOnScreenWhenNeeded() else { return } @@ -303,7 +430,86 @@ private extension SPAuthViewController { performSimperiumAuthentication() } + + @IBAction func requestLogInCode() { + guard ensureWarningsAreOnScreenWhenNeeded() else { + return + } + + Task { @MainActor in + await requestLogInCodeAsync() + } + } + + @MainActor + private func requestLogInCodeAsync() async { + lockdownInterface() + + do { + try await controller.requestLoginEmail(username: email) + self.presentCodeInterface() + SPTracker.trackLoginLinkRequested() + + } catch SPAuthError.tooManyAttempts { + self.presentPasswordInterfaceWithRateLimitingHeader() + + } catch { + let error = error as? SPAuthError ?? .generic + self.handleError(error: error) + } + + self.unlockInterface() + } + /// Requests a new Login Code, without pushing any secondary UI on success + /// + @IBAction func requestLogInCodeAndDontPush() { + Task { @MainActor in + await self.requestLogInCodeAndDontPushAsync() + } + } + + /// Requests a new Login Code, without pushing any secondary UI on success. Asynchronous API! + /// + @MainActor + private func requestLogInCodeAndDontPushAsync() async { + do { + try await controller.requestLoginEmail(username: email) + } catch { + let error = error as? SPAuthError ?? .generic + self.handleError(error: error) + } + + SPTracker.trackLoginLinkRequested() + } + + @IBAction func performLogInWithCode() { + guard ensureWarningsAreOnScreenWhenNeeded() else { + return + } + + Task { @MainActor in + lockdownInterface() + + do { + try await controller.loginWithCode(username: state.username, code: state.code) + SPTracker.trackLoginLinkConfirmationSuccess() + } catch { + /// Errors will always be of the `SPAuthError` type. Let's switch to Typed Errors, as soon as we migrate over to Xcode 16 + let error = error as? SPAuthError ?? .generic + self.handleError(error: error) + + SPTracker.trackLoginLinkConfirmationFailure() + } + + unlockInterface() + } + } + + @IBAction func performLogInWithWPCOM() { + WPAuthHandler.presentWordPressSSO(from: self) + } + @IBAction func performSignUp() { guard ensureWarningsAreOnScreenWhenNeeded() else { return @@ -325,7 +531,7 @@ private extension SPAuthViewController { self.unlockInterface() } } - + @IBAction func presentPasswordReset() { controller.presentPasswordReset(from: self, username: email) } @@ -339,12 +545,40 @@ private extension SPAuthViewController { safariViewController.modalPresentationStyle = .overFullScreen present(safariViewController, animated: true, completion: nil) } + + @IBAction func presentPasswordInterface() { + presentPasswordInterfaceWithHeader(header: AuthenticationStrings.loginWithEmailEmailHeader) + } + + @IBAction func presentPasswordInterfaceWithRateLimitingHeader() { + presentPasswordInterfaceWithHeader(header: AuthenticationStrings.loginWithEmailLimitHeader) + } - private func presentSignupVerification() { + @IBAction func presentPasswordInterfaceWithHeader(header: String?, includeUsername: Bool = false) { + let viewController = SPAuthViewController(controller: controller, mode: .loginWithPassword(header: header, includeUsername: includeUsername), state: state) + navigationController?.pushViewController(viewController, animated: true) + } + + @IBAction func presentUsernameAndPasswordInterface() { + presentPasswordInterfaceWithHeader(header: nil, includeUsername: true) + } +} + + +// MARK: - Navigation Helpers +// +private extension SPAuthViewController { + + func presentSignupVerification() { let viewController = SignupVerificationViewController(email: email) viewController.title = title navigationController?.pushViewController(viewController, animated: true) } + + func presentCodeInterface() { + let viewController = SPAuthViewController(controller: controller, mode: .loginWithCode, state: state) + navigationController?.pushViewController(viewController, animated: true) + } } @@ -380,7 +614,6 @@ private extension SPAuthViewController { } } - // MARK: - Password Reset Flow // private extension SPAuthViewController { @@ -414,6 +647,8 @@ private extension SPAuthViewController { presentPasswordCompromisedError(error: error) case .unverifiedEmail: presentUserUnverifiedError(error: error, email: email) + case .requestNotFound: + presentLoginCodeExpiredError() case .unknown(let statusCode, let response, let error) where debugEnabled: let details = NSAttributedString.stringFromNetworkError(statusCode: statusCode, response: response, error: error) presentDebugDetails(details: details) @@ -441,6 +676,14 @@ private extension SPAuthViewController { present(alertController, animated: true, completion: nil) } + + func presentLoginCodeExpiredError() { + let alertController = UIAlertController.buildLoginCodeNotFoundAlert { + self.navigationController?.popViewController(animated: true) + } + + present(alertController, animated: true, completion: nil) + } func presentUserUnverifiedError(error: SPAuthError, email: String) { let alertController = UIAlertController(title: error.title, message: error.message, preferredStyle: .alert) @@ -489,11 +732,8 @@ private extension SPAuthViewController { } // Prefill the LoginViewController - let loginViewController = SPAuthViewController(controller: controller, mode: .login) - + let loginViewController = SPAuthViewController(controller: controller, mode: .loginWithPassword(), state: state) loginViewController.loadViewIfNeeded() - loginViewController.email = email - loginViewController.password = password // Swap the current VC var updatedHierarchy = navigationController.viewControllers.filter { ($0 is SPAuthViewController) == false } @@ -502,7 +742,6 @@ private extension SPAuthViewController { } } - // MARK: - Warning Labels // private extension SPAuthViewController { @@ -516,7 +755,12 @@ private extension SPAuthViewController { passwordWarningLabel.text = string refreshPasswordInput(inErrorState: true) } - + + func displayCodeValidationWarning(_ string: String) { + codeWarningLabel.text = string + refreshCodeInput(inErrorState: true) + } + func dismissEmailValidationWarning() { refreshEmailInput(inErrorState: false) } @@ -524,6 +768,10 @@ private extension SPAuthViewController { func dismissPasswordValidationWarning() { refreshPasswordInput(inErrorState: false) } + + func dismissCodeValidationWarning() { + refreshCodeInput(inErrorState: false) + } func refreshEmailInput(inErrorState: Bool) { emailWarningLabel.animateVisibility(isHidden: !inErrorState) @@ -534,25 +782,36 @@ private extension SPAuthViewController { passwordWarningLabel.animateVisibility(isHidden: !inErrorState) passwordInputView.inErrorState = inErrorState } + + func refreshCodeInput(inErrorState: Bool) { + codeWarningLabel.animateVisibility(isHidden: !inErrorState) + codeInputView.inErrorState = inErrorState + } } - // MARK: - Validation // private extension SPAuthViewController { - func performUsernameValidation() -> AuthenticationValidator.Result { - validator.performUsernameValidation(username: email) - } - /// When we're in `.login` mode, password requirements are relaxed (since we must allow users with old passwords to sign in). /// That's where the `validationStyle` comes in. /// - func performPasswordValidation() -> AuthenticationValidator.Result { - guard !mode.isPasswordHidden else { - return .success + func performInputElementsValidation() -> [AuthenticationInputElements: AuthenticationValidator.Result] { + var result = [AuthenticationInputElements: AuthenticationValidator.Result]() + + if mode.inputElements.contains(.username) { + result[.username] = validator.performUsernameValidation(username: email) } - return validator.performPasswordValidation(username: email, password: password, style: mode.validationStyle) + + if mode.inputElements.contains(.password) { + result[.password] = validator.performPasswordValidation(username: email, password: password, style: mode.validationStyle) + } + + if mode.inputElements.contains(.code) { + result[.code] = validator.performCodeValidation(code: state.code) + } + + return result } /// Whenever we're in `.login` mode, and the password is valid in `.legacy` terms (but invalid in `.strong` mode), we must request the @@ -562,29 +821,45 @@ private extension SPAuthViewController { validator.performPasswordValidation(username: email, password: password, style: .strong) != .success } + /// Validates all of the Input Fields, and presents warnings accordingly. + /// - Returns true: When all validations are passed + /// func ensureWarningsAreOnScreenWhenNeeded() -> Bool { - let usernameValidationResult = performUsernameValidation() - let passwordValidationResult = performPasswordValidation() + let validationMap = performInputElementsValidation() - if usernameValidationResult != .success { - displayEmailValidationWarning(usernameValidationResult.description) + if let result = validationMap[.username], result != .success { + displayEmailValidationWarning(result.description) } - if passwordValidationResult != .success { - displayPasswordValidationWarning(passwordValidationResult.description) + if let result = validationMap[.password], result != .success { + displayPasswordValidationWarning(result.description) + } + + if let result = validationMap[.code], result != .success { + displayCodeValidationWarning(result.description) } - return usernameValidationResult == .success && passwordValidationResult == .success + return validationMap.values.allSatisfy { result in + result == .success + } } + /// Validates all of the Input Fields, and dismisses validation warnings, when possible + /// func ensureWarningsAreDismissedWhenNeeded() { - if performUsernameValidation() == .success { + let validationMap = performInputElementsValidation() + + if validationMap[.username] == .success { dismissEmailValidationWarning() } - if performPasswordValidation() == .success { + if validationMap[.password] == .success { dismissPasswordValidationWarning() } + + if validationMap[.code] == .success { + dismissCodeValidationWarning() + } } } @@ -594,85 +869,33 @@ private extension SPAuthViewController { extension SPAuthViewController: SPTextInputViewDelegate { func textInputDidChange(_ textInput: SPTextInputView) { - ensureStylesMatchValidationState() - ensureWarningsAreDismissedWhenNeeded() - } - - func textInputShouldReturn(_ textInput: SPTextInputView) -> Bool { switch textInput { case emailInputView: - switch performUsernameValidation() { - case .success: - if mode.isPasswordHidden { - performPrimaryActionIfPossible() - } else { - passwordInputView.becomeFirstResponder() - } - - case let error: - displayEmailValidationWarning(error.description) - } - + state.username = textInput.text ?? "" case passwordInputView: - switch performPasswordValidation() { - case .success: - performPrimaryActionIfPossible() - - case let error: - displayPasswordValidationWarning(error.description) - } - + state.password = textInput.text ?? "" + case codeInputView: + state.code = textInput.text ?? "" default: break } + } + func textInputShouldReturn(_ textInput: SPTextInputView) -> Bool { + performPrimaryActionIfPossible() return false } -} - - -// MARK: - AuthenticationMode: Signup / Login -// -struct AuthenticationMode { - let title: String - let validationStyle: AuthenticationValidator.Style - let primaryActionSelector: Selector - let primaryActionText: String - let secondaryActionSelector: Selector - let secondaryActionText: String? - let secondaryActionAttributedText: NSAttributedString? - let isPasswordHidden: Bool -} + func textInput(_ textInput: SPTextInputView, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard textInput == codeInputView else { + return true + } -// MARK: - Default Operation Modes -// -extension AuthenticationMode { - - /// Login Operation Mode: Contains all of the strings + delegate wirings, so that the AuthUI handles authentication scenarios. - /// - static var login: AuthenticationMode { - return .init(title: AuthenticationStrings.loginTitle, - validationStyle: .legacy, - primaryActionSelector: #selector(SPAuthViewController.performLogIn), - primaryActionText: AuthenticationStrings.loginPrimaryAction, - secondaryActionSelector: #selector(SPAuthViewController.presentPasswordReset), - secondaryActionText: AuthenticationStrings.loginSecondaryAction, - secondaryActionAttributedText: nil, - isPasswordHidden: false) - } + let maxLength = 6 + let currentString = (textInput.text ?? "") as NSString + let newString = currentString.replacingCharacters(in: range, with: string) - /// Signup Operation Mode: Contains all of the strings + delegate wirings, so that the AuthUI handles user account creation scenarios. - /// - static var signup: AuthenticationMode { - return .init(title: AuthenticationStrings.signupTitle, - validationStyle: .strong, - primaryActionSelector: #selector(SPAuthViewController.performSignUp), - primaryActionText: AuthenticationStrings.signupPrimaryAction, - secondaryActionSelector: #selector(SPAuthViewController.presentTermsOfService), - secondaryActionText: nil, - secondaryActionAttributedText: AuthenticationStrings.signupSecondaryAttributedAction, - isPasswordHidden: true) + return newString.count <= maxLength } } @@ -680,18 +903,15 @@ extension AuthenticationMode { // MARK: - Authentication Strings // private enum AuthenticationStrings { - static let loginTitle = NSLocalizedString("Log In", comment: "LogIn Interface Title") - static let loginPrimaryAction = NSLocalizedString("Log In", comment: "LogIn Action") - static let loginSecondaryAction = NSLocalizedString("Forgotten password?", comment: "Password Reset Action") - static let signupTitle = NSLocalizedString("Sign Up", comment: "SignUp Interface Title") - static let signupPrimaryAction = NSLocalizedString("Sign Up", comment: "SignUp Action") - static let signupSecondaryActionPrefix = NSLocalizedString("By creating an account you agree to our", comment: "Terms of Service Legend *PREFIX*: printed in dark color") - static let signupSecondaryActionSuffix = NSLocalizedString("Terms and Conditions", comment: "Terms of Service Legend *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue") + static let separatorText = NSLocalizedString("Or", comment: "Or, used as a separator between Actions") static let emailPlaceholder = NSLocalizedString("Email", comment: "Email TextField Placeholder") static let passwordPlaceholder = NSLocalizedString("Password", comment: "Password TextField Placeholder") + static let codePlaceholder = NSLocalizedString("Code", comment: "Code TextField Placeholder") static let acceptActionText = NSLocalizedString("Accept", comment: "Accept Action") static let cancelActionText = NSLocalizedString("Cancel", comment: "Cancel Action") static let loginActionText = NSLocalizedString("Log In", comment: "Log In Action") + static let loginWithEmailEmailHeader = NSLocalizedString("Enter the password for the account {{EMAIL}}", comment: "Header for Login With Password. Please preserve the {{EMAIL}} substring") + static let loginWithEmailLimitHeader = NSLocalizedString("Log in with email failed, please enter your password", comment: "Header for Enter Password UI, when the user performed too many requests") static let compromisedAlertCancel = NSLocalizedString("Cancel", comment: "Cancel action for password alert") static let compromisedAlertReset = NSLocalizedString("Change Password", comment: "Change password action") static let unverifiedCancelText = NSLocalizedString("Ok", comment: "Email unverified alert dismiss") @@ -719,26 +939,6 @@ private enum PasswordInsecureString { } -// MARK: - Strings >> Authenticated Strings Convenience Properties -// -private extension AuthenticationStrings { - - /// Returns a properly formatted Secondary Action String for Signup - /// - static var signupSecondaryAttributedAction: NSAttributedString { - let output = NSMutableAttributedString(string: String(), attributes: [ - .font: UIFont.preferredFont(forTextStyle: .subheadline) - ]) - - output.append(string: signupSecondaryActionPrefix, foregroundColor: .simplenoteGray60Color) - output.append(string: " ") - output.append(string: signupSecondaryActionSuffix, foregroundColor: .simplenoteBlue60Color) - - return output - } -} - - // MARK: - Authentication Constants // private enum AuthenticationConstants { diff --git a/Simplenote/Classes/SPAuthViewController.xib b/Simplenote/Classes/SPAuthViewController.xib index ac1bcbb84..347f23d54 100644 --- a/Simplenote/Classes/SPAuthViewController.xib +++ b/Simplenote/Classes/SPAuthViewController.xib @@ -1,24 +1,33 @@ - + - + + + + + + + + + + @@ -28,10 +37,30 @@ - + - - + + + + + + + + + + + + + + + + + @@ -41,13 +70,13 @@ - - + + @@ -57,15 +86,31 @@ + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -114,8 +227,44 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Simplenote/Classes/SPBlurEffectView.swift b/Simplenote/Classes/SPBlurEffectView.swift index aaa63f9cf..627be38ce 100644 --- a/Simplenote/Classes/SPBlurEffectView.swift +++ b/Simplenote/Classes/SPBlurEffectView.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPBlurEffectView: Reacts automatically to UserInterfaceStyle Changes // @objc @@ -24,7 +23,6 @@ class SPBlurEffectView: UIVisualEffectView { } } - // MARK: - Initializers convenience init() { @@ -41,7 +39,6 @@ class SPBlurEffectView: UIVisualEffectView { } } - // MARK: - Private Methods // private extension SPBlurEffectView { @@ -65,7 +62,6 @@ private extension SPBlurEffectView { } } - // MARK: - Static Methods // extension SPBlurEffectView { diff --git a/Simplenote/Classes/SPContactsManager.swift b/Simplenote/Classes/SPContactsManager.swift index 2ee4e2d6b..ed995f809 100644 --- a/Simplenote/Classes/SPContactsManager.swift +++ b/Simplenote/Classes/SPContactsManager.swift @@ -9,7 +9,6 @@ import Foundation import Contacts - /// Contacts Helper /// class SPContactsManager: NSObject { @@ -34,7 +33,6 @@ class SPContactsManager: NSObject { return CNContactStore.authorizationStatus(for: .contacts) } - /// Deinitializer /// deinit { @@ -75,8 +73,6 @@ class SPContactsManager: NSObject { } } - - /// Contacts Helper /// private extension SPContactsManager { diff --git a/Simplenote/Classes/SPDefaultTableViewCell.swift b/Simplenote/Classes/SPDefaultTableViewCell.swift index 83283a099..f7e0e1652 100644 --- a/Simplenote/Classes/SPDefaultTableViewCell.swift +++ b/Simplenote/Classes/SPDefaultTableViewCell.swift @@ -7,7 +7,6 @@ import Foundation - // MARK: - UITableViewCell with the `.default` Style // class SPDefaultTableViewCell: UITableViewCell { @@ -31,7 +30,6 @@ class SPDefaultTableViewCell: UITableViewCell { } } - // MARK: - Private // private extension SPDefaultTableViewCell { diff --git a/Simplenote/Classes/SPDiagnosticsViewController.swift b/Simplenote/Classes/SPDiagnosticsViewController.swift index a7be9be7e..aae96be78 100644 --- a/Simplenote/Classes/SPDiagnosticsViewController.swift +++ b/Simplenote/Classes/SPDiagnosticsViewController.swift @@ -17,7 +17,6 @@ class SPDiagnosticsViewController: UIViewController { /// var attributedText: NSAttributedString? - // MARK: - Overridden Methods override func viewDidLoad() { @@ -28,7 +27,6 @@ class SPDiagnosticsViewController: UIViewController { } } - // MARK: - Interface // private extension SPDiagnosticsViewController { @@ -55,7 +53,6 @@ private extension SPDiagnosticsViewController { } } - // MARK: - Actions // private extension SPDiagnosticsViewController { diff --git a/Simplenote/Classes/SPEditorTapRecognizerDelegate.swift b/Simplenote/Classes/SPEditorTapRecognizerDelegate.swift index 784afc656..f08beb2f7 100644 --- a/Simplenote/Classes/SPEditorTapRecognizerDelegate.swift +++ b/Simplenote/Classes/SPEditorTapRecognizerDelegate.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPEditorTapRecognizerDelegate // Since the dawn of time, UITextView itself was set as (our own) TapGestureRecognizer delegate. // As per iOS 14, the new (superclass) implementation is not allowing our Tap recognizer to work simultaneously with its (internal) @@ -21,7 +20,6 @@ class SPEditorTapRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { @objc weak var excludedView: UIView? - /// TextView only performs linkification when the `editable` flag is disabled. /// We're allowing Edition by means of a TapGestureRecognizer, which also allows us to deal with Tap events performed over TextAttachments /// diff --git a/Simplenote/Classes/SPFeedbackManager.swift b/Simplenote/Classes/SPFeedbackManager.swift index 574a71cd3..9dff32fc9 100644 --- a/Simplenote/Classes/SPFeedbackManager.swift +++ b/Simplenote/Classes/SPFeedbackManager.swift @@ -2,7 +2,6 @@ import Foundation import MessageUI import SafariServices - // MARK: - SPFeedbackManager // @objcMembers @@ -36,7 +35,6 @@ class SPFeedbackManager: NSObject { } } - // MARK: - MFMailComposeViewControllerDelegate // extension SPFeedbackManager: MFMailComposeViewControllerDelegate { diff --git a/Simplenote/Classes/SPInteractivePushPopAnimationController.m b/Simplenote/Classes/SPInteractivePushPopAnimationController.m index 60d71a893..a7ee90c41 100644 --- a/Simplenote/Classes/SPInteractivePushPopAnimationController.m +++ b/Simplenote/Classes/SPInteractivePushPopAnimationController.m @@ -60,6 +60,10 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer CGPoint location = [panGestureRecognizer locationInView:navigationBar]; CGPoint translation = [panGestureRecognizer translationInView:navigationBar]; BOOL isLeftTranslation = translation.x < 0; + BOOL isRightTranslation = translation.x > 0; + BOOL isLTR = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute: topViewController.view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionLeftToRight; + + BOOL isSwipeTranslation = isLTR ? isLeftTranslation : isRightTranslation; // Ignore touches within the navigation bar if (CGRectContainsPoint(navigationBar.bounds, location)) { @@ -67,7 +71,7 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer } // TopViewController conforms to SPInteractivePushViewControllerProvider AND We're Swiping Right to Left: Support Push! - if ([topViewController conformsToProtocol:@protocol(SPInteractivePushViewControllerProvider)] && isLeftTranslation) { + if ([topViewController conformsToProtocol:@protocol(SPInteractivePushViewControllerProvider)] && isSwipeTranslation) { UIViewController *pushProviderController = (UIViewController *)topViewController; CGPoint locationInView = [panGestureRecognizer locationInView:pushProviderController.view]; if (![pushProviderController interactivePushPopAnimationControllerShouldBeginPush:self touchPoint:locationInView]) { diff --git a/Simplenote/Classes/SPLabel.swift b/Simplenote/Classes/SPLabel.swift index d82950dc0..6f2490f6b 100644 --- a/Simplenote/Classes/SPLabel.swift +++ b/Simplenote/Classes/SPLabel.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPLabel: Standard UIKit Label with extra properties. And batteries! // @IBDesignable @@ -11,7 +10,6 @@ class SPLabel: UILabel { /// @IBInspectable var textInsets = UIEdgeInsets.zero - // MARK: - Overrides override func drawText(in rect: CGRect) { diff --git a/Simplenote/Classes/SPNoteEditorViewController+Extensions.swift b/Simplenote/Classes/SPNoteEditorViewController+Extensions.swift index 9774d4dc3..48fc05943 100644 --- a/Simplenote/Classes/SPNoteEditorViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteEditorViewController+Extensions.swift @@ -1,7 +1,6 @@ import Foundation import CoreSpotlight - // MARK: - Overridden Methods // extension SPNoteEditorViewController { @@ -26,7 +25,6 @@ extension SPNoteEditorViewController { } } - // MARK: - Interface Initialization // extension SPNoteEditorViewController { @@ -120,7 +118,6 @@ extension SPNoteEditorViewController { } } - // MARK: - Layout // extension SPNoteEditorViewController { @@ -241,7 +238,6 @@ extension SPNoteEditorViewController: KeyboardObservable { } } - // MARK: - Voiceover Support // extension SPNoteEditorViewController { @@ -277,7 +273,6 @@ extension SPNoteEditorViewController { } } - // MARK: - State Restoration // extension SPNoteEditorViewController { @@ -304,7 +299,6 @@ extension SPNoteEditorViewController { } } - // MARK: - History // extension SPNoteEditorViewController { @@ -354,7 +348,6 @@ extension SPNoteEditorViewController { } } - // MARK: - History Delegate // extension SPNoteEditorViewController: SPNoteHistoryControllerDelegate { @@ -375,7 +368,6 @@ extension SPNoteEditorViewController: SPNoteHistoryControllerDelegate { } } - // MARK: - SPCardPresentationControllerDelegate // extension SPNoteEditorViewController: SPCardPresentationControllerDelegate { @@ -500,8 +492,6 @@ private extension SPNoteEditorViewController { } } - - // MARK: - Services // extension SPNoteEditorViewController { @@ -581,7 +571,6 @@ extension SPNoteEditorViewController { } } - // MARK: - Editor // private extension SPNoteEditorViewController { @@ -629,7 +618,6 @@ private extension SPNoteEditorViewController { } } - // MARK: - OptionsControllerDelegate // extension SPNoteEditorViewController: OptionsControllerDelegate { @@ -663,7 +651,6 @@ extension SPNoteEditorViewController: OptionsControllerDelegate { } } - // MARK: - Accessibility // private extension SPNoteEditorViewController { @@ -673,7 +660,6 @@ private extension SPNoteEditorViewController { } } - // MARK: - Actions // extension SPNoteEditorViewController { @@ -717,7 +703,6 @@ extension SPNoteEditorViewController { } } - // MARK: - InterlinkProcessorDelegate // extension SPNoteEditorViewController: InterlinkProcessorDelegate { @@ -727,7 +712,6 @@ extension SPNoteEditorViewController: InterlinkProcessorDelegate { } } - // MARK: - Tags // extension SPNoteEditorViewController { @@ -777,7 +761,6 @@ extension SPNoteEditorViewController { } } - // MARK: - NoteEditorTagListViewControllerDelegate // extension SPNoteEditorViewController: NoteEditorTagListViewControllerDelegate { @@ -799,7 +782,6 @@ extension SPNoteEditorViewController: NoteEditorTagListViewControllerDelegate { } } - // MARK: - Style // extension SPNoteEditorViewController { @@ -961,8 +943,6 @@ extension SPNoteEditorViewController { title: Localization.Shortcuts.toggleMarkdown)) } - - if searching { commands.append(contentsOf: [ UIKeyCommand(input: "g", @@ -1038,7 +1018,6 @@ extension SPNoteEditorViewController { } } - // MARK: - Scroll position // extension SPNoteEditorViewController { @@ -1066,7 +1045,6 @@ extension SPNoteEditorViewController { } } - // MARK: - Metrics // private enum Metrics { @@ -1081,7 +1059,6 @@ private enum Metrics { static let additionalTagViewAndEditorCollisionDistance: CGFloat = 16.0 } - // MARK: - Localization // private enum Localization { diff --git a/Simplenote/Classes/SPNoteEditorViewController.m b/Simplenote/Classes/SPNoteEditorViewController.m index 05ca31329..57c4f9ab0 100644 --- a/Simplenote/Classes/SPNoteEditorViewController.m +++ b/Simplenote/Classes/SPNoteEditorViewController.m @@ -386,13 +386,15 @@ - (void)bounceMarkdownPreview [snapshot addSubview:fakeMarkdownPreviewSnapshot]; // Offset the fake markdown preview off to the right of the screen + bool isLTR = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute: self.view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionLeftToRight; + int multiplier = isLTR ? 1 : -1; CGRect frame = snapshot.frame; - frame.origin.x = CGRectGetWidth(self.view.bounds); + frame.origin.x = CGRectGetWidth(self.view.bounds) * multiplier; fakeMarkdownPreviewSnapshot.frame = frame; self.noteEditorTextView.hidden = YES; - CGFloat bounceDistance = -40; + CGFloat bounceDistance = -40 * multiplier; // Do a nice bounce animation [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ diff --git a/Simplenote/Classes/SPNoteListViewController+Extensions.swift b/Simplenote/Classes/SPNoteListViewController+Extensions.swift index 08ee2a98e..dbd1d8eff 100644 --- a/Simplenote/Classes/SPNoteListViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteListViewController+Extensions.swift @@ -149,7 +149,6 @@ extension SPNoteListViewController { } } - // MARK: - Internal Methods // extension SPNoteListViewController { @@ -158,8 +157,8 @@ extension SPNoteListViewController { /// @objc func refreshTableViewTopInsets() { - tableView.contentInset.top = searchBarStackView.frame.height - tableView.verticalScrollIndicatorInsets.top = searchBarStackView.frame.height + tableView.contentInset.top = searchBarStackView.frame.height / 1.75 + tableView.verticalScrollIndicatorInsets.top = searchBarStackView.frame.height / 1.75 } /// Scrolls to the First Row whenever the flag `mustScrollToFirstRow` was set to true @@ -403,7 +402,6 @@ extension SPNoteListViewController { } } - // MARK: - UIScrollViewDelegate // extension SPNoteListViewController: UIScrollViewDelegate { @@ -417,7 +415,6 @@ extension SPNoteListViewController: UIScrollViewDelegate { } } - // MARK: - UITableViewDataSource // extension SPNoteListViewController: UITableViewDataSource { @@ -467,7 +464,6 @@ extension SPNoteListViewController: UITableViewDataSource { } } - // MARK: - UITableViewDelegate // extension SPNoteListViewController: UITableViewDelegate { @@ -583,7 +579,6 @@ extension SPNoteListViewController: UITableViewDelegate { } } - // MARK: - TableViewCell(s) Initialization // private extension SPNoteListViewController { @@ -641,7 +636,6 @@ private extension SPNoteListViewController { } } - // MARK: - Table // extension SPNoteListViewController { @@ -703,6 +697,24 @@ extension SPNoteListViewController { return count > 0 ? Localization.selectedTitle(with: count) : notesListController.filter.title }() } + + @objc + func restoreSelectedRowsAfterBackgrounding() { + guard let selectedNotesEnteringBackground, selectedNotesEnteringBackground.isEmpty == false else { + return + } + + selectRows(with: selectedNotesEnteringBackground) + self.selectedNotesEnteringBackground = [] + } + + func selectRows(with indexPaths: [IndexPath]) { + guard isEditing else { + return + } + + indexPaths.forEach({ tableView.selectRow(at: $0, animated: false, scrollPosition: .none) }) + } } // MARK: - Row Actions @@ -772,12 +784,10 @@ private extension SPNoteListViewController { } shareAction.accessibilityLabel = ActionTitle.share - return [trashAction, pinAction, copyAction, shareAction] } } - // MARK: - UIMenu // private extension SPNoteListViewController { @@ -826,7 +836,6 @@ private extension SPNoteListViewController { } } - // MARK: - Services // private extension SPNoteListViewController { @@ -893,7 +902,6 @@ private extension SPNoteListViewController { } } - // MARK: - Services (Internal) // extension SPNoteListViewController { @@ -908,7 +916,6 @@ extension SPNoteListViewController { } } - // MARK: - Keyboard Handling // extension SPNoteListViewController { @@ -957,7 +964,6 @@ extension SPNoteListViewController { } } - // MARK: - Search Action Handlers // extension SPNoteListViewController { @@ -981,7 +987,6 @@ extension SPNoteListViewController { } } - // MARK: - Keyboard // extension SPNoteListViewController { @@ -1047,7 +1052,6 @@ private extension SPNoteListViewController { } } - // MARK: - Private Types // private enum ActionTitle { @@ -1092,7 +1096,6 @@ private enum Localization { static let untagged = NSLocalizedString("No untagged notes", comment: "Message shown in note list when no notes are untagged") - static func tagged(with tag: String) -> String { return String(format: NSLocalizedString("No notes tagged “%@”", comment: "Message shown in note list when no notes are tagged with the provided tag. Parameter: %@ - tag"), tag) } diff --git a/Simplenote/Classes/SPNoteListViewController.h b/Simplenote/Classes/SPNoteListViewController.h index ad058d11c..b9fc20b7e 100644 --- a/Simplenote/Classes/SPNoteListViewController.h +++ b/Simplenote/Classes/SPNoteListViewController.h @@ -33,6 +33,7 @@ @property (nonatomic, strong) UIBarButtonItem *selectAllButton; @property (nonatomic, weak) Note *selectedNote; @property (nonatomic) BOOL navigatingUsingKeyboard; +@property (nonatomic) NSArray *selectedNotesEnteringBackground; - (void)update; - (void)openNote:(Note *)note animated:(BOOL)animated; diff --git a/Simplenote/Classes/SPNoteListViewController.m b/Simplenote/Classes/SPNoteListViewController.m index 512f0d3f4..f2148c518 100644 --- a/Simplenote/Classes/SPNoteListViewController.m +++ b/Simplenote/Classes/SPNoteListViewController.m @@ -63,6 +63,8 @@ - (instancetype)init { [self refreshStyle]; [self update]; self.mustScrollToFirstRow = YES; + + self.selectedNotesEnteringBackground = [NSArray new]; } return self; @@ -192,6 +194,10 @@ - (void)startListeningToNotifications { // Themes [nc addObserver:self selector:@selector(themeDidChange) name:SPSimplenoteThemeChangedNotification object:nil]; + + // App Background + [nc addObserver:self selector:@selector(appWillEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [nc addObserver:self selector:@selector(appWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; } - (void)condensedPreferenceWasUpdated:(id)sender @@ -213,6 +219,14 @@ - (void)themeDidChange { [self refreshStyle]; } +- (void)appWillEnterBackground { + self.selectedNotesEnteringBackground = self.tableView.indexPathsForSelectedRows; +} + +- (void)appWillEnterForeground { + [self restoreSelectedRowsAfterBackgrounding]; +} + - (void)refreshStyle { // Refresh the containerView's backgroundColor self.view.backgroundColor = [UIColor simplenoteBackgroundColor]; @@ -520,10 +534,7 @@ - (void)update [self refreshTitle]; [self refreshSearchBar]; - BOOL isTrashOnScreen = self.isDeletedFilterActive; - [self refreshEmptyTrashState]; - self.tableView.allowsSelection = !isTrashOnScreen; [self displayPlaceholdersIfNeeded]; [self refershNavigationButtons]; diff --git a/Simplenote/Classes/SPNoteTableViewCell.swift b/Simplenote/Classes/SPNoteTableViewCell.swift index d1cb75cff..c12ab9d1e 100644 --- a/Simplenote/Classes/SPNoteTableViewCell.swift +++ b/Simplenote/Classes/SPNoteTableViewCell.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPNoteTableViewCell // @objcMembers @@ -137,8 +136,6 @@ class SPNoteTableViewCell: UITableViewCell { .zero } - - /// Deinitializer /// deinit { @@ -152,7 +149,6 @@ class SPNoteTableViewCell: UITableViewCell { startListeningToNotifications() } - // MARK: - Overridden Methods override func awakeFromNib() { @@ -222,7 +218,6 @@ class SPNoteTableViewCell: UITableViewCell { } } - // MARK: - Private Methods: Initialization // private extension SPNoteTableViewCell { @@ -262,7 +257,6 @@ private extension SPNoteTableViewCell { } } - // MARK: - Notifications // private extension SPNoteTableViewCell { @@ -284,7 +278,6 @@ private extension SPNoteTableViewCell { } } - // MARK: - Private Methods: Skinning // private extension SPNoteTableViewCell { @@ -317,7 +310,6 @@ private extension SPNoteTableViewCell { } } - // MARK: - SPNoteTableViewCell // extension SPNoteTableViewCell { @@ -353,8 +345,16 @@ extension SPNoteTableViewCell { return max(result.rounded(.up), Constants.minCellHeight) } -} + // Ref: https://github.com/Automattic/simplenote-ios/issues/1307 + // setHighlighted gets called on press down and on setSelected. This causes the highlighting to change on press down but selection is on press up + // By only updating the highlighting if the cell is the cell is selected fixes this issue + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + if isSelected { + super.setHighlighted(highlighted, animated: animated) + } + } +} // MARK: - Cell Styles // @@ -445,8 +445,6 @@ private enum Style { } } - - // MARK: - NSAttributedString Private Methods // private extension NSAttributedString { diff --git a/Simplenote/Classes/SPOnboardingViewController.swift b/Simplenote/Classes/SPOnboardingViewController.swift index 5914ab578..c7c453ed2 100644 --- a/Simplenote/Classes/SPOnboardingViewController.swift +++ b/Simplenote/Classes/SPOnboardingViewController.swift @@ -1,7 +1,7 @@ import Foundation import UIKit import SafariServices - +import SwiftUI // MARK: - SPOnboardingViewController // @@ -43,7 +43,6 @@ class SPOnboardingViewController: UIViewController, SPAuthenticationInterface { /// var authenticator: SPAuthenticator? - // MARK: - Overriden Properties override var supportedInterfaceOrientations: UIInterfaceOrientationMask { @@ -54,7 +53,6 @@ class SPOnboardingViewController: UIViewController, SPAuthenticationInterface { return false } - // MARK: - Overridden Methods override func viewDidLoad() { @@ -74,7 +72,6 @@ class SPOnboardingViewController: UIViewController, SPAuthenticationInterface { } } - // MARK: - Private // private extension SPOnboardingViewController { @@ -141,7 +138,6 @@ private extension SPOnboardingViewController { } } - // MARK: - Actions // private extension SPOnboardingViewController { @@ -153,20 +149,7 @@ private extension SPOnboardingViewController { @IBAction func loginWasPressed() { - let sheetController = SPSheetController() - - sheetController.setTitleForButton0(title: OnboardingStrings.loginWithEmailText) - sheetController.setTitleForButton1(title: OnboardingStrings.loginWithWpcomText) - - sheetController.onClickButton0 = { [weak self] in - self?.presentAuthenticationInterface(mode: .login) - } - - sheetController.onClickButton1 = { [weak self] in - self?.presentWordpressSSO() - } - - sheetController.present(from: self) + presentAuthenticationInterface(mode: .requestLoginCode) } @IBAction @@ -189,21 +172,18 @@ private extension SPOnboardingViewController { viewController.debugEnabled = debugEnabled navigationController?.pushViewController(viewController, animated: true) } - - func presentWordpressSSO() { - WPAuthHandler.presentWordPressSSO(from: self) - } } - // MARK: - Actions // private extension SPOnboardingViewController { func startListeningToNotifications() { let name = NSNotification.Name(rawValue: kSignInErrorNotificationName) - - NotificationCenter.default.addObserver(self, selector: #selector(handleSignInError), name: name, object: nil) + + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(handleSignInError), name: name, object: nil) + nc.addObserver(self, selector: #selector(handleMagicLinkAuthDidFail), name: .magicLinkAuthDidFail, object: nil) } @objc func handleSignInError(note: Notification) { @@ -216,7 +196,42 @@ private extension SPOnboardingViewController { presentedViewController?.dismiss(animated: true, completion: nil) present(alertController, animated: true, completion: nil) } + + @objc func handleMagicLinkAuthDidFail() { + DispatchQueue.main.async { + self.presentMagicLinkInvalidView() + } + } +} + +// MARK: - Magic Link Helpers +// +private extension SPOnboardingViewController { + + /// Presents the Invalid Magic Link UI + /// + private func presentMagicLinkInvalidView() { + var rootView = MagicLinkInvalidView() + rootView.onPressRequestNewLink = { [weak self] in + self?.presentAuthenticationInterfaceIfNeeded(mode: .requestLoginCode) + } + + let hostingController = UIHostingController(rootView: rootView) + hostingController.modalPresentationStyle = .formSheet + hostingController.sheetPresentationController?.detents = [.medium()] + + let presenter = navigationController?.visibleViewController ?? self + presenter.present(hostingController, animated: true) + } + + /// Dismisses all of the presented ViewControllers, and pushes the Authentication UI with the specified mode. + /// - Note: Whenever the required AuthUI is already onscreen, we'll do nothing + /// + func presentAuthenticationInterfaceIfNeeded(mode: AuthenticationMode) { + navigationController?.popToRootViewController(animated: true) + presentAuthenticationInterface(mode: mode) + } } @@ -229,11 +244,8 @@ private struct OnboardingStrings { static let signupText = NSLocalizedString("Sign Up", comment: "Signup Action") static let loginText = NSLocalizedString("Log In", comment: "Login Action") static let headerText = NSLocalizedString("The simplest way to keep notes.", comment: "Onboarding Header Text") - static let loginWithEmailText = NSLocalizedString("Log in with email", comment: "Presents the regular Email signin flow") - static let loginWithWpcomText = NSLocalizedString("Log in with WordPress.com", comment: "Allows the user to SignIn using their WPCOM Account") } - private struct SignInError { static let title = NSLocalizedString("Couldn't Sign In", comment: "Alert dialog title displayed on sign in error") static let genericErrorText = NSLocalizedString("An error was encountered while signing in.", comment: "Sign in error message") diff --git a/Simplenote/Classes/SPPlaceholderView.swift b/Simplenote/Classes/SPPlaceholderView.swift index 2d8d1d506..ce5867cf3 100644 --- a/Simplenote/Classes/SPPlaceholderView.swift +++ b/Simplenote/Classes/SPPlaceholderView.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPPlaceholderView // @objc @@ -23,7 +22,6 @@ class SPPlaceholderView: UIView { } } - /// Placeholder Image /// private lazy var imageView: UIImageView = { @@ -61,7 +59,6 @@ class SPPlaceholderView: UIView { return stackView }() - // MARK: - Initializers init() { @@ -83,7 +80,6 @@ class SPPlaceholderView: UIView { } } - // MARK: - Private Methods // private extension SPPlaceholderView { diff --git a/Simplenote/Classes/SPRatingsPromptView.swift b/Simplenote/Classes/SPRatingsPromptView.swift index 2a84e7877..b3b7dd8e1 100644 --- a/Simplenote/Classes/SPRatingsPromptView.swift +++ b/Simplenote/Classes/SPRatingsPromptView.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPRatingsPromptDelegate // @objc @@ -13,7 +12,6 @@ protocol SPRatingsPromptDelegate: AnyObject { func dismissRatingsUI() } - // MARK: - SPRatingsPromptView // class SPRatingsPromptView: UIView { @@ -55,7 +53,6 @@ class SPRatingsPromptView: UIView { @objc weak var delegate: SPRatingsPromptDelegate? - // MARK: - Lifecycle deinit { @@ -74,7 +71,6 @@ class SPRatingsPromptView: UIView { } } - // MARK: - Private Methods // private extension SPRatingsPromptView { @@ -126,7 +122,6 @@ private extension SPRatingsPromptView { } } - // MARK: - Notifications // private extension SPRatingsPromptView { @@ -175,7 +170,6 @@ private extension SPRatingsPromptView { } } - // MARK: - Constants // private struct Settings { @@ -183,7 +177,6 @@ private struct Settings { static let buttonCornerRAdius = CGFloat(4) } - // MARK: - Ratings State // private enum State { @@ -192,7 +185,6 @@ private enum State { case disliked } - private extension State { var title: String { diff --git a/Simplenote/Classes/SPSectionHeaderView.swift b/Simplenote/Classes/SPSectionHeaderView.swift index 4b53e363c..489219844 100644 --- a/Simplenote/Classes/SPSectionHeaderView.swift +++ b/Simplenote/Classes/SPSectionHeaderView.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPSectionHeaderView // @objcMembers @@ -29,7 +28,6 @@ class SPSectionHeaderView: UITableViewHeaderFooterView { /// private lazy var nib = UINib(nibName: type(of: self).classNameWithoutNamespaces, bundle: nil) - // MARK: - Overridden Methods override init(reuseIdentifier: String?) { @@ -50,7 +48,6 @@ class SPSectionHeaderView: UITableViewHeaderFooterView { } } - // MARK: - Private Methods // private extension SPSectionHeaderView { diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 964772b94..7c0bb73a9 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -1,48 +1,26 @@ import UIKit +import SimplenoteEndpoints // MARK: - Subscriber UI // -extension SPSettingsViewController { - - private var isActiveSustainer: Bool { - SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber - } - @objc - func setupTableHeaderView() { - let sustainerView: SustainerView = SustainerView.instantiateFromNib() - sustainerView.appliesTopInset = true - sustainerView.onPress = { [weak self] in - self?.presentSubscriptionAlertIfNeeded() - } - - tableView.tableHeaderView = sustainerView - } +// The methods in this extension are for showing and displaying the Sustainer banner in settings. We are discontinuing Sustainer, +// but we may still want to use the banner in the future, so marking these methods fileprivate and have removed their callers +fileprivate extension SPSettingsViewController { @objc func refreshTableHeaderView() { - guard let headerView = tableView.tableHeaderView as? SustainerView else { + guard let headerView = tableView.tableHeaderView as? BannerView else { return } - headerView.isActiveSustainer = isActiveSustainer headerView.preferredWidth = tableView.frame.width headerView.adjustSizeForCompressedLayout() tableView.tableHeaderView = headerView } - @available(iOS 15.0, *) - func presentSubscriptionAlertIfNeeded() { - if isActiveSustainer { - return - } - - let sustainerAlertController = UIAlertController.buildSustainerAlert() - present(sustainerAlertController, animated: true) - } - @objc func restorePurchases() { if StoreManager.shared.isActiveSubscriber { @@ -62,6 +40,22 @@ extension SPSettingsViewController { } } +extension SPSettingsViewController { + @objc + var showSustainerSwitch: Bool { + let preferences = SPAppDelegate.shared().simperium.preferencesObject() + return preferences.isActiveSubscriber || preferences.wasSustainer + } + + @objc + func sustainerSwitchDidChangeValue(sender: UISwitch) { + let isOn = sender.isOn + + let iconName = isOn ? SPSustainerAppIconName : nil + UserDefaults.standard.set(isOn, forKey: .useSustainerIcon) + UIApplication.shared.setAlternateIconName(iconName) + } +} // MARK: - Pin // @@ -95,6 +89,7 @@ extension SPSettingsViewController: PinLockSetupControllerDelegate { func pinLockSetupControllerDidComplete(_ controller: PinLockSetupController) { SPTracker.trackSettingsPinlockEnabled(true) dismissPresentedViewController() + SPPinLockManager.shared.shouldUseBiometry = true } func pinLockSetupControllerDidCancel(_ controller: PinLockSetupController) { @@ -195,12 +190,7 @@ extension SPSettingsViewController { } private func handleError(_ error: RemoteError) { - switch error { - case .network: - NoticeController.shared.present(NoticeFactory.networkError()) - case .requestError: - presentRequestErrorAlert() - } + presentRequestErrorAlert() } private func presentSuccessAlert(for user: SPUser) { @@ -214,6 +204,21 @@ extension SPSettingsViewController { alert.addDefaultActionWithTitle(AccountDeletion.ok) self.present(alert, animated: true, completion: nil) } + + @objc + func presentIndexRemovalAlert() { + DispatchQueue.main.async { + let alert = UIAlertController(title: IndexAlert.title, message: IndexAlert.message, preferredStyle: .alert) + alert.addDefaultActionWithTitle(IndexAlert.okay) + self.present(alert, animated: true, completion: nil) + } + } +} + +private struct IndexAlert { + static let title = NSLocalizedString("Index Removed", comment: "Alert title letting user know their search index has been removed") + static let message = NSLocalizedString("Spotlight history may still appear in search results, but notes have be unindexed", comment: "Details that some results may still appear in searches on device") + static let okay = NSLocalizedString("Okay", comment: "confirm button title") } private struct AccountDeletion { @@ -237,7 +242,6 @@ private struct AccountDeletion { } } - // MARK: - RestorationAlert // struct RestorationAlert { diff --git a/Simplenote/Classes/SPSettingsViewController.h b/Simplenote/Classes/SPSettingsViewController.h index f923770be..301f929a0 100644 --- a/Simplenote/Classes/SPSettingsViewController.h +++ b/Simplenote/Classes/SPSettingsViewController.h @@ -2,7 +2,6 @@ #import "SPTableViewController.h" @interface SPSettingsViewController : SPTableViewController { - //Preferences NSNumber *sortOrderPref; NSNumber *numPreviewLinesPref; @@ -11,3 +10,4 @@ @end extern NSString *const SPAlphabeticalTagSortPref; +extern NSString *const SPSustainerAppIconName; diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index b73535fe1..ee90df625 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -9,14 +9,17 @@ NSString *const SPAlphabeticalTagSortPref = @"SPAlphabeticalTagSortPref"; NSString *const SPThemePref = @"SPThemePref"; +NSString *const SPSustainerAppIconName = @"AppIcon-Sustainer"; @interface SPSettingsViewController () @property (nonatomic, strong) UISwitch *condensedNoteListSwitch; @property (nonatomic, strong) UISwitch *alphabeticalTagSortSwitch; @property (nonatomic, strong) UISwitch *biometrySwitch; +@property (nonatomic, strong) UISwitch *sustainerIconSwitch; @property (nonatomic, strong) UITextField *pinTimeoutTextField; @property (nonatomic, strong) UIPickerView *pinTimeoutPickerView; @property (nonatomic, strong) UIToolbar *doneToolbar; +@property (nonatomic, strong) UISwitch *indexNotesSwitch; @end @implementation SPSettingsViewController { @@ -30,24 +33,20 @@ @implementation SPSettingsViewController { #define kTagPasscode 5 #define kTagTimeout 6 #define kTagTouchID 7 +#define kTagSustainerIcon 8 +#define kTagIndexNotes 9 typedef NS_ENUM(NSInteger, SPOptionsViewSections) { SPOptionsViewSectionsNotes = 0, SPOptionsViewSectionsTags = 1, SPOptionsViewSectionsAppearance = 2, SPOptionsViewSectionsSecurity = 3, - SPOptionsViewSectionsSustainer = 4, - SPOptionsViewSectionsAccount = 5, - SPOptionsViewSectionsDelete = 6, - SPOptionsViewSectionsAbout = 7, - SPOptionsViewSectionsHelp = 8, - SPOptionsViewSectionsDebug = 9, - SPOptionsViewSectionsCount = 10, -}; - -typedef NS_ENUM(NSInteger, SPOptionsSustainerRow) { - SPOptionsSustainerRowRestore = 0, - SPOptionsSustainerRowCount = 1 + SPOptionsViewSectionsAccount = 4, + SPOptionsViewSectionsDelete = 5, + SPOptionsViewSectionsAbout = 6, + SPOptionsViewSectionsHelp = 7, + SPOptionsViewSectionsDebug = 8, + SPOptionsViewSectionsCount = 9, }; typedef NS_ENUM(NSInteger, SPOptionsAccountRow) { @@ -60,7 +59,8 @@ typedef NS_ENUM(NSInteger, SPOptionsAccountRow) { typedef NS_ENUM(NSInteger, SPOptionsNotesRow) { SPOptionsPreferencesRowSort = 0, SPOptionsPreferencesRowCondensed = 1, - SPOptionsNotesRowCount = 2 + SPOptionsPreferencesRowIndexNotes = 2, + SPOptionsNotesRowCount = 3 }; typedef NS_ENUM(NSInteger, SPOptionsTagsRow) { @@ -70,7 +70,8 @@ typedef NS_ENUM(NSInteger, SPOptionsTagsRow) { typedef NS_ENUM(NSInteger, SPOptionsAppearanceRow) { SPOptionsPreferencesRowTheme = 0, - SPOptionsAppearanceRowCount = 1 + SPOptionsAccountSustainerIcon = 1, + SPOptionsAppearanceRowCount = 2 }; typedef NS_ENUM(NSInteger, SPOptionsSecurityRow) { @@ -132,10 +133,6 @@ - (void)viewDidLoad self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneAction:)]; - - // Header View - [self setupTableHeaderView]; - // Setup the Switches self.alphabeticalTagSortSwitch = [UISwitch new]; [self.alphabeticalTagSortSwitch addTarget:self @@ -147,11 +144,22 @@ - (void)viewDidLoad action:@selector(condensedSwitchDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + self.indexNotesSwitch = [UISwitch new]; + [self.indexNotesSwitch addTarget:self + action:@selector(indexNotesSwitchDidChangeValue:) + forControlEvents:UIControlEventValueChanged]; + self.biometrySwitch = [UISwitch new]; [self.biometrySwitch addTarget:self action:@selector(touchIdSwitchDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + self.sustainerIconSwitch = [UISwitch new]; + [self.sustainerIconSwitch addTarget:self + action:@selector(sustainerSwitchDidChangeValueWithSender:) + forControlEvents:UIControlEventValueChanged]; + [self.sustainerIconSwitch setOn: [NSUserDefaults.standardUserDefaults boolForKey:@"useSustainerIcon"]]; + self.pinTimeoutPickerView = [UIPickerView new]; self.pinTimeoutPickerView.delegate = self; self.pinTimeoutPickerView.dataSource = self; @@ -180,46 +188,22 @@ - (void)viewDidLoad name:SPSimplenoteThemeChangedNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(subscriptionStatusDidChange) - name:SPSubscriptionStatusDidChangeNotification - object:nil]; - [self refreshThemeStyles]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self refreshTableHeaderView]; [self.tableView reloadData]; } -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator -{ - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - - [coordinator animateAlongsideTransition:^(id _Nonnull context) { - [self refreshTableHeaderView]; - } completion:nil]; -} - - (void)doneAction:(id)sender { [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } - #pragma mark - Notifications -- (void)subscriptionStatusDidChange -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self refreshTableHeaderView]; - }); -} - - #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView @@ -243,23 +227,15 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } case SPOptionsViewSectionsAppearance: { - return SPOptionsAppearanceRowCount; + return self.showSustainerSwitch ? SPOptionsAppearanceRowCount : SPOptionsAppearanceRowCount - 1; } - + case SPOptionsViewSectionsSecurity: { int rowsToRemove = [self isBiometryAvailable] ? 0 : 1; int disabledPinLockRows = [self isBiometryAvailable] ? 2 : 1; return [self isPinLockEnabled] ? SPOptionsSecurityRowRowCount - rowsToRemove : disabledPinLockRows; } - case SPOptionsViewSectionsSustainer: { - return SPOptionsSustainerRowCount; - } - - case SPOptionsViewSectionsAccount: { - return SPOptionsAccountRowCount; - } - case SPOptionsViewSectionsDelete: { return SPOptionsDeleteRowCount; } @@ -275,7 +251,11 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger case SPOptionsViewSectionsDebug: { return SPOptionsDebugRowCount; } - + + case SPOptionsViewSectionsAccount: { + return SPOptionsAccountRowCount; + } + default: break; } @@ -298,9 +278,6 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte case SPOptionsViewSectionsSecurity: return NSLocalizedString(@"Security", nil); - case SPOptionsViewSectionsSustainer: - return NSLocalizedString(@"Sustainer", nil); - case SPOptionsViewSectionsAccount: return NSLocalizedString(@"Account", nil); @@ -322,7 +299,6 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte return nil; } - - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; @@ -351,7 +327,17 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.tag = kTagCondensedNoteList; break; } - + case SPOptionsPreferencesRowIndexNotes: { + cell.textLabel.text = NSLocalizedString(@"Index Notes in Spotlight", @"Option to add notes to spotlight search"); + + self.indexNotesSwitch.on = [[Options shared] indexNotesInSpotlight]; + + cell.accessoryView = self.indexNotesSwitch; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.tag = kTagIndexNotes; + break; + } + default: break; } @@ -388,14 +374,23 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.tag = kTagTheme; break; } - + + case SPOptionsAccountSustainerIcon: { + cell.textLabel.text = NSLocalizedString(@"Sustainer App Icon", @"Switch app icon"); + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell.accessoryView = self.sustainerIconSwitch; + cell.tag = kTagSustainerIcon; + break; + } + default: break; } break; } - + case SPOptionsViewSectionsSecurity: { switch (indexPath.row) { case SPOptionsSecurityRowRowPasscode: { @@ -445,22 +440,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; - } case SPOptionsViewSectionsSustainer: { - - switch (indexPath.row) { - case SPOptionsSustainerRowRestore: { - cell.textLabel.text = NSLocalizedString(@"Restore Purchases", @"Manually Restores IAP Purchases"); - cell.selectionStyle = UITableViewCellSelectionStyleNone; - break; - } - default: - break; - } - - break; + } case SPOptionsViewSectionsAccount: { - } case SPOptionsViewSectionsAccount:{ - switch (indexPath.row) { case SPOptionsAccountRowDescription: { cell.textLabel.text = NSLocalizedString(@"Username", @"A user's Simplenote account"); @@ -593,11 +574,6 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath break; - } case SPOptionsViewSectionsSustainer: { - - [self restorePurchases]; - break; - } case SPOptionsViewSectionsAccount: { switch (indexPath.row) { @@ -739,6 +715,25 @@ - (void)condensedSwitchDidChangeValue:(UISwitch *)sender [SPTracker trackSettingsListCondensedEnabled:isOn]; } +- (void)indexNotesSwitchDidChangeValue:(UISwitch *)sender +{ + BOOL isOn = [(UISwitch *)sender isOn]; + + [[Options shared] setIndexNotesInSpotlight:isOn]; + + NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + [context setParentContext:SPAppDelegate.sharedDelegate.simperium.managedObjectContext]; + + if (isOn) { + [[CSSearchableIndex defaultSearchableIndex] indexSpotlightItemsIn:context]; + } else { + [[CSSearchableIndex defaultSearchableIndex] deleteAllSearchableItemsWithCompletionHandler:^(NSError * _Nullable error) { + + [self presentIndexRemovalAlert]; + }]; + } +} + - (void)tagSortSwitchDidChangeValue:(UISwitch *)sender { BOOL isOn = [(UISwitch *)sender isOn]; @@ -752,11 +747,11 @@ - (void)tagSortSwitchDidChangeValue:(UISwitch *)sender - (void)touchIdSwitchDidChangeValue:(UISwitch *)sender { - SPPinLockManager.shared.shouldUseBiometry = sender.on; - if (![self isPinLockEnabled] && [self.biometrySwitch isOn]) { UIAlertController* alert = [self pinLockRequiredAlert]; [self presentViewController:alert animated:YES completion:nil]; + } else { + SPPinLockManager.shared.shouldUseBiometry = sender.on; } } diff --git a/Simplenote/Classes/SPSheetController.swift b/Simplenote/Classes/SPSheetController.swift index 68de0a83f..dc9feb8d2 100644 --- a/Simplenote/Classes/SPSheetController.swift +++ b/Simplenote/Classes/SPSheetController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Renders a customized Action Sheet // class SPSheetController: UIViewController { @@ -42,8 +41,6 @@ class SPSheetController: UIViewController { /// var onClickButton1: (() -> Void)? - - /// Designated Initializer /// init() { @@ -57,7 +54,6 @@ class SPSheetController: UIViewController { fatalError("init(coder:) has not been implemented") } - // MARK: - Required Methods override func viewWillAppear(_ animated: Bool) { @@ -70,7 +66,6 @@ class SPSheetController: UIViewController { performSlideUpAnimation() } - // MARK: - Public Methods func present(from viewController: UIViewController) { @@ -93,7 +88,6 @@ class SPSheetController: UIViewController { } } - // MARK: - Private Methods // private extension SPSheetController { @@ -117,7 +111,6 @@ private extension SPSheetController { } } - // MARK: - Actions // private extension SPSheetController { @@ -137,7 +130,6 @@ private extension SPSheetController { } } - // MARK: - Animations // private extension SPSheetController { diff --git a/Simplenote/Classes/SPSortBar.swift b/Simplenote/Classes/SPSortBar.swift index f4b42953d..c1580d4e6 100644 --- a/Simplenote/Classes/SPSortBar.swift +++ b/Simplenote/Classes/SPSortBar.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPSortBar // class SPSortBar: UIView { @@ -45,7 +44,6 @@ class SPSortBar: UIView { /// var onSortModePress: (() -> Void)? - // MARK: - Lifecycle deinit { @@ -67,7 +65,6 @@ class SPSortBar: UIView { } } - // MARK: - Private Methods // private extension SPSortBar { @@ -111,7 +108,6 @@ private extension SPSortBar { } } - // MARK: - Notifications // private extension SPSortBar { @@ -130,7 +126,6 @@ private extension SPSortBar { } } - // MARK: - Action Handlers // private extension SPSortBar { diff --git a/Simplenote/Classes/SPSortOrderViewController.swift b/Simplenote/Classes/SPSortOrderViewController.swift index 850ba1888..94bf9ba6d 100644 --- a/Simplenote/Classes/SPSortOrderViewController.swift +++ b/Simplenote/Classes/SPSortOrderViewController.swift @@ -8,7 +8,6 @@ import Foundation import UIKit - // MARK: - Settings: Sort Order // class SPSortOrderViewController: UITableViewController { @@ -49,7 +48,6 @@ class SPSortOrderViewController: UITableViewController { } } - // MARK: - UITableViewDelegate Conformance // extension SPSortOrderViewController { @@ -78,7 +76,6 @@ extension SPSortOrderViewController { } } - // MARK: - Private // private extension SPSortOrderViewController { @@ -124,7 +121,6 @@ private extension SPSortOrderViewController { } } - extension SPSortOrderViewController { @objc diff --git a/Simplenote/Classes/SPSquaredButton.swift b/Simplenote/Classes/SPSquaredButton.swift index 160b0cc0e..7564dc1c3 100644 --- a/Simplenote/Classes/SPSquaredButton.swift +++ b/Simplenote/Classes/SPSquaredButton.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - SPSquaredButton: Simple convenience UIButton subclass, with a default corner radius // @IBDesignable @@ -34,7 +33,6 @@ class SPSquaredButton: UIButton { } } - // MARK: - Private Methods // private extension SPSquaredButton { diff --git a/Simplenote/Classes/SPTagHeaderView.swift b/Simplenote/Classes/SPTagHeaderView.swift index 4d4fa0f23..a46c3fb5e 100644 --- a/Simplenote/Classes/SPTagHeaderView.swift +++ b/Simplenote/Classes/SPTagHeaderView.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPTagHeaderView // @objc @objcMembers @@ -19,7 +18,6 @@ class SPTagHeaderView: UIView { } } - // MARK: - Overriden Methods override func awakeFromNib() { @@ -27,7 +25,6 @@ class SPTagHeaderView: UIView { refreshStyle() } - /// Updates the receiver's colors /// func refreshStyle() { diff --git a/Simplenote/Classes/SPTagTableViewCell.swift b/Simplenote/Classes/SPTagTableViewCell.swift index 3af45d283..9aa88322a 100644 --- a/Simplenote/Classes/SPTagTableViewCell.swift +++ b/Simplenote/Classes/SPTagTableViewCell.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SPTagTableViewCell // @objcMembers @@ -56,7 +55,6 @@ class SPTagTableViewCell: UITableViewCell { } } - /// Deinitializer /// deinit { @@ -70,7 +68,6 @@ class SPTagTableViewCell: UITableViewCell { startListeningToNotifications() } - // MARK: - Overridden Methods override func awakeFromNib() { @@ -87,7 +84,6 @@ class SPTagTableViewCell: UITableViewCell { } } - // MARK: - Private Methods // private extension SPTagTableViewCell { @@ -124,7 +120,6 @@ private extension SPTagTableViewCell { } } - // MARK: - Notifications // private extension SPTagTableViewCell { @@ -146,7 +141,6 @@ private extension SPTagTableViewCell { } } - // MARK: - Static! // extension SPTagTableViewCell { @@ -164,7 +158,6 @@ extension SPTagTableViewCell { } } - // MARK: - Cell Styles // private enum Style { diff --git a/Simplenote/Classes/SPTextInputView.swift b/Simplenote/Classes/SPTextInputView.swift index f85b72bd8..2f1d32b6e 100644 --- a/Simplenote/Classes/SPTextInputView.swift +++ b/Simplenote/Classes/SPTextInputView.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - SPTextInputViewDelegate // @objc @@ -22,7 +21,6 @@ protocol SPTextInputViewDelegate: NSObjectProtocol { func textInput(_ textInput: SPTextInputView, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool } - // MARK: - SPTextInputView: // Renders a custom UITextView with bezel. When becomes the first responder, the border color will be refreshed. // @@ -239,7 +237,6 @@ class SPTextInputView: UIView { /// weak var delegate: SPTextInputViewDelegate? - // MARK: - Initializers override init(frame: CGRect) { @@ -258,7 +255,6 @@ class SPTextInputView: UIView { refreshBorderStyle() } - // MARK: - Public Methods @discardableResult @@ -267,7 +263,6 @@ class SPTextInputView: UIView { } } - // MARK: - Private // private extension SPTextInputView { @@ -310,7 +305,6 @@ private extension SPTextInputView { } } - // MARK: - Relaying editingChanged Events // extension SPTextInputView { @@ -320,7 +314,6 @@ extension SPTextInputView { } } - // MARK: - UITextFieldDelegate // extension SPTextInputView: UITextFieldDelegate { @@ -344,7 +337,6 @@ extension SPTextInputView: UITextFieldDelegate { } } - // MARK: - Default Settings // private enum Defaults { diff --git a/Simplenote/Classes/SPThemeViewController.swift b/Simplenote/Classes/SPThemeViewController.swift index fd9733df3..3955afef7 100644 --- a/Simplenote/Classes/SPThemeViewController.swift +++ b/Simplenote/Classes/SPThemeViewController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Settings: Theme // class SPThemeViewController: UITableViewController { @@ -32,7 +31,6 @@ class SPThemeViewController: UITableViewController { /// var displaysDismissButton = false - /// Designated Initializer /// init() { @@ -50,7 +48,6 @@ class SPThemeViewController: UITableViewController { } } - // MARK: - UITableViewDelegate Conformance // extension SPThemeViewController { @@ -79,7 +76,6 @@ extension SPThemeViewController { } } - // MARK: - Private // private extension SPThemeViewController { @@ -128,7 +124,6 @@ private extension SPThemeViewController { } } - extension SPThemeViewController { @objc diff --git a/Simplenote/Classes/SPTracker.h b/Simplenote/Classes/SPTracker.h index 23e4f8ef8..9bd867c26 100644 --- a/Simplenote/Classes/SPTracker.h +++ b/Simplenote/Classes/SPTracker.h @@ -83,6 +83,11 @@ + (void)trackUserSignedIn; + (void)trackUserSignedOut; +#pragma mark - Login Links ++ (void)trackLoginLinkRequested; ++ (void)trackLoginLinkConfirmationSuccess; ++ (void)trackLoginLinkConfirmationFailure; + #pragma mark - WP.com Sign In + (void)trackWPCCButtonPressed; + (void)trackWPCCLoginSucceeded; diff --git a/Simplenote/Classes/SPTracker.m b/Simplenote/Classes/SPTracker.m index 83e656a21..548e2b786 100644 --- a/Simplenote/Classes/SPTracker.m +++ b/Simplenote/Classes/SPTracker.m @@ -322,6 +322,25 @@ + (void)trackUserSignedOut [self trackAutomatticEventWithName:@"user_signed_out" properties:nil]; } + +#pragma mark - Login Links + ++ (void)trackLoginLinkRequested +{ + [self trackAutomatticEventWithName:@"login_link_requested" properties:nil]; +} + ++ (void)trackLoginLinkConfirmationSuccess +{ + [self trackAutomatticEventWithName:@"login_link_confirmation_success" properties:nil]; +} + ++ (void)trackLoginLinkConfirmationFailure +{ + [self trackAutomatticEventWithName:@"login_link_confirmation_failure" properties:nil]; +} + + #pragma mark - WP.com Sign In + (void)trackWPCCButtonPressed diff --git a/Simplenote/Classes/SPUserInterface.swift b/Simplenote/Classes/SPUserInterface.swift index 6d8b058b5..46a056ed8 100644 --- a/Simplenote/Classes/SPUserInterface.swift +++ b/Simplenote/Classes/SPUserInterface.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Simplenote's Theme // @objc @@ -41,7 +40,6 @@ class SPUserInterface: NSObject { } } - // MARK: - Private Methods // private extension SPUserInterface { @@ -66,7 +64,6 @@ private extension SPUserInterface { } } - // MARK: - Private Theme Methods // private extension Theme { diff --git a/Simplenote/Classes/SearchDisplayController.swift b/Simplenote/Classes/SearchDisplayController.swift index 876648008..28610cfec 100644 --- a/Simplenote/Classes/SearchDisplayController.swift +++ b/Simplenote/Classes/SearchDisplayController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SearchDisplayController Delegate Methods // @objc @@ -12,7 +11,6 @@ protocol SearchDisplayControllerDelegate: NSObjectProtocol { func searchDisplayControllerDidEndSearch(_ controller: SearchDisplayController) } - // MARK: - SearchControllerPresentationContextProvider Methods // @objc @@ -20,7 +18,6 @@ protocol SearchControllerPresentationContextProvider: NSObjectProtocol { func navigationControllerForSearchDisplayController(_ controller: SearchDisplayController) -> UINavigationController } - // MARK: - Simplenote's Search Controller: Because UIKit's Search Controller is simply unusable // @objcMembers @@ -42,7 +39,6 @@ class SearchDisplayController: NSObject { /// weak var presenter: SearchControllerPresentationContextProvider? - /// Designated Initializer /// override init() { @@ -86,7 +82,6 @@ class SearchDisplayController: NSObject { } } - // MARK: - Private Methods // private extension SearchDisplayController { @@ -141,7 +136,6 @@ private extension SearchDisplayController { } } - // MARK: - UISearchBar Delegate Methods // extension SearchDisplayController: UISearchBarDelegate { @@ -169,7 +163,6 @@ extension SearchDisplayController: UISearchBarDelegate { } } - // MARK: - SPSearchBar // class SPSearchBar: UISearchBar { diff --git a/Simplenote/Classes/ShortcutsHandler.swift b/Simplenote/Classes/ShortcutsHandler.swift index edc27f57e..b347cabe1 100644 --- a/Simplenote/Classes/ShortcutsHandler.swift +++ b/Simplenote/Classes/ShortcutsHandler.swift @@ -1,6 +1,6 @@ import Foundation import CoreSpotlight - +import Intents // MARK: - AppDelegate Shortcuts Methods // @@ -24,23 +24,6 @@ class ShortcutsHandler: NSObject { NSUserActivity.launchActivity() ] - /// Registers all of the Simplenote-Y Activities. - /// - /// - Note: - /// 1. Calling `becomeCurrent()` sequentially causes the OS not to register anything, (OR) register - /// just the last activity. - /// 2. Not keeping the Activities around... also causes `becomeCurrent` to fail. That's why `activities` is - /// an ivar! - /// - func registerSimplenoteActivities() { - for (index, activity) in activities.enumerated() { - let delay = DispatchTime.now() + DispatchTimeInterval.seconds(index) - DispatchQueue.main.asyncAfter(deadline: delay) { - activity.becomeCurrent() - } - } - } - /// Removes all of the shared UserActivities, whenever the API allows. /// @objc @@ -65,10 +48,12 @@ class ShortcutsHandler: NSObject { switch type { case .launch: break - case .newNote: - SPAppDelegate.shared().presentNewNoteEditor() + case .newNote, .newNoteShortcut: + SPAppDelegate.shared().presentNewNoteEditor(useSelectedTag: false) case .openNote, .openSpotlightItem: presentNote(for: userActivity) + case .openNoteShortcut: + presentNote(for: userActivity.interaction) } return true @@ -120,7 +105,7 @@ extension ShortcutsHandler { case .search: SPAppDelegate.shared().presentSearch() case .newNote: - SPAppDelegate.shared().presentNewNoteEditor() + SPAppDelegate.shared().presentNewNoteEditor(useSelectedTag: false) case .note: if let simperiumKey = shortcut.userInfo?[shortcutUserInfoNoteIdentifierKey] as? String { SPAppDelegate.shared().presentNoteWithSimperiumKey(simperiumKey) @@ -173,4 +158,14 @@ private extension ShortcutsHandler { SPAppDelegate.shared().presentNoteWithSimperiumKey(uniqueIdentifier) } + + func presentNote(for interaction: INInteraction?) { + guard let interaction, + let activity = interaction.intentResponse?.userActivity, + let uniqueIdentifier = activity.userInfo?[IntentsConstants.noteIdentifierKey] as? String else { + return + } + + SPAppDelegate.shared().presentNoteWithSimperiumKey(uniqueIdentifier) + } } diff --git a/Simplenote/Classes/SignupRemote.swift b/Simplenote/Classes/SignupRemote.swift deleted file mode 100644 index 8a947250a..000000000 --- a/Simplenote/Classes/SignupRemote.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation - -// MARK: - SignupRemote -// -class SignupRemote: Remote { - func signup(with email: String, completion: @escaping (_ result: Result) -> Void) { - let urlRequest = request(with: email) - - performDataTask(with: urlRequest, completion: completion) - } - - private func request(with email: String) -> URLRequest { - let url = URL(string: SimplenoteConstants.signupURL)! - - var request = URLRequest(url: url, - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: RemoteConstants.timeout) - request.httpMethod = RemoteConstants.Method.POST - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = try? JSONEncoder().encode(["username": email.lowercased()]) - - return request - } -} diff --git a/Simplenote/Classes/SignupVerificationViewController.swift b/Simplenote/Classes/SignupVerificationViewController.swift index b38dc1b56..ca6d2adaf 100644 --- a/Simplenote/Classes/SignupVerificationViewController.swift +++ b/Simplenote/Classes/SignupVerificationViewController.swift @@ -66,7 +66,6 @@ class SignupVerificationViewController: UIViewController { } } - // MARK: - Localization // private struct Localization { diff --git a/Simplenote/Classes/Simperium+Simplenote.swift b/Simplenote/Classes/Simperium+Simplenote.swift index a0450575a..8c1c9dc0e 100644 --- a/Simplenote/Classes/Simperium+Simplenote.swift +++ b/Simplenote/Classes/Simperium+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simperium + Buckets // extension Simperium { @@ -46,7 +45,6 @@ extension Simperium { } } - // MARK: - Public API(s) // extension Simperium { @@ -59,9 +57,9 @@ extension Simperium { } } - // MARK: - Constants // extension Simperium { static let accountBucketName = "Account" + static let preferencesLastChangedSignatureKey = "lastChangeSignature-Preferences" } diff --git a/Simplenote/Classes/SimplenoteActivityItemSource.swift b/Simplenote/Classes/SimplenoteActivityItemSource.swift index 39ee83f17..8999edd9c 100644 --- a/Simplenote/Classes/SimplenoteActivityItemSource.swift +++ b/Simplenote/Classes/SimplenoteActivityItemSource.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UIActivityItem With Special Treatment for WordPress iOS // class SimplenoteActivityItemSource: NSObject, UIActivityItemSource { diff --git a/Simplenote/Classes/SimplenoteConstants.swift b/Simplenote/Classes/SimplenoteConstants.swift index 7e753cee6..7d1c734fd 100644 --- a/Simplenote/Classes/SimplenoteConstants.swift +++ b/Simplenote/Classes/SimplenoteConstants.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simplenote Constants! // @objcMembers @@ -29,6 +28,10 @@ class SimplenoteConstants: NSObject { /// Simplenote: Published Notes base URL /// static let simplenotePublishedBaseURL = "http://simp.ly/publish/" + + /// Simplenote: Current Platform + /// + static let simplenotePlatformName = "iOS" /// Simplenote: Domain for shared group directory /// @@ -42,6 +45,8 @@ class SimplenoteConstants: NSObject { /// static let resetPasswordURL = currentEngineBaseURL.appendingPathComponent("/reset/?redirect=simplenote://launch&email=") static let settingsURL = currentEngineBaseURL.appendingPathComponent("/settings") + static let loginRequestURL = currentEngineBaseURL.appendingPathComponent("/account/request-login") + static let loginCompletionURL = currentEngineBaseURL.appendingPathComponent("/account/complete-login") static let signupURL = currentEngineBaseURL.appendingPathComponent("/account/request-signup") static let verificationURL = currentEngineBaseURL.appendingPathComponent("/account/verify-email/") static let accountDeletionURL = currentEngineBaseURL.appendingPathComponent("/account/request-delete/") diff --git a/Simplenote/Classes/SortMode.swift b/Simplenote/Classes/SortMode.swift index 120daaaa3..a623ff8a9 100644 --- a/Simplenote/Classes/SortMode.swift +++ b/Simplenote/Classes/SortMode.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Represents all of the possible Sort Modes // @objc diff --git a/Simplenote/Classes/SortModePickerViewController.swift b/Simplenote/Classes/SortModePickerViewController.swift index ed898dab2..58f06bf21 100644 --- a/Simplenote/Classes/SortModePickerViewController.swift +++ b/Simplenote/Classes/SortModePickerViewController.swift @@ -43,7 +43,6 @@ class SortModePickerViewController: UIViewController { } } - // MARK: - UITableViewDelegate // extension SortModePickerViewController: UITableViewDelegate { @@ -53,7 +52,6 @@ extension SortModePickerViewController: UITableViewDelegate { } } - // MARK: - UITableViewDataSource // extension SortModePickerViewController: UITableViewDataSource { diff --git a/Simplenote/Classes/String+Simplenote.swift b/Simplenote/Classes/String+Simplenote.swift index edd977525..750d31869 100644 --- a/Simplenote/Classes/String+Simplenote.swift +++ b/Simplenote/Classes/String+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - String // extension String { @@ -18,7 +17,6 @@ extension String { static let tab = "\t" } - // MARK: - Helper API(s) // extension String { diff --git a/Simplenote/Classes/SwitchTableViewCell.swift b/Simplenote/Classes/SwitchTableViewCell.swift index 4ffb5e3e7..ef369c4bc 100644 --- a/Simplenote/Classes/SwitchTableViewCell.swift +++ b/Simplenote/Classes/SwitchTableViewCell.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SwitchTableViewCell // class SwitchTableViewCell: UITableViewCell { @@ -45,7 +44,6 @@ class SwitchTableViewCell: UITableViewCell { /// var onChange: ((Bool) -> Void)? - // MARK: - Initializers override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -61,7 +59,6 @@ class SwitchTableViewCell: UITableViewCell { } } - // MARK: - Private API(s) // private extension SwitchTableViewCell { @@ -90,7 +87,6 @@ private extension SwitchTableViewCell { } } - // MARK: - Action Handlers // extension SwitchTableViewCell { diff --git a/Simplenote/Classes/TagView.swift b/Simplenote/Classes/TagView.swift index 655ae7fab..831fb7da5 100644 --- a/Simplenote/Classes/TagView.swift +++ b/Simplenote/Classes/TagView.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - TagViewDelegate // protocol TagViewDelegate: AnyObject { @@ -11,7 +10,6 @@ protocol TagViewDelegate: AnyObject { func tagViewDidChange(_ tagView: TagView) } - // MARK: - TagView // class TagView: UIView { @@ -136,7 +134,6 @@ class TagView: UIView { } } - // MARK: - Private // private extension TagView { @@ -185,7 +182,6 @@ private extension TagView { } } - // MARK: - Cells // private extension TagView { @@ -221,7 +217,6 @@ private extension TagView { } } - // MARK: - Editing // private extension TagView { @@ -252,7 +247,6 @@ private extension TagView { } } - // MARK: - Tag processing // private extension TagView { @@ -313,7 +307,6 @@ extension TagView: UITextFieldDelegate { } } - // MARK: - SPTagEntryFieldDelegate // extension TagView: SPTagEntryFieldDelegate { @@ -328,7 +321,6 @@ extension TagView: SPTagEntryFieldDelegate { } } - // MARK: - Constants // private struct Constants { diff --git a/Simplenote/Classes/TagViewCell.swift b/Simplenote/Classes/TagViewCell.swift index 0bf68eabc..f86762886 100644 --- a/Simplenote/Classes/TagViewCell.swift +++ b/Simplenote/Classes/TagViewCell.swift @@ -73,7 +73,6 @@ class TagViewCell: RoundedView { } } - // MARK: - Private // private extension TagViewCell { @@ -138,7 +137,6 @@ private extension TagViewCell { } } - // MARK: - Accessibility // extension TagViewCell { @@ -157,7 +155,6 @@ extension TagViewCell { } } - // MARK: - Constants // private struct Constants { @@ -172,7 +169,6 @@ private struct Constants { static let deleteButtonHitAreaInset: CGFloat = -10 } - // MARK: - Localization // private struct Localization { diff --git a/Simplenote/Classes/Theme.swift b/Simplenote/Classes/Theme.swift index 6d2116b06..4b4898435 100644 --- a/Simplenote/Classes/Theme.swift +++ b/Simplenote/Classes/Theme.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Represents all of the available Themes // @objc @@ -18,7 +17,6 @@ enum Theme: Int, CaseIterable { /// case system - /// Returns a localized Description, matching the current rawValue /// var description: String { @@ -33,7 +31,6 @@ enum Theme: Int, CaseIterable { } } - extension Theme { static var allThemes: [Theme] { diff --git a/Simplenote/Classes/UIActivity+Simplenote.swift b/Simplenote/Classes/UIActivity+Simplenote.swift index 77196814c..5f6dc95ce 100644 --- a/Simplenote/Classes/UIActivity+Simplenote.swift +++ b/Simplenote/Classes/UIActivity+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UIActivity <> WordPress iOS! // extension UIActivity.ActivityType { diff --git a/Simplenote/Classes/UIAlertController+Helpers.swift b/Simplenote/Classes/UIAlertController+Helpers.swift index 3792fdfe5..58031cc3b 100644 --- a/Simplenote/Classes/UIAlertController+Helpers.swift +++ b/Simplenote/Classes/UIAlertController+Helpers.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - extension UIAlertController { @discardableResult @objc diff --git a/Simplenote/Classes/UIApplication+Simplenote.swift b/Simplenote/Classes/UIApplication+Simplenote.swift index 65710d440..b573e73ac 100644 --- a/Simplenote/Classes/UIApplication+Simplenote.swift +++ b/Simplenote/Classes/UIApplication+Simplenote.swift @@ -15,20 +15,20 @@ extension UIApplication { return keyWindow.windowScene?.statusBarManager?.statusBarFrame.size ?? .zero } - + /// Convenience method to return the applications window scene that's activationState is .foregroundActive /// @objc public var foregroundWindowScene: UIWindowScene? { connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene } - + /// Convenience var to return the foreground scene's windows /// public var foregroundSceneWindows: [UIWindow] { foregroundWindowScene?.windows ?? [] } - + /// Returns the first window from the current foregroundActive scene /// @objc diff --git a/Simplenote/Classes/UIBarButtonItem+Appearance.swift b/Simplenote/Classes/UIBarButtonItem+Appearance.swift index fd602043b..7f6713c0a 100644 --- a/Simplenote/Classes/UIBarButtonItem+Appearance.swift +++ b/Simplenote/Classes/UIBarButtonItem+Appearance.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UIBarButtonItem + Appearance // extension UIBarButtonItem { diff --git a/Simplenote/Classes/UIBezierPath+Simplenote.swift b/Simplenote/Classes/UIBezierPath+Simplenote.swift index 39b651496..2f1be5e5e 100644 --- a/Simplenote/Classes/UIBezierPath+Simplenote.swift +++ b/Simplenote/Classes/UIBezierPath+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIBezierPath Methods // extension UIBezierPath { diff --git a/Simplenote/Classes/UIBlurEffect+Simplenote.swift b/Simplenote/Classes/UIBlurEffect+Simplenote.swift index d505aee24..34834a681 100644 --- a/Simplenote/Classes/UIBlurEffect+Simplenote.swift +++ b/Simplenote/Classes/UIBlurEffect+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIBlurEffect Simplenote Methods // extension UIBlurEffect { diff --git a/Simplenote/Classes/UIColor+Helpers.swift b/Simplenote/Classes/UIColor+Helpers.swift index 20344cec0..5eacafc19 100644 --- a/Simplenote/Classes/UIColor+Helpers.swift +++ b/Simplenote/Classes/UIColor+Helpers.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIColor Simplenote's Helpers // extension UIColor { @@ -29,7 +28,6 @@ extension UIColor { } } - // MARK: - HTML Colors // extension UIColor { diff --git a/Simplenote/Classes/UIColor+Studio.swift b/Simplenote/Classes/UIColor+Studio.swift index d24d5775d..13f621a85 100644 --- a/Simplenote/Classes/UIColor+Studio.swift +++ b/Simplenote/Classes/UIColor+Studio.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIColor + Studio API(s) // extension UIColor { @@ -33,7 +32,6 @@ extension UIColor { } } - // MARK: - Simplenote colors! // extension UIColor { diff --git a/Simplenote/Classes/UIContextualAction+Simplenote.swift b/Simplenote/Classes/UIContextualAction+Simplenote.swift index c26a1863f..85d3a9dba 100644 --- a/Simplenote/Classes/UIContextualAction+Simplenote.swift +++ b/Simplenote/Classes/UIContextualAction+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Simplenote Methods // extension UIContextualAction { diff --git a/Simplenote/Classes/UIDevice+Simplenote.swift b/Simplenote/Classes/UIDevice+Simplenote.swift index e49b50daf..cd66e3343 100644 --- a/Simplenote/Classes/UIDevice+Simplenote.swift +++ b/Simplenote/Classes/UIDevice+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simplenote Methods // extension UIDevice { diff --git a/Simplenote/Classes/UIFont+Simplenote.swift b/Simplenote/Classes/UIFont+Simplenote.swift index 89cccb2ab..c3fdab3c6 100644 --- a/Simplenote/Classes/UIFont+Simplenote.swift +++ b/Simplenote/Classes/UIFont+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIFont Simplenote Helpers // extension UIFont { @@ -50,7 +49,6 @@ extension UIFont { } } - // MARK: - FontCache: Performance Helper! // private class FontCache { @@ -63,7 +61,6 @@ private class FontCache { /// static let shared = FontCache() - /// (Private) Initializer /// private init() { @@ -91,7 +88,6 @@ private class FontCache { } } - // MARK: - Private Methods // private extension FontCache { diff --git a/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift b/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift index 443ddea54..dbcd32bd5 100644 --- a/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift +++ b/Simplenote/Classes/UIGestureRecognizer+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIGestureRecognizer Simplenote Methods // extension UIGestureRecognizer { diff --git a/Simplenote/Classes/UIImage+Dynamic.swift b/Simplenote/Classes/UIImage+Dynamic.swift index 3ed501b00..aebc16761 100644 --- a/Simplenote/Classes/UIImage+Dynamic.swift +++ b/Simplenote/Classes/UIImage+Dynamic.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Dynamic Images // extension UIImage { @@ -15,7 +14,6 @@ extension UIImage { } } - // MARK: - Constants // private enum SearchBackgroundMetrics { diff --git a/Simplenote/Classes/UIImage+Simplenote.swift b/Simplenote/Classes/UIImage+Simplenote.swift index ae42409df..b258b622d 100644 --- a/Simplenote/Classes/UIImage+Simplenote.swift +++ b/Simplenote/Classes/UIImage+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Simplenote's UIImage Static Methods // extension UIImage { diff --git a/Simplenote/Classes/UIImageName.swift b/Simplenote/Classes/UIImageName.swift index e65d36d9f..b2b7541cc 100644 --- a/Simplenote/Classes/UIImageName.swift +++ b/Simplenote/Classes/UIImageName.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Simplenote Named Images // @objc @@ -58,7 +57,6 @@ enum UIImageName: Int, CaseIterable { case warning } - // MARK: - Public Methods // extension UIImageName { diff --git a/Simplenote/Classes/UIKeyboardAppearance+Simplenote.swift b/Simplenote/Classes/UIKeyboardAppearance+Simplenote.swift index a48378097..808866fda 100644 --- a/Simplenote/Classes/UIKeyboardAppearance+Simplenote.swift +++ b/Simplenote/Classes/UIKeyboardAppearance+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UIKeyboardAppearance // extension UIKeyboardAppearance { diff --git a/Simplenote/Classes/UIKitConstants.swift b/Simplenote/Classes/UIKitConstants.swift index d399adbda..f6ec2f8d6 100644 --- a/Simplenote/Classes/UIKitConstants.swift +++ b/Simplenote/Classes/UIKitConstants.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIKit Constants, so that we don't repeat ourselves forever! // @objcMembers diff --git a/Simplenote/Classes/UINavigationBar+Appearance.swift b/Simplenote/Classes/UINavigationBar+Appearance.swift index 5deeb6adc..47106d6b9 100644 --- a/Simplenote/Classes/UINavigationBar+Appearance.swift +++ b/Simplenote/Classes/UINavigationBar+Appearance.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UINavigationBar + Appearance // extension UINavigationBar { diff --git a/Simplenote/Classes/UINavigationBar+Simplenote.swift b/Simplenote/Classes/UINavigationBar+Simplenote.swift index d6a58192d..6fb14bea0 100644 --- a/Simplenote/Classes/UINavigationBar+Simplenote.swift +++ b/Simplenote/Classes/UINavigationBar+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UINavigationBar Simplenote Methods // extension UINavigationBar { diff --git a/Simplenote/Classes/UINavigationController+Simplenote.swift b/Simplenote/Classes/UINavigationController+Simplenote.swift index 377457f35..6649ece75 100644 --- a/Simplenote/Classes/UINavigationController+Simplenote.swift +++ b/Simplenote/Classes/UINavigationController+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UINavigationController Simplenote API // extension UINavigationController { diff --git a/Simplenote/Classes/UIPasteboard+Note.swift b/Simplenote/Classes/UIPasteboard+Note.swift index ac9f3dfef..ad89753fe 100644 --- a/Simplenote/Classes/UIPasteboard+Note.swift +++ b/Simplenote/Classes/UIPasteboard+Note.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UIPasteboard + Interlink // extension UIPasteboard { diff --git a/Simplenote/Classes/UIScreen+Simplenote.swift b/Simplenote/Classes/UIScreen+Simplenote.swift index f86af4b85..993b7472c 100644 --- a/Simplenote/Classes/UIScreen+Simplenote.swift +++ b/Simplenote/Classes/UIScreen+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UIScreen Simplenote Methods // extension UIScreen { diff --git a/Simplenote/Classes/UISearchBar+Simplenote.swift b/Simplenote/Classes/UISearchBar+Simplenote.swift index 2bc531d46..34765dbd8 100644 --- a/Simplenote/Classes/UISearchBar+Simplenote.swift +++ b/Simplenote/Classes/UISearchBar+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UISearchBar Simplenote Methods // extension UISearchBar { diff --git a/Simplenote/Classes/UITableView+Simplenote.swift b/Simplenote/Classes/UITableView+Simplenote.swift index f852ae889..286a60408 100644 --- a/Simplenote/Classes/UITableView+Simplenote.swift +++ b/Simplenote/Classes/UITableView+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - UITableView Simplenote Methods // extension UITableView { diff --git a/Simplenote/Classes/UITableViewCell+Simplenote.swift b/Simplenote/Classes/UITableViewCell+Simplenote.swift index d26003cf1..01737f431 100644 --- a/Simplenote/Classes/UITableViewCell+Simplenote.swift +++ b/Simplenote/Classes/UITableViewCell+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - /// UITableViewCell Helpers /// extension UITableViewCell { diff --git a/Simplenote/Classes/UITableViewHeaderFooterView+Simplenote.swift b/Simplenote/Classes/UITableViewHeaderFooterView+Simplenote.swift index d81209d1d..a78f4f34c 100644 --- a/Simplenote/Classes/UITableViewHeaderFooterView+Simplenote.swift +++ b/Simplenote/Classes/UITableViewHeaderFooterView+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - /// UITableViewHeaderFooterView Helpers /// extension UITableViewHeaderFooterView { diff --git a/Simplenote/Classes/UITextView+Simplenote.swift b/Simplenote/Classes/UITextView+Simplenote.swift index 046769265..ba65e2998 100644 --- a/Simplenote/Classes/UITextView+Simplenote.swift +++ b/Simplenote/Classes/UITextView+Simplenote.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import SimplenoteInterlinks - // MARK: - UITextView State // extension UITextView { @@ -21,7 +20,6 @@ extension UITextView { } } - // MARK: - Updating! // extension UITextView { @@ -40,7 +38,6 @@ extension UITextView { } } - // MARK: - Undo Stack // private extension UITextView { @@ -93,7 +90,6 @@ private extension UITextView { } } - // MARK: - Interlinks // extension UITextView { @@ -109,7 +105,6 @@ extension UITextView { } } - // MARK: - Attachments // extension UITextView { @@ -125,7 +120,6 @@ extension UITextView { } } - // MARK: - Geometry // extension UITextView { diff --git a/Simplenote/Classes/UITraitCollection+Simplenote.swift b/Simplenote/Classes/UITraitCollection+Simplenote.swift index 050f5b92b..62b7a02ba 100644 --- a/Simplenote/Classes/UITraitCollection+Simplenote.swift +++ b/Simplenote/Classes/UITraitCollection+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UITraitCollection Simplenote Methods // extension UITraitCollection { diff --git a/Simplenote/Classes/UIView+Animations.swift b/Simplenote/Classes/UIView+Animations.swift index 207eec515..fb5137f81 100644 --- a/Simplenote/Classes/UIView+Animations.swift +++ b/Simplenote/Classes/UIView+Animations.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIView's Animation Methods // extension UIView { diff --git a/Simplenote/Classes/UIView+ImageRepresentation.swift b/Simplenote/Classes/UIView+ImageRepresentation.swift index e42a6e8b1..398f34c26 100644 --- a/Simplenote/Classes/UIView+ImageRepresentation.swift +++ b/Simplenote/Classes/UIView+ImageRepresentation.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIView: Image Representation Helpers // extension UIView { diff --git a/Simplenote/Classes/UIView+Simplenote.swift b/Simplenote/Classes/UIView+Simplenote.swift index c1da0a339..ec3ff2e54 100644 --- a/Simplenote/Classes/UIView+Simplenote.swift +++ b/Simplenote/Classes/UIView+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIView's Simplenote Methods // extension UIView { @@ -62,7 +61,6 @@ extension UIView { } } - // MARK: - UIView Class Methods // extension UIView { diff --git a/Simplenote/Classes/UIViewController+Helpers.swift b/Simplenote/Classes/UIViewController+Helpers.swift index 90e29bfe6..c9d7152e1 100644 --- a/Simplenote/Classes/UIViewController+Helpers.swift +++ b/Simplenote/Classes/UIViewController+Helpers.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIViewController Helpers // extension UIViewController { diff --git a/Simplenote/Classes/UIViewController+Simplenote.swift b/Simplenote/Classes/UIViewController+Simplenote.swift index 3c50decdc..e693735f8 100644 --- a/Simplenote/Classes/UIViewController+Simplenote.swift +++ b/Simplenote/Classes/UIViewController+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - UIAlertController's Simplenote Methods // extension UIViewController { @@ -33,7 +32,6 @@ extension UIViewController { leafViewController.present(self, animated: true, completion: nil) } - /// Attaches a children ViewController (if needed) below the specified sibling view /// func attach(to parent: UIViewController, attachmentView: AttachmentView? = nil, animated: Bool = false) { diff --git a/Simplenote/Classes/UserDefaults+Simplenote.swift b/Simplenote/Classes/UserDefaults+Simplenote.swift index 444ab985b..be71a8855 100644 --- a/Simplenote/Classes/UserDefaults+Simplenote.swift +++ b/Simplenote/Classes/UserDefaults+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simplenote UserDefaults Keys // extension UserDefaults { @@ -16,10 +15,12 @@ extension UserDefaults { case wordPressSessionKey = "SPAuthSessionKey" case useBiometryInsteadOfPin = "SimplenoteUseTouchID" case accountIsLoggedIn + case useSustainerIcon + case hasMigratedSustainerPreferences + case indexNotesInSpotlight } } - // MARK: - Convenience Methods // extension UserDefaults { diff --git a/Simplenote/Color+Simplenote.swift b/Simplenote/Color+Simplenote.swift index c546cfcab..825accf4f 100644 --- a/Simplenote/Color+Simplenote.swift +++ b/Simplenote/Color+Simplenote.swift @@ -1,6 +1,5 @@ import SwiftUI - extension Color { /// Convenience initializers to get and use Simplenote color studio colors /// diff --git a/Simplenote/Controllers/AccountDeletionController.swift b/Simplenote/Controllers/AccountDeletionController.swift index c5251a05d..ac248de09 100644 --- a/Simplenote/Controllers/AccountDeletionController.swift +++ b/Simplenote/Controllers/AccountDeletionController.swift @@ -1,4 +1,5 @@ import Foundation +import SimplenoteEndpoints @objc class AccountDeletionController: NSObject { diff --git a/Simplenote/Controllers/VersionsController.swift b/Simplenote/Controllers/VersionsController.swift index b1663cf5b..23779136e 100644 --- a/Simplenote/Controllers/VersionsController.swift +++ b/Simplenote/Controllers/VersionsController.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - VersionsController // class VersionsController: NSObject { @@ -10,7 +9,6 @@ class VersionsController: NSObject { /// private let callbackMap = NSMapTable(keyOptions: .copyIn, valueOptions: .weakMemory) - /// Designated Initializer /// /// - Parameters: @@ -21,7 +19,6 @@ class VersionsController: NSObject { super.init() } - /// Requests the specified number of versions of Notes for a given SimperiumKey. /// /// - Parameters: @@ -48,7 +45,6 @@ class VersionsController: NSObject { } } - // MARK: - Simperium // extension VersionsController { diff --git a/Simplenote/Controllers/WidgetController.swift b/Simplenote/Controllers/WidgetController.swift index c5bb8a0aa..2a49e0948 100644 --- a/Simplenote/Controllers/WidgetController.swift +++ b/Simplenote/Controllers/WidgetController.swift @@ -1,7 +1,6 @@ import WidgetKit import Intents - struct WidgetController { @available(iOS 14.0, *) static func resetWidgetTimelines() { diff --git a/Simplenote/CrashLogging.swift b/Simplenote/CrashLogging.swift index 18a1a8486..8dea7fb47 100644 --- a/Simplenote/CrashLogging.swift +++ b/Simplenote/CrashLogging.swift @@ -1,7 +1,6 @@ import Foundation import AutomatticTracks - /// This exists to bridge CrashLogging with Objective-C. Once the App Delegate is moved over to Swift, /// this shim can be removed, and the cache methods moved to a `CrashLogging` extension. At that time, /// you, future developer, can just set up the Crash Logging system in the App Delegate using `SNCrashLoggingDataProvider`. diff --git a/Simplenote/EmailVerification.swift b/Simplenote/EmailVerification.swift index 194d16798..9ff8f62d6 100644 --- a/Simplenote/EmailVerification.swift +++ b/Simplenote/EmailVerification.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - EmailVerification // struct EmailVerification { diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Contents.json b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Contents.json index 29910c6df..c04602c05 100644 --- a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Contents.json +++ b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Contents.json @@ -1,147 +1,9 @@ { "images" : [ - { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29-1.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "1x", - "size" : "57x57" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "57x57" - }, - { - "filename" : "Icon-App-60x60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-60x60@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-20x20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@2x-2.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "50x50" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "50x50" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "72x72" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "72x72" - }, - { - "filename" : "Icon-App-76x76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-76x76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-83.5x83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, { "filename" : "Icon-App-iTunes.png", - "idiom" : "ios-marketing", - "scale" : "1x", + "idiom" : "universal", + "platform" : "ios", "size" : "1024x1024" } ], diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20.png deleted file mode 100644 index 8114c9836..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@2x-1.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@2x-1.png deleted file mode 100644 index bec0614d0..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@2x-1.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@2x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index bec0614d0..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@3x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 745562322..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29-1.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29-1.png deleted file mode 100644 index 362083632..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29-1.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29.png deleted file mode 100644 index 362083632..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@2x-1.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@2x-1.png deleted file mode 100644 index 8b471dec9..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@2x-1.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@2x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 8b471dec9..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@3x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index e1c26f8dc..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40.png deleted file mode 100644 index bec0614d0..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@2x-2.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@2x-2.png deleted file mode 100644 index 5b9bce6e4..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@2x-2.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@2x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c791c5e0a..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@3x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 2659a24e9..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-60x60@2x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 2659a24e9..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-60x60@3x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 7a2877dc2..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-76x76.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-76x76.png deleted file mode 100644 index 876d7786a..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-76x76.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-76x76@2x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 1501bd95a..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-83.5x83.5@2x.png b/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 759dbbece..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon-Beta.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json new file mode 100644 index 000000000..2bd75b76d --- /dev/null +++ b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json @@ -0,0 +1,125 @@ +{ + "images" : [ + { + "filename" : "SN-Sustainer @40x40.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "SN-Sustainer @60x60.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "SN-Sustainer @58x58.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "SN-Sustainer @87x87.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "SN-Sustainer @76x76.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "38x38" + }, + { + "filename" : "SN-Sustainer @114x114.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "38x38" + }, + { + "filename" : "SN-Sustainer @80x80.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "SN-Sustainer @120x120.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "SN-Sustainer @120x120 1.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "SN-Sustainer @180x180.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "SN-Sustainer @128x128 1.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "64x64" + }, + { + "filename" : "SN-Sustainer @192x192.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "64x64" + }, + { + "filename" : "SN-Sustainer @136x136.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "68x68" + }, + { + "filename" : "SN-Sustainer @152x152.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "SN-Sustainer @167x167.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "SN-Sustainer @1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "filename" : "SN-Sustainer @128x128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @1024x1024.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @1024x1024.png new file mode 100644 index 000000000..f1180a775 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @1024x1024.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @114x114.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @114x114.png new file mode 100644 index 000000000..df06a3cf7 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @114x114.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120 1.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120 1.png new file mode 100644 index 000000000..51db22c3c Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120 1.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120.png new file mode 100644 index 000000000..51db22c3c Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128 1.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128 1.png new file mode 100644 index 000000000..40ff25e90 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128 1.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128.png new file mode 100644 index 000000000..40ff25e90 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @136x136.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @136x136.png new file mode 100644 index 000000000..e1c62d360 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @136x136.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @152x152.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @152x152.png new file mode 100644 index 000000000..8609a2758 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @152x152.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @167x167.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @167x167.png new file mode 100644 index 000000000..b604e577c Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @167x167.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @180x180.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @180x180.png new file mode 100644 index 000000000..364479440 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @180x180.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @192x192.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @192x192.png new file mode 100644 index 000000000..c291d5109 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @192x192.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @40x40.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @40x40.png new file mode 100644 index 000000000..4bce01eca Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @40x40.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @58x58.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @58x58.png new file mode 100644 index 000000000..c10f17548 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @58x58.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @60x60.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @60x60.png new file mode 100644 index 000000000..ed3dc6465 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @60x60.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @76x76.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @76x76.png new file mode 100644 index 000000000..9c8e76db8 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @76x76.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @80x80.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @80x80.png new file mode 100644 index 000000000..dbb038766 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @80x80.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @87x87.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @87x87.png new file mode 100644 index 000000000..e56ce0654 Binary files /dev/null and b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @87x87.png differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Contents.json b/Simplenote/Icons.xcassets/AppIcon.appiconset/Contents.json index b970ad913..c04602c05 100644 --- a/Simplenote/Icons.xcassets/AppIcon.appiconset/Contents.json +++ b/Simplenote/Icons.xcassets/AppIcon.appiconset/Contents.json @@ -1,137 +1,9 @@ { "images" : [ - { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-60x60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-60x60@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-20x20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29-1.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "50x50" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "50x50" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "72x72" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "72x72" - }, - { - "filename" : "Icon-App-76x76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-76x76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-83.5x83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, { "filename" : "Icon-App-iTunes.png", - "idiom" : "ios-marketing", - "scale" : "1x", + "idiom" : "universal", + "platform" : "ios", "size" : "1024x1024" } ], diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20.png deleted file mode 100644 index a7aa03329..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png deleted file mode 100644 index b91fb2496..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index b91fb2496..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 5ca72b1ee..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29-1.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29-1.png deleted file mode 100644 index a50524dab..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29-1.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29.png deleted file mode 100644 index a50524dab..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png deleted file mode 100644 index 00db0210a..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 00db0210a..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 1a1f5521b..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40.png deleted file mode 100644 index f7737b6ef..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png deleted file mode 100644 index 68d79dd7e..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 68d79dd7e..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index b5ff8684f..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index b5ff8684f..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index aef70c2fa..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-76x76.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-76x76.png deleted file mode 100644 index 6297cd1ea..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-76x76.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index d3133502a..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index ee50a72ab..000000000 Binary files a/Simplenote/Icons.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/Simplenote/Icons.xcassets/Contents.json b/Simplenote/Icons.xcassets/Contents.json index da4a164c9..73c00596a 100644 --- a/Simplenote/Icons.xcassets/Contents.json +++ b/Simplenote/Icons.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Simplenote/Information/NoteInformationViewController.swift b/Simplenote/Information/NoteInformationViewController.swift index b8a4e6ada..5a999f7a8 100644 --- a/Simplenote/Information/NoteInformationViewController.swift +++ b/Simplenote/Information/NoteInformationViewController.swift @@ -173,7 +173,6 @@ extension NoteInformationViewController: UITableViewDelegate { } } - // MARK: - UITableViewDataSource // extension NoteInformationViewController: UITableViewDataSource { diff --git a/Simplenote/KeychainPasswordItem.swift b/Simplenote/KeychainPasswordItem.swift index 24c3c4b01..cc7ae4cbc 100644 --- a/Simplenote/KeychainPasswordItem.swift +++ b/Simplenote/KeychainPasswordItem.swift @@ -8,7 +8,6 @@ import Foundation - enum KeychainError: Error { case noPassword case unexpectedPasswordData diff --git a/Simplenote/MagicLinkInvalidView.swift b/Simplenote/MagicLinkInvalidView.swift new file mode 100644 index 000000000..dcb6faf81 --- /dev/null +++ b/Simplenote/MagicLinkInvalidView.swift @@ -0,0 +1,87 @@ +import Foundation +import SwiftUI +import Gridicons + + +// MARK: - MagicLinkConfirmationView +// +struct MagicLinkInvalidView: View { + @Environment(\.presentationMode) var presentationMode + + var onPressRequestNewLink: (() -> Void)? + + + var body: some View { + NavigationView { + VStack(alignment: .center, spacing: 10) { + Image(uiImage: MagicLinkImages.cross) + .renderingMode(.template) + .foregroundColor(Color(.simplenoteLightBlueColor)) + + Text("Link no longer valid") + .bold() + .font(.system(size: Metrics.titleFontSize)) + .multilineTextAlignment(.center) + .padding(.bottom, Metrics.titlePaddingBottom) + + Button(action: pressedRequestNewLink) { + Text("Request a new Link") + .fontWeight(.bold) + .foregroundStyle(.white) + } + .padding() + .background(Color(.simplenoteBlue50Color)) + .cornerRadius(Metrics.actionCornerRadius) + .buttonStyle(PlainButtonStyle()) + + } + .padding() + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + presentationMode.wrappedValue.dismiss() + }) { + Image(uiImage: MagicLinkImages.dismiss) + .renderingMode(.template) + .foregroundColor(Color(.darkGray)) + } + } + } + } + .navigationViewStyle(.stack) + + /// Force Light Mode (since the Authentication UI is all light!) + .environment(\.colorScheme, .light) + } + + func pressedRequestNewLink() { + presentationMode.wrappedValue.dismiss() + onPressRequestNewLink?() + } +} + + +// MARK: - Constants +// +private enum Metrics { + static let crossIconSize = CGSize(width: 100, height: 100) + static let dismissSize = CGSize(width: 30, height: 30) + static let titleFontSize: CGFloat = 20 + static let titlePaddingBottom: CGFloat = 30 + static let actionCornerRadius: CGFloat = 10 +} + +private enum MagicLinkImages { + static let cross = Gridicon.iconOfType(.crossCircle, withSize: Metrics.crossIconSize) + static let dismiss = Gridicon.iconOfType(.crossCircle, withSize: Metrics.dismissSize) +} + + +// MARK: - Preview +// +struct MagicLinkInvalidView_Previews: PreviewProvider { + static var previews: some View { + MagicLinkInvalidView() + } +} diff --git a/Simplenote/MagicLinkRequestedView.swift b/Simplenote/MagicLinkRequestedView.swift new file mode 100644 index 000000000..d1afdbca6 --- /dev/null +++ b/Simplenote/MagicLinkRequestedView.swift @@ -0,0 +1,80 @@ +import Foundation +import SwiftUI +import Gridicons + + +// MARK: - MagicLinkRequestedView +// +struct MagicLinkRequestedView: View { + @Environment(\.presentationMode) var presentationMode + @State private var displaysFullImage: Bool = false + let email: String + + + var body: some View { + NavigationView { + VStack(alignment: .center, spacing: 10) { + Image(uiImage: MagicLinkImages.mail) + .renderingMode(.template) + .foregroundColor(Color(.simplenoteBlue60Color)) + .scaleEffect(displaysFullImage ? 1 : 0.4) + .onAppear { + withAnimation(.spring(response: 0.3, dampingFraction: 0.3)) { + displaysFullImage = true + } + } + + Text("Check your email") + .bold() + .font(.system(size: Metrics.titleFontSize)) + + Spacer() + .frame(height: Metrics.titlePaddingBottom) + + Text("If an account exists, we've sent an email with a link that'll log you in to **\(email)**") + .font(.system(size: Metrics.detailsFontSize)) + .multilineTextAlignment(.center) + } + .padding() + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + presentationMode.wrappedValue.dismiss() + }) { + Image(uiImage: MagicLinkImages.dismiss) + .renderingMode(.template) + .foregroundColor(Color(.darkGray)) + } + } + } + } + .navigationViewStyle(.stack) + + /// Force Light Mode (since the Authentication UI is all light!) + .environment(\.colorScheme, .light) + } +} + + +// MARK: - Constants +// +private enum Metrics { + static let titleFontSize: CGFloat = 22 + static let titlePaddingBottom: CGFloat = 10 + static let detailsFontSize: CGFloat = 17 + static let mailIconSize = CGSize(width: 100, height: 100) + static let dismissSize = CGSize(width: 30, height: 30) +} + +private enum MagicLinkImages { + static let mail = Gridicon.iconOfType(.mail, withSize: Metrics.mailIconSize) + static let dismiss = Gridicon.iconOfType(.crossCircle, withSize: Metrics.dismissSize) +} + + +struct MagicLinkRequestedView_Previews: PreviewProvider { + static var previews: some View { + MagicLinkRequestedView(email: "lord@yosemite.com") + } +} diff --git a/Simplenote/NSAttributedStringToMarkdownConverter.swift b/Simplenote/NSAttributedStringToMarkdownConverter.swift index ca1fb53e1..8c9bc4ba9 100644 --- a/Simplenote/NSAttributedStringToMarkdownConverter.swift +++ b/Simplenote/NSAttributedStringToMarkdownConverter.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSAttributedString to Markdown Converter // struct NSAttributedStringToMarkdownConverter { @@ -13,7 +12,6 @@ struct NSAttributedStringToMarkdownConverter { /// private static let checked = "- [x]" - /// Returns the NSString representation of a given NSAttributedString. /// static func convert(string: NSAttributedString) -> String { diff --git a/Simplenote/NSObject+Helpers.swift b/Simplenote/NSObject+Helpers.swift index 88c9eb366..257c557f4 100644 --- a/Simplenote/NSObject+Helpers.swift +++ b/Simplenote/NSObject+Helpers.swift @@ -1,6 +1,5 @@ import Foundation - /// NSObject: Helper Methods /// extension NSObject { diff --git a/Simplenote/NSPredicate+Email.swift b/Simplenote/NSPredicate+Email.swift index 83e68052f..f815b5725 100644 --- a/Simplenote/NSPredicate+Email.swift +++ b/Simplenote/NSPredicate+Email.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSPredicate Validation Methods // extension NSPredicate { diff --git a/Simplenote/Note+Links.swift b/Simplenote/Note+Links.swift index b3b9261a6..4d6fbffc7 100644 --- a/Simplenote/Note+Links.swift +++ b/Simplenote/Note+Links.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Note + Links // extension Note { diff --git a/Simplenote/NoteMetrics.swift b/Simplenote/NoteMetrics.swift index 49f7e4737..e6f5dba4d 100644 --- a/Simplenote/NoteMetrics.swift +++ b/Simplenote/NoteMetrics.swift @@ -20,7 +20,6 @@ struct NoteMetrics { /// let modifiedDate: Date - /// Designed Initializer /// - Parameter note: Note from which we should extract metrics /// diff --git a/Simplenote/Notice.swift b/Simplenote/Notice.swift index d3041783f..a83966602 100644 --- a/Simplenote/Notice.swift +++ b/Simplenote/Notice.swift @@ -1,7 +1,7 @@ import Foundation struct Notice { - + let message: String let action: NoticeAction? diff --git a/Simplenote/NoticeView.swift b/Simplenote/NoticeView.swift index e9ed9935d..1b3c3d763 100644 --- a/Simplenote/NoticeView.swift +++ b/Simplenote/NoticeView.swift @@ -36,7 +36,6 @@ class NoticeView: UIView { weak var delegate: NoticeInteractionDelegate? - // MARK: Initialization // override func awakeFromNib() { diff --git a/Simplenote/Options.swift b/Simplenote/Options.swift index 445529956..8251594a9 100644 --- a/Simplenote/Options.swift +++ b/Simplenote/Options.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Wraps access to all of the UserDefault Values // class Options: NSObject { @@ -14,8 +13,6 @@ class Options: NSObject { /// private let defaults: UserDefaults - - /// Designated Initializer /// /// - Note: Should be *private*, but for unit testing purposes, we're opening this up. @@ -28,7 +25,6 @@ class Options: NSObject { } } - // MARK: - Actual Options! // extension Options { @@ -115,7 +111,6 @@ extension Options { } } - // MARK: - ObjC Convenience Methods // extension Options { @@ -150,8 +145,20 @@ extension Options { var numberOfPreviewLines: Int { return condensedNotesList ? Settings.numberOfPreviewLinesCondensed : Settings.numberOfPreviewLinesRegular } -} + /// Index notes in spotlight + /// + @objc + var indexNotesInSpotlight: Bool { + get { + defaults.bool(forKey: .indexNotesInSpotlight) + } + + set { + defaults.set(newValue, forKey: .indexNotesInSpotlight) + } + } +} // MARK: - Private // @@ -179,7 +186,6 @@ private extension Options { } } - // MARK: - Constants! // private enum Settings { diff --git a/Simplenote/PopoverPresenter.swift b/Simplenote/PopoverPresenter.swift index c71575383..0c2340730 100644 --- a/Simplenote/PopoverPresenter.swift +++ b/Simplenote/PopoverPresenter.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - PopoverPresenter // final class PopoverPresenter { @@ -23,12 +22,10 @@ final class PopoverPresenter { /// var dismissOnInteractionWithPassthruView: Bool = false - /// Dismiss on container frame change /// var dismissOnContainerFrameChange: Bool = false - /// Center content relative to anchor /// var centerContentRelativeToAnchor: Bool = false @@ -115,7 +112,6 @@ final class PopoverPresenter { } } - // MARK: - Geometry // private extension PopoverPresenter { @@ -177,7 +173,6 @@ private extension PopoverPresenter { } } - // MARK: - Defines the vertical orientation in which we'll display Popover // private enum Orientation { @@ -185,7 +180,6 @@ private enum Orientation { case below } - // MARK: - Metrics // private enum Metrics { diff --git a/Simplenote/Preferences+IAP.swift b/Simplenote/Preferences+IAP.swift index e9e68312f..c3d869877 100644 --- a/Simplenote/Preferences+IAP.swift +++ b/Simplenote/Preferences+IAP.swift @@ -8,7 +8,6 @@ import Foundation - // MARK: - Preferences Extensions // extension Preferences { @@ -17,4 +16,9 @@ extension Preferences { var isActiveSubscriber: Bool { subscription_level == StoreConstants.activeSubscriptionLevel } + + @objc + var wasSustainer: Bool { + was_sustainer == true + } } diff --git a/Simplenote/Preferences.h b/Simplenote/Preferences.h index d6e1aae61..654ced021 100644 --- a/Simplenote/Preferences.h +++ b/Simplenote/Preferences.h @@ -10,4 +10,6 @@ @property (nullable, nonatomic, copy) NSString *subscription_level; @property (nullable, nonatomic, copy) NSString *subscription_platform; +@property (nullable, nonatomic, copy) NSNumber *was_sustainer; + @end diff --git a/Simplenote/Preferences.m b/Simplenote/Preferences.m index 022f58cd6..24f52e968 100644 --- a/Simplenote/Preferences.m +++ b/Simplenote/Preferences.m @@ -16,6 +16,8 @@ @implementation Preferences @dynamic subscription_level; @dynamic subscription_platform; +@dynamic was_sustainer; + - (void) didChangeValueForKey:(NSString *)key { diff --git a/Simplenote/RecoveryUnarchiver.swift b/Simplenote/RecoveryUnarchiver.swift new file mode 100644 index 000000000..0e67e8c46 --- /dev/null +++ b/Simplenote/RecoveryUnarchiver.swift @@ -0,0 +1,51 @@ +import Foundation +import UniformTypeIdentifiers +import CoreData + +public class RecoveryUnarchiver { + private let fileManager: FileManager + private let simperium: Simperium + + public init(fileManager: FileManager = .default, simperium: Simperium) { + self.fileManager = fileManager + self.simperium = simperium + } + + // MARK: Restore + // + public func insertNotesFromRecoveryFilesIfNeeded() { + guard let recoveryURL = fileManager.recoveryDirectoryURL(), + let recoveryFiles = try? fileManager.contentsOfDirectory(at: recoveryURL, includingPropertiesForKeys: nil), + !recoveryFiles.isEmpty else { + return + } + + recoveryFiles.forEach { url in + insertNote(from: url) + try? fileManager.removeItem(at: url) + } + } + + private func insertNote(from url: URL) { + guard let data = fileManager.contents(atPath: url.path), + let recoveredContent = String(data: data, encoding: .utf8), + let note = simperium.notesBucket.insertNewObject() as? Note else { + return + } + + var content = Constants.recoveredContentHeader + content += "\n\n" + content += recoveredContent + note.content = content + + note.modificationDate = Date() + note.creationDate = Date() + note.markdown = UserDefaults.standard.bool(forKey: .markdown) + + simperium.save() + } + } + +private struct Constants { + static let recoveredContentHeader = NSLocalizedString("Recovered Note Cotent - ", comment: "Header to put on any files that need to be recovered") +} diff --git a/Simplenote/Remote.swift b/Simplenote/Remote.swift deleted file mode 100644 index 6b9424613..000000000 --- a/Simplenote/Remote.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -class Remote { - private let urlSession: URLSession - - init(urlSession: URLSession = URLSession.shared) { - self.urlSession = urlSession - } - - /// Send task for remote - /// Sublcassing Notes: To be able to send a task it is required to first setup the URL request for the task to use - /// - func performDataTask(with request: URLRequest, completion: @escaping (_ result: Result) -> Void) { - let dataTask = urlSession.dataTask(with: request) { (data, response, dataTaskError) in - DispatchQueue.main.async { - let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 - - // Check for 2xx status code - guard statusCode / 100 == 2 else { - let error = statusCode > 0 ? - RemoteError.requestError(statusCode, dataTaskError): - RemoteError.network - completion(.failure(error)) - return - } - - completion(.success(data)) - } - } - - dataTask.resume() - } -} diff --git a/Simplenote/RemoteError.swift b/Simplenote/RemoteError.swift deleted file mode 100644 index 74a96c9eb..000000000 --- a/Simplenote/RemoteError.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -enum RemoteError: Error { - case network - case requestError(Int, Error?) -} - -extension RemoteError: Equatable { - static func == (lhs: RemoteError, rhs: RemoteError) -> Bool { - switch (lhs, rhs) { - case (.network, .network): - return true - case (.requestError(let lhsStatus, let lhsError), .requestError(let rhsStatus, let rhsError)): - return lhsStatus == rhsStatus && lhsError?.localizedDescription == rhsError?.localizedDescription - default: - return false - } - } -} diff --git a/Simplenote/Resources/AppStoreStrings.pot b/Simplenote/Resources/AppStoreStrings.pot index 9ab8e3981..6acd42935 100644 --- a/Simplenote/Resources/AppStoreStrings.pot +++ b/Simplenote/Resources/AppStoreStrings.pot @@ -53,7 +53,7 @@ msgid "" "\n" "Privacy Policy: https://automattic.com/privacy/\n" "Terms of Service: https://simplenote.com/terms/\n" -"California Users Privacy Notice: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa\n" +"California Users Privacy Notice: https://automattic.com/privacy/#us-privacy-laws\n" "\n" "--\n" "\n" @@ -66,8 +66,11 @@ msgctxt "app_store_keywords" msgid "notes,note,markdown,journal,sync,to-do,list,cloud,notebook,simple,notepad,tag,todo,writing,memo" msgstr "" -msgctxt "v4.50-whats-new" +msgctxt "v4.54-whats-new" msgid "" -"• Behind-the-scenes reliability improvements\n" +"- Updated icons to work with new iOS 18 styles\n" +"- Add fall back login with username and password option to login\n" +"- Updated link to privacy notice for California users\n" +"\n" msgstr "" diff --git a/Simplenote/Resources/PrivacyInfo.xcprivacy b/Simplenote/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..374b5efe9 --- /dev/null +++ b/Simplenote/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,96 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + 3B52.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 7D9E.1 + + + + + diff --git a/Simplenote/Resources/release_notes.txt b/Simplenote/Resources/release_notes.txt deleted file mode 100644 index 81d1894ea..000000000 --- a/Simplenote/Resources/release_notes.txt +++ /dev/null @@ -1 +0,0 @@ -• Behind-the-scenes reliability improvements diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index dcd793697..151fc1c60 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -1,7 +1,6 @@ import Foundation import WidgetKit - // MARK: - Initialization // extension SPAppDelegate { @@ -151,8 +150,28 @@ extension SPAppDelegate { /// Opens editor with a new note /// + @objc + func presentNewNoteEditor(useSelectedTag: Bool = true, animated: Bool = false) { + performActionAfterUnlock { + if useSelectedTag { + self.presentNote(nil, animated: animated) + } else { + // If we use the standard new note option and a tag is selected then the tag is applied + // in some cases, like shortcuts, we don't want it do apply the tag cause you can't see + // what tag is selected when the shortcut runs + let note = SPObjectManager.shared().newDefaultNote() + self.presentNote(note, animated: true) + } + } + } + + @objc func presentNewNoteEditor(animated: Bool = false) { - presentNote(nil, animated: animated) + presentNewNoteEditor(useSelectedTag: true, animated: animated) + } + + var verifyController: PinLockVerifyController? { + (pinLockWindow?.rootViewController as? PinLockViewController)?.controller as? PinLockVerifyController } /// Opens a note with specified simperium key @@ -194,8 +213,17 @@ extension SPAppDelegate { func setupNoticeController() { NoticeController.shared.setupNoticeController() } -} + func performActionAfterUnlock(action: @escaping () -> Void) { + if isPresentingPasscodeLock && SPPinLockManager.shared.isEnabled { + verifyController?.addOnSuccesBlock { + action() + } + } else { + action() + } + } +} // MARK: - UIViewControllerRestoration // @@ -259,7 +287,6 @@ extension SPAppDelegate: UIViewControllerRestoration { } } - // MARK: - SimperiumDelegate // extension SPAppDelegate: SimperiumDelegate { @@ -276,7 +303,6 @@ extension SPAppDelegate: SimperiumDelegate { SPTracker.refreshMetadata(withEmail: user.email) // Shortcuts! - ShortcutsHandler.shared.registerSimplenoteActivities() ShortcutsHandler.shared.updateHomeScreenQuickActionsIfNeeded() // Now that the user info is present, cache it for use by the crash logging system. @@ -320,7 +346,6 @@ extension SPAppDelegate: SimperiumDelegate { } } - // MARK: - Passcode // extension SPAppDelegate { @@ -329,6 +354,7 @@ extension SPAppDelegate { @objc func showPasscodeLockIfNecessary() { guard SPPinLockManager.shared.isEnabled, !isPresentingPasscodeLock else { + verifyController?.removeSuccesBlocks() return } @@ -363,7 +389,6 @@ extension SPAppDelegate { } } - // MARK: - PinLockVerifyControllerDelegate // extension SPAppDelegate: PinLockVerifyControllerDelegate { @@ -419,16 +444,24 @@ private extension SPAppDelegate { } } - // MARK: - Magic Link authentication // extension SPAppDelegate { - @objc - func performMagicLinkAuthentication(with url: URL) { + + @objc @discardableResult + func performMagicLinkAuthentication(with url: URL) -> Bool { MagicLinkAuthenticator(authenticator: simperium.authenticator).handle(url: url) } -} + @objc(performMagicLinkAuthenticationWithUserActivity:) + func performMagicLinkAuthentication(with userActivity: NSUserActivity) -> Bool { + guard let url = userActivity.webpageURL else { + return false + } + + return performMagicLinkAuthentication(with: url) + } +} // MARK: - Scroll position cache // @@ -511,3 +544,35 @@ extension SPAppDelegate { WidgetController.syncWidgetDefaults(authenticated: authenticated, sortMode: sortMode) } } + +// MARK: - Sustainer migration +extension SPAppDelegate { + @objc + func migrateSimperiumPreferencesIfNeeded() { + guard UserDefaults.standard.bool(forKey: .hasMigratedSustainerPreferences) == false else { + return + } + + guard isFirstLaunch() == false else { + UserDefaults.standard.set(true, forKey: .hasMigratedSustainerPreferences) + return + } + + NSLog("Migrating Simperium Preferences object to include was_sustainer value") + UserDefaults.standard.removeObject(forKey: Simperium.preferencesLastChangedSignatureKey) + let prefs = simperium.preferencesObject() + prefs.ghostData = "" + simperium.saveWithoutSyncing() + + UserDefaults.standard.set(true, forKey: .hasMigratedSustainerPreferences) + } +} + +// MARK: - Content Recovery +// +extension SPAppDelegate { + @objc + func attemptContentRecoveryIfNeeded() { + RecoveryUnarchiver(simperium: simperium).insertNotesFromRecoveryFilesIfNeeded() + } +} diff --git a/Simplenote/SPAppDelegate.h b/Simplenote/SPAppDelegate.h index 41899d857..3c1db17c7 100644 --- a/Simplenote/SPAppDelegate.h +++ b/Simplenote/SPAppDelegate.h @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SPAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; -@property (nullable, strong, nonatomic) UIWindow *pinLockWindow; +@property (nullable, strong, nonatomic) UIWindow *pinLockWindow; @property (strong, nonatomic) Simperium *simperium; @property (strong, nonatomic) CoreDataManager *coreDataManager; @@ -45,6 +45,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)save; - (void)logoutAndReset:(id)sender; +- (BOOL)isFirstLaunch; + (SPAppDelegate *)sharedDelegate; diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index 76c1e5afe..6d2979ba4 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -131,6 +131,8 @@ - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions: [self setupDefaultWindow]; [self configureStateRestoration]; + [self migrateSimperiumPreferencesIfNeeded]; + return YES; } @@ -172,6 +174,7 @@ - (void)applicationDidBecomeActive:(UIApplication *)application { [SPTracker trackApplicationOpened]; [self syncWidgetDefaults]; + [self attemptContentRecoveryIfNeeded]; } - (void)applicationDidEnterBackground:(UIApplication *)application @@ -197,6 +200,10 @@ - (void)applicationWillEnterForeground:(UIApplication *)application - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler { + if ([self performMagicLinkAuthenticationWithUserActivity:userActivity]) { + return YES; + } + return [[ShortcutsHandler shared] handleUserActivity:userActivity]; } @@ -466,14 +473,8 @@ - (void)indexSpotlightItems { NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [context setParentContext:self.simperium.managedObjectContext]; - - [context performBlock:^{ - NSArray *deleted = [context fetchObjectsForEntityName:@"Note" withPredicate:[NSPredicate predicateWithFormat:@"deleted == YES"]]; - [[CSSearchableIndex defaultSearchableIndex] deleteSearchableNotes:deleted]; - - NSArray *notes = [context fetchObjectsForEntityName:@"Note" withPredicate:[NSPredicate predicateWithFormat:@"deleted == NO"]]; - [[CSSearchableIndex defaultSearchableIndex] indexSearchableNotes:notes]; - }]; + + [[CSSearchableIndex defaultSearchableIndex] indexSpotlightItemsIn:context]; } @@ -510,7 +511,11 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDiction [self presentNote:newNote animated:NO]; } - + + if ([[components host] isEqualToString:@"widgetNew"]) { + [self presentNewNoteEditorWithUseSelectedTag:NO animated:NO]; + } + return YES; } diff --git a/Simplenote/SPCardPresentationController.swift b/Simplenote/SPCardPresentationController.swift index 51a4a6f6a..e55e01b1d 100644 --- a/Simplenote/SPCardPresentationController.swift +++ b/Simplenote/SPCardPresentationController.swift @@ -12,14 +12,12 @@ enum SPCardDismissalReason { case outsideTap } - // MARK: - SPCardPresentationControllerDelegate // protocol SPCardPresentationControllerDelegate: AnyObject { func cardDidDismiss(_ viewController: UIViewController, reason: SPCardDismissalReason) } - // MARK: - SPCardPresentationController: Manages presentation and swipe to dismiss // final class SPCardPresentationController: UIPresentationController { diff --git a/Simplenote/SPEditorTextView+Simplenote.swift b/Simplenote/SPEditorTextView+Simplenote.swift index 1c9bb04bb..bc2924e2d 100644 --- a/Simplenote/SPEditorTextView+Simplenote.swift +++ b/Simplenote/SPEditorTextView+Simplenote.swift @@ -31,7 +31,6 @@ extension SPEditorTextView { } } - // MARK: - Observer content position // extension SPEditorTextView { diff --git a/Simplenote/SPHistoryVersion.swift b/Simplenote/SPHistoryVersion.swift index 93e81fc85..94f479d3c 100644 --- a/Simplenote/SPHistoryVersion.swift +++ b/Simplenote/SPHistoryVersion.swift @@ -15,7 +15,6 @@ struct SPHistoryVersion { /// let modificationDate: Date - /// Designated Initializer /// init?(version: Int, payload: [AnyHashable: Any]) { diff --git a/Simplenote/SPNoteHistoryController.swift b/Simplenote/SPNoteHistoryController.swift index 5418618b4..c761905cd 100644 --- a/Simplenote/SPNoteHistoryController.swift +++ b/Simplenote/SPNoteHistoryController.swift @@ -44,7 +44,6 @@ final class SPNoteHistoryController { /// weak var delegate: SPNoteHistoryControllerDelegate? - private let note: Note private let versionsController: VersionsController private var versionsToken: Any? diff --git a/Simplenote/SPPrivacyViewController.swift b/Simplenote/SPPrivacyViewController.swift index 36cd6fde0..c3f744254 100644 --- a/Simplenote/SPPrivacyViewController.swift +++ b/Simplenote/SPPrivacyViewController.swift @@ -34,7 +34,6 @@ class SPPrivacyViewController: SPTableViewController { return isAnalyticsEnabled.boolValue } - // MARK: - Overridden Methods override func viewDidLoad() { @@ -88,7 +87,6 @@ class SPPrivacyViewController: SPTableViewController { } } - // MARK: - Event Handlers // extension SPPrivacyViewController { @@ -114,7 +112,6 @@ extension SPPrivacyViewController { } } - // MARK: - Initialization Methods // private extension SPPrivacyViewController { @@ -181,7 +178,6 @@ private extension SPPrivacyViewController { } } - // MARK: - Private Types // private struct Section { diff --git a/Simplenote/SPTextAttachment.swift b/Simplenote/SPTextAttachment.swift index 804135295..f78689c61 100644 --- a/Simplenote/SPTextAttachment.swift +++ b/Simplenote/SPTextAttachment.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - SPTextAttachment // @objcMembers @@ -30,7 +29,6 @@ class SPTextAttachment: NSTextAttachment { /// var sizingFont: UIFont = .preferredFont(forTextStyle: .headline) - // MARK: - Overridden Methods override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect { @@ -41,7 +39,6 @@ class SPTextAttachment: NSTextAttachment { } } - // MARK: - Private // private extension SPTextAttachment { diff --git a/Simplenote/SPTracker+Extensions.swift b/Simplenote/SPTracker+Extensions.swift index 7a3c6a560..eaa370597 100644 --- a/Simplenote/SPTracker+Extensions.swift +++ b/Simplenote/SPTracker+Extensions.swift @@ -58,7 +58,6 @@ extension SPTracker { } } - // MARK: - Shortcuts // extension SPTracker { @@ -91,7 +90,6 @@ extension SPTracker { } } - // MARK: Account Deletion // extension SPTracker { diff --git a/Simplenote/SPUser+UserProtocol.swift b/Simplenote/SPUser+UserProtocol.swift new file mode 100644 index 000000000..b555a4e96 --- /dev/null +++ b/Simplenote/SPUser+UserProtocol.swift @@ -0,0 +1,8 @@ +import Foundation +import SimplenoteEndpoints +import Simperium + + +// MARK: - Simperium's SPUser Conformance +// +extension SPUser: UserProtocol { } diff --git a/Simplenote/SearchQuery+Simplenote.swift b/Simplenote/SearchQuery+Simplenote.swift index a947da955..cdca39499 100644 --- a/Simplenote/SearchQuery+Simplenote.swift +++ b/Simplenote/SearchQuery+Simplenote.swift @@ -13,4 +13,3 @@ extension SearchQuerySettings { return SearchQuerySettings(tagsKeyword: "tag:", localizedTagKeyword: localizedKeyword) } } - diff --git a/Simplenote/SeparatorsView.swift b/Simplenote/SeparatorsView.swift index b2c8c0755..c6080605e 100644 --- a/Simplenote/SeparatorsView.swift +++ b/Simplenote/SeparatorsView.swift @@ -63,8 +63,6 @@ open class SeparatorsView: UIView { } } - - // MARK: - UIView methods convenience init() { self.init(frame: CGRect.zero) diff --git a/Simplenote/Simplenote-Bridging-Header.h b/Simplenote/Simplenote-Bridging-Header.h index ff2d8992f..d433b86f9 100644 --- a/Simplenote/Simplenote-Bridging-Header.h +++ b/Simplenote/Simplenote-Bridging-Header.h @@ -32,6 +32,7 @@ #import "SPTracker.h" #import "SPTagEntryField.h" #import "WPAuthHandler.h" +#import "NSManagedObjectContext+CoreDataExtensions.h" #pragma mark - Extensions diff --git a/Simplenote/Simplenote.xcdatamodeld/.xccurrentversion b/Simplenote/Simplenote.xcdatamodeld/.xccurrentversion index ffa847231..2db50145c 100755 --- a/Simplenote/Simplenote.xcdatamodeld/.xccurrentversion +++ b/Simplenote/Simplenote.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Simplenote 6.xcdatamodel + Simplenote 7.xcdatamodel diff --git a/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents b/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents new file mode 100644 index 000000000..e915eb1f9 --- /dev/null +++ b/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Simplenote/SpinnerViewController.swift b/Simplenote/SpinnerViewController.swift index 340a9d07a..ce8b4a7af 100644 --- a/Simplenote/SpinnerViewController.swift +++ b/Simplenote/SpinnerViewController.swift @@ -35,7 +35,6 @@ class SpinnerViewController: UIViewController { ]) alertView.layer.cornerRadius = Constants.cornerRadius - alertView.addSubview(activityIndicator) alertView.sizeToFit() NSLayoutConstraint.activate([ @@ -52,7 +51,6 @@ class SpinnerViewController: UIViewController { activityIndicator.stopAnimating() } - private func setupViewAppearance() { modalPresentationStyle = .overFullScreen view.backgroundColor = UIColor(lightColor: .black, darkColor: .black, lightColorAlpha: 0.2, darkColorAlpha: 0.43) diff --git a/Simplenote/StoreConstants.swift b/Simplenote/StoreConstants.swift index 3eb6e2bd0..536fb7826 100644 --- a/Simplenote/StoreConstants.swift +++ b/Simplenote/StoreConstants.swift @@ -8,7 +8,6 @@ import Foundation - // MARK: - Settings // enum StoreConstants { diff --git a/Simplenote/StoreManager.swift b/Simplenote/StoreManager.swift index b87bd6780..e62c3d935 100644 --- a/Simplenote/StoreManager.swift +++ b/Simplenote/StoreManager.swift @@ -9,24 +9,20 @@ import Foundation import StoreKit - // MARK: - StoreError // enum StoreError: Error { case failedVerification } - // MARK: - StoreManager // -@available(iOS 15, *) class StoreManager { // MARK: - Static // static let shared = StoreManager() - // MARK: - Aliases // typealias SubscriptionStatus = Product.SubscriptionInfo.Status @@ -53,14 +49,12 @@ class StoreManager { return subscriptionGroupStatus.isActive } - // MARK: - Deinit deinit { updateListenerTask?.cancel() } - // MARK: - Public API(s) /// Initialization involves three major steps: @@ -84,7 +78,6 @@ class StoreManager { } } - /// Purchases the specified Product (as long as we don't own it already?) /// func purchase(storeProduct: StoreProduct) { @@ -126,10 +119,8 @@ class StoreManager { } } - // MARK: - Private API(s) // -@available(iOS 15, *) private extension StoreManager { func listenForTransactions() -> Task { @@ -216,10 +207,8 @@ private extension StoreManager { } } - // MARK: - Private Helpers // -@available(iOS 15, *) private extension StoreManager { func buildStoreProductMap(products: [Product]) -> [StoreProduct: Product] { @@ -248,10 +237,8 @@ private extension StoreManager { } } - // MARK: - Simperium Kung Fu // -@available(iOS 15, *) private extension StoreManager { func refreshSimperiumPreferences(status: SubscriptionStatus?) { @@ -272,10 +259,6 @@ private extension StoreManager { preferences.subscription_level = subscriptionLevel(from: status) preferences.subscription_date = subscriptionDate(from: status) preferences.subscription_platform = StoreConstants.platform - } else { - preferences.subscription_date = nil - preferences.subscription_level = nil - preferences.subscription_platform = nil } simperium.save() @@ -307,10 +290,8 @@ private extension StoreManager { } } - // MARK: - SubscriptionStatus Helpers // -@available(iOS 15, *) private extension Product.SubscriptionInfo.Status { var isActive: Bool { diff --git a/Simplenote/StoreProduct.swift b/Simplenote/StoreProduct.swift index da86964d9..716d0da5d 100644 --- a/Simplenote/StoreProduct.swift +++ b/Simplenote/StoreProduct.swift @@ -8,7 +8,6 @@ import Foundation - // MARK: - StoreProduct // enum StoreProduct: String, CaseIterable { diff --git a/Simplenote/SubtitleTableViewCell.swift b/Simplenote/SubtitleTableViewCell.swift index 351f4de3b..dc5022398 100644 --- a/Simplenote/SubtitleTableViewCell.swift +++ b/Simplenote/SubtitleTableViewCell.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - SubtitleTableViewCell // final class SubtitleTableViewCell: UITableViewCell { @@ -27,7 +26,6 @@ final class SubtitleTableViewCell: UITableViewCell { } } - // MARK: - Initializers override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -48,7 +46,6 @@ final class SubtitleTableViewCell: UITableViewCell { } } - // MARK: - Private API(s) // private extension SubtitleTableViewCell { diff --git a/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition new file mode 100644 index 000000000..04ca75224 --- /dev/null +++ b/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition @@ -0,0 +1,1065 @@ + + + + + INEnums + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + BxdWY5 + INIntentDefinitionSystemVersion + 23E214 + INIntentDefinitionToolsBuildVersion + 15F31d + INIntentDefinitionToolsVersion + 15.4 + INIntents + + + INIntentCategory + generic + INIntentClassName + OpenNewNoteIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescription + Open Simplenote and create a new note + INIntentDescriptionID + fgSbr5 + INIntentIneligibleForSuggestions + + INIntentManagedParameterCombinations + + + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Open a new note in Simplenote + INIntentParameterCombinationTitleID + OZjn1w + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + OpenNewNote + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Open New Note + INIntentTitleID + WsSu5K + INIntentType + Custom + INIntentVerb + Do + + + INIntentCategory + information + INIntentClassName + OpenNoteIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescription + Open Note in Simplenote + INIntentDescriptionID + sqM3pN + INIntentIneligibleForSuggestions + + INIntentInput + note + INIntentLastParameterTag + 2 + INIntentManagedParameterCombinations + + note + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Open ${note} in Simplenote + INIntentParameterCombinationTitleID + bjR9IG + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + OpenNote + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + note + INIntentParameterDisplayNameID + 4OEbmY + INIntentParameterDisplayPriority + 1 + INIntentParameterName + note + INIntentParameterObjectType + IntentNote + INIntentParameterObjectTypeNamespace + BxdWY5 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + ${note} + INIntentParameterPromptDialogFormatStringID + hDNku0 + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 2 + INIntentParameterType + Object + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Open Note + INIntentTitleID + IQnYW1 + INIntentType + Custom + INIntentVerb + Open + + + INIntentCategory + generic + INIntentClassName + AppendNoteIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescription + Append content to an existing note in Simplenote + INIntentDescriptionID + EeqvcH + INIntentIneligibleForSuggestions + + INIntentInput + note + INIntentLastParameterTag + 3 + INIntentManagedParameterCombinations + + note,content + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Append ${content}to ${note} + INIntentParameterCombinationTitleID + lSBG7V + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + AppendNote + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Note + INIntentParameterDisplayNameID + PaQ5Kk + INIntentParameterDisplayPriority + 1 + INIntentParameterName + note + INIntentParameterObjectType + IntentNote + INIntentParameterObjectTypeNamespace + BxdWY5 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 2 + INIntentParameterType + Object + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Content + INIntentParameterDisplayNameID + cqm5AM + INIntentParameterDisplayPriority + 2 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + skTbDU + INIntentParameterMetadataMultiline + + + INIntentParameterName + content + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + What would you like to append to ${note} + INIntentParameterPromptDialogFormatStringID + m6yrkX + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 3 + INIntentParameterType + String + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeFormatString + ${failureReason} + INIntentResponseCodeFormatStringID + Lyqq4K + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 1 + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Failure Reason + INIntentResponseParameterDisplayNameID + PueDbo + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + failureReason + INIntentResponseParameterTag + 1 + INIntentResponseParameterType + String + + + + INIntentTitle + Append To Note + INIntentTitleID + d6MjpX + INIntentType + Custom + INIntentVerb + Do + + + INIntentCategory + create + INIntentClassName + CreateNewNoteIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescription + Creates a note with supplied content and save it in Simplenote + INIntentDescriptionID + sXkMgW + INIntentIneligibleForSuggestions + + INIntentInput + content + INIntentLastParameterTag + 1 + INIntentManagedParameterCombinations + + content + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Create new note with ${content} + INIntentParameterCombinationTitleID + CNjhWs + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + CreateNewNote + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Content + INIntentParameterDisplayNameID + U9Yl4n + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + hM7vDg + + INIntentParameterName + content + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 1 + INIntentParameterType + String + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeFormatString + ${failureReason} + INIntentResponseCodeFormatStringID + d67xB8 + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 1 + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Failure Reason + INIntentResponseParameterDisplayNameID + 2w6Mce + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + failureReason + INIntentResponseParameterTag + 1 + INIntentResponseParameterType + String + + + + INIntentTitle + Create New Note With Content + INIntentTitleID + Ag8ugh + INIntentType + Custom + INIntentVerb + Create + + + INIntentCategory + search + INIntentClassName + FindNoteIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescription + Search the content of your notes to find a note to use in shortcuts + INIntentDescriptionID + V4ssp4 + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 13 + INIntentManagedParameterCombinations + + content,note + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Find a note with ${content} + INIntentParameterCombinationTitleID + VDjU7n + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + FindNote + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Content + INIntentParameterDisplayNameID + VGM0yX + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + g3QW1u + + INIntentParameterName + content + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Which One? + INIntentParameterPromptDialogFormatStringID + yuIu0n + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 4 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Note + INIntentParameterDisplayNameID + pFkRQX + INIntentParameterDisplayPriority + 2 + INIntentParameterName + note + INIntentParameterObjectType + IntentNote + INIntentParameterObjectTypeNamespace + BxdWY5 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Which Note? + INIntentParameterPromptDialogFormatStringID + OOdDHV + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Which One? + INIntentParameterPromptDialogFormatStringID + sOjweW + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 13 + INIntentParameterType + Object + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 10 + INIntentResponseOutput + note + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Note + INIntentResponseParameterDisplayNameID + Mz9mrb + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + note + INIntentResponseParameterObjectType + IntentNote + INIntentResponseParameterObjectTypeNamespace + BxdWY5 + INIntentResponseParameterTag + 10 + INIntentResponseParameterType + Object + + + + INIntentTitle + Find Note With Content + INIntentTitleID + oawbyy + INIntentType + Custom + INIntentVerb + Search + + + INIntentCategory + generic + INIntentClassName + CopyNoteContentIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescriptionID + e09Rd3 + INIntentIneligibleForSuggestions + + INIntentInput + note + INIntentLastParameterTag + 2 + INIntentManagedParameterCombinations + + note + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Copy content from ${note} + INIntentParameterCombinationTitleID + lXNZsS + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + CopyNoteContent + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Note + INIntentParameterDisplayNameID + cH6X2m + INIntentParameterDisplayPriority + 1 + INIntentParameterName + note + INIntentParameterObjectType + IntentNote + INIntentParameterObjectTypeNamespace + BxdWY5 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 2 + INIntentParameterType + Object + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 1 + INIntentResponseOutput + noteContent + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Note Content + INIntentResponseParameterDisplayNameID + raYk2S + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + noteContent + INIntentResponseParameterTag + 1 + INIntentResponseParameterType + String + + + + INIntentTitle + Copy Note Content + INIntentTitleID + s11KbQ + INIntentType + Custom + INIntentVerb + Do + + + INIntentCategory + search + INIntentClassName + FindNoteWithTagIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescriptionID + R4WdO6 + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 4 + INIntentManagedParameterCombinations + + tag,note + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Find a note with ${tag} + INIntentParameterCombinationTitleID + YxSeRd + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + FindNoteWithTag + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Tag + INIntentParameterDisplayNameID + s9MSi2 + INIntentParameterDisplayPriority + 1 + INIntentParameterName + tag + INIntentParameterObjectType + IntentTag + INIntentParameterObjectTypeNamespace + BxdWY5 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 3 + INIntentParameterType + Object + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Note + INIntentParameterDisplayNameID + wLeRnn + INIntentParameterDisplayPriority + 2 + INIntentParameterName + note + INIntentParameterObjectType + IntentNote + INIntentParameterObjectTypeNamespace + BxdWY5 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Which One? + INIntentParameterPromptDialogFormatStringID + uDDlMr + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 4 + INIntentParameterType + Object + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 2 + INIntentResponseOutput + note + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Note + INIntentResponseParameterDisplayNameID + CUkV8q + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + note + INIntentResponseParameterObjectType + IntentNote + INIntentResponseParameterObjectTypeNamespace + BxdWY5 + INIntentResponseParameterTag + 2 + INIntentResponseParameterType + Object + + + + INIntentTitle + Find Note With Tag + INIntentTitleID + nF2vZQ + INIntentType + Custom + INIntentVerb + Search + + + INTypes + + + INTypeClassName + IntentNote + INTypeClassPrefix + SP + INTypeDisplayName + Note + INTypeDisplayNameID + I19gXv + INTypeLastPropertyTag + 99 + INTypeName + IntentNote + INTypeProperties + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 1 + INTypePropertyName + identifier + INTypePropertyTag + 1 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 2 + INTypePropertyName + displayString + INTypePropertyTag + 2 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 3 + INTypePropertyName + pronunciationHint + INTypePropertyTag + 3 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 4 + INTypePropertyName + alternativeSpeakableMatches + INTypePropertySupportsMultipleValues + + INTypePropertyTag + 4 + INTypePropertyType + SpeakableString + + + + + INTypeClassName + IntentTag + INTypeClassPrefix + SP + INTypeDisplayName + Intent Tag + INTypeDisplayNameID + IIfrvb + INTypeLastPropertyTag + 99 + INTypeName + IntentTag + INTypeProperties + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 1 + INTypePropertyName + identifier + INTypePropertyTag + 1 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 2 + INTypePropertyName + displayString + INTypePropertyTag + 2 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 3 + INTypePropertyName + pronunciationHint + INTypePropertyTag + 3 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 4 + INTypePropertyName + alternativeSpeakableMatches + INTypePropertySupportsMultipleValues + + INTypePropertyTag + 4 + INTypePropertyType + SpeakableString + + + + + + diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index 895a310de..3f1354c73 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -74,8 +74,15 @@ This app does not require Photo Library access. NSUserActivityTypes + AppendNoteIntent + CopyNoteContentIntent + CreateNewNoteIntent + FindNoteIntent + FindNoteWithTagIntent ListWidgetIntent NoteWidgetIntent + OpenNewNoteIntent + OpenNoteIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/Simplenote/Supporting Files/Simplenote-Internal.entitlements b/Simplenote/Supporting Files/Simplenote-Internal.entitlements index 523982a37..c16966fec 100644 --- a/Simplenote/Supporting Files/Simplenote-Internal.entitlements +++ b/Simplenote/Supporting Files/Simplenote-Internal.entitlements @@ -5,6 +5,7 @@ com.apple.developer.associated-domains webcredentials:simplenote.com + applinks:app.simplenote.com com.apple.security.application-groups diff --git a/Simplenote/Supporting Files/Simplenote.entitlements b/Simplenote/Supporting Files/Simplenote.entitlements index 523982a37..c16966fec 100644 --- a/Simplenote/Supporting Files/Simplenote.entitlements +++ b/Simplenote/Supporting Files/Simplenote.entitlements @@ -5,6 +5,7 @@ com.apple.developer.associated-domains webcredentials:simplenote.com + applinks:app.simplenote.com com.apple.security.application-groups diff --git a/Simplenote/Supporting Files/ar.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/ar.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/ar.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/cy.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/cy.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/cy.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/de.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/de.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/de.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/el.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/el.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/el.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/en.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/en.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/en.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/es.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/es.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/es.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/fa.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/fa.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/fa.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/fr.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/fr.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/fr.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/he.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/he.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/he.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/id.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/id.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/id.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/it.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/it.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/it.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/ja.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/ja.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/ja.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/ko.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/ko.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/ko.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/nl.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/nl.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/nl.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/pt-BR.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/pt-BR.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/pt-BR.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/ru.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/ru.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/ru.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/sv.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/sv.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/sv.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/tr.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/tr.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/tr.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/zh-Hans-CN.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/zh-Hans-CN.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/zh-Hans-CN.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/Supporting Files/zh-Hant-TW.lproj/ShortcutIntents.strings b/Simplenote/Supporting Files/zh-Hant-TW.lproj/ShortcutIntents.strings new file mode 100644 index 000000000..099146dce --- /dev/null +++ b/Simplenote/Supporting Files/zh-Hant-TW.lproj/ShortcutIntents.strings @@ -0,0 +1,86 @@ +"4OEbmY" = "note"; + +"Ag8ugh" = "Create New Note With Content"; + +"CNjhWs" = "Create new note with ${content}"; + +"CUkV8q" = "Note"; + +"EeqvcH" = "Append content to an existing note in Simplenote"; + +"I19gXv" = "Note"; + +"IIfrvb" = "Intent Tag"; + +"IQnYW1" = "Open Note"; + +"MdiZid" = "Just to confirm, you wanted ‘${note}’?"; + +"Mz9mrb" = "Note"; + +"OOdDHV" = "Which Note?"; + +"OZjn1w" = "Open a new note in Simplenote"; + +"PaQ5Kk" = "Note"; + +"U4ce2H" = "There are ${count} options matching ‘${tag}’."; + +"U9Yl4n" = "Content"; + +"V4ssp4" = "Search the content of your notes to find a note to use in shortcuts"; + +"VBtgac" = "There are ${count} options matching ‘${note}’."; + +"VDjU7n" = "Find a note with ${content}"; + +"VGM0yX" = "Content"; + +"WsSu5K" = "Open New Note"; + +"YxSeRd" = "Find a note with ${tag}"; + +"bjR9IG" = "Open ${note} in Simplenote"; + +"cH6X2m" = "Note"; + +"cqm5AM" = "Content"; + +"d6MjpX" = "Append To Note"; + +"fgSbr5" = "Open Simplenote and create a new note"; + +"hDNku0" = "${note}"; + +"lSBG7V" = "Append ${content}to ${note}"; + +"lXNZsS" = "Copy content from ${note}"; + +"m6yrkX" = "What would you like to append to ${note}"; + +"nF2vZQ" = "Find Note With Tag"; + +"oawbyy" = "Find Note With Content"; + +"pFkRQX" = "Note"; + +"qYKqOP" = "Just to confirm, you wanted ‘${tag}’?"; + +"raYk2S" = "Note Content"; + +"s11KbQ" = "Copy Note Content"; + +"s9MSi2" = "Tag"; + +"sOjweW" = "Which One?"; + +"sXkMgW" = "Creates a note with supplied content and save it in Simplenote"; + +"sqM3pN" = "Open Note in Simplenote"; + +"uDDlMr" = "Which One?"; + +"wLeRnn" = "Note"; + +"yuIu0n" = "Which One?"; + diff --git a/Simplenote/TableHeaderViewCell.swift b/Simplenote/TableHeaderViewCell.swift index 2c352f9a9..b7f90960c 100644 --- a/Simplenote/TableHeaderViewCell.swift +++ b/Simplenote/TableHeaderViewCell.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - TableHeaderViewCell // final class TableHeaderViewCell: UITableViewCell { @@ -29,7 +28,6 @@ final class TableHeaderViewCell: UITableViewCell { } } - // MARK: - Private API(s) // private extension TableHeaderViewCell { diff --git a/Simplenote/TagListViewController.swift b/Simplenote/TagListViewController.swift index f34801e33..8779e97f0 100644 --- a/Simplenote/TagListViewController.swift +++ b/Simplenote/TagListViewController.swift @@ -18,7 +18,7 @@ final class TagListViewController: UIViewController { private var renameTag: Tag? - private var sustainerHeaderView: SustainerView? + private var bannerView: BannerView? private var isActiveSustainer: Bool { SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber } @@ -49,7 +49,6 @@ final class TagListViewController: UIViewController { super.viewWillAppear(animated) startListeningToKeyboardNotifications() - refreshTableHeaderView() reloadTableView() startListeningForChanges() becomeFirstResponder() @@ -64,22 +63,8 @@ final class TagListViewController: UIViewController { resignFirstResponder() } - - override func viewSafeAreaInsetsDidChange() { - super.viewSafeAreaInsetsDidChange() - refreshTableHeaderView() - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - - coordinator.animate { _ in - self.refreshTableHeaderView() - } - } } - // MARK: - Configuration // private extension TagListViewController { @@ -94,15 +79,6 @@ private extension TagListViewController { tableView.separatorInsetReference = .fromAutomaticInsets tableView.automaticallyAdjustsScrollIndicatorInsets = false - - sustainerHeaderView = { - let sustainerView: SustainerView = SustainerView.instantiateFromNib() - sustainerView.onPress = { [weak self] in - self?.presentSubscriptionAlertIfNeeded() - } - - return sustainerView - }() } func configureTableHeaderView() { @@ -127,23 +103,6 @@ private extension TagListViewController { } } - -// MARK: - Action Handlers -// -private extension TagListViewController { - - @available(iOS 15.0, *) - func presentSubscriptionAlertIfNeeded() { - if isActiveSustainer { - return - } - - let sustainerAlertController = UIAlertController.buildSustainerAlert() - present(sustainerAlertController, animated: true) - } -} - - // MARK: - Notifications // private extension TagListViewController { @@ -153,7 +112,6 @@ private extension TagListViewController { nc.addObserver(self, selector: #selector(menuDidChangeVisibility), name: UIMenuController.didHideMenuNotification, object: nil) nc.addObserver(self, selector: #selector(tagsSortOrderWasUpdated), name: NSNotification.Name.SPAlphabeticalTagSortPreferenceChanged, object: nil) nc.addObserver(self, selector: #selector(themeDidChange), name: NSNotification.Name.SPSimplenoteThemeChanged, object: nil) - nc.addObserver(self, selector: #selector(subscriptionStatusDidChange), name: .SPSubscriptionStatusDidChange, object: nil) } func startListeningToKeyboardNotifications() { @@ -186,13 +144,6 @@ private extension TagListViewController { func tagsSortOrderWasUpdated() { refreshSortDescriptorsAndPerformFetch() } - - @objc - func subscriptionStatusDidChange() { - DispatchQueue.main.async { - self.refreshTableHeaderView() - } - } } // MARK: - Style @@ -550,6 +501,13 @@ extension TagListViewController: TagListViewCellDelegate { preferredStyle: .actionSheet) alertController.addDestructiveActionWithTitle(Localization.TagDeletionConfirmation.confirmationButton) { (_) in + guard self.verifyTagIsAtIndexPath(tag, at: indexPath) else { + self.present(UIAlertController.dismissableAlert( + title: Localization.tagDeleteFailedTitle, + message: Localization.tagDeleteFailedMessage), animated: true) + return + } + switch source { case .accessory: SPTracker.trackTagRowDeleted() @@ -568,6 +526,17 @@ extension TagListViewController: TagListViewCellDelegate { present(alertController, animated: true, completion: nil) } + + private func verifyTagIsAtIndexPath(_ tagToRemove: Tag, at indexPath: IndexPath) -> Bool { + // REF: https://github.com/Automattic/simplenote-ios/issues/1312 + // If you initiate deleting a tag and the tag is deleted before you confirm then another tag is deleted + // This method confirms the selected tag is the same as the tag at index path before removing. + guard let tagAtIndexPath = tag(at: indexPath) else { + return false + } + + return tagToRemove.simperiumKey == tagAtIndexPath.simperiumKey + } } // MARK: - Helper Methods @@ -591,8 +560,10 @@ private extension TagListViewController { tagsHeaderView.actionButton.setTitle(title, for: .normal) } + // This method is for refreshing the size of the sustainer view. We are retiring sustainer so this is no longer needed + // but we may want to use the banner again in the future, so leaving this here in case. Method is not being called anywhere func refreshTableHeaderView() { - guard let sustainerHeaderView else { + guard let bannerView else { return } @@ -601,10 +572,10 @@ private extension TagListViewController { return } - sustainerHeaderView.preferredWidth = tableView.frame.width - view.safeAreaInsets.left - sustainerHeaderView.adjustSizeForCompressedLayout() + bannerView.preferredWidth = tableView.frame.width - view.safeAreaInsets.left + bannerView.adjustSizeForCompressedLayout() - tableView.tableHeaderView = sustainerHeaderView + tableView.tableHeaderView = bannerView } func openNoteListForTagName(_ tagName: String?, isTraversing: Bool) { @@ -935,6 +906,9 @@ private struct Localization { static let tags = NSLocalizedString("Tags", comment: "Tags List Header") static let untaggedNotes = NSLocalizedString("Untagged Notes", comment: "Allows selecting notes with no tags") + static let tagDeleteFailedTitle = NSLocalizedString("Could not delete tag", comment: "Notifies user tag delete failed") + static let tagDeleteFailedMessage = NSLocalizedString("Please try again", comment: "Encourages trying delete again") + struct TagDeletionConfirmation { static func title(with tagName: String) -> String { let template = NSLocalizedString("Are you sure you want to delete \"%1$@\"?", comment: "Title of deletion confirmation message for a tag. Parameters: %1$@ - tag name") diff --git a/Simplenote/Tags/NoteEditorTagListViewController.swift b/Simplenote/Tags/NoteEditorTagListViewController.swift index 54ce7c77e..21198b3a7 100644 --- a/Simplenote/Tags/NoteEditorTagListViewController.swift +++ b/Simplenote/Tags/NoteEditorTagListViewController.swift @@ -83,7 +83,6 @@ class NoteEditorTagListViewController: UIViewController { } } - // MARK: - First Responder // extension NoteEditorTagListViewController { @@ -100,7 +99,6 @@ extension NoteEditorTagListViewController { } } - // MARK: - Object Manager // private extension NoteEditorTagListViewController { @@ -130,7 +128,6 @@ private extension NoteEditorTagListViewController { } } - // MARK: - SPTagViewDelegate // extension NoteEditorTagListViewController: TagViewDelegate { @@ -199,14 +196,12 @@ extension NoteEditorTagListViewController: TagViewDelegate { } } - // MARK: - Constants // private struct Constants { static let clearRecentlyCreatedTagTimeout: TimeInterval = 3.5 } - // MARK: - Localization // private struct Localization { diff --git a/Simplenote/Tags/NoteEditorTagSuggestionsViewController.swift b/Simplenote/Tags/NoteEditorTagSuggestionsViewController.swift index 83b73430a..da15a0780 100644 --- a/Simplenote/Tags/NoteEditorTagSuggestionsViewController.swift +++ b/Simplenote/Tags/NoteEditorTagSuggestionsViewController.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - NoteEditorTagSuggestionsViewController // class NoteEditorTagSuggestionsViewController: UIViewController { @@ -74,7 +73,6 @@ class NoteEditorTagSuggestionsViewController: UIViewController { } } - // MARK: - UITableViewDelegate // extension NoteEditorTagSuggestionsViewController: UITableViewDelegate { @@ -84,7 +82,6 @@ extension NoteEditorTagSuggestionsViewController: UITableViewDelegate { } } - // MARK: - UITableViewDataSource // extension NoteEditorTagSuggestionsViewController: UITableViewDataSource { @@ -106,7 +103,6 @@ extension NoteEditorTagSuggestionsViewController: UITableViewDataSource { } } - // MARK: - Metrics // private struct Metrics { diff --git a/Simplenote/UIActivityViewController+Simplenote.swift b/Simplenote/UIActivityViewController+Simplenote.swift index 6e57a3dc8..e33670830 100644 --- a/Simplenote/UIActivityViewController+Simplenote.swift +++ b/Simplenote/UIActivityViewController+Simplenote.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - ActivityViewController Simplenote Methods // extension UIActivityViewController { diff --git a/Simplenote/UIAlertController+Auth.swift b/Simplenote/UIAlertController+Auth.swift new file mode 100644 index 000000000..73702271f --- /dev/null +++ b/Simplenote/UIAlertController+Auth.swift @@ -0,0 +1,22 @@ +import Foundation + + +// MARK: - UIAlertController Helpers +// +extension UIAlertController { + + /// Builds an alert indicating that the Login Code has Expired + /// + static func buildLoginCodeNotFoundAlert(onRequestCode: @escaping () -> Void) -> UIAlertController { + let title = NSLocalizedString("Sorry!", comment: "Email TextField Placeholder") + let message = NSLocalizedString("The authentication code you've requested has expired. Please request a new one", comment: "Email TextField Placeholder") + let acceptText = NSLocalizedString("Accept", comment: "Accept Message") + + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addDefaultActionWithTitle(acceptText) { _ in + onRequestCode() + } + + return alertController + } +} diff --git a/Simplenote/UIAlertController+Sustainer.swift b/Simplenote/UIAlertController+Sustainer.swift deleted file mode 100644 index 29f591679..000000000 --- a/Simplenote/UIAlertController+Sustainer.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// UIAlertController+Sustainer.swift -// Simplenote -// -// Created by Jorge Leandro Perez on 11/8/22. -// Copyright © 2022 Automattic. All rights reserved. -// - -import Foundation - - -// MARK: - UIAlertController+Sustainer -// -@available(iOS 15, *) -extension UIAlertController { - - static func buildSustainerAlert() -> UIAlertController { - let manager = StoreManager.shared - let monthlyActionTitle = Localization.monthlyActionTitle(price: manager.displayPrice(for: .sustainerMonthly)) - let yearlyActionTitle = Localization.yearlyActionTitle(price: manager.displayPrice(for: .sustainerYearly)) - - let style: UIAlertController.Style = UIDevice.isPad ? .alert : .actionSheet - let alert = UIAlertController(title: Localization.title, message: Localization.message, preferredStyle: style) - alert.addActionWithTitle(monthlyActionTitle, style: .default) { _ in - SPTracker.trackSustainerMonthlyButtonTapped() - manager.purchase(storeProduct: .sustainerMonthly) - } - - alert.addActionWithTitle(yearlyActionTitle, style: .default) { _ in - SPTracker.trackSustainerYearlyButtonTapped() - manager.purchase(storeProduct: .sustainerYearly) - } - - alert.addCancelActionWithTitle(Localization.dismissActionTitle) { _ in - SPTracker.trackSustainerDismissButtonTapped() - } - - return alert - } -} - - -// MARK: - Localization -// -private enum Localization { - static let title = NSLocalizedString("Simplenote Sustainer", comment: "Sustainer Alert's Title") - static let message = NSLocalizedString("Choose a plan and help unlock future features", comment: "Sustainer Alert's Message") - static let dismissActionTitle = NSLocalizedString("Cancel", comment: "Dismisses the alert") - - static func monthlyActionTitle(price: String?) -> String { - guard let price else { - return NSLocalizedString("Monthly", comment: "Monthly Subscription Option (Used when / if the price fails to load)") - } - - let text = NSLocalizedString("%@ per Month", comment: "Monthly Subscription Option. Please preserve the special marker!") - return String(format: text, price) - } - - static func yearlyActionTitle(price: String?) -> String { - guard let price else { - return NSLocalizedString("Yearly", comment: "Yearly Subscription Option (Used when / if the price fails to load)") - } - - let text = NSLocalizedString("%@ per Year", comment: "Yearly Subscription Option. Please preserve the special marker!") - return String(format: text, price) - } -} diff --git a/Simplenote/UIButton+Simplenote.swift b/Simplenote/UIButton+Simplenote.swift new file mode 100644 index 000000000..1d2dad366 --- /dev/null +++ b/Simplenote/UIButton+Simplenote.swift @@ -0,0 +1,19 @@ +import Foundation + + +extension UIButton { + + func setTitleWithoutAnimation(_ title: String?, for state: UIControl.State) { + UIView.performWithoutAnimation { + self.setTitle(title, for: state) + self.layoutIfNeeded() + } + } + + func setAttributedTitleWithoutAnimation(_ title: NSAttributedString?, for state: UIControl.State) { + UIView.performWithoutAnimation { + self.setAttributedTitle(title, for: state) + self.layoutIfNeeded() + } + } +} diff --git a/Simplenote/URL+Simplenote.swift b/Simplenote/URL+Simplenote.swift index 17e5287b4..b2a976f60 100644 --- a/Simplenote/URL+Simplenote.swift +++ b/Simplenote/URL+Simplenote.swift @@ -15,7 +15,7 @@ extension URL { } static func newNoteURL(withTag tag: String? = nil) -> URL { - guard var components = URLComponents.simplenoteURLComponents(with: Constants.newNotePath) else { + guard var components = URLComponents.simplenoteURLComponents(with: Constants.widgetNewNotePath) else { return URL(string: .simplenotePath())! } @@ -27,9 +27,18 @@ extension URL { return components.url! } + + static func newNoteWidgetURL() -> URL { + guard let components = URLComponents.simplenoteURLComponents(with: Constants.newNotePath) else { + return URL(string: .simplenotePath())! + } + + return components.url! + } } private struct Constants { static let tagQueryBase = "tag" static let newNotePath = "new" + static let widgetNewNotePath = "widgetNew" } diff --git a/Simplenote/URLRequest+Simplenote.swift b/Simplenote/URLRequest+Simplenote.swift deleted file mode 100644 index 5adddd4cf..000000000 --- a/Simplenote/URLRequest+Simplenote.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension URLRequest { - func decodeHtmlBody() throws -> T? { - guard let _ = httpBody else { - return nil - } - - return try httpBody.map { - try JSONDecoder().decode(T.self, from: $0) - } - } -} diff --git a/Simplenote/Value1TableViewCell.swift b/Simplenote/Value1TableViewCell.swift index 43f3df3ec..33199d3ca 100644 --- a/Simplenote/Value1TableViewCell.swift +++ b/Simplenote/Value1TableViewCell.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Value1TableViewCell // class Value1TableViewCell: UITableViewCell { @@ -56,7 +55,6 @@ class Value1TableViewCell: UITableViewCell { } } - // MARK: - Initializers override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -78,7 +76,6 @@ class Value1TableViewCell: UITableViewCell { } } - // MARK: - Private API(s) // private extension Value1TableViewCell { diff --git a/Simplenote/Verification/AccountVerificationController.swift b/Simplenote/Verification/AccountVerificationController.swift index 4bab434a6..3f11cc042 100644 --- a/Simplenote/Verification/AccountVerificationController.swift +++ b/Simplenote/Verification/AccountVerificationController.swift @@ -1,4 +1,5 @@ import Foundation +import SimplenoteEndpoints // MARK: - AccountVerificationController // diff --git a/Simplenote/ar.lproj/Localizable.strings b/Simplenote/ar.lproj/Localizable.strings index 85a9ae2f3..b14070493 100644 --- a/Simplenote/ar.lproj/Localizable.strings +++ b/Simplenote/ar.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-24 00:43:04+0000 */ +/* Translation-Revision-Date: 2024-09-19 12:54:03+0000 */ /* Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ar */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ شهريًا "; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ سنويًا"; - -/* Number of found search results */ "%d Result" = "%d من النتائج"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "نبذة عن"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "قبول"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "كل الملاحظات"; -/* A short description to access the account login screen */ -"Already have an account?" = "هل لديك حساب بالفعل؟"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "تم إرسال بريد إلكتروني إلى %@، راجع علبة الوارد لديك واتبع الإرشادات لتأكيد حذف الحساب.\n\nلن يتم حذف حسابك حتى نتلقى تأكيدك"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "هل أنت متأكد من رغبتك في إفراغ سلة المهملات؟ يتعذر التراجع عن هذا."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "فشلت محاولة إحضار محتوى الملاحظات الحالي. يرجى المحاولة مرة أخرى لاحقًا."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "فشلت محاولة إحضار الكيانات. يرجى المحاولة لاحقًا."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "فشلت محاولة إحضار الملاحظات. يرجى المحاولة مرة أخرى لاحقًا."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "فشلت محاولة إحضار الوسوم. يرجى المحاولة مرة أخرى لاحقًا."; + /* User Authenticated */ "Authenticated" = "مصادَق عليه"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "عن طريق حذف الحساب الخاص بـ %@,، سيتم حذف جميع الملحوظات التي تم إنشاؤها باستخدام هذا الحساب نهائيًا. لا يمكن التراجع عن هذا الإجراء"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "يعني تسجيلك موافقتك على شروط الخدمة »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "اختيار رمز مرور مكوَّن من 4 أرقام"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "اختيار خطة للمساعدة على فتح الميزات في المستقبل"; +/* Code TextField Placeholder */ +"Code" = "رمز"; /* Opens the Collaborate UI */ "Collaborate" = "تشارك"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "قائمة الملاحظات الموجزة"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "تأكيد"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "تعذر إنشاء حساب بعنوان البريد الإلكتروني وكلمة المرور اللذين تم إدخالهما."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "تعذر حذف الوسم"; + +/* Note fetch error title */ +"Could not fetch Notes" = "يتعذر إحضار الملاحظات"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "يتعذر إحضار الوسوم"; + /* Fetch error title */ "Could not fetch entities" = "يتعذر إحضار الكيانات"; +/* note content fetch error title */ +"Could not fetch note content" = "يتعذر إحضار المحتوى"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "تعذر تسجيل الدخول بعنوان البريد الإلكتروني وكلمة المرور اللذين تم إدخالهما."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "مستند"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "ليس لديك حساب؟"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "بقائمة الانتظار"; +/* LogIn Interface Title */ +"Enter Code" = "إدخال الكود"; + +/* Enter Password fallback Action */ +"Enter password" = "إدخال كلمة المرور"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "إدخال كلمة المرور الخاصة بالحساب {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "إدخال رمز المرور الخاص بك"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "خطأ"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "خطأ: يجب أن يكون persistentStoreCoordinator الخاص بـ NSManagedObjectContext عديم القيمة. سيقوم Simperium بمعالجة اتصالات CoreData من أجلك."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "خطأ: تجب عليك تهيئة سياقك الذي يحمل نوع التزامن \"NSMainQueueConcurrencyType\"."; - /* Get Help Description Label */ "FAQ or contact us" = "الأسئلة المتداولة أو اتصل بنا"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "سحب الإصدار"; -/* Forgot password Button Text */ -"Forgot password? »" = "هل نسيت كلمة المرور؟ »"; - /* Password Reset Action */ -"Forgotten password?" = "هل نسيت كلمة المرور؟"; +"Forgot your password?" = "هل نسيت كلمة مرورك؟"; /* FAQ or contact us */ "Get Help" = "إحصل على المساعدة"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "يعجبني هذا"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "ملاحظات الفهرس في Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "تمت إزالة الفهرس"; + /* Note Information Button (metrics + references) */ "Information" = "معلومات"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "تسجيل الدخول"; +/* LogIn Interface Title */ +"Log In with Password" = "تسجيل الدخول باستخدام كلمة المرور"; + /* Log out of the active account in the app */ "Log Out" = "تسجيل الخروج"; /* Widget warning if user is logged out */ "Log in to see your notes" = "تسجيل الدخول للاطلاع على ملحوظاتك"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "تسجيل دخول باستخدام ووردبريس.كوم"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "تسجيل الدخول باستخدام البريد الإلكتروني"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "فشل تسجل الدخول باستخدام البريد الإلكتروني، يرجى إدخال كلمة مرورك"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "كود تسجيل الدخول قصير للغاية"; + /* Month and day date formatter */ "MMM d" = "MMM d"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "تاريخ التعديل: الأقدم"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "شهريًا"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "نقل الملحوظات المحدَّدة إلى سلة المهملات"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "لا توجد أي ملاحظات غير موسومة"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "ليس بريدًا إلكترونيًا صالحًا"; - /* Note Widget Title */ "Note" = "ملاحظة"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "موافق"; +/* confirm button title */ +"Okay" = "حسنًا"; + /* No comment provided by engineer. */ "On" = "تشغيل"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "خيارات"; +/* Or, used as a separator between Actions */ +"Or" = "أو"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "رمز المرور"; /* Pin Lock */ "Passcodes did not match. Try again." = "رموز المرور غير متطابقة. حاول مرة أخرى."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "كلمة المرور"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "يتعذر تطابق كلمة المرور مع البريد الإلكتروني"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "يجب أن تحتوي كلمة المرور على %d من الأحرف على الأقل"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "يجب ألا تحتوي كلمة المرور على علامات جدولة أو خطوط جديدة"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "كلمات المرور غير متطابقة"; - /* Number of changes pending to be sent */ "Pendings" = "معلّق"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "يرجى تسجيل الدخول إلى حسابك على Simplenote أولاً عن طريق استخدام تطبيق Simplenote."; +/* Encourages trying delete again */ +"Please try again" = "يرجى المحاولة مجددًا"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "ترجى الترقية إلى أحدث إصدار من نظام التشغيل iOS لاستعادة عمليات الشراء"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "الحديث"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "تم استرداد Note Cotent - "; + /* References section header on Info Card */ "Referenced In" = "تمت الإشارة في"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "استعادة الملاحظة"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "استعادة عمليات الشراء"; - /* Title -> Review you account screen */ "Review Your Account" = "مراجعة حسابك"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "الشريط الجانبي"; -/* Title of button for logging in (must be short) */ -"Sign In" = "تسجيل الدخول"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "التسجيل"; -/* A short link to access the account login screen */ -"Sign in" = "تسجيل الدخول"; - -/* A short link to access the account creation screen */ -"Sign up" = "التسجيل"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "سيؤدي تسجيل الخروج إلى حذف أي ملحوظات غير متزامنة. يمكنك التحقق من ملحوظاتك المتزامنة عن طريق تسجيل الدخول إلى تطبيق الويب."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "يجب تكوين Simplenote وتسجيل الدخول إليه لإعداد المربعات الجانبية"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "عذرًا!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "فرز حسب:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "قد يستمر سجل Spotlight في الظهور في نتائج البحث، لكن يجب إلغاء فهرسة الملاحظات"; + /* Title for the response's Status Code */ "Status Code" = "كود الحالة"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "دعم تطبيق الملحوظات المفضَّل لديك للمساعدة على فتح الميزات في المستقبل"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "أيقونة تطبيق Sustainer"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "تم استعادة اشتراك Sustainer. شكرًا لك على دعم Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "شكرًا لك على دعمك المتواصل"; -/* Error when address is in use */ -"That email is already being used" = "يتم استخدام ذلك البريد الإلكتروني بالفعل"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "انتهت صلاحية كود المصادقة الذي طلبته. يرجى طلب واحد جديد"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "الكود الذي أدخلته غير صحيح. يرجى المحاولة مجددًا"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "البريد الإلكتروني الذي أدخلته مرتبط بالفعل بحساب Simplenote."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "تراجع"; -/* WebSocket not initialized */ -"Uninitialized" = "غير مهيأ"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "وسم بلا اسم."; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "زيارة تطبيق الويب"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "سنرسل إليك كودًا بالبريد لتسجيل الدخول، أو يمكنك"; + /* Generic error */ "We're having problems. Please try again soon." = "لدينا مشكلات. يُرجى المحاولة مرة أخرى لاحقًا."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "لقد أرسلنا كودًا إلى {{EMAIL}} سيكون هذا الكود صالحًا لبضع دقائق."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "ما رأيك في Simplenote؟"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "ستتم محاولة استعادة محتوى الاختصارات في الطرح المقبل"; + /* Number of words in the note */ "Words" = "الكلمات"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "سنوي"; - /* Proceeds with the Empty Trash OP */ "Yes" = "نعم"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "أصبحت Sustainer بالفعل. شكرًا لك على دعم Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "انتهت صلاحية كود المصادقة الخاص بك. يرجى طلب واحد جديد"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "عنوان البريد الإلكتروني الخاص بك غير صالح."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "سلة المهملات لديك فارغة"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "التسجيل يدويًا."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "وسم:"; diff --git a/Simplenote/cy.lproj/Localizable.strings b/Simplenote/cy.lproj/Localizable.strings index 3ab207cd5..7141a3f50 100644 --- a/Simplenote/cy.lproj/Localizable.strings +++ b/Simplenote/cy.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: cy_GB */ -/* Number of found search results */ +/* No comment provided by engineer. */ "%d Result" = "%d Canlyniad"; /* Number of found search results */ @@ -16,6 +16,7 @@ "%i Failed Passcode Attempts" = "%i Ymgais Aflwyddiannus gyda'r Cod Cyfrin"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Derbyn"; @@ -33,23 +34,15 @@ Title: No filters applied */ "All Notes" = "Pob Nodyn"; -/* A short description to access the account login screen */ -"Already have an account?" = "Eisoes â chyfrif?"; - /* The Simplenote blog */ "Blog" = "Blog"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Drwy ymuno, rydych yn cytuno i'n Amodau Gwasanaeth »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -64,8 +57,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Rhestr Gryno o Nodiadau"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Cadarnhau"; /* Rating view - initial - could be better button */ @@ -102,9 +94,6 @@ /* Dismiss Keyboard Button */ "Dismiss keyboard" = "Cau'r bysellfwrdd"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Heb greu cyfrif?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -127,9 +116,6 @@ Error Title */ "Error" = "Gwall"; -/* Forgot password Button Text */ -"Forgot password? »" = "Wedi anghofio eich cyfrinair? »"; - /* Opens the Note's History */ "History" = "Hanes"; @@ -186,12 +172,10 @@ /* Pin Lock */ "Passcodes did not match. Try again." = "Nid oedd y codau cyfrin yn cyfateb. Rhowch gynnig arall arni."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Cyfrinair"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Rhaid i'r cyfrinair gynnwys o leiaf %d nod."; /* Pin State Accessibility Hint */ @@ -246,21 +230,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Bar ochr"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Mewngofnodi"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Cofrestru"; -/* A short link to access the account login screen */ -"Sign in" = "Mewngofnodi"; - -/* A short link to access the account creation screen */ -"Sign up" = "Cofrestru"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Bydd all-gofnodi'n dileu unrhyw nodau heb eu cydweddu. Gallwch ddilysu eich nodau wedi eu cydweddu drwy fewngofnodi i'r Web App."; diff --git a/Simplenote/de.lproj/Localizable.strings b/Simplenote/de.lproj/Localizable.strings index f0b51ad56..62cff2571 100644 --- a/Simplenote/de.lproj/Localizable.strings +++ b/Simplenote/de.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 09:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-19 12:54:03+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: de */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ pro Monat"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ pro Jahr"; - -/* Number of found search results */ "%d Result" = "%d Ergebnis"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Über"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Akzeptieren"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Alle Notizen"; -/* A short description to access the account login screen */ -"Already have an account?" = "Du hast bereits ein Konto?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Wir haben eine E-Mail an %@ gesendet. Bitte sieh in deinem Posteingang nach und folge den Anweisungen, um die Kontolöschung zu bestätigen.\n\nDein Konto wird erst gelöscht, wenn wir die Bestätigung dafür erhalten haben."; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Soll der Papierkorb wirklich geleert werden? Dies kann nicht rückgängig gemacht werden."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Versuch, aktuellen Notizeninhalt abzurufen, fehlgeschlagen. Bitte versuche es später erneut."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Abrufen von Einheiten fehlgeschlagen. Bitte versuche es später erneut."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Versuch, Notizen abzurufen, fehlgeschlagen. Bitte versuche es später erneut."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Versuch, Schlagwörter abzurufen, fehlgeschlagen. Bitte versuche es später erneut."; + /* User Authenticated */ "Authenticated" = "Authentifiziert"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "Wenn du das Konto für %@ löschst, werden alle Notizen, die in diesem Konto erstellt wurden, unwiderruflich gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden."; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Indem du dich registrierst, stimmst du unseren Geschäftsbedingungen zu »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Vierstelligen Zugangscode auswählen"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Wähle einen Tarif und profitiere von zukünftigen Funktionen"; +/* Code TextField Placeholder */ +"Code" = "Code"; /* Opens the Collaborate UI */ "Collaborate" = "Zusammenarbeiten"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Notizenliste Kurzfassung"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Bestätigen"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Mit der angegebenen E-Mail-Adresse und dem Passwort konnte kein Konto erstellt werden."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Schlagwort konnte nicht gelöscht werden"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Notizen konnten nicht abgerufen werden"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Schlagwörter konnten nicht abgerufen werden"; + /* Fetch error title */ "Could not fetch entities" = "Einheiten konnten nicht abgerufen werden"; +/* note content fetch error title */ +"Could not fetch note content" = "Notizeninhalt konnte nicht abgerufen werden"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Mit der angegebenen E-Mail-Adresse und dem Passwort war keine Anmeldung möglich."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Dokument"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Du hast noch kein Konto?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "In der Warteschlange"; +/* LogIn Interface Title */ +"Enter Code" = "Code eingeben"; + +/* Enter Password fallback Action */ +"Enter password" = "Passwort eingeben"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Gib das Passwort für das Konto {{EMAIL}} ein"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Gib deinen Passcode ein"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Fehler"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Fehler: Der persistente Store-Koordinator von NSManagedObjectContext muss null sein. Simperium kümmert sich um Core Data-Verbindungen."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Fehler: Du musst deinen Kontext mit dem Parallelitätstyp „NSMainQueueConcurrencyType“ initialisieren."; - /* Get Help Description Label */ "FAQ or contact us" = "FAQ oder Kontakt"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Version wird abgerufen"; -/* Forgot password Button Text */ -"Forgot password? »" = "Passwort vergessen? »"; - /* Password Reset Action */ -"Forgotten password?" = "Passwort vergessen?"; +"Forgot your password?" = "Passwort vergessen?"; /* FAQ or contact us */ "Get Help" = "Hilfe bekommen"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Gefällt mir"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Notizen in Spotlight indizieren"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Index entfernt"; + /* Note Information Button (metrics + references) */ "Information" = "Informationen"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Anmelden"; +/* LogIn Interface Title */ +"Log In with Password" = "Mit Passwort anmelden"; + /* Log out of the active account in the app */ "Log Out" = "Abmelden"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Melde dich an, um deine Notizen zu sehen"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Mit WordPress.com anmelden"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Mit E-Mail-Adresse anmelden"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Die Anmeldung per E-Mail ist fehlgeschlagen, bitte gib dein Passwort ein"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Anmeldecode ist zu kurz"; + /* Month and day date formatter */ "MMM d" = "d. MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Geändert: Älteste"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Monatlich"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Verschiebe die ausgewählten Notizen in den Papierkorb"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Keine nicht markierten Notizen"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Keine gültige E-Mail-Adresse"; - /* Note Widget Title */ "Note" = "Notiz"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "OK"; +/* confirm button title */ +"Okay" = "Okay"; + /* No comment provided by engineer. */ "On" = "Ein"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Optionen"; +/* Or, used as a separator between Actions */ +"Or" = "Oder"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Passcode"; /* Pin Lock */ "Passcodes did not match. Try again." = "Die Passcodes stimmten nicht überein. Versuch's nochmal."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Passwort"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "Das Passwort darf nicht der E-Mail entsprechen."; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Das Passwort muss mindestens %d Zeichen enthalten."; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "Das Passwort darf keine Tabs oder Zeilenvorschübe enthalten."; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Passwörter stimmen nicht überein"; - /* Number of changes pending to be sent */ "Pendings" = "Ausstehend"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Bitte melde dich zuerst über die Simplenote-App bei deinem Simplenote-Konto an."; +/* Encourages trying delete again */ +"Please try again" = "Bitte versuche es erneut"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Führe ein Upgrade auf die neueste iOS-Version durch, um Käufe wiederherzustellen"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Kürzlich"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Notizeninhalt wiederhergestellt – "; + /* References section header on Info Card */ "Referenced In" = "Referenziert in"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Notiz wiederherstellen"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Käufe wiederherstellen"; - /* Title -> Review you account screen */ "Review Your Account" = "Konto prüfen"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Seitenleiste"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Anmelden"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Registrieren"; -/* A short link to access the account login screen */ -"Sign in" = "Anmelden"; - -/* A short link to access the account creation screen */ -"Sign up" = "Registrieren"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Bei der Abmeldung werden alle nicht synchronisierten Notizen gelöscht. Du kannst überprüfen, ob deine Notizen synchronisiert wurden, indem du dich in der Web-App anmeldest."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote-Unterstützer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Um Widgets einzurichten, musst du Simplenote konfigurieren und dich dort anmelden"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Entschuldigung!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Sortiert nach:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "Der Spotlight-Verlauf wird gegebenenfalls noch in den Suchergebnissen angezeigt, aber die Notizen wurden vom Index entfernt"; + /* Title for the response's Status Code */ "Status Code" = "Statuscode"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Unterstütze deine bevorzugte Notizen-App, um von zukünftigen Funktionen zu profitieren"; -/* No comment provided by engineer. */ -"Sustainer" = "Unterstützer"; +/* Switch app icon */ +"Sustainer App Icon" = "Unterstützer-App-Icon"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Unterstützer-Abonnement wiederhergestellt. Danke für deine Unterstützung von Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Danke für deine kontinuierliche Unterstützung"; -/* Error when address is in use */ -"That email is already being used" = "Diese E-Mail-Adresse wird bereits verwendet"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "Der angeforderte Authentifizierungscode ist abgelaufen. Bitte fordere einen neuen an"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "Der eingegebene Code ist nicht korrekt. Bitte versuche es erneut"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "Die eingegebene E-Mail-Adresse ist bereits mit einem Simplenote-Konto verknüpft."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Rückgängig"; -/* WebSocket not initialized */ -"Uninitialized" = "Nicht initialisiert"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Unbenanntes Schlagwort"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Web-App aufrufen"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Wir senden dir eine E-Mail mit einem Code zur Anmeldung."; + /* Generic error */ "We're having problems. Please try again soon." = "Momentan gibt es ein paar Probleme. Bitte versuche es später erneut."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Wir haben einen Code gesendet an: {{EMAIL}}. Der Code ist einige Minuten gültig."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Was hältst du von Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Wir werden versuchen, den Inhalt der Verknüpfungen bei der nächsten Veröffentlichung wiederherzustellen"; + /* Number of words in the note */ "Words" = "Wörter"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Jährlich"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Ja"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Du bist bereits ein Unterstützer. Danke für deine Unterstützung von Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Dein Authentifizierungscode ist abgelaufen. Bitte fordere einen neuen an"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Deine E-Mail-Adresse ist ungültig."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Der Papierkorb ist leer"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "Alternativ kannst du dich manuell anmelden."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "Schlagwort:"; diff --git a/Simplenote/el.lproj/Localizable.strings b/Simplenote/el.lproj/Localizable.strings index 2100e34f9..9dec8d35e 100644 --- a/Simplenote/el.lproj/Localizable.strings +++ b/Simplenote/el.lproj/Localizable.strings @@ -1,9 +1,9 @@ -/* Translation-Revision-Date: 2020-06-15 15:27:25+0000 */ +/* Translation-Revision-Date: 2019-11-22 16:18:55+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: el_GR */ -/* Number of found search results */ +/* No comment provided by engineer. */ "%d Result" = "%d αποτέλεσμα"; /* Number of found search results */ @@ -16,6 +16,7 @@ "%i Failed Passcode Attempts" = "%i αποτυχημένες απόπειρες συνθηματικού"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Αποδοχή"; @@ -28,17 +29,12 @@ /* Label on button to add a new tag to a note */ "Add tag" = "Προσθήκη ετικέτας"; -/* A short description to access the account login screen */ -"Already have an account?" = "Έχετε ήδη λογαριασμό;"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -50,8 +46,7 @@ /* Noun - collaborators are other Simplenote users who you chose to share a note with */ "Collaborators" = "Συνεργάτες"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Επιβεβαίωση"; /* Error for bad email or password */ @@ -69,9 +64,6 @@ /* Dismiss Keyboard Button */ "Dismiss keyboard" = "Απόκρυψη"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Δεν έχετε λογαριασμό;"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -119,8 +111,7 @@ /* Pin Lock */ "Passcodes did not match. Try again." = "Τα συνθηματικά δεν συμφωνούν. Δοκιμάστε ξανά."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Κωδικός"; /* Pin State Accessibility Hint */ @@ -156,21 +147,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Στήλη"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Σύνδεση"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Εγγραφή"; -/* A short link to access the account login screen */ -"Sign in" = "Σύνδεση"; - -/* A short link to access the account creation screen */ -"Sign up" = "Εγγραφή"; - /* Displayed as a date in the case where a note was modified today, for example */ "Today" = "Σήμερα"; diff --git a/Simplenote/en.lproj/Localizable.strings b/Simplenote/en.lproj/Localizable.strings index 46c39dc75..cbd0bf2e2 100644 Binary files a/Simplenote/en.lproj/Localizable.strings and b/Simplenote/en.lproj/Localizable.strings differ diff --git a/Simplenote/es.lproj/Localizable.strings b/Simplenote/es.lproj/Localizable.strings index 93ca839f3..9e521bf46 100644 --- a/Simplenote/es.lproj/Localizable.strings +++ b/Simplenote/es.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 17:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-18 16:54:03+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: es */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ al mes"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ al año"; - -/* Number of found search results */ "%d Result" = "%d Resultados"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Acerca de"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Aceptar"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Todas las notas"; -/* A short description to access the account login screen */ -"Already have an account?" = "Ya esta registrado?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Se ha enviado un correo electrónico a %@ Comprueba tu bandeja de entrada y sigue las instrucciones para confirmar la eliminación de la cuenta.\n\nTu cuenta no se eliminará hasta que recibamos tu confirmación."; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "¿Seguro que quieres vaciar la papelera? Esta acción no se puede deshacer."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Se ha producido un error al intentar recuperar el contenido de la nota actual. Inténtalo de nuevo más tarde."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Se ha producido un error al intentar recuperar las entidades. Por favor, inténtalo de nuevo más tarde."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Se ha producido un error al intentar recuperar las notas. Inténtalo de nuevo más tarde."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Se ha producido un error al intentar recuperar las etiquetas. Inténtalo de nuevo más tarde."; + /* User Authenticated */ "Authenticated" = "Autenticado"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "Si eliminas la cuenta de %@, se eliminarán de forma permanente todas las notas que hayas creado con ella. Esta acción es irreversible."; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Al registrarte, aceptas nuestros Términos de servicio »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Elige un código de acceso de 4 dígitos"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Elige un plan y accede a nuevas funciones"; +/* Code TextField Placeholder */ +"Code" = "Código"; /* Opens the Collaborate UI */ "Collaborate" = "Colaborar"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Lista de notas condensada"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Confirmar"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "No se ha podido crear una cuenta con el correo y contraseña ingresados."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "No se ha podido borrar la etiqueta"; + +/* Note fetch error title */ +"Could not fetch Notes" = "No se han podido recuperar las notas"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "No se han podido recuperar las etiquetas"; + /* Fetch error title */ "Could not fetch entities" = "No se han podido recuperar las entidades"; +/* note content fetch error title */ +"Could not fetch note content" = "No se ha podido recuperar el contenido de la nota"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "No se ha podido iniciar sesion con el correo y contraseña ingresados."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Documento"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Aun no tiene una cuenta?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "En cola"; +/* LogIn Interface Title */ +"Enter Code" = "Introducir código"; + +/* Enter Password fallback Action */ +"Enter password" = "Introducir contraseña"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Introduce la contraseña de la cuenta {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Ingrese su contraseña"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Error"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Error: persistentStoreCoordinator de NSManagedObjectContext debe ser nulo. Simperium gestionará las conexiones CoreData automáticamente."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Error: inicializa el contexto con el tipo de concurrencia \"NSMainQueueConcurrencyType\"."; - /* Get Help Description Label */ "FAQ or contact us" = "Consulta las preguntas frecuentes o contacta con nosotros"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Recuperando version"; -/* Forgot password Button Text */ -"Forgot password? »" = "¿Olvidaste tu contraseña? »"; - /* Password Reset Action */ -"Forgotten password?" = "¿Olvidaste tu contraseña?"; +"Forgot your password?" = "¿Has olvidado tu contraseña?"; /* FAQ or contact us */ "Get Help" = "Obtener ayuda"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Me gusta"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Notas de índice en Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Índice eliminado"; + /* Note Information Button (metrics + references) */ "Information" = "Información"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Acceder"; +/* LogIn Interface Title */ +"Log In with Password" = "Acceder con contraseña"; + /* Log out of the active account in the app */ "Log Out" = "Salir"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Inicia sesión para ver tus notas"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Accede con WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Accede con el correo electrónico"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "No se ha podido acceder con el correo electrónico; introduce tu contraseña"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "El código de acceso es demasiado corto"; + /* Month and day date formatter */ "MMM d" = "d MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Modificación: Más antiguo"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Mensual"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Mover las notas seleccionadas a la papelera"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "No se han encontrado notas sin etiquetar"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "No es un email valido"; - /* Note Widget Title */ "Note" = "Nota"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "Aceptar"; +/* confirm button title */ +"Okay" = "De acuerdo"; + /* No comment provided by engineer. */ "On" = "Encendido"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Opciones"; +/* Or, used as a separator between Actions */ +"Or" = "O"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Codigo"; /* Pin Lock */ "Passcodes did not match. Try again." = "Contraseña incorrecta. Intente nuevamente."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Contraseña"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "La contraseña no puede coincidir con el correo electrónico"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "La contraseña debe contener al menos %d caracteres"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "La contraseña no debe contener pestañas ni saltos de línea"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "La contraseña no coincide"; - /* Number of changes pending to be sent */ "Pendings" = "Pendientes"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Inicia sesión en tu cuenta de Simplenote desde la aplicación."; +/* Encourages trying delete again */ +"Please try again" = "Inténtalo de nuevo"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Mejora a la versión más reciente de iOS para restablecer las compras"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Reciente"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Se ha recuperado el contenido de la nota: "; + /* References section header on Info Card */ "Referenced In" = "Con referencia en"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Restaurar Nota"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Restablecer compras"; - /* Title -> Review you account screen */ "Review Your Account" = "Revisa tu cuenta"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Barra Lateral"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Iniciar Sesion"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Crear Cuenta"; -/* A short link to access the account login screen */ -"Sign in" = "Iniciar Sesion"; - -/* A short link to access the account creation screen */ -"Sign up" = "Crear Cuenta"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Si cierras sesión, se eliminarán las notas no sincronizadas. Inicia sesión en la aplicación web para verificar tus notas sincronizadas."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Contribuidor de Simplenote"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Para configurar los widgets, Simplenote debe estar configurado y conectado"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Lo sentimos."; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Ordenar por:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "Es posible que el historial de Spotlight siga apareciendo en los resultados de búsqueda, pero las notas se han desindexado"; + /* Title for the response's Status Code */ "Status Code" = "Código de estado"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Da soporte a tu app de notas favorita y accede a nuevas funciones"; -/* No comment provided by engineer. */ -"Sustainer" = "Contribuidor"; +/* Switch app icon */ +"Sustainer App Icon" = "Icono de aplicación de Sustainer"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Se ha restablecido la suscripción de Contribuidor. ¡Gracias por apoyar a Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Gracias por apoyarnos siempre"; -/* Error when address is in use */ -"That email is already being used" = "El email ya se encuentra en uso"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "El código de autenticación que has solicitado ha caducado. Solicita uno nuevo"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "El código que has introducido no es correcto. Inténtalo de nuevo"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "El correo electrónico que has introducido ya está asociado a una cuenta de Simplenote."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Deshacer"; -/* WebSocket not initialized */ -"Uninitialized" = "Sin iniciar"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Etiqueta sin nombre"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Visitar aplicación web"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Te enviaremos por correo electrónico un código para iniciar sesión; también puedes"; + /* Generic error */ "We're having problems. Please try again soon." = "Estamos teniendo algunos problemas. Inténtalo de nuevo pronto."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Hemos enviado un código a {{EMAIL}}. El código será válido durante unos minutos."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "¿Qué opinas de Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Intentaremos recuperar el contenido del acceso directo en el próximo lanzamiento"; + /* Number of words in the note */ "Words" = "Palabras"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Anual"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Sí"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Ya eres Contribuidor. ¡Gracias por apoyar a Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Tu código de autenticación ha caducado. Solicita uno nuevo"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Su dirección de correo electrónico no es valida."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "La papelera está vacía"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "hacerlo manualmente."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "etiqueta:"; diff --git a/Simplenote/fa.lproj/Localizable.strings b/Simplenote/fa.lproj/Localizable.strings index 11901b89b..777d53652 100644 --- a/Simplenote/fa.lproj/Localizable.strings +++ b/Simplenote/fa.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2020-05-26 04:07:15+0000 */ +/* Translation-Revision-Date: 2024-08-02 02:10:07+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: fa */ @@ -64,9 +64,6 @@ /* Email TextField Placeholder */ "Email" = "ایمیل"; -/* Password Reset Action */ -"Forgotten password?" = "گذرواژه فراموش شده؟"; - /* Rating view liked title */ "Great! Mind leaving a review to tell us what you like?" = "عالی! اگر مشکلی نیست لطفاً با نوشتنِ یک بررسی به ما بگویید چه چیزی دوست دارید؟"; @@ -109,15 +106,13 @@ /* Rating view - liked or disliked - no thanks button */ "No thanks" = "نه ممنون"; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "گذرواژه"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "گذرواژه نمی‌تواند مطابقِ ایمیل باشد"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "گذرواژه دست‌ِکم باید حاویِ %d نویسه باشد"; /* Message displayed when a password contains a disallowed character */ @@ -171,16 +166,16 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "نوارِ کناری"; -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "نام‌نویسی"; /* Simplenote's Feedback Email Title */ "Simplenote iOS Feedback" = "بازخوردِ Simplenote iOS"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "پوزش!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ diff --git a/Simplenote/fr.lproj/Localizable.strings b/Simplenote/fr.lproj/Localizable.strings index da1c0ef9b..a15125a77 100644 --- a/Simplenote/fr.lproj/Localizable.strings +++ b/Simplenote/fr.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 15:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-19 12:54:03+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: fr */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ par mois"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ par an"; - -/* Number of found search results */ "%d Result" = "%d résultats"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "À propos"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Accepter"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Toutes les notes"; -/* A short description to access the account login screen */ -"Already have an account?" = "Déjà un compte ?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Un e-mail a été envoyé à %@ Consultez votre boîte de réception et suivez les instructions pour confirmer la suppression du compte.\n\nVotre compte ne sera pas supprimé tant que nous n’aurons pas reçu votre confirmation."; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Voulez-vous vraiment vider la corbeille ? Cette opération ne pourra pas être annulée."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "La tentative d’extraction du contenu de la note actuelle a échoué. Veuillez réessayer plus tard."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "La tentative de récupération des entités a échoué Veuillez réessayer plus tard."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "La tentative d’extraction des notes a échoué. Veuillez réessayer plus tard."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "La tentative d’extraction des étiquettes a échoué. Veuillez réessayer plus tard."; + /* User Authenticated */ "Authenticated" = "Authentifié"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "En supprimant le compte associé à %@, toutes les notes créées avec ce compte seront définitivement supprimées. Cette action est irréversible."; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "En vous inscrivant, vous acceptez les conditions d'utilisation »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Choisir un code d’accès à 4 chiffres"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Choisissez un plan et aidez à créer de nouvelles fonctionnalités."; +/* Code TextField Placeholder */ +"Code" = "Code"; /* Opens the Collaborate UI */ "Collaborate" = "Collaborer"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Liste de notes condensée"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Confirmer"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Impossible de créer un compte avec l'e-mail et le mot de passe fourni."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Impossible de supprimer l’étiquette"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Impossible d’extraire les notes"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Impossible d’extraire les étiquettes"; + /* Fetch error title */ "Could not fetch entities" = "Impossible de récupérer les entités"; +/* note content fetch error title */ +"Could not fetch note content" = "Impossible d’extraire le contenu des notes"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Impossible de se connecter avec l'e-mail et le mot de passe fourni."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Document"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Vous n'avez pas encore de compte ?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "En file d'attente"; +/* LogIn Interface Title */ +"Enter Code" = "Saisir le code"; + +/* Enter Password fallback Action */ +"Enter password" = "Saisir le mot de passe"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Saisir le mot de passe associé au compte {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Entrez votre code PIN"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Erreur"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Erreur : « NSManagedObjectContext’s persistentStoreCoordinator » doit être nul. Simperium se chargera des connexions CoreData pour vous."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Erreur : vous devez initialiser votre contexte avec le type de concurrence « NSMainQueueConcurrencyType »."; - /* Get Help Description Label */ "FAQ or contact us" = "FAQ ou nous contacter"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Récupération de version"; -/* Forgot password Button Text */ -"Forgot password? »" = "Mot de passe oublié ?"; - /* Password Reset Action */ -"Forgotten password?" = "Mot de passe oublié ?"; +"Forgot your password?" = "Mot de passe oublié ?"; /* FAQ or contact us */ "Get Help" = "Obtenir de l’aide"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "J’aime"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Indexer les notes dans Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Index supprimé"; + /* Note Information Button (metrics + references) */ "Information" = "Information"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Connexion"; +/* LogIn Interface Title */ +"Log In with Password" = "Se connecter avec un mot de passe"; + /* Log out of the active account in the app */ "Log Out" = "Se déconnecter"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Connectez-vous pour consulter vos notes"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Se connecter avec WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Se connecter avec une adresse e-mail"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Échec de la connexion avec l’adresse e-mail, veuillez saisir votre mot de passe"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Le code de connexion est trop court"; + /* Month and day date formatter */ "MMM d" = "d MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Modification : Plus anciennes"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Mensuel"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Déplacer les notes sélectionnées vers la corbeille"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Aucune note sans étiquette"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Adresse e-mail non valide"; - /* Note Widget Title */ "Note" = "Remarque :"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "Valider"; +/* confirm button title */ +"Okay" = "OK"; + /* No comment provided by engineer. */ "On" = "Activé"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Options"; +/* Or, used as a separator between Actions */ +"Or" = "Ou"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Code PIN"; /* Pin Lock */ "Passcodes did not match. Try again." = "Codes PIN différents. Essayez de nouveau."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Mot de passe"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "Le mot de passe et l'adresse e-mail ne doivent pas être identiques"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Le mot de passe doit contenir au moins %d caractères."; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "Le mot de passe ne doit contenir ni tabulation ni retours à la ligne"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Les mots de passe ne correspondent pas"; - /* Number of changes pending to be sent */ "Pendings" = "En attente"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Commencez par vous connecter à votre compte Simplenote à l’aide de l’application Simplenote."; +/* Encourages trying delete again */ +"Please try again" = "Veuillez réessayer"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Veuillez mettre à niveau vers la dernière version d’iOS pour rétablir les achats."; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Récent"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Contenu de note récupéré - "; + /* References section header on Info Card */ "Referenced In" = "Référencé dans"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Restaurer la note"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Rétablir les achats"; - /* Title -> Review you account screen */ "Review Your Account" = "Passez en revue votre compte"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Barre latérale"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Se connecter"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "S'inscrire"; -/* A short link to access the account login screen */ -"Sign in" = "Se connecter"; - -/* A short link to access the account creation screen */ -"Sign up" = "S'inscrire"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "La déconnexion entraînera la suppression de l’ensemble des notes non synchronisées. Vous pouvez vérifier vos notes synchronisées en vous connectant à l’application Web. "; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Contributeur de Simplenote"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Vous devez configurer Simplenote en étant connecté pour configurer des widgets"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Désolé !"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Trier par :"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "L’historique Spotlight peut encore apparaître dans les résultats de recherche, mais les notes ont été désindexées"; + /* Title for the response's Status Code */ "Status Code" = "Code d’état"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Soutenez votre application de notes préférée pour aider à créer de nouvelles fonctionnalités."; -/* No comment provided by engineer. */ -"Sustainer" = "Contributeur"; +/* Switch app icon */ +"Sustainer App Icon" = "Icône de l’application Sustainer"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "L’abonnement de soutien est restauré. Nous vous remercions de soutenir Simplenote !"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Nous vous remercions de votre soutien continu !"; -/* Error when address is in use */ -"That email is already being used" = "Cette adresse e-mail est déjà utilisée"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "Le code d’authentification que vous avez demandé a expiré. Veuillez en demander un nouveau"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "Le code que vous avez saisi est incorrect. Veuillez réessayer"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "L'adresse e-mail saisie est déjà associée à un compte Simplenote."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Annuler"; -/* WebSocket not initialized */ -"Uninitialized" = "Non initialisé"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Étiquette sans nom"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Visiter l'application Web"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Nous vous enverrons par e-mail un code pour vous connecter, ou vous pouvez"; + /* Generic error */ "We're having problems. Please try again soon." = "Nous rencontrons des problèmes. Veuillez réessayer dans quelques instants."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Nous avons envoyé un code à {{EMAIL}}. Ce code sera valide pendant quelques minutes."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Que pensez-vous de Simplenote ?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Tentera de récupérer le contenu du raccourci au prochain lancement"; + /* Number of words in the note */ "Words" = "Mots"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Annuellement"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Oui"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Vous êtes déjà contributeur. Nous vous remercions de soutenir Simplenote !"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Votre code d’authentification a expiré. Veuillez en demander un nouveau"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Votre adresse e-mail n'est pas valide."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Votre corbeille est vide"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "vous connecter manuellement."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "étiquette :"; diff --git a/Simplenote/he.lproj/Localizable.strings b/Simplenote/he.lproj/Localizable.strings index 8c76cb5c1..e91e2f7a8 100644 --- a/Simplenote/he.lproj/Localizable.strings +++ b/Simplenote/he.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 15:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-19 14:54:02+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: he_IL */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ לחודש"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ לשנה"; - -/* Number of found search results */ "%d Result" = "תוצאה %d"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "אודות"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "קבלה"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "כל הפתקים"; -/* A short description to access the account login screen */ -"Already have an account?" = "יש לך כבר חשבון?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "הודעת אימייל נשלחה לכתובת %@. יש לבדוק את תיבת הדואר הנכנס ולפעול לפי ההוראות לאישור המחיקה של החשבון.\n\nהחשבון שלך לא יימחק עד שנקבל את אישורך"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "האם ברצונך לרוקן את הפח? לא ניתן לבטל את הפעולה."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "ניסיון להביא את תוכן הפתק הנוכחי נכשל. יש לנסות שוב מאוחר יותר."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "ניסיון להביא את הישויות נכשל. יש לנסות שוב מאוחר יותר."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "ניסיון להביא 'פתקים' נכשל. יש לנסות שוב מאוחר יותר."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "ניסיון להביא 'תגיות' נכשל. יש לנסות שוב מאוחר יותר."; + /* User Authenticated */ "Authenticated" = "האימות בוצע"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "על ידי מחיקת החשבון עבור %@, כל הפתקים שנוצרו באמצעות החשבון הזה יימחקו לצמיתות. לא ניתן לבטל את הפעולה הזאת"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "הרשמתך מהווה הסכמה לתנאי השימוש שלנו »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "לבחור קוד סיסמה בן 4 ספרות"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "יש לבחור תוכנית כדי לעזור בהפעלת אפשרויות בעתיד"; +/* Code TextField Placeholder */ +"Code" = "קוד"; /* Opens the Collaborate UI */ "Collaborate" = "שיתוף פעולה בעריכה"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "רשימת פתקים מכווצת"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "פעם נוספת"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "לא ניתן ליצור חשבון באמצעות כתובת האימייל והסיסמה שסופקו."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "לא ניתן למחוק את התגית"; + +/* Note fetch error title */ +"Could not fetch Notes" = "לא ניתן היה להביא 'פתקים'"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "לא ניתן היה להביא 'תגיות'"; + /* Fetch error title */ "Could not fetch entities" = "לא ניתן היה להביא את הישויות"; +/* note content fetch error title */ +"Could not fetch note content" = "לא ניתן היה להביא את תוכן הפתקים"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "לא ניתן להיכנס באמצעות כתובת האימייל והסיסמה שסופקו."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "מסמך"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "אין לך חשבון עדיין?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "צורף לתור"; +/* LogIn Interface Title */ +"Enter Code" = "להזין קוד"; + +/* Enter Password fallback Action */ +"Enter password" = "להזין סיסמה"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "יש להזין את הסיסמה לחשבון {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "הזנת קוד סיסמה"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "שגיאה"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "שגיאה: הערך 'persistentStoreCoordinator' של 'NSManagedObjectContext' חייב להיות \"nil\". השירות של Simperium יטפל בחיבורים של CoreData עבורך."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "שגיאה: עליך להפעיל את ההקשר באמצעות סוג המטבע 'NSMainQueueConcurrencyType'."; - /* Get Help Description Label */ "FAQ or contact us" = "שאלות נפוצות או יצירת קשר"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "מוריד גרסה"; -/* Forgot password Button Text */ -"Forgot password? »" = "שכחת את הסיסמה? »"; - /* Password Reset Action */ -"Forgotten password?" = "שכחת את הסיסמה?"; +"Forgot your password?" = "שכחת סיסמה?"; /* FAQ or contact us */ "Get Help" = "לקבלת עזרה"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "אהבתי את זה"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "להוסיף אינדקס של פתקים אל Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "האינדקס הוסר"; + /* Note Information Button (metrics + references) */ "Information" = "מידע"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "התחבר"; +/* LogIn Interface Title */ +"Log In with Password" = "להתחבר באמצעות סיסמה"; + /* Log out of the active account in the app */ "Log Out" = "התנתקות"; /* Widget warning if user is logged out */ "Log in to see your notes" = "יש להתחבר לחשבון כדי לצפות בפתקים שלך"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "התחברו באמצעות WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "התחברות באמצעות אימייל"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "ההתחברות באמצעות האימייל נכשלה, יש להזין סיסמה"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "קוד ההתחברות קצר מדי"; + /* Month and day date formatter */ "MMM d" = "d ‏MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "עודכן: הישן ביותר"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "חודשי"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "להעביר את הפתקים שנבחרו לפח"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "אין פתקים ללא תגיות"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "כתובת האימייל לא חוקית"; - /* Note Widget Title */ "Note" = "פתק"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "OK"; +/* confirm button title */ +"Okay" = "בסדר"; + /* No comment provided by engineer. */ "On" = "מופעל"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "אפשרויות"; +/* Or, used as a separator between Actions */ +"Or" = "או"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "קוד סיסמה"; /* Pin Lock */ "Passcodes did not match. Try again." = "קודי הסיסמה לא התאימו. יש לנסות שוב."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "סיסמה"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "הסיסמה לא יכולה להיות כתובת האימייל"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "הסיסמה חייבת להכיל לפחות %d תווים"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "הסיסמה לא יכולה להכיל סימני Tab או שורות חדשות"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "הסיסמאות לא תואמות"; - /* Number of changes pending to be sent */ "Pendings" = "בהמתנה"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "ראשית עליך להתחבר לחשבון שלך ב-Simplenote באמצעות האפליקציה של Simplenote."; +/* Encourages trying delete again */ +"Please try again" = "יש לנסות שוב"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "יש לשדרג לגרסה האחרונה של iOS כדי לשחזר רכישות"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "אחרונים"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "תוכן הפתק ששוחזר – "; + /* References section header on Info Card */ "Referenced In" = "מאוזכר בפתק"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "שחזור פתק"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "לשחזר רכישות"; - /* Title -> Review you account screen */ "Review Your Account" = "לבדוק את החשבון שלך"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "סרגל צדדי"; -/* Title of button for logging in (must be short) */ -"Sign In" = "התחברות"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "הרשמה"; -/* A short link to access the account login screen */ -"Sign in" = "כניסה למערכת"; - -/* A short link to access the account creation screen */ -"Sign up" = "הרשמה"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "לאחר ההתנתקות, כל הפתקים שלא סוכרנו יימחקו. אפשר לאמת אילו פתקים סונכרנו באמצעות התחברות לאפליקציית הרשת."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "יש להגדיר את Simplenote ולהתחבר אל השירות כדי להגדיר את הווידג'טים"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "מצטערים!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "למיין לפי:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "תוצאות החיפוש עדיין עשויות להציג את ההיסטוריה של Spotlight, אבל הפתקים הוסרו מהאינדקס"; + /* Title for the response's Status Code */ "Status Code" = "קוד מצב"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "ניתן לתמוך באפליקציית הפתקים המועדפת עליך כדי לעזור בהפעלת אפשרויות בעתיד"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "סמל האפליקציה של Sustainer"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "המינוי של Sustainer שוחזר. תודה על התמיכה ב-Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "תודה על התמיכה המתמשכת"; -/* Error when address is in use */ -"That email is already being used" = "כתובת האימייל הזאת כבר נמצאת בשימוש"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "פג תוקפו של קוד האימות שביקשת. יש לבקש קוד חדש"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "הקוד שהזנת שגוי. יש לנסות שוב"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "כתובת האימייל שהזנת כבר משויכת לחשבון Simplenote."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "ביטול פעולה אחרונה"; -/* WebSocket not initialized */ -"Uninitialized" = "לא הופעל"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "תגית ללא שם"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "מעבר לאפליקציית הרשת"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "אנחנו נשלח לך אימייל עם הקוד להתחברות, או שאפשר"; + /* Generic error */ "We're having problems. Please try again soon." = "נתקלנו בבעיות. יש לנסות שוב בקרוב."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "שלחנו קוד אל {{EMAIL}}. הקוד הזה יהיה בתוקף למשך מספר דקות."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "מה דעתך על Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "ננסה לשחזר את התוכן של הקיצור בהפעלה הבאה"; + /* Number of words in the note */ "Words" = "מילים"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "שנתי"; - /* Proceeds with the Empty Trash OP */ "Yes" = "כן"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "כבר הצטרפת בתור Sustainer. תודה על התמיכה ב-Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "פג תוקפו של קוד האימות. יש לבקש קוד חדש"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "כתובת האימייל שלך אינה חוקית."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "הפח שלך ריק"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "להתחבר באופן ידני."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "תגית:"; diff --git a/Simplenote/id.lproj/Localizable.strings b/Simplenote/id.lproj/Localizable.strings index 245e44f3b..570efc088 100644 --- a/Simplenote/id.lproj/Localizable.strings +++ b/Simplenote/id.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-21 22:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-19 09:54:03+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: id */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ per Bulan"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ per Tahun"; - -/* Number of found search results */ "%d Result" = "%d Hasil"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Tentang"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Terima"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Semua Catatan"; -/* A short description to access the account login screen */ -"Already have an account?" = "Sudah memiliki akun?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Email sudah dikirimkan ke %@ Periksa kotak masuk Anda dan ikuti petunjuk untuk mengonfirmasi penghapusan akun.\n\nAkun tidak akan dihapus hingga kami menerima konfirmasi Anda."; @@ -120,9 +112,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Anda yakin ingin mengosongkan tempat sampah? Ini tidak dapat dibatalkan."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Upaya untuk mengambil konten catatan terbaru gagal. Coba lagi nanti."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Upaya untuk mengambil entitas gagal. Silakan coba lagi nanti."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Upaya untuk mengambil catatan gagal. Coba lagi nanti."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Upaya untuk mengambil tag gagal. Coba lagi nanti."; + /* User Authenticated */ "Authenticated" = "Diautentikasi"; @@ -138,17 +139,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "Dengan menghapus akun untuk %@, semua catatan yang dibuat dengan akun ini akan dihapus secara permanen. Tindakan ini tidak dapat dibatalkan"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Dengan mendaftar, berarti Anda menyetujui Ketentuan Layanan »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -184,8 +180,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Pilih kode sandi 4 digit"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Pilih paket untuk membantu pengembangan fitur masa depan"; +/* Code TextField Placeholder */ +"Code" = "Kode"; /* Opens the Collaborate UI */ "Collaborate" = "Kolaborasi"; @@ -202,8 +198,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Daftar Catatan Ringkas"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Konfirmasi"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -228,9 +223,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Tidak dapat membuat akun dengan alamat email dan kata sandi yang diberikan."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Tidak dapat menghapus tag"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Tidak dapat mengambil Catatan"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Tidak dapat mengambil Tag"; + /* Fetch error title */ "Could not fetch entities" = "Tidak dapat mengambil entitas"; +/* note content fetch error title */ +"Could not fetch note content" = "Tidak dapat mengambil konten catatan"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Tidak dapat masuk dengan alamat email dan kata sandi yang diberikan."; @@ -323,9 +330,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Dokumen"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Belum memiliki akun?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -357,6 +361,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "Dalam antrean"; +/* LogIn Interface Title */ +"Enter Code" = "Masukkan Kode"; + +/* Enter Password fallback Action */ +"Enter password" = "Masukkan kata sandi"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Masukkan kata sandi untuk akun {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Masukkan kode sandi Anda"; @@ -364,12 +377,6 @@ Error Title */ "Error" = "Kesalahan"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Error: persistentStoreCoordinator NSManagedObjectContext harus nol. Simperium akan menangani penyambungan CoreData untuk Anda."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Error: Anda harus memulai konteks dengan tipe konkurensi 'NSMainQueueConcurrencyType'."; - /* Get Help Description Label */ "FAQ or contact us" = "FAQ atau hubungi kami"; @@ -379,11 +386,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Mengambil Versi"; -/* Forgot password Button Text */ -"Forgot password? »" = "Lupa kata sandi? »"; - /* Password Reset Action */ -"Forgotten password?" = "Lupa kata sandi?"; +"Forgot your password?" = "Lupa kata sandi?"; /* FAQ or contact us */ "Get Help" = "Dapatkan Bantuan"; @@ -409,6 +413,12 @@ /* Rating view - initial - liked button */ "I like it" = "Saya Menyukainya"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Indeks Catatan dalam Sorotan"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Indeks Dihapus"; + /* Note Information Button (metrics + references) */ "Information" = "Informasi"; @@ -445,18 +455,27 @@ LogIn Interface Title */ "Log In" = "Masuk"; +/* LogIn Interface Title */ +"Log In with Password" = "Login dengan Kata Sandi"; + /* Log out of the active account in the app */ "Log Out" = "Logout"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Masuk untuk melihat catatan Anda"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Login dengan WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Login dengan email"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Login dengan email gagal, masukkan kata sandi"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Kode Login terlalu pendek"; + /* Month and day date formatter */ "MMM d" = "d MMM"; @@ -487,9 +506,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Diubah: Paling lama"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Bulanan"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Pindahkan catatan yang dipilih ke tong sampah"; @@ -545,9 +561,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Tidak ada catatan tanpa tag"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Bukan alamat email yang valid"; - /* Note Widget Title */ "Note" = "Catatan"; @@ -576,6 +589,9 @@ Email unverified alert dismiss */ "Ok" = "OK"; +/* confirm button title */ +"Okay" = "Oke"; + /* No comment provided by engineer. */ "On" = "Menyala"; @@ -591,29 +607,27 @@ /* Note Options Title */ "Options" = "Opsi-opsi"; +/* Or, used as a separator between Actions */ +"Or" = "Atau"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Kode sandi"; /* Pin Lock */ "Passcodes did not match. Try again." = "Kode sandi tidak cocok. Coba lagi."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Kata sandi"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "Kata sandi tidak boleh sama dengan email"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Kata sandi harus berisi setidaknya %d karakter"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "Kata sandi tidak boleh berisi tab atau baris baru"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Kata sandi tidak cocok"; - /* Number of changes pending to be sent */ "Pendings" = "Tertunda"; @@ -633,6 +647,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Silakan masuk ke akun Simplenote Anda terlebih dahulu menggunakan aplikasi Simplenote."; +/* Encourages trying delete again */ +"Please try again" = "Coba lagi"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Upgrade ke rilis iOS terbaru untuk memulihkan pembelian"; @@ -675,6 +692,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Terkini"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Pemulihan Konten Catatan - "; + /* References section header on Info Card */ "Referenced In" = "Dirujuk di"; @@ -712,9 +732,6 @@ Restore a note to a previous version */ "Restore Note" = "Pulihkan Catatan"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Pulihkan Pembelian"; - /* Title -> Review you account screen */ "Review Your Account" = "Tinjau Akun Anda"; @@ -775,21 +792,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Panel Sisi"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Masuk"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Mendaftar"; -/* A short link to access the account login screen */ -"Sign in" = "Masuk"; - -/* A short link to access the account creation screen */ -"Sign up" = "Mendaftar"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Catatan yang tidak disinkronkan akan dihapus saat Anda keluar. Anda dapat memverifikasi catatan Anda yang disinkronkan dengan masuk ke Aplikasi Web."; @@ -797,8 +804,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -807,7 +813,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Simplenote harus dikonfigurasi dan masuk ke widget pengaturan"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Maaf!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -820,14 +827,17 @@ /* Sort By Title */ "Sort by:" = "Urutkan berdasarkan:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "Riwayat sorotan mungkin masih muncul di hasil pencarian, tetapi catatan tidak terindeks"; + /* Title for the response's Status Code */ "Status Code" = "Kode Status"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Dukung aplikasi pencatatan favorit Anda untuk membantu pengembangan fitur masa depan"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "Ikon Aplikasi Sustainer"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Langganan Sustainer dipulihkan. Terima kasih telah mendukung Simplenote!"; @@ -862,8 +872,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Terima kasih atas dukungan Anda yang berkelanjutan"; -/* Error when address is in use */ -"That email is already being used" = "Email sudah digunakan"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "Kode autentikasi yang diminta telah kedaluwarsa. Silakan minta kode yang baru"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "Kode yang Anda masukkan salah. Coba lagi"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "Email yang Anda masukkan telah digunakan oleh akun Simplenote lain."; @@ -926,9 +939,6 @@ /* Undo action */ "Undo" = "Urungkan"; -/* WebSocket not initialized */ -"Uninitialized" = "Belum Dimulai"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Tag Tanpa Nama"; @@ -974,9 +984,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Kunjungi Aplikasi Web"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Kami akan mengirimkan email berisi kode untuk login atau Anda dapat"; + /* Generic error */ "We're having problems. Please try again soon." = "Kami mengalami masalah. Harap coba lagi nanti."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Kami telah mengirimkan kode ke {{EMAIL}}. Kode ini valid selama beberapa menit."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -990,12 +1006,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Apa pendapat Anda tentang Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Konten pintasan akan coba dipulihkan pada saat aplikasi dibuka lagi"; + /* Number of words in the note */ "Words" = "Kata"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Tahunan"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Ya"; @@ -1014,6 +1030,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Anda sudah menjadi Sustainer. Terima kasih telah mendukung Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Kode autentikasi telah kedaluwarsa. Silakan minta kode yang baru"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Alamat email Anda tidak valid."; @@ -1023,6 +1042,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Sampah Anda kosong"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "login secara manual"; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "tag:"; diff --git a/Simplenote/it.lproj/Localizable.strings b/Simplenote/it.lproj/Localizable.strings index bc600e39d..d0dc71239 100644 --- a/Simplenote/it.lproj/Localizable.strings +++ b/Simplenote/it.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 11:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-18 20:54:02+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: it */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ al mese"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ all'anno"; - -/* Number of found search results */ "%d Result" = "%d Resultato"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Informazioni"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Ok"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Tutte le note"; -/* A short description to access the account login screen */ -"Already have an account?" = "Hai già un account?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "È stata inviata un'e-mail a %@ Controlla la posta in arrivo e segui le istruzioni per confermare l'eliminazione dell'account.\n\nIl tuo account non verrà eliminato fino alla ricezione della conferma"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Desideri svuotare il cestino? Questa operazione non può essere annullata."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Il tentativo di recuperare il contenuto della nota attuale non è riuscito. Riprova più tardi."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Il tentativo di recuperare le entità non è riuscito. Riprova più tardi."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Il tentativo di recuperare le note non è riuscito. Riprova più tardi."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Il tentativo di recuperare i tag non è riuscito. Riprova più tardi."; + /* User Authenticated */ "Authenticated" = "Autenticato"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "Eliminando il tuo account per %@, tutte le note create con esso verranno eliminate definitivamente. Questa azione non potrà essere annullata"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Iscrivendoti, accetti i nostri Termini di servizio »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Scegli un PIN di 4 cifre"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Scegli un piano e aiuta a sbloccare funzionalità future"; +/* Code TextField Placeholder */ +"Code" = "Codice"; /* Opens the Collaborate UI */ "Collaborate" = "Collabora"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Elenco note ristretto"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Conferma"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Non è possibile creare un account con l'indirizzo email e la password forniti."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Impossibile eliminare il tag"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Impossibile recuperare le note"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Impossibile recuperare i tag"; + /* Fetch error title */ "Could not fetch entities" = "Impossibile recuperare le entità"; +/* note content fetch error title */ +"Could not fetch note content" = "Impossibile recuperare il contenuto delle note"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Nome utente o password errata."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Documento"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Non hai un account?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "In coda"; +/* LogIn Interface Title */ +"Enter Code" = "Inserisci codice"; + +/* Enter Password fallback Action */ +"Enter password" = "Inserisci password"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Digita la password per l'account {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Inserisci il tuo PIN"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Errore"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Errore: NSManagedObjectContext's persistentStoreCoordinator deve essere nil. Simperium gestirà le connessioni CoreData per te."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Errore: devi inizializzare il tuo contesto con il tipo di concomitanza 'NSMainQueueConcurrencyType'."; - /* Get Help Description Label */ "FAQ or contact us" = "FAQ o contattaci"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Recupero della versione"; -/* Forgot password Button Text */ -"Forgot password? »" = "Hai dimenticato la tua password? »"; - /* Password Reset Action */ -"Forgotten password?" = "Password dimenticata?"; +"Forgot your password?" = "Password dimenticata?"; /* FAQ or contact us */ "Get Help" = "Richiedi supporto"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Mi piace"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Note dell'indice in Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Indice rimosso"; + /* Note Information Button (metrics + references) */ "Information" = "Informazioni"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Accedi"; +/* LogIn Interface Title */ +"Log In with Password" = "Accedi con la password"; + /* Log out of the active account in the app */ "Log Out" = "Esci"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Accedi per visualizzare le note"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Accedi con WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Accedi con l'e-mail"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Accesso con e-mail non riuscito, inserisci la tua password"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Il codice di accesso è troppo breve"; + /* Month and day date formatter */ "MMM d" = "d MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Data di modifica: meno recente"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Mensile"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Sposta le note selezionate nel cestino"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Nessuna nota senza tag"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Indirizzo e-mail non valido"; - /* Note Widget Title */ "Note" = "Nota"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "OK"; +/* confirm button title */ +"Okay" = "Ok"; + /* No comment provided by engineer. */ "On" = "On"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Opzioni"; +/* Or, used as a separator between Actions */ +"Or" = "Or"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "PIN"; /* Pin Lock */ "Passcodes did not match. Try again." = "PIN non coincidono. Prova ancora."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Password"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "La password non può corrispondere all'e-mail"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "La password deve contenere almeno %d caratteri"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "La password non deve contenere schede o nuove righe"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Le password non corrispondono"; - /* Number of changes pending to be sent */ "Pendings" = "In attesa"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Accedi al tuo account Simplenote prima usando l'app Simplenote."; +/* Encourages trying delete again */ +"Please try again" = "Riprova"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Per ripristinare gli acquisti, effettua l'aggiornamento alla versione iOS più recente"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Recente"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Contenuto della nota recuperato - "; + /* References section header on Info Card */ "Referenced In" = "Riferimento in"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Ripristina la nota"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Ripristina acquisti"; - /* Title -> Review you account screen */ "Review Your Account" = "Verifica il tuo account"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Barra laterale"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Accedi"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Crea account"; -/* A short link to access the account login screen */ -"Sign in" = "Accedi"; - -/* A short link to access the account creation screen */ -"Sign up" = "Registrati"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "La disconnessione causerà l’eliminazione di tutte le note non sincronizzate. È possibile verificare le note sincronizzate accedendo all’app web."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Sostenitore Simplenote"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Simplenote deve essere configurato e deve essere stato effettuato l'accesso per configurare i widget"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Siamo spiacenti."; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Ordina per:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "La cronologia Spotlight potrebbe apparire ancora nei risultati di ricerca, ma le note sono state rimosse dall'indice"; + /* Title for the response's Status Code */ "Status Code" = "Codice stato"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Supporta la tua app di annotazione preferita per aiutarci a sbloccare funzionalità future"; -/* No comment provided by engineer. */ -"Sustainer" = "Sosteniitore"; +/* Switch app icon */ +"Sustainer App Icon" = "Icona app sostenitore"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Abbonamento di sostenitore ripristinato. Grazie per il supporto a Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Grazie per il supporto continuo"; -/* Error when address is in use */ -"That email is already being used" = "Quell'indirizzo e-mail è già in uso"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "Il codice di autenticazione che hai richiesto è scaduto. Richiedine uno nuovo"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "Il codice che hai inserito non è corretto. Riprova"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "L'indirizzo e-mail inserito è già associato a un account Simplenote."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Annulla"; -/* WebSocket not initialized */ -"Uninitialized" = "Non inizializzato"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Tag senza nome"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Visita l'app web"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Ti invieremo un codice per l'accesso, oppure puoi"; + /* Generic error */ "We're having problems. Please try again soon." = "Stiamo riscontrando dei problemi. Riprova a breve."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Abbiamo inviato un codice a {{EMAIL}}. Il codice sarà valido per pochi minuti."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Che cosa pensi di Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Riproveremo a recuperare il contenuto della scorciatoia al prossimo avvio."; + /* Number of words in the note */ "Words" = "Parole"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Annuale"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Si"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Sei già un sostenitore. Grazie per il supporto a Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Il codice di autenticazione è scaduto. Richiedine uno nuovo"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Il tuo indirizzo email non è valido."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Il tuo cestino è vuoto"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "accedere manualmente."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "- Tag:"; diff --git a/Simplenote/ja.lproj/Localizable.strings b/Simplenote/ja.lproj/Localizable.strings index e1b84beb6..c42fb097e 100644 --- a/Simplenote/ja.lproj/Localizable.strings +++ b/Simplenote/ja.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-23 11:54:04+0000 */ +/* Translation-Revision-Date: 2024-09-19 09:54:03+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ja_JP */ /* No comment provided by engineer. */ -"%@ per Month" = "月額 %@"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "年額 %@"; - -/* Number of found search results */ "%d Result" = "%d件の検索結果"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "このサイトについて"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "承認"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "すべてのノート"; -/* A short description to access the account login screen */ -"Already have an account?" = "すでにアカウントを持ちですか ?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "%@ にメールが送信されました。受信トレイを確認し、手順に従ってアカウントの削除を確定してください。\n\n当社がお客様から確認を得るまでアカウントは削除されません。"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "ゴミ箱を空にしてもよいですか ?この操作は元に戻すことができません。"; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "現在のメモ内容を取得しようとしましたが失敗しました。 後でもう一度お試しください。"; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "エンティティの取得の試みに失敗しました。 後ほど、もう一度お試しください。"; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "メモを取得しようとしましたが失敗しました。 後でもう一度お試しください。"; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "タグを取得しようとしましたが失敗しました。 後でもう一度お試しください。"; + /* User Authenticated */ "Authenticated" = "認証済み"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "%@ のアカウントを削除すると、このアカウントで作成したすべてのメモが完全に削除されます。 この操作を元に戻すことはできません"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "登録すると、利用規約に合意したものとみなされます。»"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "4桁のパスコードを選択してください"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "プランを選択して将来の機能のロックを解除する"; +/* Code TextField Placeholder */ +"Code" = "ソースコード"; /* Opens the Collaborate UI */ "Collaborate" = "共同編集"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "要約ノート一覧"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "確認"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "入力されたメールアドレスとパスワードではアカウントを作成できませんでした。"; +/* Notifies user tag delete failed */ +"Could not delete tag" = "タグを削除できませんでした"; + +/* Note fetch error title */ +"Could not fetch Notes" = "メモを取得できませんでした"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "タグを取得できませんでした"; + /* Fetch error title */ "Could not fetch entities" = "エンティティを取得できませんでした"; +/* note content fetch error title */ +"Could not fetch note content" = "メモの内容を取得できませんでした"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "入力されたメールアドレスとパスワードではログインできませんでした。"; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "文書"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "アカウントが無い場合"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "待機状態"; +/* LogIn Interface Title */ +"Enter Code" = "コードを入力"; + +/* Enter Password fallback Action */ +"Enter password" = "パスワードを入力"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "アカウント {{EMAIL}} のパスワードを入力"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "パスコードを入力してください"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "エラー"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "エラー: NSManagedObjectContext の persistentStoreCoordinator が nil です。 Simperium が CoreData の接続に対応します。"; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "エラー: 「NSMainQueueConcurrencyType」同時実行タイプでコンテキストを初期化する必要があります。"; - /* Get Help Description Label */ "FAQ or contact us" = "FAQ またはお問い合わせ"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "バージョンを取得中"; -/* Forgot password Button Text */ -"Forgot password? »" = "パスワードをお忘れですか ? »"; - /* Password Reset Action */ -"Forgotten password?" = "パスワードを忘れましたか ?"; +"Forgot your password?" = "パスワードを忘れた場合"; /* FAQ or contact us */ "Get Help" = "ヘルプ"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "満足している"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "スポットライトでメモをインデックス"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "インデックスが削除されました"; + /* Note Information Button (metrics + references) */ "Information" = "情報"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "ログイン"; +/* LogIn Interface Title */ +"Log In with Password" = "パスワードでログイン"; + /* Log out of the active account in the app */ "Log Out" = "ログアウト"; /* Widget warning if user is logged out */ "Log in to see your notes" = "ノートを見るにはログインしてください"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "WordPress.com アカウントでログイン"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "メールアドレスでログイン"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "メールアドレスでのログインに失敗しました。パスワードを入力してください"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "ログインコードが短すぎます"; + /* Month and day date formatter */ "MMM d" = "M月d日"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "更新日 :最も古い"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "毎月"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "選択したメモをゴミ箱に移動"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "タグが付けられていないメモはありません"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "メールアドレスが無効です"; - /* Note Widget Title */ "Note" = "ノート"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "OK"; +/* confirm button title */ +"Okay" = "OK"; + /* No comment provided by engineer. */ "On" = "オン"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "オプション"; +/* Or, used as a separator between Actions */ +"Or" = "OR"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "パスコード"; /* Pin Lock */ "Passcodes did not match. Try again." = "パスコードが一致しませんでした。もう一度お試しください。"; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "パスワード"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "パスワードがメールと一致しません"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "パスワードは%d文字以上にしてください"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "パスワードにタブや改行を含めることはできません"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "パスワードが一致しません"; - /* Number of changes pending to be sent */ "Pendings" = "保留中"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "まず、Simplenote アプリを使用して Simplenote アカウントにログインしてください。"; +/* Encourages trying delete again */ +"Please try again" = "もう一度お試しください"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "最新の iOS リリースをアップグレードして購入したものを復元してください"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "最近"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "復元されたメモの内容 - "; + /* References section header on Info Card */ "Referenced In" = "参照先"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "ノートを復元"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "購入したものを復元"; - /* Title -> Review you account screen */ "Review Your Account" = "アカウントの設定を確認"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "サイドバー"; -/* Title of button for logging in (must be short) */ -"Sign In" = "ログイン"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "登録"; -/* A short link to access the account login screen */ -"Sign in" = "ログイン"; - -/* A short link to access the account creation screen */ -"Sign up" = "登録"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "ログアウトすると、同期されていないメモはすべて削除されます。同期したメモを確認するには、Web アプリにログインします。"; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "ウィジェットを設定するには Simplenote を構成してログインする必要があります"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "申し訳ございません。"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "並び順 :"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "スポットライト履歴が検索結果に引き続き表示される可能性がありますが、メモのインデックスは解除されます"; + /* Title for the response's Status Code */ "Status Code" = "ステータスコード"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "お気に入りのメモアプリをサポートして将来の機能のロックを解除できるようにする"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "Sustainer アプリアイコン"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Sustainer サブスクリプションが復元されました。 Simplenote をサポートいただきありがとうございます。"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "今後とも末永くご支援いただきますよう、宜しくお願い申し上げます"; -/* Error when address is in use */ -"That email is already being used" = "そのメールアドレスはすでに使用されています"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "リクエストした認証コードの有効期限が切れています。 新しいコードをリクエストしてください"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "入力したコードが正しくありません。 もう一度お試しください"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "このメールアドレスは、すでに他の Simplenote アカウントで使用されています。"; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "元に戻す"; -/* WebSocket not initialized */ -"Uninitialized" = "未初期化"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "名前のないタグ"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Web アプリにアクセス"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "ログインするためのコードをメールでお送りします。または、"; + /* Generic error */ "We're having problems. Please try again soon." = "問題が発生しました。すぐにもう一度実行してください。"; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "コードを {{EMAIL}} に送信しました。 コードは数分間有効です。"; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Simplenote アプリに満足していますか ?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "次回起動時に、ショートカットの内容の復元を試みます"; + /* Number of words in the note */ "Words" = "単語"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "毎年"; - /* Proceeds with the Empty Trash OP */ "Yes" = "はい"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "すでに Sustainer です。 Simplenote をサポートいただきありがとうございます。"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "認証コードの有効期限が切れています。 新しいコードをリクエストしてください"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "無効なメールアドレスです。"; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "ゴミ箱は空です"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "手動でログインすることもできます。"; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "タグ :"; diff --git a/Simplenote/ko.lproj/Localizable.strings b/Simplenote/ko.lproj/Localizable.strings index dd233661c..d0284fbaa 100644 --- a/Simplenote/ko.lproj/Localizable.strings +++ b/Simplenote/ko.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-23 13:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-20 09:54:08+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ko_KR */ /* No comment provided by engineer. */ -"%@ per Month" = "월 %@"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "연 %@"; - -/* Number of found search results */ "%d Result" = "결과 %d개"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "정보"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "수락"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "모든 메모"; -/* A short description to access the account login screen */ -"Already have an account?" = "이미 계정이 있으세요?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "이메일을 %@(으)로 보냈으니 받은 편지함을 확인하고 지침에 따라 계정 삭제를 확인하세요.\n\n시스템에 확인이 수신될 때까지 계정이 삭제되지 않음"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "휴지통을 비우시겠습니까? 이 작업은 되돌릴 수 없습니다."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "현재 메모 내용 가져오기 시도가 실패했습니다. 나중에 다시 시도해 주세요."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "엔터티 가져오기 시도가 실패했습니다. 나중에 다시 시도하세요."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "메모 가져오기 시도가 실패했습니다. 나중에 다시 시도해 주세요."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "태그 가져오기 시도가 실패했습니다. 나중에 다시 시도해 주세요."; + /* User Authenticated */ "Authenticated" = "인증됨"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "%@에 대한 계정을 삭제하면 이 계정을 통해 생성된 모든 메모가 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다."; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "가입하려면 서비스 약관에 동의해야 합니다. »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "네 자리 패스코드 선택"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "향후 기능 활용에 도움이 되도록 요금제 선택"; +/* Code TextField Placeholder */ +"Code" = "코드"; /* Opens the Collaborate UI */ "Collaborate" = "협업"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "메모 요약 목록"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "확인"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "입력하신 이메일 주소와 비밀번호로 계정을 만들지 못했습니다."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "태그를 삭제할 수 없습니다"; + +/* Note fetch error title */ +"Could not fetch Notes" = "메모를 가져올 수 없음"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "태그를 가져올 수 없음"; + /* Fetch error title */ "Could not fetch entities" = "엔터티를 가져올 수 없음"; +/* note content fetch error title */ +"Could not fetch note content" = "메모 내용을 가져올 수 없음"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "입력하신 이메일 주소와 비밀번호로 로그인하지 못했습니다."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "문서"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "아직 계정이 없으세요?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "활성화됨"; +/* LogIn Interface Title */ +"Enter Code" = "코드 입력"; + +/* Enter Password fallback Action */ +"Enter password" = "비밀번호 입력"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "계정 {{EMAIL}}의 비밀번호를 입력하세요."; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "패스코드 입력"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "오류"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "오류: NSManagedObjectContext의 persistentStoreCoordinator는 nil이어야 합니다. Simperium에서 자동으로 CoreData 연결을 처리합니다."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "오류: 동시성 유형 'NSMainQueueConcurrencyType'으로 컨텍스트를 초기화해야 합니다."; - /* Get Help Description Label */ "FAQ or contact us" = "FAQ 또는 문의"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "버전 가져오기"; -/* Forgot password Button Text */ -"Forgot password? »" = "비밀번호를 잊으셨나요? »"; - /* Password Reset Action */ -"Forgotten password?" = "비밀번호를 잊으셨나요?"; +"Forgot your password?" = "비밀번호를 잊으셨나요?"; /* FAQ or contact us */ "Get Help" = "도움말"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "좋아요"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "스포트라이트 메모 색인화"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "색인 제거됨"; + /* Note Information Button (metrics + references) */ "Information" = "정보"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "로그인"; +/* LogIn Interface Title */ +"Log In with Password" = "비밀번호로 로그인"; + /* Log out of the active account in the app */ "Log Out" = "로그아웃"; /* Widget warning if user is logged out */ "Log in to see your notes" = "로그인하여 메모 보기"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "워드프레스닷컴으로 로그인"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "이메일로 로그인"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "이메일로 로그인하지 못했습니다. 비밀번호를 입력해 주세요."; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "로그인 코드가 너무 짧습니다."; + /* Month and day date formatter */ "MMM d" = "MMM d"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "수정됨: 오래된 항목"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "월간"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "선택한 메모를 휴지통으로 이동"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "태그 없는 메모 없음"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "유효한 이메일 주소가 아닙니다."; - /* Note Widget Title */ "Note" = "메모"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "확인"; +/* confirm button title */ +"Okay" = "확인"; + /* No comment provided by engineer. */ "On" = "켬"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "옵션"; +/* Or, used as a separator between Actions */ +"Or" = "또는"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "패스코드"; /* Pin Lock */ "Passcodes did not match. Try again." = "패스코드가 일치하지 않습니다. 다시 시도해 주세요."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "비밀번호"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "비밀번호는 이메일과 일치할 수 없습니다."; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "비밀번호는 %d자 이상이어야 합니다."; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "비밀번호에는 탭이나 줄 바꿈이 없어야 합니다."; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "비밀번호가 일치하지 않습니다."; - /* Number of changes pending to be sent */ "Pendings" = "대기 중"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Simplenote 앱을 사용하여 먼저 Simplenote에 로그인하세요."; +/* Encourages trying delete again */ +"Please try again" = "다시 시도해 주세요."; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "구매 항목을 복원하려면 최신 iOS 릴리스로 업그레이드하세요."; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "최근"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "복구된 메모 내용 - "; + /* References section header on Info Card */ "Referenced In" = "참조"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "메모 복구"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "구매 항목 복원"; - /* Title -> Review you account screen */ "Review Your Account" = "계정 검토"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "사이드바"; -/* Title of button for logging in (must be short) */ -"Sign In" = "로그인"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "가입"; -/* A short link to access the account login screen */ -"Sign in" = "로그인"; - -/* A short link to access the account creation screen */ -"Sign up" = "가입"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "로그아웃하면 동기화되지 않은 모든 메모가 삭제됩니다. 웹 앱에 로그인하여 동기화된 메모를 확인할 수 있습니다."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote 지지자"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "위젯을 설정하려면 Simplenote를 구성하고 로그인해야 함"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "죄송합니다!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "정렬 기준:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "스포트라이트 기록은 여전히 검색 결과에 표시될 수 있지만 메모는 색인화되지 않습니다."; + /* Title for the response's Status Code */ "Status Code" = "상태 코드"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "향후 기능 활용에 도움이 되도록 즐겨 찾는 메모 앱 지원"; -/* No comment provided by engineer. */ -"Sustainer" = "지지자"; +/* Switch app icon */ +"Sustainer App Icon" = "지지자 앱 아이콘"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "지지자 구독이 복원되었습니다. Simplenote을 지지해 주셔서 감사합니다!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "계속 지지해 주셔서 감사합니다."; -/* Error when address is in use */ -"That email is already being used" = "해당 이메일 주소가 이미 사용되고 있습니다."; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "요청하신 인증 코드가 만료되었습니다. 새 인증 코드를 요청하세요."; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "입력한 코드가 올바르지 않습니다. 다시 시도해 주세요."; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "입력한 이메일이 이미 Simplenote 계정과 연결되어 있습니다."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "실행 취소"; -/* WebSocket not initialized */ -"Uninitialized" = "초기화되지 않음"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "이름 없는 태그"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "웹 앱 방문"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "이메일을 통해 받으신 코드로 로그인할 수 있으며"; + /* Generic error */ "We're having problems. Please try again soon." = "문제가 있습니다. 나중에 다시 시도하세요."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "{{EMAIL}}(으)로 코드를 보냈습니다. 코드는 몇 분 동안 유효합니다."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Simplenote를 어떻게 생각하시나요?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "다음 실행 시 단축키 내용의 복구를 시도합니다."; + /* Number of words in the note */ "Words" = "단어"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "연간"; - /* Proceeds with the Empty Trash OP */ "Yes" = "예"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "회원님은 이미 지지자입니다. Simplenote을 지지해 주셔서 감사합니다!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "인증 코드가 만료되었습니다. 새 인증 코드를 요청하세요."; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "이메일 주소가 올바르지 않습니다."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "휴지통이 비어 있습니다."; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "직접 로그인하셔도 됩니다."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "태그:"; diff --git a/Simplenote/nl.lproj/Localizable.strings b/Simplenote/nl.lproj/Localizable.strings index c0aca05a6..5d8d33126 100644 --- a/Simplenote/nl.lproj/Localizable.strings +++ b/Simplenote/nl.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-24 00:43:48+0000 */ +/* Translation-Revision-Date: 2024-09-20 09:54:07+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: nl */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ Per maand"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ Per jaar"; - -/* Number of found search results */ "%d Result" = "%d resultaat"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Over"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Accepteren"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Alle notities"; -/* A short description to access the account login screen */ -"Already have an account?" = "Heb je al een account?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Er is een e-mail verzonden naar %@ Controleer je inbox en volg de instructies om het verwijderen van je account te bevestigen.\n\nJe account zal niet worden verwijderd totdat we je bevestiging hebben ontvangen"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Weet je zeker dat je de prullenbak wilt leegmaken? Deze actie kan niet ongedaan worden gemaakt."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Het is niet gelukt om de inhoud van de huidige notitie op te halen. Probeer het later opnieuw."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Het is niet gelukt om entiteiten op te halen. Probeer het later opnieuw."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Het is niet gelukt om notities op te halen. Probeer het later opnieuw."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Het is niet gelukt om tags op te halen. Probeer het later opnieuw."; + /* User Authenticated */ "Authenticated" = "Geverifieerd"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "Door het account voor %@ te verwijderen zullen alle opmerkingen die gemaakt zijn met dit account permanent worden verwijderd. Dit kan niet worden teruggedraaid"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Als je je registreert, ga je akkoord met onze Terms of Service »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Kies een viercijferige pincode"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Kies een abonnement en help mee toekomstige functies mogelijk te maken"; +/* Code TextField Placeholder */ +"Code" = "Code"; /* Opens the Collaborate UI */ "Collaborate" = "Samenwerken"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Beknopte notitielijst"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Bevestigen"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Kan geen account maken met het opgegeven e-mailadres en wachtwoord."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Kon tag niet verwijderen"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Het is niet gelukt om notities op te halen"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Het is niet gelukt om tags op te halen"; + /* Fetch error title */ "Could not fetch entities" = "Het is niet gelukt om entiteiten op te halen"; +/* note content fetch error title */ +"Could not fetch note content" = "Het is niet gelukt om de notitie-inhoud op te halen"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Kan niet inloggen met het opgegeven e-mailadres en wachtwoord."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Document"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Heb je nog geen account?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "Aantal in wachtrij"; +/* LogIn Interface Title */ +"Enter Code" = "Voer code in"; + +/* Enter Password fallback Action */ +"Enter password" = "Voer wachtwoord in"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Voer het wachtwoord van het account {{EMAIL}} in"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Voer je wachtwoordcode in"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Fout"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Fout: NSManagedObjectContext's persistentStoreCoordinator moet nul zijn. Simperium verwerkt CoreData-verbindingen voor je."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Fout: je moet je context laten beginnen met het gelijktijdigheidstype ‘NSMainQueueConcurrencyType’."; - /* Get Help Description Label */ "FAQ or contact us" = "Veelgestelde vragen of neem contact met ons op"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Versie ophalen"; -/* Forgot password Button Text */ -"Forgot password? »" = "Wachtwoord vergeten? »"; - /* Password Reset Action */ -"Forgotten password?" = "Wachtwoord vergeten?"; +"Forgot your password?" = "Wachtwoord vergeten?"; /* FAQ or contact us */ "Get Help" = "Ontvang hulp"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Ik vind het leuk"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Notities indexeren in Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Index verwijderd"; + /* Note Information Button (metrics + references) */ "Information" = "Informatie"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Inloggen"; +/* LogIn Interface Title */ +"Log In with Password" = "Inloggen met wachtwoord"; + /* Log out of the active account in the app */ "Log Out" = "Uitloggen"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Log in om je notitites te bekijken"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Inloggen met WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Inloggen met e-mail"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Inloggen met e-mail is mislukt, voer je wachtwoord in"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Inlogcode is te kort"; + /* Month and day date formatter */ "MMM d" = "d MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Aangepast op: Oudste"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Maandelijks"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Verplaats de geselecteerde notities naar de prullenbak"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Geen niet-getagde opmerkingen"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Geen geldig e-mailadres"; - /* Note Widget Title */ "Note" = "Notitie"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "Ok"; +/* confirm button title */ +"Okay" = "Oké"; + /* No comment provided by engineer. */ "On" = "Aan"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Opties"; +/* Or, used as a separator between Actions */ +"Or" = "Of"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Wachtwoordcode"; /* Pin Lock */ "Passcodes did not match. Try again." = "Wachtwoordcodes komen niet overeen. Probeer het opnieuw."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Wachtwoord"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "Wachtwoord mag niet hetzelfde zijn als e-mailadres"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Wachtwoord moet minstens %d tekens bevatten"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "Wachtwoord mag geen tabs of nieuwe regels bevatten"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Wachtwoorden komen niet overeen"; - /* Number of changes pending to be sent */ "Pendings" = "Aantal in behandeling"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Log eerst in bij je Simplenote-account met behulp van de Simplenote-app."; +/* Encourages trying delete again */ +"Please try again" = "Probeer het nog eens"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Upgrade naar de nieuwste versie van iOS om aankopen te herstellen"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Recent"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Inhoud herstelde notitie - "; + /* References section header on Info Card */ "Referenced In" = "Naar verwezen in"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Notitie herstellen"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Herstel aankopen"; - /* Title -> Review you account screen */ "Review Your Account" = "Controleer je account"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Sidebar"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Aanmelden"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Registreren"; -/* A short link to access the account login screen */ -"Sign in" = "Aanmelden"; - -/* A short link to access the account creation screen */ -"Sign up" = "Registreren"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Door je af te melden worden gesynchroniseerde notities verwijderd. Je kunt je gesynchroniseerde notities verifiëren door je aan te melden via de web-app."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Simplenote moet worden geconfigureerd en ingelogd bij configuratiewidgets"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Sorry!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Sorteren op:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "Spotlight-geschiedenis kan nog steeds in zoekresultaten verschijnen, maar notities zijn niet meer geïndexeerd"; + /* Title for the response's Status Code */ "Status Code" = "Statuscode"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Steun je favoriete notitie-app en help toekomstige functies mogelijk te maken"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "Sustainer-app-pictogram"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Sustainer-abonnement hersteld. Bedankt voor je steun aan Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Bedankt voor je voortdurende ondersteuning"; -/* Error when address is in use */ -"That email is already being used" = "Dit e-mailadres is al in gebruik"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "De authenticatiecode die je aangevraagd hebt, is verlopen. Vraag een nieuwe aan"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "De code die je hebt ingevuld, is onjuist. Probeer het nog eens"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "Het e-mailadres dat je hebt ingevoerd, is al gekoppeld aan een Simplenote-account."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Ongedaan maken"; -/* WebSocket not initialized */ -"Uninitialized" = "Niet gestart"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Ongetitelde tag"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Web-app bezoeken"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "We sturen je via e-mail een code die je kan gebruiken om in te loggen, of je kan"; + /* Generic error */ "We're having problems. Please try again soon." = "We ondervinden wat problemen. Probeer het later opnieuw."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "We hebben een code gestuurd naar {{EMAIL}}. De code is een paar minuten geldig."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Wat vind je van Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Bij het opstarten wordt geprobeerd de shortcut-inhoud te herstellen"; + /* Number of words in the note */ "Words" = "Woorden"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Jaarlijks"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Ja"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Je bent al een Sustainer. Bedankt voor je steun aan Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Je authenticatiecode is verlopen. Vraag een nieuwe aan"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Je e-mailadres is niet geldig."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Je prullenbak is leeg"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "handmatig inloggen."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "tag:"; diff --git a/Simplenote/pt-BR.lproj/Localizable.strings b/Simplenote/pt-BR.lproj/Localizable.strings index 4f710de36..b98672fb0 100644 --- a/Simplenote/pt-BR.lproj/Localizable.strings +++ b/Simplenote/pt-BR.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-23 09:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-18 16:54:03+0000 */ /* Plural-Forms: nplurals=2; plural=(n > 1); */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: pt_BR */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ por mês"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ por ano"; - -/* Number of found search results */ "%d Result" = "%d resultado"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Sobre"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Aceitar"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Todas as anotações"; -/* A short description to access the account login screen */ -"Already have an account?" = "Já tem uma conta?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Um e-mail foi enviado para %@. Verifique sua caixa de entrada e siga as instruções para confirmar a exclusão da conta.\n\nSua conta não será excluída até recebermos sua confirmação."; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Tem certeza de que deseja esvaziar a lixeira? Essa ação não pode ser desfeita."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Falha na tentativa de obter conteúdo da nota atual. Tente novamente mais tarde."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Falha na tentativa de buscar entidades. Tente novamente mais tarde."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Falha na tentativa de obter notas. Tente novamente mais tarde."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Falha na tentativa de obter tags. Tente novamente mais tarde."; + /* User Authenticated */ "Authenticated" = "Autenticado"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "Ao excluir a conta de %@, todas as anotações criadas com ela serão excluídas de forma permanente. Essa ação não é reversível"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Ao se registrar, você concorda com os nossos Termos de Serviço »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Escolha uma senha de quatro dígitos"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Escolha um plano e ajude a garantir funcionalidades no futuro"; +/* Code TextField Placeholder */ +"Code" = "Código"; /* Opens the Collaborate UI */ "Collaborate" = "Colaborar"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Lista condensada de anotações"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Confirmar"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Não foi possível criar uma conta com o endereço de email e senha fornecidos."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Não foi possível excluir a tag"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Não foi possível obter notas"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Não foi possível obter tags"; + /* Fetch error title */ "Could not fetch entities" = "Não foi possível buscar entidades"; +/* note content fetch error title */ +"Could not fetch note content" = "Não foi possível obter conteúdo da nota"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Não foi possível fazer login com o endereço de email e senha fornecidos."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Documento"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Não tem uma conta?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "Enfileirado"; +/* LogIn Interface Title */ +"Enter Code" = "Digite o código"; + +/* Enter Password fallback Action */ +"Enter password" = "Digite a senha"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Digite a senha da conta {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Insira sua senha"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Erro"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Erro: o persistentStoreCoordinator de NSManagedObjectContext deve ser nulo. A Simperium cuidará das conexões CoreData para você."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Erro: você deve inicializar seu contexto com o tipo de simultaneidade 'NSMainQueueConcurrencyType'."; - /* Get Help Description Label */ "FAQ or contact us" = "Veja as perguntas frequentes ou fale conosco"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Buscando versão"; -/* Forgot password Button Text */ -"Forgot password? »" = "Esqueceu sua senha? »"; - /* Password Reset Action */ -"Forgotten password?" = "Esqueceu a senha?"; +"Forgot your password?" = "Esqueceu sua senha?"; /* FAQ or contact us */ "Get Help" = "Obter ajuda"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Curti"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Notas do índice no Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Índice removido"; + /* Note Information Button (metrics + references) */ "Information" = "Informações"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Fazer login"; +/* LogIn Interface Title */ +"Log In with Password" = "Fazer login com senha"; + /* Log out of the active account in the app */ "Log Out" = "Fazer logout"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Faça login para ver suas anotações"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Fazer login com o WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Fazer login com o e-mail"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Falha no login com o e-mail. Insira sua senha."; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Código de login muito curto"; + /* Month and day date formatter */ "MMM d" = "d MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Modificado: mais antiga"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Mensalmente"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Mova as anotações selecionadas para a lixeira"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Nenhuma anotação sem tag"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Endereço de e-mail inválido"; - /* Note Widget Title */ "Note" = "Anotação"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "Ok"; +/* confirm button title */ +"Okay" = "OK"; + /* No comment provided by engineer. */ "On" = "Ativado"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Opções"; +/* Or, used as a separator between Actions */ +"Or" = "Ou"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Senha"; /* Pin Lock */ "Passcodes did not match. Try again." = "As senhas não corresponderam. Tente novamente."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Senha"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "A senha não pode corresponder ao e-mail"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "A senha deve ter no mínimo %d caracteres"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "A senha não pode conter tabulações nem novas linhas"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "As senhas não coincidem"; - /* Number of changes pending to be sent */ "Pendings" = "Pendentes"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Primeiro, faça login na sua conta do Simplenote usando o aplicativo."; +/* Encourages trying delete again */ +"Please try again" = "Tente novamente"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Faça upgrade para a versão mais recente do iOS para restaurar as compras"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Recente"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Conteúdo da nota recuperado "; + /* References section header on Info Card */ "Referenced In" = "Mencionado em"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Restaurar anotação"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Restaurar compras"; - /* Title -> Review you account screen */ "Review Your Account" = "Revisar sua conta"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Barra lateral"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Fazer login"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Registrar-se"; -/* A short link to access the account login screen */ -"Sign in" = "Fazer login"; - -/* A short link to access the account creation screen */ -"Sign up" = "Registrar-se"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Sair resultará na exclusão de quaisquer anotações não sincronizadas. Para verificar suas anotações sincronizadas, entre no Aplicativo para Web."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Apoiador do Simplenote"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Configure e faça login no Simplenote para configurar widgets"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Desculpe!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Ordenar por:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "O histórico do Spotlight ainda pode aparecer nos resultados da pesquisa, mas as notas foram removidas"; + /* Title for the response's Status Code */ "Status Code" = "Código do status"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Apoie seu aplicativo de anotações favorito para ajudar a garantir funcionalidades no futuro"; -/* No comment provided by engineer. */ -"Sustainer" = "Apoiador"; +/* Switch app icon */ +"Sustainer App Icon" = "Ícone do aplicativo Sustainer"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Assinatura de apoiador restaurada. Agradecemos por apoiar o Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Agradecemos pelo seu apoio contínuo"; -/* Error when address is in use */ -"That email is already being used" = "Este e-mail já está em uso"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "O código de autenticação solicitado expirou. Solicite um novo."; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "O código inserido não está correto. Tente novamente"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "O e-mail informado já está associado a uma conta do Simplenote."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Desfazer"; -/* WebSocket not initialized */ -"Uninitialized" = "Não inicializado"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Tag sem nome"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Acessar o aplicativo para Web"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Enviaremos o código de login por e-mail. Se preferir,"; + /* Generic error */ "We're having problems. Please try again soon." = "Estamos com problemas. Tente novamente mais tarde."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Enviamos um código para {{EMAIL}}. O código será válido por alguns minutos."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "O que você acha do Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Tentará recuperar o conteúdo do atalho no próximo lançamento"; + /* Number of words in the note */ "Words" = "Palavras"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Anualmente"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Sim"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Você já é um apoiador. Agradecemos por apoiar o Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Seu código de autenticação expirou. Solicite um novo."; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Seu endereço de email é inválido."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Sua lixeira está vazia"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "faça login manualmente."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "tag:"; diff --git a/Simplenote/ru.lproj/Localizable.strings b/Simplenote/ru.lproj/Localizable.strings index 8deef0e41..0c93c28b7 100644 --- a/Simplenote/ru.lproj/Localizable.strings +++ b/Simplenote/ru.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 11:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-19 09:54:03+0000 */ /* Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ru */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ в месяц"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ в год"; - -/* Number of found search results */ "%d Result" = "%d Найдено"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "О нас"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Согласен"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Все заметки"; -/* A short description to access the account login screen */ -"Already have an account?" = "Уже есть учетная запись?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Мы выслали письмо на адрес %@. Проверьте почту и следуйте инструкциям, чтобы подтвердить удаление учётной записи.\n\nВаша учётная запись будет удалена только после получения вашего подтверждения"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Очистить корзину? Это действие нельзя отменить."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Попытка получить содержимое текущей заметки завершилась неудачей. Повторите попытку позже."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Сбой получения объектов. Повторите попытку позже."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Попытка получить заметки завершилась неудачей. Повторите попытку позже."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Попытка получить метки завершилась неудачей. Повторите попытку позже."; + /* User Authenticated */ "Authenticated" = "Прошедший проверку"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "При удалении учётной записи для %@ все созданные в ней заметки будут безвозвратно удалены. Это действие нельзя отменить"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Регистрируясь, вы подтверждаете свое согласие с нашими Условиями предоставления услуг »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Введите 4-значный пароль"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Выберите план и сделайте вклад в разработку новых функций"; +/* Code TextField Placeholder */ +"Code" = "Код"; /* Opens the Collaborate UI */ "Collaborate" = "Партнер"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Сократить список заметок"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Подтвердить"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Невозможно создать учетную запись с указанными адресом email и паролем."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Не удалось удалить метку"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Не удалось получить заметки"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Не удалось получить метки"; + /* Fetch error title */ "Could not fetch entities" = "Не удалось получить объекты"; +/* note content fetch error title */ +"Could not fetch note content" = "Не удалось получить содержимое заметки"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Невозможно авторизоваться с указанными адресом email и паролем."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Документ"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Нет учетной записи?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "Требуется"; +/* LogIn Interface Title */ +"Enter Code" = "Введите код"; + +/* Enter Password fallback Action */ +"Enter password" = "Введите пароль"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Введите пароль для учётной записи {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Введите ваш пароль"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Ошибка"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Ошибка: persistentStoreCoordinator объекта «NSManagedObjectContext» должен быть равен нулю. Simperium возьмет на себя управление подключениями CoreData."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Ошибка: необходимо инициализировать контекст с типом многопоточности «NSMainQueueConcurrencyType»."; - /* Get Help Description Label */ "FAQ or contact us" = "«Часто задаваемые вопросы» или «Свяжитесь с нами»"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Получение сведений о версии"; -/* Forgot password Button Text */ -"Forgot password? »" = "Забыли пароль? »"; - /* Password Reset Action */ -"Forgotten password?" = "Забыли пароль?"; +"Forgot your password?" = "Забыли пароль?"; /* FAQ or contact us */ "Get Help" = "Справка"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Мне нравится"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Индексировать заметки в Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Индекс удалён"; + /* Note Information Button (metrics + references) */ "Information" = "Информация"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Войти"; +/* LogIn Interface Title */ +"Log In with Password" = "Войти с помощью пароля"; + /* Log out of the active account in the app */ "Log Out" = "Выйти"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Войдите для просмотра заметок"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Войти через WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Войти с помощью эл. почты"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Не удалось войти с адресом эл. почты. Введите пароль."; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Слишком короткий код для входа в систему"; + /* Month and day date formatter */ "MMM d" = "Д ммм"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Дата изменения: сначала старые"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Ежемесячно"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Перемещение выбранных заметок в корзину"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Нет заметок без тегов"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Этот адрес эл. почты недействителен"; - /* Note Widget Title */ "Note" = "Заметка"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "OK"; +/* confirm button title */ +"Okay" = "ОК"; + /* No comment provided by engineer. */ "On" = "Включить"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Опции"; +/* Or, used as a separator between Actions */ +"Or" = "или"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Пароль"; /* Pin Lock */ "Passcodes did not match. Try again." = "Пароль не подходит. Попробуйте снова. "; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Пароль"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "Пароль не должен совпадать с адресом эл. почты"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Пароль должен содержать не менее %d симв."; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "Пароль не должен содержать знаки табуляции и перехода на новую строку"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Пароли не совпадают"; - /* Number of changes pending to be sent */ "Pendings" = "На утверждении"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Сначала войдите в учётную запись в приложении Simplenote."; +/* Encourages trying delete again */ +"Please try again" = "Повторите попытку"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Установите последнюю версию для iOS, чтобы восстановить покупки"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Недавние"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Восстановленный контент заметки — "; + /* References section header on Info Card */ "Referenced In" = "Упоминание в"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Восстановить заметку"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Восстановить покупки"; - /* Title -> Review you account screen */ "Review Your Account" = "Проверить учётную запись"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Боковая панель"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Войти"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Зарегистрироваться"; -/* A short link to access the account login screen */ -"Sign in" = "Войти"; - -/* A short link to access the account creation screen */ -"Sign up" = "Зарегистрироваться"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "При выходе из учётной записи все несинхронизированные заметки будут удалены. Чтобы убедиться, что заметки синхронизированы, войдите в веб-приложение."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Спонсор Simplenote"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Для настройки виджетов необходимо настроить приложение Simplenote и войти в него"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Извините!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Сортировать по:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "История Spotlight может по-прежнему появляться в результатах поиска, однако заметки индексироваться не будут."; + /* Title for the response's Status Code */ "Status Code" = "Код статуса"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Поддержите любимое приложение для заметок и внесите вклад в разработку новых функций"; -/* No comment provided by engineer. */ -"Sustainer" = "Спонсор"; +/* Switch app icon */ +"Sustainer App Icon" = "Значок приложения Sustainer"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Спонсорская подписка восстановлена. Благодарим за поддержку Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Благодарим за вашу неизменную поддержку"; -/* Error when address is in use */ -"That email is already being used" = "Этот адрес эл. почты уже используется"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "Срок действия запрошенного вами кода аутентификации истёк. Запросите новый код"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "Вы ввели неверный код. Повторите попытку"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "Указанный адрес эл. почты уже связан с учетной записью Simplenote."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Назад"; -/* WebSocket not initialized */ -"Uninitialized" = "Не инициализировано"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Метка без имени"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Перейти в веб-приложение"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Мы пришлём вам код для входа по электронной почте, а кроме того, вы можете"; + /* Generic error */ "We're having problems. Please try again soon." = "Произошла ошибка. Повторите попытку позже."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Код отправлен на адрес {{EMAIL}}. Код будет действителен в течение нескольких минут."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Каково ваше мнение о Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Будет предпринята попытка восстановить контент ярлыка при следующем запуске"; + /* Number of words in the note */ "Words" = "Слова"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Ежегодно"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Да"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Вы уже являетесь спонсором. Благодарим за поддержку Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Срок действия вашего кода аутентификации истёк. Запросите новый код"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Ваш адрес электронной почты не действителен."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Корзина пуста"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "выполнить вход вручную."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "метка:"; diff --git a/Simplenote/sv.lproj/Localizable.strings b/Simplenote/sv.lproj/Localizable.strings index e39afe02e..96fbfe7ac 100644 --- a/Simplenote/sv.lproj/Localizable.strings +++ b/Simplenote/sv.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 13:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-18 10:54:02+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: sv_SE */ /* No comment provided by engineer. */ -"%@ per Month" = "%@ per månad"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "%@ per år"; - -/* Number of found search results */ "%d Result" = "%d resultat"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Om"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Acceptera"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Alla anteckningar"; -/* A short description to access the account login screen */ -"Already have an account?" = "Har du redan ett konto?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "Ett e-postmeddelande har skickats till %@. Kolla din inkorg och följ instruktionerna för att bekräfta kontoborttagningen.\n\nDitt konto kommer inte att tas bort förrän vi har mottagit din bekräftelse"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Är du säker på att du vill tömma papperskorgen? Detta kan inte göras ogjort."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Försöket att hämta aktuellt anteckningsinnehåll misslyckades. Försök igen senare."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Försöket att hämta enheter misslyckades. Försök igen senare."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Försöket att hämta anteckningar misslyckades. Försök igen senare."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Försök att hämta etiketter misslyckades. Försök igen senare."; + /* User Authenticated */ "Authenticated" = "Autentiserad"; @@ -141,24 +142,19 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "Om du tar bort kontot för %@ kommer alla anteckningar som har skapats med kontot också att tas bort permanent. Det går inte att ångra den här åtgärden."; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Genom att skapa ett konto godkänner du användaravtalet »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ "Cancel" = "Avbryt"; /* Name of button to cancel iOS share extension in missing token alert */ -"Cancel Share" = "Avbryt Dela"; +"Cancel Share" = "Avbryt dela"; /* Error message title. Review you account screen */ "Cannot Confirm Account" = "Kan inte bekräfta konto"; @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "Välj en fyrsiffrig låskod"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Välj ett paket och hjälp till att låsa upp framtida funktioner"; +/* Code TextField Placeholder */ +"Code" = "Kod"; /* Opens the Collaborate UI */ "Collaborate" = "Samarbeta"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Komprimerad anteckningslista"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Bekräfta"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Kunde inte skapa ett konto med den e-postadressen och det lösenordet."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Det gick inte att ta bort etiketten"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Det gick inte att hämta anteckningar"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Kunde inte hämta etiketter"; + /* Fetch error title */ "Could not fetch entities" = "Kunde inte hämta enheter"; +/* note content fetch error title */ +"Could not fetch note content" = "Det gick inte att hämta anteckningsinnehållet"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Kunde inte logga in med den e-postadressen och det lösenordet."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Dokument"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Har du inget konto?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "I kö"; +/* LogIn Interface Title */ +"Enter Code" = "Ange kod"; + +/* Enter Password fallback Action */ +"Enter password" = "Ange lösenord"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "Ange lösenordet för kontot {{EMAIL}}"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Ange lösenkod"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Fel"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Fel: persistentStoreCoordinator för NSManagedObjectContext måste vara noll. Simperium kommer att hantera CoreData-anslutningar åt dig."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Fel: Du måste initiera ditt sammanhang med samtidighetstypen \"NSMainQueueConcurrencyType\"."; - /* Get Help Description Label */ "FAQ or contact us" = "Vanliga frågor eller kontakta oss"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Hämtar version"; -/* Forgot password Button Text */ -"Forgot password? »" = "Glömt lösenord? »"; - /* Password Reset Action */ -"Forgotten password?" = "Glömt lösenordet?"; +"Forgot your password?" = "Glömt ditt lösenord?"; /* FAQ or contact us */ "Get Help" = "Skaffa hjälp"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Jag gillar den"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Indexera anteckningar i Spotlight"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Index borttaget"; + /* Note Information Button (metrics + references) */ "Information" = "Information"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Logga in"; +/* LogIn Interface Title */ +"Log In with Password" = "Logga in med lösenord"; + /* Log out of the active account in the app */ "Log Out" = "Logga ut"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Logga in för att se dina anteckningar"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "Logga in med WordPress.com"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "Logga in med e-post"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "Inloggning med e-post misslyckades, ange ditt lösenord"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Inloggningskod är för kort"; + /* Month and day date formatter */ "MMM d" = "d MMM"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Modifierad: Äldsta"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Månadsvis"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Flytta markerade anteckningar till papperskorgen"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Inga omärkta anteckningar"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Inte en giltig e-postadress"; - /* Note Widget Title */ "Note" = "Notis"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "Okej"; +/* confirm button title */ +"Okay" = "Okey"; + /* No comment provided by engineer. */ "On" = "På"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Alternativ"; +/* Or, used as a separator between Actions */ +"Or" = "Eller"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Lösenkod"; /* Pin Lock */ "Passcodes did not match. Try again." = "Lösenkoderna matchar inte varandra. Försök igen."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Lösenord"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "Lösenord kan inte matcha e-post"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Lösenord måste innehålla minst %d tecken"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "Lösenordet får inte innehålla tabbstopp eller nya rader"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Lösenorden matchar inte"; - /* Number of changes pending to be sent */ "Pendings" = "Väntande"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Logga in på ditt Simplenote-konto först genom att använda Simplenote-appen."; +/* Encourages trying delete again */ +"Please try again" = "Försök igen"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Uppgradera till den senaste iOS-versionen för att återställa köp"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Senaste"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Återställt anteckningsinnehåll – "; + /* References section header on Info Card */ "Referenced In" = "Refereras i"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Återställ anteckning"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Återställ köp"; - /* Title -> Review you account screen */ "Review Your Account" = "Granska ditt konto"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Sidopanel"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Logga in"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Registrera"; -/* A short link to access the account login screen */ -"Sign in" = "Logga in"; - -/* A short link to access the account creation screen */ -"Sign up" = "Skapa konto"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Utloggning kommer att radera alla osynkroniserade anteckningar. Du kan verifiera dina synkroniserade anteckningar genom att logga in på webbappen."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Du måste konfigurera Simplenote och logga in för att konfigurera widgetar"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Tyvärr!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Sortera efter:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "Spotlight-historik kan fortfarande visas i sökresultaten, men anteckningar är inte längre indexerade"; + /* Title for the response's Status Code */ "Status Code" = "Statuskod"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Stöd din favoritanteckningsapp och hjälp till att låsa upp framtida funktioner"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "Sustainer-appikon"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Sustainer-prenumeration återställd. Tack för att du stödjer Simplenote."; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Tack för ditt fortsatta stöd"; -/* Error when address is in use */ -"That email is already being used" = "Den e-posten används redan"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "Autentiseringskoden du har begärt har löpt ut. Begär en ny"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "Koden du har angett är inte korrekt. Försök igen"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "Den angivna e-postadressen är redan associerad med ett Simplenote-konto."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Ångra"; -/* WebSocket not initialized */ -"Uninitialized" = "Oinitierad"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Etikett utan namn"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Besök webbappen"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Vi skickar en kod till dig via e-post för att logga in, eller så kan du"; + /* Generic error */ "We're having problems. Please try again soon." = "Vi har problem. Försök igen om en stund."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Vi har skickat en kod till {{EMAIL}}. Koden kommer vara giltig i några minuter."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Vad tycker du om Simplenote?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Kommer att försöka återställa genvägsinnehållet vid nästa start"; + /* Number of words in the note */ "Words" = "ord"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Årligen"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Ja"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Du är redan en Sustainer. Tack för att du stödjer Simplenote."; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Din autentiseringskod har löpt ut. Begär en ny"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "Din e-postadress är inte giltig"; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Din papperskorg är tom"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "logga in manuellt."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "etikett:"; diff --git a/Simplenote/tr.lproj/Localizable.strings b/Simplenote/tr.lproj/Localizable.strings index 599617e24..5090e984f 100644 --- a/Simplenote/tr.lproj/Localizable.strings +++ b/Simplenote/tr.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 15:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-18 13:54:02+0000 */ /* Plural-Forms: nplurals=2; plural=(n > 1); */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: tr */ /* No comment provided by engineer. */ -"%@ per Month" = "Aylık %@"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "Yıllık %@"; - -/* Number of found search results */ "%d Result" = "%d Sonuç"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "Hakkında"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "Kabul"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "Tüm Notlar"; -/* A short description to access the account login screen */ -"Already have an account?" = "Hesabınız zaten var mı?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "%@ adresine bir e-posta gönderildi Gelen kutunuzu kontrol edin ve hesap silme işlemini onaylamak için talimatları izleyin.\n\nOnayınız alınana kadar hesabınız silinmeyecek."; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "Çöp kutusunu boşaltmak istediğinizden emin misiniz? Bu işlem geri alınamaz."; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "Mevcut not içeriğini getirme denemesi başarısız oldu. Lütfen daha sonra tekrar deneyin."; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "Varlıkları getirme işlemi başarısız oldu. Lütfen daha sonra tekrar deneyin."; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "Notları getirme denemesi başarısız oldu. Lütfen daha sonra tekrar deneyin."; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "Etiketleri alma denemesi başarısız oldu. Lütfen daha sonra tekrar deneyin."; + /* User Authenticated */ "Authenticated" = "Kimliği Doğrulandı"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "%@ için hesabınızı sildiğinizde bu hesapla oluşturulan tüm notlar kalıcı olarak silinecektir. Bu eylem geri alınamaz."; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "Kaydolmakla Hizmet Koşulları'nı kabul etmiş olursunuz »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "4 basamaklı bir parola seçin"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "Bir paket seçerek gelecekte yeni özelliklerin sunulmasına yardımcı olun"; +/* Code TextField Placeholder */ +"Code" = "Kod"; /* Opens the Collaborate UI */ "Collaborate" = "İşbirliği Yap"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "Sıkıştırılmış Not Listesi"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "Onayla"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "Verilen e-posta adresi ve parolayla hesap oluşturulamadı."; +/* Notifies user tag delete failed */ +"Could not delete tag" = "Etiket silinemedi"; + +/* Note fetch error title */ +"Could not fetch Notes" = "Notlar getirilemedi"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "Etiketler getirilemedi"; + /* Fetch error title */ "Could not fetch entities" = "Varlıklar getirilemedi"; +/* note content fetch error title */ +"Could not fetch note content" = "Not içeriği getirilemedi"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "Verilen e-posta adresi ve parolayla oturum açılamadı."; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "Belge"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "Hesabınız yok mu?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "Sıraya Alındı"; +/* LogIn Interface Title */ +"Enter Code" = "Kod Girin"; + +/* Enter Password fallback Action */ +"Enter password" = "Şifre girin"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "{{EMAIL}} hesabınızın şifresini girin"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "Parolanızı girin"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "Hata"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "Hata: NSManagedObjectContext'in persistentStoreCoordinator değeri sıfır olmalıdır. Simperium, CoreData bağlantılarını sizin için yönetecek."; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "Hata: Bağlamınızı 'NSMainQueueConcurrencyType' eşzamanlılık türüyle başlatmalısınız."; - /* Get Help Description Label */ "FAQ or contact us" = "SSS veya bize ulaşın"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "Sürüm Alınıyor"; -/* Forgot password Button Text */ -"Forgot password? »" = "Şifrenizi mi unuttunuz? »"; - /* Password Reset Action */ -"Forgotten password?" = "Parolayı mı unuttunuz?"; +"Forgot your password?" = "Şifrenizi mi unuttunuz?"; /* FAQ or contact us */ "Get Help" = "Yardım Alın"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "Beğendim"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Öne Çıkanlarda Notları Dizine Ekleyin"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "Dizin Kaldırıldı"; + /* Note Information Button (metrics + references) */ "Information" = "Bilgi"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "Giriş yap"; +/* LogIn Interface Title */ +"Log In with Password" = "Şifre ile Oturum Açın"; + /* Log out of the active account in the app */ "Log Out" = "Çıkış yap"; /* Widget warning if user is logged out */ "Log in to see your notes" = "Notlarınızı görmek için giriş yapın"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "WordPress.com ile oturum aç"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "E-postayla oturum aç"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "E-postayla oturum açma başarısız oldu, lütfen şifrenizi girin"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "Oturum Açma Kodu çok kısa"; + /* Month and day date formatter */ "MMM d" = "AAA g"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "Değiştirme Tarihi: Eski"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "Aylık"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "Seçili notları çöp kutusuna taşı"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "Etiketsiz not yok"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "Geçerli bir e-posta adresi değil"; - /* Note Widget Title */ "Note" = "Not"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "Tamam"; +/* confirm button title */ +"Okay" = "Tamam"; + /* No comment provided by engineer. */ "On" = "Açık"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "Seçenekler"; +/* Or, used as a separator between Actions */ +"Or" = "Veya"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "Geçiş kodu"; /* Pin Lock */ "Passcodes did not match. Try again." = "Parolalar eşleşmiyor. Tekrar deneyin."; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "Parola"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "Parola ile e-posta adresi aynı olamaz"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "Parola en az %d karakter içermelidir"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "Parola sekme veya satır başı içermemelidir"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "Parolalar eşleşmiyor"; - /* Number of changes pending to be sent */ "Pendings" = "Bekleyenler"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "Lütfen önce Simplenote uygulamasını kullanarak Simplenote hesabınıza giriş yapın."; +/* Encourages trying delete again */ +"Please try again" = "Lütfen tekrar deneyin"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "Satın alınanları geri yüklemek için lütfen en son iOS sürümüne yükseltin"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "Son"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "Kurtarılan Not İçeriği - "; + /* References section header on Info Card */ "Referenced In" = "Referans Verildiği Yer"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "Notu Geri Yükle"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "Satın Alınanları Geri Yükle"; - /* Title -> Review you account screen */ "Review Your Account" = "Hesabınızı İnceleyin"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "Kenar çubuğu"; -/* Title of button for logging in (must be short) */ -"Sign In" = "Oturum Aç"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "Kaydol"; -/* A short link to access the account login screen */ -"Sign in" = "Oturum aç"; - -/* A short link to access the account creation screen */ -"Sign up" = "Kaydol"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "Oturumu kapattığınızda, senkronize edilmemiş tüm notlar silinir. Senkronize edilmiş notlarınızı Web Uygulamasında oturum açarak doğrulayabilirsiniz."; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "Bileşenleri ayarlamak için Simplenote yapılandırılmalı ve oturum açılmalıdır"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "Üzgünüz!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "Sıralama ölçütü:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "Öne çıkanlar geçmişi arama sonuçlarında gösterilmeye devam edebilir ancak notlar dizine eklenmez"; + /* Title for the response's Status Code */ "Status Code" = "Durum Kodu"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "Gelecekte yeni özelliklerin sunulmasına yardımcı olmak için favori notlar uygulamanızı destekleyin"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "Sustainer Uygulaması Simgesi"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "Sustainer aboneliği geri yüklendi. Simplenote'u desteklediğiniz için teşekkür ederiz!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "Devam eden desteğiniz için teşekkür ederiz"; -/* Error when address is in use */ -"That email is already being used" = "Bu e-posta zaten kullanılıyor"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "Talep ettiğiniz doğrulama kodunun geçerlilik süresi doldu. Lütfen yeni bir kod talep edin"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "Girdiğiniz kod geçersiz. Lütfen tekrar deneyin"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "Girdiğiniz e-posta adresi zaten bir Simplenote hesabıyla ilişkilendirilmiş."; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "Geri al"; -/* WebSocket not initialized */ -"Uninitialized" = "Başlatılmamış"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "Adsız Etiket"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "Web Uygulamasını Ziyaret Et"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "Oturum açmanız için size e-postayla bir kod göndereceğiz veya manuel olarak"; + /* Generic error */ "We're having problems. Please try again soon." = "Sorun yaşanıyor. Lütfen kısa bir süre sonra tekrar deneyin."; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "Şuraya bir kod gönderildi: {{EMAIL}}. Kod birkaç dakikalığına geçerli olacak."; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "Simplenote hakkında ne düşünüyorsunuz?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "Sonraki kullanıma sunmada kısayol içeriği kurtarılmaya çalışılacak"; + /* Number of words in the note */ "Words" = "Sözcük"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "Yıllık"; - /* Proceeds with the Empty Trash OP */ "Yes" = "Evet"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "Zaten bir Sustainer'sınız. Simplenote'u desteklediğiniz için teşekkür ederiz!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "Doğrulama kodunuzun geçerlilik süresi doldu. Lütfen yeni bir kod talep edin"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "E-posta adresiniz geçerli değil."; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "Çöp kutusu boş"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "oturum açabilirsiniz."; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "etiket:"; diff --git a/Simplenote/zh-Hans-CN.lproj/Localizable.strings b/Simplenote/zh-Hans-CN.lproj/Localizable.strings index ed6913856..9547b91df 100644 --- a/Simplenote/zh-Hans-CN.lproj/Localizable.strings +++ b/Simplenote/zh-Hans-CN.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 09:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-18 10:54:02+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: zh_CN */ /* No comment provided by engineer. */ -"%@ per Month" = "每月 %@"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "每年 %@"; - -/* Number of found search results */ "%d Result" = "%d 个结果"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "关于"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "接受"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "全部笔记"; -/* A short description to access the account login screen */ -"Already have an account?" = "已有帐户?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "我们已向 %@ 发送一封电子邮件。请检查您的收件箱,按照电子邮件中的说明来确认账户删除。\n\n在收到您的确认之前,我们不会删除您的账户。"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "是否确定要清空回收站?此操作无法撤消。"; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "尝试获取当前注释内容失败。 请稍后重试。"; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "尝试获取实体失败。 请稍后重试。"; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "尝试获取注释失败。 请稍后重试。"; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "尝试获取标签失败。 请稍后重试。"; + /* User Authenticated */ "Authenticated" = "已验证"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "删除 %@ 的账户后,使用此账户创建的所有笔记将永久删除。 此操作不可撤消"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "注册即表示您同意我们的服务条款 »"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "选择一个 4 位数密码"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "选择套餐,帮助该产品在未来提供更多功能"; +/* Code TextField Placeholder */ +"Code" = "代码"; /* Opens the Collaborate UI */ "Collaborate" = "协作"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "压缩的笔记列表"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "确认"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "无法使用所提供的电子邮件地址和密码创建帐户。"; +/* Notifies user tag delete failed */ +"Could not delete tag" = "无法删除标签"; + +/* Note fetch error title */ +"Could not fetch Notes" = "无法获取注释"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "无法获取标签"; + /* Fetch error title */ "Could not fetch entities" = "无法获取实体"; +/* note content fetch error title */ +"Could not fetch note content" = "无法获取注释内容"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "无法使用所提供的电子邮件地址和密码登录。"; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "文档"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "还没有帐户?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "已排入队列数"; +/* LogIn Interface Title */ +"Enter Code" = "输入代码"; + +/* Enter Password fallback Action */ +"Enter password" = "输入密码"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "输入账户 {{EMAIL}} 的密码"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "输入您的密码"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "错误"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "错误:NSManagedObjectContext 的 persistentStoreCoordinator 必须为 nil。 Simperium 将为您处理 CoreData 连接。"; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "错误:您必须使用“NSMainQueueConcurrencyType”并发类型初始化您的上下文。"; - /* Get Help Description Label */ "FAQ or contact us" = "常见问题解答或与我们联系"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "正在提取版本"; -/* Forgot password Button Text */ -"Forgot password? »" = "忘记密码?»"; - /* Password Reset Action */ -"Forgotten password?" = "忘记密码?"; +"Forgot your password?" = "忘记密码?"; /* FAQ or contact us */ "Get Help" = "获取帮助"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "我喜欢"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "Spotlight 中的索引注释"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "索引已删除"; + /* Note Information Button (metrics + references) */ "Information" = "信息"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "登录"; +/* LogIn Interface Title */ +"Log In with Password" = "使用密码登录"; + /* Log out of the active account in the app */ "Log Out" = "注销"; /* Widget warning if user is logged out */ "Log in to see your notes" = "登录以查看您的笔记"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "使用 WordPress.com 帐户登录"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "使用电子邮件登录"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "使用电子邮箱登录失败,请输入您的密码"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "登录代码过短"; + /* Month and day date formatter */ "MMM d" = "MMM d"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "修改时间:最旧"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "每月"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "将选中的笔记放入回收站"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "没有未标记的笔记"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "电子邮件地址无效"; - /* Note Widget Title */ "Note" = "注意"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "确定"; +/* confirm button title */ +"Okay" = "好的"; + /* No comment provided by engineer. */ "On" = "打开"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "选项"; +/* Or, used as a separator between Actions */ +"Or" = "或"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "密码"; /* Pin Lock */ "Passcodes did not match. Try again." = "密码不匹配。重试。"; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "密码"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "密码不能与电子邮件相同"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "密码中必须至少包含 %d个字符"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "密码不得包含制表符或换行符"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "密码不匹配"; - /* Number of changes pending to be sent */ "Pendings" = "待处理数"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "请先使用 Simplenote 应用程序登录您的 Simplenote 账户。"; +/* Encourages trying delete again */ +"Please try again" = "请重试"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "请升级至最新 iOS 版本以恢复购买"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "最近"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "已恢复注释内容 — "; + /* References section header on Info Card */ "Referenced In" = "引用于"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "恢复笔记"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "恢复购买"; - /* Title -> Review you account screen */ "Review Your Account" = "检查您的帐户"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "侧边栏"; -/* Title of button for logging in (must be short) */ -"Sign In" = "登录"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "注册"; -/* A short link to access the account login screen */ -"Sign in" = "登录"; - -/* A short link to access the account creation screen */ -"Sign up" = "注册"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "注销会删除所有未同步的备注。您可以通过登录 Web 应用程序以确认您已同步的备注。"; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote 支持者"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "必须配置 Simplenote 并登录以设置小组件"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "抱歉!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "排序方式:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "Spotlight 历史记录仍可能出现在搜索结果中,但注释已取消索引"; + /* Title for the response's Status Code */ "Status Code" = "状态码"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "支持您喜爱的笔记应用,帮助该产品在未来提供更多功能"; -/* No comment provided by engineer. */ -"Sustainer" = "支持者"; +/* Switch app icon */ +"Sustainer App Icon" = "Sustainer 应用程序图标"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "支持者订阅已恢复。 感谢您支持 Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "感谢您的继续支持"; -/* Error when address is in use */ -"That email is already being used" = "该电子邮件已被使用"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "您请求的验证码已过期。 请重新请求"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "您输入的代码不正确。 请重试"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "您输入的电子邮件已与 Simplenote 帐户关联。"; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "撤消"; -/* WebSocket not initialized */ -"Uninitialized" = "未初始化"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "未命名标签"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "访问 Web 应用程序"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "我们将通过电子邮件向您发送登录代码,您也可以"; + /* Generic error */ "We're having problems. Please try again soon." = "我们遇到了问题。请稍后重试。"; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "我们已向 {{EMAIL}} 发送代码。 此代码将在几分钟后失效。"; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "您觉得 Simplenote 怎么样?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "下次启动时将尝试恢复快捷方式内容"; + /* Number of words in the note */ "Words" = "字数"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "每年"; - /* Proceeds with the Empty Trash OP */ "Yes" = "是"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "您已成为支持者。 感谢您支持 Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "您的验证码已过期。 请重新请求"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "您输入的电子邮件地址无效。"; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "您的回收站为空"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "手动登录。"; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "标签:"; diff --git a/Simplenote/zh-Hant-TW.lproj/Localizable.strings b/Simplenote/zh-Hant-TW.lproj/Localizable.strings index f688d6846..451127cdf 100644 --- a/Simplenote/zh-Hant-TW.lproj/Localizable.strings +++ b/Simplenote/zh-Hant-TW.lproj/Localizable.strings @@ -1,15 +1,9 @@ -/* Translation-Revision-Date: 2022-11-22 09:54:03+0000 */ +/* Translation-Revision-Date: 2024-09-20 11:54:02+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/2.4.0-alpha */ /* Language: zh_TW */ /* No comment provided by engineer. */ -"%@ per Month" = "每月 %@"; - -/* Yearly Subscription Option. Please preserve the special marker! */ -"%@ per Year" = "每年 %@"; - -/* Number of found search results */ "%d Result" = "%d 個搜尋結果"; /* Number of found search results */ @@ -73,6 +67,7 @@ "About" = "關於"; /* Accept Action + Accept Message Label of accept button on alert dialog */ "Accept" = "接受"; @@ -99,9 +94,6 @@ Title: No filters applied */ "All Notes" = "所有筆記"; -/* A short description to access the account login screen */ -"Already have an account?" = "已經有帳號了?"; - /* Delete account confirmation instructions */ "An email has been sent to %@. Check your inbox and follow the instructions to confirm account deletion.\n\nYour account won't be deleted until we receive your confirmation." = "電子郵件已傳送至 %@。請檢查你的收件匣,並按照說明確認刪除帳號。\n\n在收到你的確認之前,我們不會刪除你的帳號"; @@ -123,9 +115,18 @@ /* Empty Trash Warning */ "Are you sure you want to empty the trash? This cannot be undone." = "確定要清空垃圾桶?此動作無法復原。"; +/* Data Fetch error message */ +"Attempt to fetch current note content failed. Please try again later." = "嘗試擷取目前備註內容失敗。 請稍後再試。"; + /* Data Fetch error message */ "Attempt to fetch entities failed. Please try again later." = "嘗試擷取實體失敗。 請稍後再試一次。"; +/* Data Fetch error message */ +"Attempt to fetch notes failed. Please try again later." = "嘗試擷取備註失敗。 請稍後再試。"; + +/* Data Fetch error message */ +"Attempt to fetch tags failed. Please try again later." = "嘗試擷取標籤失敗。 請稍後再試。"; + /* User Authenticated */ "Authenticated" = "已驗證"; @@ -141,17 +142,12 @@ /* Delete account confirmation alert message */ "By deleting the account for %@, all notes created with this account will be permanently deleted. This action is not reversible" = "若刪除 %@ 的帳號,此帳號建立的所有筆記將永久刪除。 此動作無法復原"; -/* Terms Button Text */ -"By signing up, you agree to our Terms of Service »" = "註冊即代表你同意我們的「服務條款」»"; - /* Cancel Action Cancel action button Cancel action for password alert Cancel action on share extension. - Cancel button for authentication Cancel button title Cancellation button of deletion confirmation message for a tag. - Dismisses the alert Dismissing an interface PinLock screen \"cancel\" button Verb, cancel an alert dialog */ @@ -187,8 +183,8 @@ /* Title on the PinLock screen asking to create a passcode */ "Choose a 4 digit passcode" = "選擇 4 位數的密碼"; -/* Sustainer Alert's Message */ -"Choose a plan and help unlock future features" = "選擇方案,協助日後解鎖更多功能"; +/* Code TextField Placeholder */ +"Code" = "程式碼"; /* Opens the Collaborate UI */ "Collaborate" = "協作"; @@ -205,8 +201,7 @@ /* Option to make the note list show only 1 line of text. The default is 3. */ "Condensed Note List" = "精簡版筆記清單"; -/* Confirm button -> Review you account screen - Hint displayed in the password confirmation field */ +/* Confirm button -> Review you account screen */ "Confirm" = "確認"; /* Title on the PinLock screen asking to confirm a passcode */ @@ -231,9 +226,21 @@ /* Error for bad email or password */ "Could not create an account with the provided email address and password." = "無法使用提供的電子郵件地址和密碼建立帳號。"; +/* Notifies user tag delete failed */ +"Could not delete tag" = "無法刪除標籤"; + +/* Note fetch error title */ +"Could not fetch Notes" = "無法擷取備註"; + +/* Tag fetch error title */ +"Could not fetch Tags" = "無法擷取標籤"; + /* Fetch error title */ "Could not fetch entities" = "無法擷取實體"; +/* note content fetch error title */ +"Could not fetch note content" = "無法擷取備註內容"; + /* Message displayed when login fails */ "Could not login with the provided email address and password." = "無法使用提供的電子郵件地址和密碼登入。"; @@ -326,9 +333,6 @@ /* Card title showing information about the note (metrics, references) */ "Document" = "內容"; -/* A short description to access the account creation screen */ -"Don't have an account?" = "還沒有帳號?"; - /* Dismisses the Note Information UI Dismisses the Note Options UI Done editing tags @@ -360,6 +364,15 @@ /* Number of objects enqueued for processing */ "Enqueued" = "加入佇列"; +/* LogIn Interface Title */ +"Enter Code" = "輸入驗證碼"; + +/* Enter Password fallback Action */ +"Enter password" = "輸入密碼"; + +/* Header for Login With Password. Please preserve the {{EMAIL}} substring */ +"Enter the password for the account {{EMAIL}}" = "輸入 {{EMAIL}} 帳號的密碼"; + /* Title on the PinLock screen asking to enter a passcode */ "Enter your passcode" = "輸入你的密碼"; @@ -367,12 +380,6 @@ Error Title */ "Error" = "錯誤"; -/* No comment provided by engineer. */ -"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you." = "錯誤:NSManagedObjectContext 的 persistentStoreCoordinator 必須為 nil。 Simperium 將為你處理 CoreData 連結。"; - -/* No comment provided by engineer. */ -"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type." = "錯誤:你必須使用「NSMainQueueConcurrencyType」並行類型來初始化內容。"; - /* Get Help Description Label */ "FAQ or contact us" = "常見問題集或與我們聯絡"; @@ -382,11 +389,8 @@ /* Accessibility hint used when previous versions of a note are being fetched */ "Fetching Version" = "正在擷取版本"; -/* Forgot password Button Text */ -"Forgot password? »" = "忘記密碼?»"; - /* Password Reset Action */ -"Forgotten password?" = "忘記密碼了嗎?"; +"Forgot your password?" = "忘記密碼?"; /* FAQ or contact us */ "Get Help" = "取得協助"; @@ -412,6 +416,12 @@ /* Rating view - initial - liked button */ "I like it" = "我喜歡"; +/* Option to add notes to spotlight search */ +"Index Notes in Spotlight" = "焦點中的索引備註"; + +/* Alert title letting user know their search index has been removed */ +"Index Removed" = "已移除索引"; + /* Note Information Button (metrics + references) */ "Information" = "資訊"; @@ -448,18 +458,27 @@ LogIn Interface Title */ "Log In" = "登入"; +/* LogIn Interface Title */ +"Log In with Password" = "使用密碼登入"; + /* Log out of the active account in the app */ "Log Out" = "登出"; /* Widget warning if user is logged out */ "Log in to see your notes" = "你需要登入才能查看筆記"; -/* Allows the user to SignIn using their WPCOM Account */ +/* Password fallback Action */ "Log in with WordPress.com" = "透過 WordPress.com 登入"; -/* Presents the regular Email signin flow */ +/* Sends the User an email with an Authentication Code */ "Log in with email" = "透過電子郵件登入"; +/* Header for Enter Password UI, when the user performed too many requests */ +"Log in with email failed, please enter your password" = "透過電子郵件地址登入失敗,請輸入你的密碼"; + +/* Message displayed when a login code is too short */ +"Login Code is too short" = "登入碼過短"; + /* Month and day date formatter */ "MMM d" = "MMM d"; @@ -490,9 +509,6 @@ /* Sort Mode: Modified Date, ascending */ "Modified: Oldest" = "修改時間:最舊"; -/* Monthly Subscription Option (Used when / if the price fails to load) */ -"Monthly" = "每月"; - /* Accessibility hint for trash selected notes button */ "Move selected notes to trash" = "將選取的筆記移至垃圾桶"; @@ -548,9 +564,6 @@ /* Message shown in note list when no notes are untagged */ "No untagged notes" = "沒有未標籤筆記"; -/* Error when you enter a bad email address */ -"Not a valid email address" = "電子郵件地址格式不正確"; - /* Note Widget Title */ "Note" = "備註"; @@ -579,6 +592,9 @@ Email unverified alert dismiss */ "Ok" = "確定"; +/* confirm button title */ +"Okay" = "確定"; + /* No comment provided by engineer. */ "On" = "開啟"; @@ -594,29 +610,27 @@ /* Note Options Title */ "Options" = "選項"; +/* Or, used as a separator between Actions */ +"Or" = "或者"; + /* A 4-digit code to lock the app when it is closed */ "Passcode" = "密碼"; /* Pin Lock */ "Passcodes did not match. Try again." = "密碼不相符。請再試一次。"; -/* Hint displayed in the password field - Password TextField Placeholder */ +/* Password TextField Placeholder */ "Password" = "密碼"; /* Message displayed when password is invalid (Signup) */ "Password cannot match email" = "密碼不能和電子郵件地址相同"; -/* Message displayed when password is too short. Please preserve the Percent D! - Message displayed when password is too short. The %%d is a placeholder for a numeral. Please preserve it! */ +/* Message displayed when password is too short. Please preserve the Percent D! */ "Password must contain at least %d characters" = "密碼必須包含至少 %d 個字元"; /* Message displayed when a password contains a disallowed character */ "Password must not contain tabs nor newlines" = "密碼不得包含定位字元或換行"; -/* Password Validation: Confirmation doesn't match */ -"Passwords do not match" = "密碼不符"; - /* Number of changes pending to be sent */ "Pendings" = "待確認"; @@ -636,6 +650,9 @@ /* Extension Missing Token Alert Title */ "Please log into your Simplenote account first by using the Simplenote app." = "請先使用 Simplenote 應用程式登入 Simplenote 帳號。"; +/* Encourages trying delete again */ +"Please try again" = "請再試一次"; + /* Upgrade Alert Message */ "Please upgrade to the latest iOS release to restore purchases" = "請升級至最新 iOS 版本以恢復購買"; @@ -678,6 +695,9 @@ /* Home screen quick action: Recent Note */ "Recent" = "最近的"; +/* Header to put on any files that need to be recovered */ +"Recovered Note Cotent - " = "已還原的備註內容 - "; + /* References section header on Info Card */ "Referenced In" = "參考位置"; @@ -715,9 +735,6 @@ Restore a note to a previous version */ "Restore Note" = "還原筆記"; -/* Manually Restores IAP Purchases */ -"Restore Purchases" = "恢復購買"; - /* Title -> Review you account screen */ "Review Your Account" = "檢閱你的帳號"; @@ -778,21 +795,11 @@ /* UI region to the left of the note list which shows all of a users tags */ "Sidebar" = "側選單"; -/* Title of button for logging in (must be short) */ -"Sign In" = "登入"; - -/* SignUp Action - Signup Action - SignUp Interface Title - Title of button to create a new account (must be short) */ +/* Signup Action + SignUp Action + SignUp Interface Title */ "Sign Up" = "註冊"; -/* A short link to access the account login screen */ -"Sign in" = "登入"; - -/* A short link to access the account creation screen */ -"Sign up" = "註冊"; - /* Alert message displayed when an account has unsynced notes */ "Signing out will delete any unsynced notes. You can verify your synced notes by signing in to the Web App." = "登出將會刪除任何尚未同步的備註。登入網頁應用程式即可驗證已同步的備註。"; @@ -800,8 +807,7 @@ Title of main share extension view */ "Simplenote" = "Simplenote"; -/* Restoration Successful Title - Sustainer Alert's Title */ +/* Restoration Successful Title */ "Simplenote Sustainer" = "Simplenote Sustainer"; /* Simplenote's Feedback Email Title */ @@ -810,7 +816,8 @@ /* Message displayed when app is not configured */ "Simplenote must be configured and logged in to setup widgets" = "你必須設定並登入 Simplenote 才能設定小工具"; -/* Authentication Error Alert Title */ +/* Authentication Error Alert Title + Email TextField Placeholder */ "Sorry!" = "很抱歉!"; /* Option to sort tags alphabetically. The default is by manual ordering. */ @@ -823,14 +830,17 @@ /* Sort By Title */ "Sort by:" = "排序依據:"; +/* Details that some results may still appear in searches on device */ +"Spotlight history may still appear in search results, but notes have be unindexed" = "焦點記錄可能仍會在搜尋結果中顯示,但備註已取消索引"; + /* Title for the response's Status Code */ "Status Code" = "狀態代碼"; /* Become a Sustainer Details */ "Support your favorite notes app to help unlock future features" = "支持你最愛的筆記應用程式,協助日後解鎖更多功能"; -/* No comment provided by engineer. */ -"Sustainer" = "Sustainer"; +/* Switch app icon */ +"Sustainer App Icon" = "Sustainer 應用程式圖示"; /* Restoration Successful Message */ "Sustainer subscription restored. Thank you for supporting Simplenote!" = "已恢復 Sustainer 訂購。 感謝你支持 Simplenote!"; @@ -865,8 +875,11 @@ /* Current Sustainer Details */ "Thank you for your continued support" = "感謝你持續支持"; -/* Error when address is in use */ -"That email is already being used" = "此電子信箱已被使用"; +/* Email TextField Placeholder */ +"The authentication code you've requested has expired. Please request a new one" = "你要求的驗證碼已過期。 請申請新的驗證碼"; + +/* Error message for Invalid Login Code */ +"The code you've entered is not correct. Please try again" = "你輸入的驗證碼不正確。 請再試一次"; /* Error when address is in use */ "The email you've entered is already associated with a Simplenote account." = "你輸入的電子郵件地址已與 Simplenote 帳號建立關聯。"; @@ -929,9 +942,6 @@ /* Undo action */ "Undo" = "復原"; -/* WebSocket not initialized */ -"Uninitialized" = "未初始化"; - /* Default title for an unnamed tag */ "Unnamed Tag" = "未命名標籤"; @@ -977,9 +987,15 @@ /* Visit app.simplenote.com in the browser */ "Visit Web App" = "前往網頁應用程式"; +/* Option to login with username and password *PREFIX*: printed in dark color */ +"We'll email you a code to log in, or you can" = "我們會透過電子郵件傳送登入代碼給你,你也可以"; + /* Generic error */ "We're having problems. Please try again soon." = "發生問題,請再試一次。"; +/* Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is! */ +"We've sent a code to {{EMAIL}}. The code will be valid for a few minutes." = "我們已傳送驗證碼到 {{EMAIL}}。 此驗證碼在幾分鐘內有效。"; + /* WebSocket Status */ "WebSocket" = "WebSocket"; @@ -993,12 +1009,12 @@ /* Rating view initial title */ "What do you think about Simplenote?" = "你覺得 Simplenote 如何?"; +/* Alerting users that we will attempt to restore lost content on next launch */ +"Will attempt to recover shortcut content on next launch" = "會嘗試在下一次推出時還原捷徑內容"; + /* Number of words in the note */ "Words" = "字"; -/* Yearly Subscription Option (Used when / if the price fails to load) */ -"Yearly" = "每年"; - /* Proceeds with the Empty Trash OP */ "Yes" = "是"; @@ -1017,6 +1033,9 @@ /* Restoration Successful Message */ "You're already a Sustainer. Thank you for supporting Simplenote!" = "你已經是 Sustainer。 感謝你支持 Simplenote!"; +/* Error message for Invalid Login Code */ +"Your authentication code has expired. Please request a new one" = "你的驗證碼已過期。 請申請新的驗證碼"; + /* Message displayed when email address is invalid */ "Your email address is not valid" = "你的電子郵件地址無效。"; @@ -1026,6 +1045,9 @@ /* Message shown in note list when no notes are in the trash */ "Your trash is empty" = "你的垃圾桶是空的"; +/* Option to login with username and password *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue */ +"log in manually." = "手動登入。"; + /* Search Operator for tags. Please preserve the semicolons when translating! */ "tag:" = "標籤:"; diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift new file mode 100644 index 000000000..4a9350882 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -0,0 +1,55 @@ +import Intents + +class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func resolveContent(for intent: AppendNoteIntent) async -> INStringResolutionResult { + guard let content = intent.content else { + return INStringResolutionResult.needsValue() + } + return INStringResolutionResult.success(with: content) + } + + func resolveNote(for intent: AppendNoteIntent) async -> IntentNoteResolutionResult { + IntentNoteResolutionResult.resolve(intent.note, in: coreDataWrapper) + } + + func provideNoteOptionsCollection(for intent: AppendNoteIntent) async throws -> INObjectCollection { + let intentNotes = try IntentNote.allNotes(in: coreDataWrapper) + return INObjectCollection(items: intentNotes) + } + + func handle(intent: AppendNoteIntent) async -> AppendNoteIntentResponse { + guard let identifier = intent.note?.identifier, + let content = intent.content, + let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier), + let token = KeychainManager.extensionToken else { + return AppendNoteIntentResponse(code: .failure, userActivity: nil) + } + + do { + let existingContent = try await downloadNoteContent(for: identifier, token: token) ?? String() + note.content = existingContent + "\n\(content)" + + try await uploadNoteContent(note, token: token) + return AppendNoteIntentResponse(code: .success, userActivity: nil) + } catch { + return handleFailure(with: error, content: content) + } + } + + private func downloadNoteContent(for identifier: String, token: String) async throws -> String? { + let downloader = Downloader(simperiumToken: token) + return try await downloader.getNoteContent(for: identifier) + } + + private func uploadNoteContent(_ note: Note, token: String) async throws { + let uploader = Uploader(simperiumToken: token) + _ = try await uploader.send(note) + } + + private func handleFailure(with error: Error, content: String) -> AppendNoteIntentResponse { + RecoveryArchiver().archiveContent(content) + return AppendNoteIntentResponse.failure(failureReason: "\(error.localizedDescription) - \(IntentsConstants.recoveryMessage)") + } +} diff --git a/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift new file mode 100644 index 000000000..7b526493d --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift @@ -0,0 +1,33 @@ +// +// CopyNoteContentIntentHandler.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/9/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class CopyNoteContentIntentHandler: NSObject, CopyNoteContentIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func provideNoteOptionsCollection(for intent: CopyNoteContentIntent) async throws -> INObjectCollection { + let intentNotes = try IntentNote.allNotes(in: coreDataWrapper) + return INObjectCollection(items: intentNotes) + } + + func handle(intent: CopyNoteContentIntent) async -> CopyNoteContentIntentResponse { + guard let note = intent.note else { + return CopyNoteContentIntentResponse(code: .unspecified, userActivity: nil) + } + + guard let identifier = note.identifier, + let content = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier)?.content else { + return CopyNoteContentIntentResponse(code: .failure, userActivity: nil) + } + + let response = CopyNoteContentIntentResponse(code: .success, userActivity: nil) + response.noteContent = content + return response + } +} diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift new file mode 100644 index 000000000..c36b20a72 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift @@ -0,0 +1,29 @@ +import Intents + +class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func handle(intent: CreateNewNoteIntent) async -> CreateNewNoteIntentResponse { + guard let content = intent.content, + let token = KeychainManager.extensionToken else { + return CreateNewNoteIntentResponse(code: .failure, userActivity: nil) + } + + do { + _ = try await Uploader(simperiumToken: token).send(note(with: content)) + return CreateNewNoteIntentResponse(code: .success, userActivity: nil) + } catch { + RecoveryArchiver().archiveContent(content) + return CreateNewNoteIntentResponse.failure(failureReason: "\(error.localizedDescription) - \(IntentsConstants.recoveryMessage)") + } + } + + private func note(with content: String) -> Note { + let note = Note(context: coreDataWrapper.context()) + note.creationDate = .now + note.modificationDate = .now + note.content = content + + return note + } +} diff --git a/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift new file mode 100644 index 000000000..a73c24e76 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift @@ -0,0 +1,42 @@ +// +// FindNoteIntentHandler.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/9/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class FindNoteIntentHandler: NSObject, FindNoteIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func resolveNote(for intent: FindNoteIntent) async -> IntentNoteResolutionResult { + // If the user has already selected a note return that note with success + if let selectedNote = intent.note { + return IntentNoteResolutionResult.success(with: selectedNote) + } + + guard let content = intent.content else { + return IntentNoteResolutionResult.needsValue() + } + + return IntentNoteResolutionResult.resolveIntentNote(for: content, in: coreDataWrapper) + } + + func provideNoteOptionsCollection(for intent: FindNoteIntent) async throws -> INObjectCollection { + let intentNotes = try IntentNote.allNotes(in: coreDataWrapper) + return INObjectCollection(items: intentNotes) + } + + func handle(intent: FindNoteIntent) async -> FindNoteIntentResponse { + guard let intentNote = intent.note else { + return FindNoteIntentResponse(code: .failure, userActivity: nil) + } + + let success = FindNoteIntentResponse(code: .success, userActivity: nil) + success.note = intentNote + + return success + } +} diff --git a/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift b/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift new file mode 100644 index 000000000..78eaf8aca --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift @@ -0,0 +1,40 @@ +// +// FindNoteWithTagIntentHandler.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/9/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class FindNoteWithTagIntentHandler: NSObject, FindNoteWithTagIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func resolveNote(for intent: FindNoteWithTagIntent) async -> IntentNoteResolutionResult { + if let selectedNote = intent.note { + return IntentNoteResolutionResult.success(with: selectedNote) + } + + guard let selectedTag = intent.tag else { + return IntentNoteResolutionResult.needsValue() + } + + return IntentNoteResolutionResult.resolveIntentNote(forTag: selectedTag, in: coreDataWrapper) + } + + func provideTagOptionsCollection(for intent: FindNoteWithTagIntent) async throws -> INObjectCollection { + let tags = try IntentTag.allTags(in: coreDataWrapper) + return INObjectCollection(items: tags) + } + + func handle(intent: FindNoteWithTagIntent) async -> FindNoteWithTagIntentResponse { + guard let note = intent.note else { + return FindNoteWithTagIntentResponse(code: .failure, userActivity: nil) + } + + let response = FindNoteWithTagIntentResponse(code: .success, userActivity: nil) + response.note = note + return response + } +} diff --git a/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift b/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift new file mode 100644 index 000000000..99c95b427 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift @@ -0,0 +1,45 @@ +// +// ListWidgetIntentHandler.swift +// Simplenote +// +// Created by Charlie Scheer on 5/2/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class ListWidgetIntentHandler: NSObject, ListWidgetIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func provideTagOptionsCollection(for intent: ListWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + guard WidgetDefaults.shared.loggedIn else { + completion(nil, WidgetError.appConfigurationError) + return + } + + guard let tags = coreDataWrapper.resultsController()?.tags() else { + completion(nil, WidgetError.fetchError) + return + } + + // Return collection to intents + let collection = tagNoteInObjectCollection(from: tags) + completion(collection, nil) + } + + private func tagNoteInObjectCollection(from tags: [Tag]) -> INObjectCollection { + var items = [WidgetTag(kind: .allNotes)] + + tags.forEach { tag in + let tag = WidgetTag(kind: .tag, name: tag.name) + tag.kind = .tag + items.append(tag) + } + + return INObjectCollection(items: items) + } + + func defaultTag(for intent: ListWidgetIntent) -> WidgetTag? { + WidgetTag(kind: .allNotes) + } +} diff --git a/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift b/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift new file mode 100644 index 000000000..0d9d34f8e --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift @@ -0,0 +1,44 @@ +// +// NoteWidgetIntentHandler.swift +// Simplenote +// +// Created by Charlie Scheer on 5/2/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class NoteWidgetIntentHandler: NSObject, NoteWidgetIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func provideNoteOptionsCollection(for intent: NoteWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + guard WidgetDefaults.shared.loggedIn else { + completion(nil, WidgetError.appConfigurationError) + return + } + + guard let notes = coreDataWrapper.resultsController()?.notes() else { + completion(nil, WidgetError.fetchError) + return + } + + let collection = widgetNoteInObjectCollection(from: notes) + completion(collection, nil) + } + + private func widgetNoteInObjectCollection(from notes: [Note]) -> INObjectCollection { + let widgetNotes = notes.map({ note in + WidgetNote(identifier: note.simperiumKey, display: note.title) + }) + return INObjectCollection(items: widgetNotes) + } + + func defaultNote(for intent: NoteWidgetIntent) -> WidgetNote? { + guard WidgetDefaults.shared.loggedIn, + let note = coreDataWrapper.resultsController()?.firstNote() else { + return nil + } + + return WidgetNote(identifier: note.simperiumKey, display: note.title) + } +} diff --git a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift new file mode 100644 index 000000000..00bf44ac7 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift @@ -0,0 +1,15 @@ +// +// OpenNewNoteIntentHandler.swift +// Simplenote +// +// Created by Charlie Scheer on 5/2/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class OpenNewNoteIntentHandler: NSObject, OpenNewNoteIntentHandling { + func handle(intent: OpenNewNoteIntent) async -> OpenNewNoteIntentResponse { + OpenNewNoteIntentResponse(code: .continueInApp, userActivity: nil) + } +} diff --git a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift new file mode 100644 index 000000000..1fec7c593 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift @@ -0,0 +1,31 @@ +// +// OpenNoteIntentHandler.swift +// Simplenote +// +// Created by Charlie Scheer on 5/2/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class OpenNoteIntentHandler: NSObject, OpenNoteIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func resolveNote(for intent: OpenNoteIntent) async -> IntentNoteResolutionResult { + IntentNoteResolutionResult.resolve(intent.note, in: coreDataWrapper) + } + + func provideNoteOptionsCollection(for intent: OpenNoteIntent) async throws -> INObjectCollection { + let intentNotes = try IntentNote.allNotes(in: coreDataWrapper) + return INObjectCollection(items: intentNotes) + } + + func handle(intent: OpenNoteIntent) async -> OpenNoteIntentResponse { + guard let identifier = intent.note?.identifier else { + return OpenNoteIntentResponse(code: .failure, userActivity: nil) + } + let activity = NSUserActivity(activityType: ActivityType.openNoteShortcut.rawValue) + activity.userInfo = [IntentsConstants.noteIdentifierKey: identifier] + return OpenNoteIntentResponse(code: .continueInApp, userActivity: activity) + } +} diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index d62dd97d0..253026efc 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -2,76 +2,29 @@ import Intents import CoreData class IntentHandler: INExtension { - let coreDataWrapper = WidgetCoreDataWrapper() - override func handler(for intent: INIntent) -> Any { - return self - } -} - -extension IntentHandler: NoteWidgetIntentHandling { - func provideNoteOptionsCollection(for intent: NoteWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { - guard WidgetDefaults.shared.loggedIn else { - completion(nil, WidgetError.appConfigurationError) - return - } - - guard let notes = coreDataWrapper.resultsController()?.notes() else { - completion(nil, WidgetError.fetchError) - return - } - - let collection = widgetNoteInObjectCollection(from: notes) - completion(collection, nil) - } - - private func widgetNoteInObjectCollection(from notes: [Note]) -> INObjectCollection { - let widgetNotes = notes.map({ note in - WidgetNote(identifier: note.simperiumKey, display: note.title) - }) - return INObjectCollection(items: widgetNotes) - } - - func defaultNote(for intent: NoteWidgetIntent) -> WidgetNote? { - guard WidgetDefaults.shared.loggedIn, - let note = coreDataWrapper.resultsController()?.firstNote() else { - return nil - } - return WidgetNote(identifier: note.simperiumKey, display: note.title) - } -} - -extension IntentHandler: ListWidgetIntentHandling { - func provideTagOptionsCollection(for intent: ListWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { - guard WidgetDefaults.shared.loggedIn else { - completion(nil, WidgetError.appConfigurationError) - return + switch intent { + case is NoteWidgetIntent: + return NoteWidgetIntentHandler() + case is ListWidgetIntent: + return ListWidgetIntentHandler() + case is OpenNewNoteIntent: + return OpenNewNoteIntentHandler() + case is OpenNoteIntent: + return OpenNoteIntentHandler() + case is AppendNoteIntent: + return AppendNoteIntentHandler() + case is CreateNewNoteIntent: + return CreateNewNoteIntentHandler() + case is FindNoteIntent: + return FindNoteIntentHandler() + case is CopyNoteContentIntent: + return CopyNoteContentIntentHandler() + case is FindNoteWithTagIntent: + return FindNoteWithTagIntentHandler() + default: + return self } - - guard let tags = coreDataWrapper.resultsController()?.tags() else { - completion(nil, WidgetError.fetchError) - return - } - - // Return collection to intents - let collection = tagNoteInObjectCollection(from: tags) - completion(collection, nil) - } - - private func tagNoteInObjectCollection(from tags: [Tag]) -> INObjectCollection { - var items = [WidgetTag(kind: .allNotes)] - - tags.forEach { tag in - let tag = WidgetTag(kind: .tag, name: tag.name) - tag.kind = .tag - items.append(tag) - } - - return INObjectCollection(items: items) - } - - func defaultTag(for intent: ListWidgetIntent) -> WidgetTag? { - WidgetTag(kind: .allNotes) } } diff --git a/SimplenoteIntents/IntentsConstants.swift b/SimplenoteIntents/IntentsConstants.swift new file mode 100644 index 000000000..f91def3a8 --- /dev/null +++ b/SimplenoteIntents/IntentsConstants.swift @@ -0,0 +1,15 @@ +// +// IntentsConstants.swift +// Simplenote +// +// Created by Charlie Scheer on 5/2/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation + +struct IntentsConstants { + static let noteIdentifierKey = "OpenNoteIntentHandlerIdentifierKey" + + static let recoveryMessage = NSLocalizedString("Will attempt to recover shortcut content on next launch", comment: "Alerting users that we will attempt to restore lost content on next launch") +} diff --git a/SimplenoteIntents/IntentsError.swift b/SimplenoteIntents/IntentsError.swift new file mode 100644 index 000000000..821b7edbc --- /dev/null +++ b/SimplenoteIntents/IntentsError.swift @@ -0,0 +1,32 @@ +// +// IntentsError.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/3/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation + +enum IntentsError: Error { + case couldNotFetchNotes + case couldNotFetchTags + + var title: String { + switch self { + case .couldNotFetchNotes: + return NSLocalizedString("Could not fetch Notes", comment: "Note fetch error title") + case .couldNotFetchTags: + return NSLocalizedString("Could not fetch Tags", comment: "Tag fetch error title") + } + } + + var message: String { + switch self { + case .couldNotFetchNotes: + return NSLocalizedString("Attempt to fetch notes failed. Please try again later.", comment: "Data Fetch error message") + case .couldNotFetchTags: + return NSLocalizedString("Attempt to fetch tags failed. Please try again later.", comment: "Data Fetch error message") + } + } +} diff --git a/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift new file mode 100644 index 000000000..ddbca72df --- /dev/null +++ b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift @@ -0,0 +1,71 @@ +// +// IntentNote+Helpers.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/8/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +extension IntentNoteResolutionResult { + static func resolve(_ intentNote: IntentNote?, in coreDataWrapper: ExtensionCoreDataWrapper) -> IntentNoteResolutionResult { + guard let intentNote = intentNote, + let identifier = intentNote.identifier else { + return IntentNoteResolutionResult.needsValue() + } + + guard coreDataWrapper.resultsController()?.noteExists(forSimperiumKey: identifier) == true else { + return IntentNoteResolutionResult.unsupported() + } + + return IntentNoteResolutionResult.success(with: intentNote) + } + + static func resolveIntentNote(for content: String, in coreDataWrapper: ExtensionCoreDataWrapper) -> IntentNoteResolutionResult { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + return IntentNoteResolutionResult.unsupported() + } + let filteredNotes = notes.filter({ $0.content?.contains(content) == true }) + let intentNotes = IntentNote.makeIntentNotes(from: filteredNotes) + + return resolve(intentNotes) + } + + static func resolveIntentNote(forTag tag: IntentTag, in coreDataWrapper: ExtensionCoreDataWrapper) -> IntentNoteResolutionResult { + guard let notesForTag = coreDataWrapper.resultsController()?.notes(filteredBy: .tag(tag.displayString)) else { + return IntentNoteResolutionResult.unsupported() + } + + let intentNotes = IntentNote.makeIntentNotes(from: notesForTag) + + return resolve(intentNotes) + } + + private static func resolve(_ intentNotes: [IntentNote]) -> IntentNoteResolutionResult { + guard intentNotes.isEmpty == false else { + return IntentNoteResolutionResult.unsupported() + } + + if intentNotes.count == 1, + let intentNote = intentNotes.first { + return IntentNoteResolutionResult.success(with: intentNote) + } + + return IntentNoteResolutionResult.disambiguation(with: intentNotes) + } +} + +extension IntentNote { + static func allNotes(in coreDataWrapper: ExtensionCoreDataWrapper) throws -> [IntentNote] { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + throw IntentsError.couldNotFetchNotes + } + + return makeIntentNotes(from: notes) + } + + static func makeIntentNotes(from notes: [Note]) -> [IntentNote] { + notes.map({ IntentNote(identifier: $0.simperiumKey, display: $0.title) }) + } +} diff --git a/SimplenoteIntents/ResolutionResults/IntentTag+Helpers.swift b/SimplenoteIntents/ResolutionResults/IntentTag+Helpers.swift new file mode 100644 index 000000000..a11a1226e --- /dev/null +++ b/SimplenoteIntents/ResolutionResults/IntentTag+Helpers.swift @@ -0,0 +1,19 @@ +// +// IntentTag+Helpers.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/9/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +extension IntentTag { + static func allTags(in coreDataWrapper: ExtensionCoreDataWrapper) throws -> [IntentTag] { + guard let tags = coreDataWrapper.resultsController()?.tags() else { + throw IntentsError.couldNotFetchTags + } + + return tags.map({ IntentTag(identifier: $0.simperiumKey, display: $0.name ?? String()) }) + } +} diff --git a/SimplenoteIntents/Simplenote-Intents-Bridging-Header.h b/SimplenoteIntents/Simplenote-Intents-Bridging-Header.h index 401ca562b..4df3baf3c 100644 --- a/SimplenoteIntents/Simplenote-Intents-Bridging-Header.h +++ b/SimplenoteIntents/Simplenote-Intents-Bridging-Header.h @@ -4,3 +4,4 @@ #import "Foundation/Foundation.h" +#import "SPConstants.h" diff --git a/SimplenoteIntents/SimplenoteIntentsRelease.entitlements b/SimplenoteIntents/SimplenoteIntentsRelease.entitlements new file mode 100644 index 000000000..084553593 --- /dev/null +++ b/SimplenoteIntents/SimplenoteIntentsRelease.entitlements @@ -0,0 +1,19 @@ + + + + + com.apple.security.application-groups + + group.com.codality.NotationalFlow + + keychain-access-groups + + $(AppIdentifierPrefix)com.codality.NotationalFlow + $(AppIdentifierPrefix)4ESDVWK654.com.codality.NotationalFlow + + previous-application-identifiers + + 4ESDVWK654.com.codality.NotationalFlow + + + diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index f632dc379..4d033e2a1 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -30,8 +30,15 @@ IntentsSupported + AppendNoteIntent + CopyNoteContentIntent + CreateNewNoteIntent + FindNoteIntent + FindNoteWithTagIntent ListWidgetIntent NoteWidgetIntent + OpenNewNoteIntent + OpenNoteIntent NSExtensionPointIdentifier diff --git a/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..321483656 --- /dev/null +++ b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy @@ -0,0 +1,80 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + + diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Debug.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Debug.entitlements index 5de89cd5d..ee3f29675 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Debug.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Debug.entitlements @@ -6,5 +6,9 @@ group.com.codality.NotationalFlow.Development + keychain-access-groups + + $(AppIdentifierPrefix)com.codality.NotationalFlow.Development + diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionAlpha.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionAlpha.entitlements index 402ef9d34..2483a1da9 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionAlpha.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionAlpha.entitlements @@ -6,5 +6,9 @@ group.com.codality.NotationalFlow.Alpha + keychain-access-groups + + $(AppIdentifierPrefix)com.codality.NotationalFlow.Alpha + diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements index bd8e7994c..f69540697 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements @@ -6,5 +6,9 @@ group.com.codality.NotationalFlow.Internal + keychain-access-groups + + $(AppIdentifierPrefix)com.codality.NotationalFlow.Internal + diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements index b11d6f107..084553593 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements @@ -6,6 +6,11 @@ group.com.codality.NotationalFlow + keychain-access-groups + + $(AppIdentifierPrefix)com.codality.NotationalFlow + $(AppIdentifierPrefix)4ESDVWK654.com.codality.NotationalFlow + previous-application-identifiers 4ESDVWK654.com.codality.NotationalFlow diff --git a/SimplenoteIntents/Tools/RecoveryArchiver.swift b/SimplenoteIntents/Tools/RecoveryArchiver.swift new file mode 100644 index 000000000..17f516c9c --- /dev/null +++ b/SimplenoteIntents/Tools/RecoveryArchiver.swift @@ -0,0 +1,36 @@ +import Foundation +import UniformTypeIdentifiers + +public class RecoveryArchiver { + private let fileManager: FileManager + + public init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + // MARK: Archive + // + public func archiveContent(_ content: String) { + guard let fileURL = url(for: UUID().uuidString) else { + return + } + + guard let data = content.data(using: .utf8) else { + return + } + try? data.write(to: fileURL) + } + + private func url(for identifier: String) -> URL? { + guard let recoveryDirURL = fileManager.recoveryDirectoryURL() else { + return nil + } + let formattedID = identifier.replacingOccurrences(of: "/", with: "-") + let fileName = "\(Constants.recoveredContent)-\(formattedID)" + return recoveryDirURL.appendingPathComponent(fileName, conformingTo: UTType.json) + } +} + +private struct Constants { + static let recoveredContent = "recoveredContent" +} diff --git a/SimplenoteShare/Extensions/NSExtensionContext+Simplenote.swift b/SimplenoteShare/Extensions/NSExtensionContext+Simplenote.swift index 6aaaa4b5e..b02df6323 100644 --- a/SimplenoteShare/Extensions/NSExtensionContext+Simplenote.swift +++ b/SimplenoteShare/Extensions/NSExtensionContext+Simplenote.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - NSExtensionContext's Simplenote Methods // extension NSExtensionContext { diff --git a/SimplenoteShare/Extensions/NSURLSessionConfiguration+Extensions.swift b/SimplenoteShare/Extensions/NSURLSessionConfiguration+Extensions.swift index d633a5e17..55e8b0dac 100644 --- a/SimplenoteShare/Extensions/NSURLSessionConfiguration+Extensions.swift +++ b/SimplenoteShare/Extensions/NSURLSessionConfiguration+Extensions.swift @@ -1,6 +1,5 @@ import Foundation - /// Encapsulates NSURLSessionConfiguration Helpers /// extension URLSessionConfiguration { diff --git a/SimplenoteShare/Extractors/Extractor.swift b/SimplenoteShare/Extractors/Extractor.swift index 5cd70163d..74dac5291 100644 --- a/SimplenoteShare/Extractors/Extractor.swift +++ b/SimplenoteShare/Extractors/Extractor.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Extractor // protocol Extractor { diff --git a/SimplenoteShare/Extractors/Extractors.swift b/SimplenoteShare/Extractors/Extractors.swift index 63073ddf7..a72856782 100644 --- a/SimplenoteShare/Extractors/Extractors.swift +++ b/SimplenoteShare/Extractors/Extractors.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Extractors: Convenience Struct that manages access to all of the known Extractors. // struct Extractors { diff --git a/SimplenoteShare/Extractors/PlainTextExtractor.swift b/SimplenoteShare/Extractors/PlainTextExtractor.swift index 8d4f7e5a4..b731d7dbf 100644 --- a/SimplenoteShare/Extractors/PlainTextExtractor.swift +++ b/SimplenoteShare/Extractors/PlainTextExtractor.swift @@ -2,7 +2,6 @@ import Foundation import MobileCoreServices import UniformTypeIdentifiers - // MARK: - PlainTextExtractor // struct PlainTextExtractor: Extractor { diff --git a/SimplenoteShare/Extractors/URLExtractor.swift b/SimplenoteShare/Extractors/URLExtractor.swift index 8b75cb108..b561f4548 100644 --- a/SimplenoteShare/Extractors/URLExtractor.swift +++ b/SimplenoteShare/Extractors/URLExtractor.swift @@ -3,7 +3,6 @@ import MobileCoreServices import ZIPFoundation import UniformTypeIdentifiers - // MARK: - URLExtractor // struct URLExtractor: Extractor { @@ -38,7 +37,6 @@ struct URLExtractor: Extractor { } } - // MARK: - Loading Notes from a file! // private extension URLExtractor { @@ -109,7 +107,6 @@ private extension URLExtractor { } } - // MARK: - Fallback: Handling external URL(s) // private extension URLExtractor { @@ -131,7 +128,6 @@ private extension URLExtractor { } } - // MARK: - Path Extensions // private enum PathExtension: String { diff --git a/SimplenoteShare/Presentation/ExtensionPresentationAnimator.swift b/SimplenoteShare/Presentation/ExtensionPresentationAnimator.swift index 56726ac36..3542487c9 100644 --- a/SimplenoteShare/Presentation/ExtensionPresentationAnimator.swift +++ b/SimplenoteShare/Presentation/ExtensionPresentationAnimator.swift @@ -1,6 +1,5 @@ import UIKit - /// Direction to transition from/to. /// /// - left: Enter/leave screen via the left edge. @@ -15,7 +14,6 @@ enum Direction { case bottom } - /// Animator that animates the presented View Controller from/to a specific `Direction`. /// final class ExtensionPresentationAnimator: NSObject { @@ -29,7 +27,6 @@ final class ExtensionPresentationAnimator: NSObject { } } - // MARK: - UIViewControllerAnimatedTransitioning Conformance // extension ExtensionPresentationAnimator: UIViewControllerAnimatedTransitioning { @@ -72,7 +69,6 @@ extension ExtensionPresentationAnimator: UIViewControllerAnimatedTransitioning { } } - // MARK: - Constants // private struct Constants { diff --git a/SimplenoteShare/Presentation/ExtensionPresentationController.swift b/SimplenoteShare/Presentation/ExtensionPresentationController.swift index 69326c811..3017dfcc4 100644 --- a/SimplenoteShare/Presentation/ExtensionPresentationController.swift +++ b/SimplenoteShare/Presentation/ExtensionPresentationController.swift @@ -1,6 +1,5 @@ import UIKit - /// Allows certain presented view controllers to request themselves to be /// presented at full size instead of inset within the container. /// @@ -8,7 +7,6 @@ protocol ExtensionPresentationTarget { var shouldFillContentContainer: Bool { get } } - final class ExtensionPresentationController: UIPresentationController { // MARK: - Private Properties @@ -46,7 +44,6 @@ final class ExtensionPresentationController: UIPresentationController { removeKeyboardObservers(with: tokens) } - // MARK: Presentation Controller Overrides override var frameOfPresentedViewInContainerView: CGRect { @@ -103,7 +100,6 @@ final class ExtensionPresentationController: UIPresentationController { } } - // MARK: - KeyboardObservable Conformance // extension ExtensionPresentationController: KeyboardObservable { @@ -148,7 +144,6 @@ extension ExtensionPresentationController: KeyboardObservable { } } - // MARK: - Constants // private extension ExtensionPresentationController { diff --git a/SimplenoteShare/Presentation/ExtensionTransitioningManager.swift b/SimplenoteShare/Presentation/ExtensionTransitioningManager.swift index 439110485..eff4f821c 100644 --- a/SimplenoteShare/Presentation/ExtensionTransitioningManager.swift +++ b/SimplenoteShare/Presentation/ExtensionTransitioningManager.swift @@ -1,12 +1,10 @@ import UIKit - final class ExtensionTransitioningManager: NSObject { var presentDirection = Direction.bottom var dismissDirection = Direction.bottom } - // MARK: - UIViewControllerTransitioningDelegate Conformance // extension ExtensionTransitioningManager: UIViewControllerTransitioningDelegate { diff --git a/SimplenoteShare/Simperium/Downloader.swift b/SimplenoteShare/Simperium/Downloader.swift new file mode 100644 index 000000000..d86fd86c5 --- /dev/null +++ b/SimplenoteShare/Simperium/Downloader.swift @@ -0,0 +1,74 @@ +// +// Downloader.swift +// SimplenoteShare +// +// Created by Charlie Scheer on 5/13/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation + +enum DownloaderError: Error { + case couldNotFetchNoteContent + + var title: String { + switch self { + case .couldNotFetchNoteContent: + return NSLocalizedString("Could not fetch note content", comment: "note content fetch error title") + } + } + + var message: String { + switch self { + case .couldNotFetchNoteContent: + return NSLocalizedString("Attempt to fetch current note content failed. Please try again later.", comment: "Data Fetch error message") + } + } +} + +class Downloader: NSObject { + + /// Simperium's Token + /// + private let token: String + + /// Designated Initializer + /// + init(simperiumToken: String) { + token = simperiumToken + } + + func getNoteContent(for simperiumKey: String) async throws -> String? { + let endpoint = String(format: "%@/%@/%@/i/%@", kSimperiumBaseURL, SPCredentials.simperiumAppID, Settings.bucketName, simperiumKey) + let targetURL = URL(string: endpoint.lowercased())! + + // Request + var request = URLRequest(url: targetURL) + request.httpMethod = Settings.httpMethodGet + request.setValue(token, forHTTPHeaderField: Settings.authHeader) + + let sc = URLSessionConfiguration.default + let session = Foundation.URLSession(configuration: sc, delegate: nil, delegateQueue: .main) + + let downloadedData = try await session.data(for: request) + + return try extractNoteContent(from: downloadedData.0) + } + + func extractNoteContent(from data: Data) throws -> String? { + let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] + guard let content = jsonObject?["content"] as? String else { + throw DownloaderError.couldNotFetchNoteContent + } + + return content + } +} + +// MARK: - Settings +// +private struct Settings { + static let authHeader = "X-Simperium-Token" + static let bucketName = "note" + static let httpMethodGet = "GET" +} diff --git a/SimplenoteShare/Simperium/Note.swift b/SimplenoteShare/Simperium/Note.swift index 3d9956c3a..e585974ae 100644 --- a/SimplenoteShare/Simperium/Note.swift +++ b/SimplenoteShare/Simperium/Note.swift @@ -1,6 +1,5 @@ import Foundation - /// This class encapsulates the Note entity. It's the main project (non core data) counterpart. /// class Note { @@ -27,7 +26,6 @@ class Note { /// let modificationDate = Date() - /// Designated Initializer /// init(content: String, markdown: Bool = false) { diff --git a/SimplenoteShare/Simperium/Uploader.swift b/SimplenoteShare/Simperium/Uploader.swift index a12f22c1f..dc57082a6 100644 --- a/SimplenoteShare/Simperium/Uploader.swift +++ b/SimplenoteShare/Simperium/Uploader.swift @@ -1,6 +1,5 @@ import Foundation - /// The purpose of this class is to encapsulate NSURLSession's interaction code, required to upload /// a note to Simperium's REST endpoint. /// @@ -16,29 +15,40 @@ class Uploader: NSObject { token = simperiumToken } - // MARK: - Public Methods func send(_ note: Note) { + let request = requestToSend(note) + + // Task! + let sc = URLSessionConfiguration.backgroundSessionConfigurationWithRandomizedIdentifier() + + let session = Foundation.URLSession(configuration: sc, delegate: self, delegateQueue: .main) + let task = session.downloadTask(with: request) + task.resume() + } + + func send(_ note: Note) async throws -> (URL, URLResponse) { + let request = requestToSend(note) + + let session = Foundation.URLSession(configuration: .default) + return try await session.download(for: request) + } + + private func requestToSend(_ note: Note) -> URLRequest { // Build the targetURL let endpoint = String(format: "%@/%@/%@/i/%@", kSimperiumBaseURL, SPCredentials.simperiumAppID, Settings.bucketName, note.simperiumKey) let targetURL = URL(string: endpoint.lowercased())! // Request var request = URLRequest(url: targetURL) - request.httpMethod = Settings.httpMethod + request.httpMethod = Settings.httpMethodPost request.httpBody = note.toJsonData() request.setValue(token, forHTTPHeaderField: Settings.authHeader) - // Task! - let sc = URLSessionConfiguration.backgroundSessionConfigurationWithRandomizedIdentifier() - - let session = Foundation.URLSession(configuration: sc, delegate: self, delegateQueue: .main) - let task = session.downloadTask(with: request) - task.resume() + return request } } - // MARK: - URLSessionDelegate // extension Uploader: URLSessionDelegate { @@ -54,7 +64,6 @@ extension Uploader: URLSessionDelegate { } } - // MARK: - URLSessionTaskDelegate // extension Uploader: URLSessionTaskDelegate { @@ -65,11 +74,10 @@ extension Uploader: URLSessionTaskDelegate { } } - // MARK: - Settings // private struct Settings { static let authHeader = "X-Simperium-Token" static let bucketName = "note" - static let httpMethod = "POST" + static let httpMethodPost = "POST" } diff --git a/SimplenoteShare/Themes/SPUserInterface.swift b/SimplenoteShare/Themes/SPUserInterface.swift index 8ac4f7fae..0cce96085 100644 --- a/SimplenoteShare/Themes/SPUserInterface.swift +++ b/SimplenoteShare/Themes/SPUserInterface.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - Simplenote's Theme // @objc diff --git a/SimplenoteShare/ViewControllers/SharePresentationController.swift b/SimplenoteShare/ViewControllers/SharePresentationController.swift index 061cfea2c..8612aa04c 100644 --- a/SimplenoteShare/ViewControllers/SharePresentationController.swift +++ b/SimplenoteShare/ViewControllers/SharePresentationController.swift @@ -1,6 +1,5 @@ import UIKit - class SharePresentationController: UIViewController { private let extensionTransitioningManager: ExtensionTransitioningManager = { @@ -25,7 +24,6 @@ class SharePresentationController: UIViewController { } } - // MARK: - Private Helpers // private extension SharePresentationController { @@ -44,7 +42,6 @@ private extension SharePresentationController { } } - // MARK: - Appearance Helpers // private extension SharePresentationController { diff --git a/SimplenoteShare/ViewControllers/ShareViewController.swift b/SimplenoteShare/ViewControllers/ShareViewController.swift index da1c26e4e..f770d4845 100644 --- a/SimplenoteShare/ViewControllers/ShareViewController.swift +++ b/SimplenoteShare/ViewControllers/ShareViewController.swift @@ -1,7 +1,6 @@ import UIKit import Social - typealias CompletionBlock = () -> Void /// Main VC for Simplenote's Share Extension @@ -12,7 +11,6 @@ class ShareViewController: UIViewController { /// @objc var dismissalCompletionBlock: CompletionBlock? - // MARK: Private Properties @IBOutlet private weak var textView: UITextView! @@ -68,7 +66,6 @@ class ShareViewController: UIViewController { fatalError("init(coder:) has not been implemented") } - // MARK: UIViewController Lifecycle override func viewDidLoad() { @@ -89,7 +86,6 @@ class ShareViewController: UIViewController { } } - // MARK: - Actions // private extension ShareViewController { @@ -134,7 +130,6 @@ private extension ShareViewController { } } - // MARK: - Token Validation // private extension ShareViewController { @@ -166,7 +161,6 @@ private extension ShareViewController { } } - // MARK: - Configuration // private extension ShareViewController { @@ -194,7 +188,6 @@ private extension ShareViewController { } } - // MARK: - Constants // private struct Constants { diff --git a/SimplenoteTests/AuthenticationValidatorTests.swift b/SimplenoteTests/AuthenticationValidatorTests.swift index 189197e44..6f986827d 100644 --- a/SimplenoteTests/AuthenticationValidatorTests.swift +++ b/SimplenoteTests/AuthenticationValidatorTests.swift @@ -1,8 +1,6 @@ import XCTest -import XCTest @testable import Simplenote - // MARK: - AuthenticationValidator Tests // class AuthenticationValidatorTests: XCTestCase { @@ -11,7 +9,6 @@ class AuthenticationValidatorTests: XCTestCase { /// let validator = AuthenticationValidator() - /// Verifies that `performUsernameValidation` returns `true` when the input string is valid /// func testPerformUsernameValidationReturnsTrueWheneverInputEmailIsValid() { diff --git a/SimplenoteTests/CGRectSimplenoteTests.swift b/SimplenoteTests/CGRectSimplenoteTests.swift index 33d1cb6e2..edd4dc319 100644 --- a/SimplenoteTests/CGRectSimplenoteTests.swift +++ b/SimplenoteTests/CGRectSimplenoteTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - CGRect Tests // class CGRectSimplenoteTests: XCTestCase { diff --git a/SimplenoteTests/Constants.swift b/SimplenoteTests/Constants.swift index 3a37c8b76..a9683ae5c 100644 --- a/SimplenoteTests/Constants.swift +++ b/SimplenoteTests/Constants.swift @@ -1,6 +1,5 @@ import Foundation - /// Unit Tests Constants /// struct Constants { diff --git a/SimplenoteTests/EmailVerificationTests.swift b/SimplenoteTests/EmailVerificationTests.swift index 601a109ef..2b992dfae 100644 --- a/SimplenoteTests/EmailVerificationTests.swift +++ b/SimplenoteTests/EmailVerificationTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - EmailVerificationTests // class EmailVerificationTests: XCTestCase { diff --git a/SimplenoteTests/InterlinkResultsControllerTests.swift b/SimplenoteTests/InterlinkResultsControllerTests.swift index d93a3ebb7..9894ddadd 100644 --- a/SimplenoteTests/InterlinkResultsControllerTests.swift +++ b/SimplenoteTests/InterlinkResultsControllerTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - InterlinkResultsController Tests // class InterlinkResultsControllerTests: XCTestCase { @@ -14,7 +13,6 @@ class InterlinkResultsControllerTests: XCTestCase { /// private var resultsController: InterlinkResultsController! - // MARK: - Overridden Methods override func setUp() { @@ -27,7 +25,6 @@ class InterlinkResultsControllerTests: XCTestCase { storage.reset() } - // MARK: - Tests: Filtering /// Verifies that only Notes with matching keywords in their title are returned by `searchNotes:byTitleKeyword:` @@ -107,7 +104,6 @@ class InterlinkResultsControllerTests: XCTestCase { } } - // MARK: - Private // private extension InterlinkResultsControllerTests { @@ -132,7 +128,6 @@ private extension InterlinkResultsControllerTests { } } - // MARK: - Constants // private enum Settings { diff --git a/SimplenoteTests/MockupStorage+Sample.swift b/SimplenoteTests/MockupStorage+Sample.swift index 925994a91..c4243ff19 100644 --- a/SimplenoteTests/MockupStorage+Sample.swift +++ b/SimplenoteTests/MockupStorage+Sample.swift @@ -1,7 +1,6 @@ import Foundation @testable import Simplenote - // MARK: - MockupStorage Sample Entity Insertion Methods // extension MockupStorageManager { diff --git a/SimplenoteTests/MockupStorage.swift b/SimplenoteTests/MockupStorage.swift index 83aab212f..02123fe4e 100644 --- a/SimplenoteTests/MockupStorage.swift +++ b/SimplenoteTests/MockupStorage.swift @@ -2,7 +2,6 @@ import Foundation import CoreData @testable import Simplenote - /// MockupStorageManager: InMemory CoreData Stack. /// class MockupStorageManager { @@ -73,7 +72,6 @@ class MockupStorageManager { } } - // MARK: - Descriptors // extension MockupStorageManager { @@ -97,7 +95,6 @@ extension MockupStorageManager { } } - // MARK: - Stack URL's // extension MockupStorageManager { diff --git a/SimplenoteTests/NSMutableAttributedStringStylingTests.swift b/SimplenoteTests/NSMutableAttributedStringStylingTests.swift index ee3cc22d5..1682fbd1f 100644 --- a/SimplenoteTests/NSMutableAttributedStringStylingTests.swift +++ b/SimplenoteTests/NSMutableAttributedStringStylingTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - NSMutableAttributedString Styling Tests // class NSMutableAttributedStringStylingTests: XCTestCase { diff --git a/SimplenoteTests/NSPredicateEmailTests.swift b/SimplenoteTests/NSPredicateEmailTests.swift index 2de77a7f3..77692975f 100644 --- a/SimplenoteTests/NSPredicateEmailTests.swift +++ b/SimplenoteTests/NSPredicateEmailTests.swift @@ -2,7 +2,6 @@ import Foundation import XCTest @testable import Simplenote - // MARK: - NSPredicate+Email Unit Tests // class NSPredicateEmailTests: XCTestCase { diff --git a/SimplenoteTests/NSStringCondensingTests.swift b/SimplenoteTests/NSStringCondensingTests.swift index 6b73e6599..e9080ae02 100644 --- a/SimplenoteTests/NSStringCondensingTests.swift +++ b/SimplenoteTests/NSStringCondensingTests.swift @@ -2,7 +2,6 @@ import Foundation import XCTest @testable import Simplenote - // MARK: - NSString+Condensing Unit Tests // class NSStringCondensingTests: XCTestCase { diff --git a/SimplenoteTests/NSStringSimplenoteTests.swift b/SimplenoteTests/NSStringSimplenoteTests.swift index 1a2dbc2ad..981cc184a 100644 --- a/SimplenoteTests/NSStringSimplenoteTests.swift +++ b/SimplenoteTests/NSStringSimplenoteTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - NSString Simplenote Tests // class NSStringSimplenoteTests: XCTestCase { diff --git a/SimplenoteTests/Network/MockURLSession.swift b/SimplenoteTests/Network/MockURLSession.swift deleted file mode 100644 index 70b5042d2..000000000 --- a/SimplenoteTests/Network/MockURLSession.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -// MARK: - MockURLSession -// -class MockURLSession: URLSession { - var data: (Data?, URLResponse?, Error?)? - var lastRequest: URLRequest? - - override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { - lastRequest = request - return MockURLSessionDataTask { - completionHandler(self.data?.0, self.data?.1, self.data?.2) - } - } -} diff --git a/SimplenoteTests/Network/MockURLSessionDataTask.swift b/SimplenoteTests/Network/MockURLSessionDataTask.swift deleted file mode 100644 index 32a97ea9a..000000000 --- a/SimplenoteTests/Network/MockURLSessionDataTask.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -// MARK: - MockURLSessionDataTask -// -class MockURLSessionDataTask: URLSessionDataTask { - private let completion: () -> Void - - init(completion: @escaping () -> Void) { - self.completion = completion - } - - override func resume() { - completion() - } -} diff --git a/SimplenoteTests/NoteBodyExcerptTests.swift b/SimplenoteTests/NoteBodyExcerptTests.swift index 090ea0c6f..569697eae 100644 --- a/SimplenoteTests/NoteBodyExcerptTests.swift +++ b/SimplenoteTests/NoteBodyExcerptTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - String Truncation Tests // class NoteBodyExcerptTests: XCTestCase { diff --git a/SimplenoteTests/NoteContentHelperTests.swift b/SimplenoteTests/NoteContentHelperTests.swift index 7900c181f..a604c6010 100644 --- a/SimplenoteTests/NoteContentHelperTests.swift +++ b/SimplenoteTests/NoteContentHelperTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - NoteContentHelperTests Tests // class NoteContentHelperTests: XCTestCase { diff --git a/SimplenoteTests/NoteContentPreviewTests.swift b/SimplenoteTests/NoteContentPreviewTests.swift index f43419856..7484cd41e 100644 --- a/SimplenoteTests/NoteContentPreviewTests.swift +++ b/SimplenoteTests/NoteContentPreviewTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - NoteContentPreviewTests Tests // class NoteContentPreviewTests: XCTestCase { diff --git a/SimplenoteTests/NoteLinkTests.swift b/SimplenoteTests/NoteLinkTests.swift index 6a0e5ca4c..89067433e 100644 --- a/SimplenoteTests/NoteLinkTests.swift +++ b/SimplenoteTests/NoteLinkTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - Note+Extension Tests // class NoteLinkTests: XCTestCase { diff --git a/SimplenoteTests/NotesListControllerTests.swift b/SimplenoteTests/NotesListControllerTests.swift index 8e4fbc44a..296f1e8f6 100644 --- a/SimplenoteTests/NotesListControllerTests.swift +++ b/SimplenoteTests/NotesListControllerTests.swift @@ -2,7 +2,6 @@ import XCTest import SimplenoteFoundation @testable import Simplenote - // MARK: - NotesListControllerTests // class NotesListControllerTests: XCTestCase { @@ -15,7 +14,6 @@ class NotesListControllerTests: XCTestCase { /// private var noteListController: NotesListController! - // MARK: - Overridden Methods override func setUp() { @@ -29,7 +27,6 @@ class NotesListControllerTests: XCTestCase { storage.reset() } - // MARK: - Tests: Filters /// Verifies that the Filter property properly filters out non matching entities @@ -51,7 +48,6 @@ class NotesListControllerTests: XCTestCase { XCTAssertEqual(noteListController.numberOfObjects, 1) } - // MARK: - Tests: Sorting /// Verifies that the SortMode property properly applies the specified order mode to the retrieved entities @@ -73,7 +69,6 @@ class NotesListControllerTests: XCTestCase { } } - // MARK: - Tests: Sections /// Verifies that the Tag Entities aren't fetched when in Results Mode @@ -92,7 +87,6 @@ class NotesListControllerTests: XCTestCase { XCTAssertEqual(noteListController.sections[0].numberOfObjects, notes.count) } - // MARK: - Tests: Search /// Verifies that the Tag Entities are fetched when in search mode @@ -122,7 +116,6 @@ class NotesListControllerTests: XCTestCase { XCTAssertEqual(noteListController.sections.count, 2) } - /// Verifies that the `endSearch` switches the NotesList back to a single section /// func testEndSearchSwitchesBackToSingleSectionMode() { @@ -169,7 +162,6 @@ class NotesListControllerTests: XCTestCase { XCTAssert(noteListController.sections[0].numberOfObjects <= noteListController.limitForTagResults) } - // MARK: - Tests: `object(at:)` /// Verifies that `object(at: IndexPath)` returns the proper Note when in results mode @@ -225,7 +217,6 @@ class NotesListControllerTests: XCTestCase { XCTAssertEqual(note.content, "055") } - // MARK: - Tests: `indexPath(forObject:)` /// Verifies that `indexPath(forObject:)` returns the proper Note when in Results Mode @@ -476,7 +467,6 @@ class NotesListControllerTests: XCTestCase { waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) } - /// Verifies that `onBatchChanges` does not relay duplicated Changesets /// func testOnBatchChangesDoesNotRelayDuplicatedEvents() { @@ -493,7 +483,6 @@ class NotesListControllerTests: XCTestCase { waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) } - /// Verifies that `onBatchChanges` relays move events /// func testOnBatchChangesRelaysMoveEvents() { @@ -514,7 +503,6 @@ class NotesListControllerTests: XCTestCase { } } - // MARK: - Private APIs // private extension NotesListControllerTests { diff --git a/SimplenoteTests/OptionsTests.swift b/SimplenoteTests/OptionsTests.swift index 6083362bd..fb58e6d1c 100644 --- a/SimplenoteTests/OptionsTests.swift +++ b/SimplenoteTests/OptionsTests.swift @@ -2,7 +2,6 @@ import Foundation import XCTest @testable import Simplenote - // MARK: - Options Unit Tests // class OptionsTests: XCTestCase { diff --git a/SimplenoteTests/RemoteResult+TestHelpers.swift b/SimplenoteTests/RemoteResult+TestHelpers.swift index 585b9ae16..ba37ff02f 100644 --- a/SimplenoteTests/RemoteResult+TestHelpers.swift +++ b/SimplenoteTests/RemoteResult+TestHelpers.swift @@ -1,4 +1,5 @@ import Foundation +import SimplenoteEndpoints @testable import Simplenote extension Remote { @@ -7,6 +8,6 @@ extension Remote { return .success(nil) } - return .failure(RemoteError.requestError(0, nil)) + return .failure(RemoteError(statusCode: 0, response: nil, networkError: nil)) } } diff --git a/SimplenoteTests/SignupRemoteTests.swift b/SimplenoteTests/SignupRemoteTests.swift deleted file mode 100644 index 239dbb818..000000000 --- a/SimplenoteTests/SignupRemoteTests.swift +++ /dev/null @@ -1,75 +0,0 @@ -import XCTest -@testable import Simplenote - -class SignupRemoteTests: XCTestCase { - private lazy var urlSession = MockURLSession() - private lazy var signupRemote = SignupRemote(urlSession: urlSession) - - func testSuccessWhenStatusCodeIs2xx() { - for _ in 0..<5 { - test(withStatusCode: Int.random(in: 200..<300), email: "email@gmail.com", expectedResult: .success(nil)) - } - } - - func testFailureWhenStatusCodeIs4xxOr5xx() { - for _ in 0..<5 { - let statusCode = Int.random(in: 400..<600) - test(withStatusCode: statusCode, email: "email@gmail.com", expectedResult: .failure(RemoteError.requestError(statusCode, nil))) - } - } - - func testRequestSetsEmailToCorrectCase() throws { - signupRemote.signup(with: "EMAIL@gmail.com", completion: { _ in }) - - let expecation = "email@gmail.com" - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - let decodedEmail = try XCTUnwrap(body["username"]) - - XCTAssertEqual(expecation, decodedEmail) - } - - func testRequestSetsEmailToCorrectCaseWithSpecialCharacters() throws { - signupRemote.signup(with: "EMAIL123456@#$%^@gmail.com", completion: { _ in }) - - let expecation = "email123456@#$%^@gmail.com" - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - let decodedEmail = try XCTUnwrap(body["username"]) - - XCTAssertEqual(expecation, decodedEmail) - } - - func testRequestSetsEmailToCorrectCaseWithMixedCase() throws { - signupRemote.signup(with: "eMaIl@gmail.com", completion: { _ in }) - - let expecation = "email@gmail.com" - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - let decodedEmail = try XCTUnwrap(body["username"]) - - XCTAssertEqual(expecation, decodedEmail) - } - - private func test(withStatusCode statusCode: Int?, email: String, expectedResult: Result) { - urlSession.data = (nil, - response(with: statusCode), - nil) - - let expectation = self.expectation(description: "Verify is called") - - signupRemote.signup(with: email) { (result) in - XCTAssertEqual(result, expectedResult) - expectation.fulfill() - } - - waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) - } - - private func response(with statusCode: Int?) -> HTTPURLResponse? { - guard let statusCode = statusCode else { - return nil - } - return HTTPURLResponse(url: URL(fileURLWithPath: "/"), - statusCode: statusCode, - httpVersion: nil, - headerFields: nil) - } -} diff --git a/SimplenoteTests/StringContentSliceTests.swift b/SimplenoteTests/StringContentSliceTests.swift index a9acdba7c..b90a7e807 100644 --- a/SimplenoteTests/StringContentSliceTests.swift +++ b/SimplenoteTests/StringContentSliceTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - String Content Slice Tests // class StringContentSliceTests: XCTestCase { diff --git a/SimplenoteTests/StringSimplenoteTests.swift b/SimplenoteTests/StringSimplenoteTests.swift index dde1dd716..34233daa0 100644 --- a/SimplenoteTests/StringSimplenoteTests.swift +++ b/SimplenoteTests/StringSimplenoteTests.swift @@ -1,7 +1,6 @@ import XCTest @testable import Simplenote - // MARK: - String Truncation Tests // class StringSimplenoteTests: XCTestCase { diff --git a/SimplenoteTests/Tags/TagTextFieldInputValidatorTests.swift b/SimplenoteTests/Tags/TagTextFieldInputValidatorTests.swift index 1e33ec28e..ad20d6507 100644 --- a/SimplenoteTests/Tags/TagTextFieldInputValidatorTests.swift +++ b/SimplenoteTests/Tags/TagTextFieldInputValidatorTests.swift @@ -43,7 +43,7 @@ class TagTextFieldInputValidatorTests: XCTestCase { XCTAssertEqual(validator.validateInput(originalText: text, range: range, replacement: replacement), expectedResult) } } - + func testValidationFailsWhenReplacementHasComma() { let text = "tag" let range = text.endIndex..) { - urlSession.data = (nil, - response(with: statusCode), - nil) - - let expectation = self.expectation(description: "Verify is called") - - remote.verify(email: UUID().uuidString) { (result) in - XCTAssertEqual(result, expectedResult) - expectation.fulfill() - } - - waitForExpectations(timeout: 3, handler: nil) - } - - private func response(with statusCode: Int?) -> HTTPURLResponse? { - guard let statusCode = statusCode else { - return nil - } - return HTTPURLResponse(url: URL(fileURLWithPath: "/"), - statusCode: statusCode, - httpVersion: nil, - headerFields: nil) - } -} diff --git a/SimplenoteTests/Verification/Helpers/MockAccountVerificationRemote.swift b/SimplenoteTests/Verification/Helpers/MockAccountVerificationRemote.swift index f09f4f411..d4add1b4e 100644 --- a/SimplenoteTests/Verification/Helpers/MockAccountVerificationRemote.swift +++ b/SimplenoteTests/Verification/Helpers/MockAccountVerificationRemote.swift @@ -1,4 +1,5 @@ import XCTest +@testable import SimplenoteEndpoints @testable import Simplenote class MockAccountVerificationRemote: AccountRemote { diff --git a/SimplenoteUITests/EmailLogin.swift b/SimplenoteUITests/EmailLogin.swift index 6fae2609a..fd7c55152 100644 --- a/SimplenoteUITests/EmailLogin.swift +++ b/SimplenoteUITests/EmailLogin.swift @@ -5,15 +5,39 @@ class EmailLogin { class func open() { app.buttons[UID.Button.logIn].waitForIsHittable() app.buttons[UID.Button.logIn].tap() - app.buttons[UID.Button.logInWithEmail].waitForIsHittable() - app.buttons[UID.Button.logInWithEmail].tap() } class func close() { - let backButton = app.navigationBars[UID.NavBar.logIn].buttons[UID.Button.back] - guard backButton.isHittable else { return } + /// Exit: We're already in the Onboarding UI + /// + if app.buttons[UID.Button.logIn].exists, app.buttons[UID.Button.signUp].exists { + return + } + + /// Back from Password > Code UI + /// + let backFromPasswordUI = app.navigationBars[UID.NavBar.logInWithPassword].buttons.element(boundBy: 0) + if backFromPasswordUI.exists { + backFromPasswordUI.tap() + _ = app.navigationBars[UID.NavBar.enterCode].waitForExistence(timeout: minLoadTimeout) + } + + /// Back from Code UI > Email UI + /// Important: When rate-limited, the Code UI is skipped + /// + let codeNavigationBar = app.navigationBars[UID.NavBar.enterCode] + if codeNavigationBar.exists { + codeNavigationBar.buttons.element(boundBy: 0).tap() + _ = app.navigationBars[UID.NavBar.logIn].waitForExistence(timeout: minLoadTimeout) + } + + /// Back from Email UI > Onboarding + /// + let emailNavigationBar = app.navigationBars[UID.NavBar.logIn] + if emailNavigationBar.exists { + emailNavigationBar.buttons.element(boundBy: 0).tap() + } - backButton.tap() handleSavePasswordPrompt() } @@ -34,11 +58,31 @@ class EmailLogin { class func logIn(email: String, password: String) { enterEmail(enteredValue: email) + app.buttons[UID.Button.logInWithEmail].tap() + + /// Code UI > Password UI + /// Important: When rate-limited, the Code UI is skipped + /// + let codeNavigationBar = app.navigationBars[UID.NavBar.enterCode] + _ = codeNavigationBar.waitForExistence(timeout: minLoadTimeout) + + if codeNavigationBar.exists { + app.buttons[UID.Button.enterPassword].tap() + } + + /// Password UI + /// + _ = app.buttons[UID.Button.logIn].waitForExistence(timeout: minLoadTimeout) enterPassword(enteredValue: password) - app.buttons[UID.Button.logIn].tap() + app.buttons[UID.Button.mainAction].tap() handleSavePasswordPrompt() waitForSpinnerToDisappear() } + + class func enterEmailAndAttemptLogin(email: String) { + enterEmail(enteredValue: email) + app.buttons[UID.Button.logInWithEmail].tap() + } class func enterEmail(enteredValue: String) { let field = app.textFields[UID.TextField.email] diff --git a/SimplenoteUITests/NoteEditor.swift b/SimplenoteUITests/NoteEditor.swift index 4f5abf4c1..b8117d0a6 100644 --- a/SimplenoteUITests/NoteEditor.swift +++ b/SimplenoteUITests/NoteEditor.swift @@ -131,7 +131,6 @@ class NoteEditor { toggleMarkdownState() } - class func getLink(byText linkText: String) -> XCUIElement { // As of iOS 16.0, a link in Note Editor is presented by two elements: // First is of `link` type and has zero dimensions, the other is its child diff --git a/SimplenoteUITests/SimplenoteUITestsLogin.swift b/SimplenoteUITests/SimplenoteUITestsLogin.swift index 2e1c97511..8874d0d65 100644 --- a/SimplenoteUITests/SimplenoteUITestsLogin.swift +++ b/SimplenoteUITests/SimplenoteUITestsLogin.swift @@ -17,24 +17,13 @@ class SimplenoteUISmokeTestsLogin: XCTestCase { let _ = attemptLogOut() } - func testLogInWithNoEmailNoPassword() throws { - trackTest() - - trackStep() - EmailLogin.open() - EmailLogin.logIn(email: "", password: "") - app.assertLabelExists(withText: Text.loginEmailInvalid) - app.assertLabelExists(withText: Text.loginPasswordShort) - } - func testLogInWithNoEmail() throws { trackTest() trackStep() EmailLogin.open() - EmailLogin.logIn(email: "", password: testDataPassword) + EmailLogin.enterEmailAndAttemptLogin(email: "") Assert.labelExists(labelText: Text.loginEmailInvalid) - Assert.labelAbsent(labelText: Text.loginPasswordShort) } func testLogInWithInvalidEmail() throws { @@ -42,7 +31,7 @@ class SimplenoteUISmokeTestsLogin: XCTestCase { trackStep() EmailLogin.open() - EmailLogin.logIn(email: testDataInvalidEmail, password: testDataPassword) + EmailLogin.enterEmailAndAttemptLogin(email: testDataInvalidEmail) Assert.labelExists(labelText: Text.loginEmailInvalid) Assert.labelAbsent(labelText: Text.loginPasswordShort) } diff --git a/SimplenoteUITests/UIDs.swift b/SimplenoteUITests/UIDs.swift index 65ae52e39..2a964cbf4 100644 --- a/SimplenoteUITests/UIDs.swift +++ b/SimplenoteUITests/UIDs.swift @@ -11,6 +11,8 @@ enum UID { enum NavBar { static let allNotes = "All Notes" static let logIn = "Log In" + static let logInWithPassword = "Log In with Password" + static let enterCode = "Enter Code" static let noteEditorPreview = "Preview" static let noteEditorOptions = "Options" static let trash = "Trash" @@ -26,6 +28,8 @@ enum UID { static let menu = "menu" static let signUp = "Sign Up" static let logIn = "Log In" + static let enterPassword = "Enter password" + static let mainAction = "Main Action" static let logInWithEmail = "Log in with email" static let allNotes = "All Notes" static let trash = "Trash" diff --git a/SimplenoteWidgets/Models/Note+Widget.swift b/SimplenoteWidgets/Models/Note+Widget.swift index 093fe923f..e9cf7a01a 100644 --- a/SimplenoteWidgets/Models/Note+Widget.swift +++ b/SimplenoteWidgets/Models/Note+Widget.swift @@ -16,6 +16,14 @@ extension Note { return NSFetchRequest(entityName: "Note") } + public override func awakeFromInsert() { + super.awakeFromInsert() + + if simperiumKey.isEmpty { + simperiumKey = UUID().uuidString.replacingOccurrences(of: "-", with: "") + } + } + @NSManaged public var content: String? @NSManaged public var creationDate: Date? @NSManaged public override var isDeleted: Bool @@ -60,11 +68,57 @@ extension Note { } var url: URL { - guard let simperiumKey = simperiumKey else { - return URL(string: .simplenotePath())! - } return URL(string: .simplenotePath(withHost: SimplenoteConstants.simplenoteInterlinkHost) + simperiumKey)! } + + private func objectFromJSONString(_ json: String) -> Any? { + guard let data = json.data(using: .utf8) else { + return nil + } + + return try? JSONSerialization.jsonObject(with: data) + } + + var tagsArray: [String] { + guard let tagsString = tags, + let array = objectFromJSONString(tagsString) as? [String] else { + return [] + } + + return array + } + + var systemTagsArray: [String] { + guard let systemTagsString = systemTags, + let array = objectFromJSONString(systemTagsString) as? [String] else { + return [] + } + + return array + } + + func toDictionary() -> [String: Any] { + + return [ + "tags": tagsArray, + "deleted": 0, + "shareURL": shareURL ?? String(), + "publishURL": publishURL ?? String(), + "content": content ?? "", + "systemTags": systemTagsArray, + "creationDate": (creationDate ?? .now).timeIntervalSince1970, + "modificationDate": (modificationDate ?? .now).timeIntervalSince1970 + ] + } + + func toJsonData() -> Data? { + do { + return try JSONSerialization.data(withJSONObject: toDictionary(), options: .prettyPrinted) + } catch { + print("Error converting Note to JSON: \(error)") + return nil + } + } } private struct Constants { diff --git a/SimplenoteWidgets/Models/SPManagedObject+Widget.swift b/SimplenoteWidgets/Models/SPManagedObject+Widget.swift index c695f28fe..73c0eb618 100644 --- a/SimplenoteWidgets/Models/SPManagedObject+Widget.swift +++ b/SimplenoteWidgets/Models/SPManagedObject+Widget.swift @@ -17,5 +17,5 @@ extension SPManagedObject { } @NSManaged public var ghostData: String? - @NSManaged public var simperiumKey: String? + @NSManaged public var simperiumKey: String } diff --git a/SimplenoteWidgets/Providers/ListWidgetProvider.swift b/SimplenoteWidgets/Providers/ListWidgetProvider.swift index 823971869..213d985d8 100644 --- a/SimplenoteWidgets/Providers/ListWidgetProvider.swift +++ b/SimplenoteWidgets/Providers/ListWidgetProvider.swift @@ -42,7 +42,7 @@ struct ListWidgetProvider: IntentTimelineProvider { typealias Intent = ListWidgetIntent typealias Entry = ListWidgetEntry - let coreDataWrapper = WidgetCoreDataWrapper() + let coreDataWrapper = ExtensionCoreDataWrapper() func placeholder(in context: Context) -> ListWidgetEntry { return ListWidgetEntry.placeholder() diff --git a/SimplenoteWidgets/Providers/NoteWidgetProvider.swift b/SimplenoteWidgets/Providers/NoteWidgetProvider.swift index 642c956b4..67e240948 100644 --- a/SimplenoteWidgets/Providers/NoteWidgetProvider.swift +++ b/SimplenoteWidgets/Providers/NoteWidgetProvider.swift @@ -40,7 +40,7 @@ struct NoteWidgetProvider: IntentTimelineProvider { typealias Intent = NoteWidgetIntent typealias Entry = NoteWidgetEntry - let coreDataWrapper = WidgetCoreDataWrapper() + let coreDataWrapper = ExtensionCoreDataWrapper() func placeholder(in context: Context) -> NoteWidgetEntry { return NoteWidgetEntry.placeholder() diff --git a/SimplenoteWidgets/SimplenoteWidgets.swift b/SimplenoteWidgets/SimplenoteWidgets.swift index 8658d954d..da94f0cc5 100644 --- a/SimplenoteWidgets/SimplenoteWidgets.swift +++ b/SimplenoteWidgets/SimplenoteWidgets.swift @@ -1,7 +1,6 @@ import SwiftUI import WidgetKit - @available(iOS 14.0, *) @main struct SimplenoteWidgets: WidgetBundle { diff --git a/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..321483656 --- /dev/null +++ b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy @@ -0,0 +1,80 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + + diff --git a/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift b/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift new file mode 100644 index 000000000..a688a7788 --- /dev/null +++ b/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift @@ -0,0 +1,27 @@ +import Foundation +import CoreData + +class ExtensionCoreDataWrapper { + private lazy var coreDataManager: CoreDataManager = { + do { + return try CoreDataManager(StorageSettings().sharedStorageURL, for: .widgets) + } catch { + fatalError() + } + }() + + private lazy var extensionResultsController: ExtensionResultsController = { + ExtensionResultsController(context: coreDataManager.managedObjectContext) + }() + + func resultsController() -> ExtensionResultsController? { + guard FileManager.default.fileExists(atPath: StorageSettings().sharedStorageURL.path) else { + return nil + } + return extensionResultsController + } + + func context() -> NSManagedObjectContext { + coreDataManager.managedObjectContext + } +} diff --git a/SimplenoteWidgets/Tools/WidgetResultsController.swift b/SimplenoteWidgets/Tools/ExtensionResultsController.swift similarity index 96% rename from SimplenoteWidgets/Tools/WidgetResultsController.swift rename to SimplenoteWidgets/Tools/ExtensionResultsController.swift index 61b775a0b..9ddf181a3 100644 --- a/SimplenoteWidgets/Tools/WidgetResultsController.swift +++ b/SimplenoteWidgets/Tools/ExtensionResultsController.swift @@ -3,7 +3,7 @@ import SimplenoteFoundation import SimplenoteSearch import CoreData -class WidgetResultsController { +class ExtensionResultsController { /// Data Controller /// @@ -44,6 +44,10 @@ class WidgetResultsController { return fetched?.first } + func noteExists(forSimperiumKey key: String) -> Bool { + note(forSimperiumKey: key) != nil + } + /// Creates a predicate for notes given a tag name. If not specified the predicate is for all notes that are not deleted /// private func predicateForNotes(filteredBy tagFilter: TagsFilter = .allNotes) -> NSPredicate { diff --git a/SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift b/SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift deleted file mode 100644 index 40d3048cf..000000000 --- a/SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -class WidgetCoreDataWrapper { - private lazy var coreDataManager: CoreDataManager = { - do { - return try CoreDataManager(StorageSettings().sharedStorageURL, for: .widgets) - } catch { - fatalError() - } - }() - - private lazy var widgetResultsController: WidgetResultsController = { - WidgetResultsController(context: coreDataManager.managedObjectContext) - }() - - func resultsController() -> WidgetResultsController? { - guard FileManager.default.fileExists(atPath: StorageSettings().sharedStorageURL.path) else { - return nil - } - return widgetResultsController - } -} diff --git a/SimplenoteWidgets/Tools/WidgetsState.swift b/SimplenoteWidgets/Tools/WidgetsState.swift index 42ac2545b..9be1192ea 100644 --- a/SimplenoteWidgets/Tools/WidgetsState.swift +++ b/SimplenoteWidgets/Tools/WidgetsState.swift @@ -7,7 +7,6 @@ enum WidgetsState { case loggedOut } - extension WidgetsState { var message: String { switch self { diff --git a/SimplenoteWidgets/ViewModifiers/Filling.swift b/SimplenoteWidgets/ViewModifiers/Filling.swift index 6ac8cba2d..169c7717d 100644 --- a/SimplenoteWidgets/ViewModifiers/Filling.swift +++ b/SimplenoteWidgets/ViewModifiers/Filling.swift @@ -12,4 +12,3 @@ struct Filling: ViewModifier { ) } } - diff --git a/SimplenoteWidgets/Views/ListWidgetHeaderView.swift b/SimplenoteWidgets/Views/ListWidgetHeaderView.swift index 369c08ebc..236c653e0 100644 --- a/SimplenoteWidgets/Views/ListWidgetHeaderView.swift +++ b/SimplenoteWidgets/Views/ListWidgetHeaderView.swift @@ -1,7 +1,6 @@ import SwiftUI import WidgetKit - struct ListWidgetHeaderView: View { let tag: WidgetTag diff --git a/SimplenoteWidgets/Views/ListWidgetView.swift b/SimplenoteWidgets/Views/ListWidgetView.swift index 1447b63ff..8d7b09b92 100644 --- a/SimplenoteWidgets/Views/ListWidgetView.swift +++ b/SimplenoteWidgets/Views/ListWidgetView.swift @@ -52,7 +52,6 @@ enum Row { case empty } - private struct Constants { static let mediumRows = 3 static let largeRows = 8 diff --git a/SimplenoteWidgets/Views/NoteListRow.swift b/SimplenoteWidgets/Views/NoteListRow.swift index ce033493d..5e75ffd98 100644 --- a/SimplenoteWidgets/Views/NoteListRow.swift +++ b/SimplenoteWidgets/Views/NoteListRow.swift @@ -29,4 +29,3 @@ struct NoteListRow_Previews: PreviewProvider { .previewContext(WidgetPreviewContext(family: .systemLarge)) } } - diff --git a/UITestsFoundation/PasscodeScreen.swift b/UITestsFoundation/PasscodeScreen.swift index a33a0693c..94c36342a 100644 --- a/UITestsFoundation/PasscodeScreen.swift +++ b/UITestsFoundation/PasscodeScreen.swift @@ -5,11 +5,7 @@ public class PasscodeScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( - expectedElementGetters: [ - { $0.staticTexts["1"].firstMatch }, - { $0.staticTexts["4"].firstMatch }, - { $0.staticTexts["7"].firstMatch }, - { $0.staticTexts["0"].firstMatch } + expectedElementGetters: [ { $0.staticTexts["1"].firstMatch }, { $0.staticTexts["4"].firstMatch }, { $0.staticTexts["7"].firstMatch }, { $0.staticTexts["0"].firstMatch } ], app: app ) diff --git a/config/Project.Release.xcconfig b/config/Project.Release.xcconfig new file mode 100644 index 000000000..7b29190ff --- /dev/null +++ b/config/Project.Release.xcconfig @@ -0,0 +1 @@ +DEVELOPMENT_TEAM = PZYM8XX95Q diff --git a/config/Simplenote.release.xcconfig b/config/Simplenote.release.xcconfig index f77a796b2..26a784bda 100644 --- a/config/Simplenote.release.xcconfig +++ b/config/Simplenote.release.xcconfig @@ -1,3 +1,6 @@ #include "Version.public.xcconfig" +#include "Project.Release.xcconfig" DISPLAY_NAME=Simplenote + +PROVISIONING_PROFILE_SPECIFIER = match AppStore $(PRODUCT_BUNDLE_IDENTIFIER) diff --git a/config/Version.Public.xcconfig b/config/Version.Public.xcconfig index 63c766f02..48aa834d9 100644 --- a/config/Version.Public.xcconfig +++ b/config/Version.Public.xcconfig @@ -1,4 +1,2 @@ -VERSION_SHORT=4.50 - -// Public long version example: VERSION_LONG=4.8.1.1 -VERSION_LONG=4.50.0.1 +VERSION_LONG = 4.54.0.1 +VERSION_SHORT = 4.54 diff --git a/fastlane/Appfile b/fastlane/Appfile deleted file mode 100644 index 87770d1ef..000000000 --- a/fastlane/Appfile +++ /dev/null @@ -1,2 +0,0 @@ -app_identifier "com.codality.NotationalFlow" # The bundle identifier of your app -team_id "PZYM8XX95Q" # Developer Portal Team ID \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a4a88625d..aa1cb1fb3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,520 +1,95 @@ +# frozen_string_literal: true + default_platform(:ios) fastlane_require 'xcodeproj' fastlane_require 'dotenv' fastlane_require 'open-uri' fastlane_require 'buildkit' -unless FastlaneCore::Helper.bundler? - UI.user_error!('Please run fastlane via `bundle exec`') -end +UI.user_error!('Please run fastlane via `bundle exec`') unless FastlaneCore::Helper.bundler? + +PROJECT_ROOT_FOLDER = File.dirname(File.expand_path(__dir__)) +VERSION_FILE_PATH = File.join(PROJECT_ROOT_FOLDER, 'config', 'Version.Public.xcconfig') +OUTPUT_DIRECTORY_PATH = File.join(PROJECT_ROOT_FOLDER, 'build', 'results') +APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow' +DEFAULT_BRANCH = 'trunk' + +TEAM_ID_APP_STORE_CONNECT = 'PZYM8XX95Q' +TEAM_ID_ENTERPRISE = '99KV9Z6BKV' + +ORGANIZATION = 'automattic' +PROJECT_SLUG = 'simplenote-ios' +GITHUB_REPO = "#{ORGANIZATION}/#{PROJECT_SLUG}".freeze +BUILDKITE_ORGANIZATION = ORGANIZATION +BUILDKITE_PIPELINE = PROJECT_SLUG +APPCENTER_OWNER_NAME = ORGANIZATION + +WORKSPACE = 'Simplenote.xcworkspace' + +VERSION_CALCULATOR = Fastlane::Wpmreleasetoolkit::Versioning::SemanticVersionCalculator.new +VERSION_FORMATTER = Fastlane::Wpmreleasetoolkit::Versioning::FourPartVersionFormatter.new +BUILD_CODE_FORMATTER = Fastlane::Wpmreleasetoolkit::Versioning::FourPartBuildCodeFormatter.new +PUBLIC_VERSION_FILE = Fastlane::Wpmreleasetoolkit::Versioning::IOSVersionFile.new(xcconfig_path: VERSION_FILE_PATH) -USER_ENV_FILE_PATH = File.join(Dir.home, '.simplenoteios-env.default') -PROJECT_ENV_FILE_PATH = File.join( - Dir.home, '.configure', 'simplenote-ios', 'secrets', 'project.env' -) SECRETS_ROOT = File.join(Dir.home, '.configure', 'simplenote-ios', 'secrets') APP_STORE_CONNECT_KEY_PATH = File.join(SECRETS_ROOT, 'app_store_connect_fastlane_api_key.json') $used_test_account_index = nil +GLOTPRESS_BASE_URL = 'https://translate.wordpress.com/projects' +# Notice the trailing / is required. +# Without it, GlotPress will redirect to the version with / +GLOTPRESS_APP_STRINGS_PROJECT_URL = "#{GLOTPRESS_BASE_URL}/simplenote/ios/".freeze +GLOTPRESS_STORE_METADATA_PROJECT_URL = "#{GLOTPRESS_APP_STRINGS_PROJECT_URL}release-notes/".freeze + +APP_RESOURCES_DIR = File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'Resources') +STORE_METADATA_FOLDER = File.join(PROJECT_ROOT_FOLDER, 'fastlane', 'metadata') +STORE_METADATA_DEFAULT_LOCALE_FOLDER = File.join(STORE_METADATA_FOLDER, 'default') +RELEASE_NOTES_SOURCE_PATH = File.join(STORE_METADATA_DEFAULT_LOCALE_FOLDER, 'release_notes.txt') + +require_relative 'lib/env_manager' + +# Important: These need to be imported after all the constants have been defined because they access them. +# A bit of a leaky abstraction but makes for lanes that are easier to write... +import 'lanes/build.rb' +import 'lanes/localization.rb' +import 'lanes/release.rb' + before_all do # Ensure we use the latest version of the toolkit check_for_toolkit_updates unless is_ci || ENV['FASTLANE_SKIP_TOOLKIT_UPDATE_CHECK'] - # Check that the env files exist - unless is_ci || File.file?(USER_ENV_FILE_PATH) - error_message = <<~ERROR - ~/.simplenoteios-env.default not found. Please copy env/user.env-example to #{USER_ENV_FILE_PATH} and fill in the values") using the following command: - - \tcp #{PROJECT_ROOT_FOLDER}/fastlane/env/user.env-example #{USER_ENV_FILE_PATH} - ERROR - UI.user_error!(error_message) - 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 + EnvManager.set_up(env_file_name: 'simplenote-ios') setup_ci end -error do |lane, exception, options| +error do |lane, _exception, _options| deoccupy_test_account if lane == :run_ui_tests end -platform :ios do ######################################################################## # Environment ######################################################################## -PROJECT_ROOT_FOLDER = File.dirname(File.expand_path(__dir__)) -VERSION_FILE_PATH = File.join(PROJECT_ROOT_FOLDER, 'config', 'Version.Public.xcconfig') -OUTPUT_DIRECTORY_PATH = File.join(PROJECT_ROOT_FOLDER, 'build', 'results') - -Dotenv.load(USER_ENV_FILE_PATH) -Dotenv.load(PROJECT_ENV_FILE_PATH) -ENV[GHHELPER_REPO="Automattic/simplenote-ios"] -ENV["PROJECT_NAME"]="Simplenote" -ENV["PROJECT_ROOT_FOLDER"]=PROJECT_ROOT_FOLDER -ENV['APP_STORE_STRINGS_FILE_NAME']='AppStoreStrings.pot' -ENV["PUBLIC_CONFIG_FILE"]=VERSION_FILE_PATH -ENV["DOWNLOAD_METADATA"]="./fastlane/download_metadata.swift" +ENV['PROJECT_NAME'] = 'Simplenote' +ENV['PROJECT_ROOT_FOLDER'] = PROJECT_ROOT_FOLDER +ENV['PUBLIC_CONFIG_FILE'] = VERSION_FILE_PATH ENV['FL_RELEASE_TOOLKIT_DEFAULT_BRANCH'] = 'trunk' -REPOSITORY_NAME="simplenote-ios" -APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow'.freeze - -# List of locales used for the app strings (GlotPress code => `*.lproj` folder name`) -# -# TODO: Replace with `LocaleHelper` once provided by release toolkit (https://github.com/wordpress-mobile/release-toolkit/pull/296) -GLOTPRESS_TO_LPROJ_APP_LOCALE_CODES = { - 'ar' => 'ar', # Arabic - 'cy' => 'cy', # Welsh - 'de' => 'de', # German - 'el' => 'el', # Greek - 'en' => 'en', # English - 'es' => 'es', # Spanish - 'fa' => 'fa', # Persian - 'fr' => 'fr', # French - 'he' => 'he', # Hebrew - 'id' => 'id', # Indonesian - 'it' => 'it', # Italian - 'ja' => 'ja', # Japanese - 'ko' => 'ko', # Korean - 'nl' => 'nl', # Dutch - 'pt-br' => 'pt-BR', # Portuguese (Brazil) - 'ru' => 'ru', # Russian - 'sv' => 'sv', # Swedish - 'tr' => 'tr', # Turkish - 'zh-cn' => 'zh-Hans-CN', # Chinese (China) - 'zh-tw' => 'zh-Hant-TW' # Chinese (Taiwan) -}.freeze - -# Use this instead of getting values from ENV directly -# It will throw an error if the requested value is missing -def get_required_env(key) - unless ENV.key?(key) - UI.user_error!("Environment variable '#{key}' is not set. Have you setup #{USER_ENV_FILE_PATH} correctly?") - end - ENV[key] -end - -######################################################################## -# Release Lanes -######################################################################## - ##################################################################################### - # code_freeze - # ----------------------------------------------------------------------------------- - # This lane executes the steps planned on code freeze - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane code_freeze [skip_confirm:] - # - # Example: - # bundle exec fastlane code_freeze - # bundle exec fastlane code_freeze skip_confirm:true - ##################################################################################### - desc "Creates a new release branch from the current trunk" - lane :code_freeze do | options | - old_version = ios_codefreeze_prechecks(options) - - ios_bump_version_release - new_version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) - extract_release_notes_for_version(version: new_version, - release_notes_file_path: File.join(PROJECT_ROOT_FOLDER, 'RELEASE-NOTES.txt'), - extracted_notes_file_path: File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'Resources', 'release_notes.txt')) - ios_update_release_notes(new_version: new_version) - - push_to_git_remote(tags: false) - setbranchprotection(repository:GHHELPER_REPO, branch: "release/#{new_version}") - setfrozentag(repository:GHHELPER_REPO, milestone: new_version) - - generate_strings_file_for_glotpress - trigger_beta_build(branch_to_build: "release/#{new_version}") - end - - ##################################################################################### - # update_appstore_strings - # ----------------------------------------------------------------------------------- - # This lane updates the AppStoreStrings.pot files with the latest content from - # the release_notes.txt file and the other text sources - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane update_appstore_strings version: - # - # Example: - # bundle exec fastlane update_appstore_strings version:1.1 - ##################################################################################### - desc "Updates the AppStoreStrings.pot file with the latest data" - lane :update_appstore_strings do | options | - prj_folder = Pathname.new(File.join(Dir.pwd, "..")).expand_path.to_s - source_metadata_folder = File.join(prj_folder, "fastlane/appstoreres/metadata/source") - - files = { - whats_new: File.join(prj_folder, "/Simplenote/Resources/release_notes.txt"), - app_store_subtitle: File.join(source_metadata_folder, "subtitle.txt"), - app_store_desc: File.join(source_metadata_folder, "description.txt"), - app_store_keywords: File.join(source_metadata_folder, "keywords.txt") - } - - ios_update_metadata_source( - po_file_path: prj_folder + "/Simplenote/Resources/AppStoreStrings.pot", - source_files: files, - release_version: options[:version] - ) - end - - # This lane lacks documentation because it ought to be part of the localized - # metadata download script instead! - desc "Updates the files with the localized keywords values for App Store Connect to match the 100 characters requirement" - lane :sanitize_appstore_keywords do - Dir["./metadata/**"].each do |locale_dir| - keywords_path = File.join(locale_dir, 'keywords.txt') - - unless File.exist?(keywords_path) - UI.message "Could not find keywords file in #{locale_dir}. Skipping." - next - end - - keywords = File.read(keywords_path) - app_store_connect_keywords_length_limit = 100 - - if keywords.length <= app_store_connect_keywords_length_limit - UI.verbose "#{keywords_path} has less than #{app_store_connect_keywords_length_limit} characters. Not trimming." - next - end - - UI.message "#{keywords_path} has more than #{app_store_connect_keywords_length_limit} characters. Trimming it..." - - english_comma = ',' - arabic_comma = '،' - keywords = keywords.gsub(arabic_comma, english_comma) if File.basename(locale_dir) == 'ar-SA' - - until keywords.length <= app_store_connect_keywords_length_limit - keywords = keywords.split(english_comma)[0...-1].join(english_comma) - end - - File.write(keywords_path, keywords) - end - end - - ##################################################################################### - # new_beta_release - # ----------------------------------------------------------------------------------- - # This lane updates the release branch for a new beta release. It will update the - # current release branch by default. If you want to update a different branch - # (i.e. hotfix branch) pass the related version with the 'base_version' param - # (example: base_version:10.6.1 will work on the 10.6.1 branch) - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane new_beta_release [skip_confirm:] [base_version:] - # - # Example: - # bundle exec fastlane new_beta_release - # bundle exec fastlane new_beta_release skip_confirm:true - # bundle exec fastlane new_beta_release base_version:10.6.1 - ##################################################################################### - desc "Updates a release branch for a new beta release" - lane :new_beta_release do | options | - ios_betabuild_prechecks(options) - download_localized_strings_and_metadata_from_glotpress - ios_lint_localizations(input_dir: 'Simplenote', allow_retry: true) - ios_bump_version_beta - version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) - trigger_beta_build(branch_to_build: "release/#{version}") - end - - ##################################################################################### - # new_hotfix_release - # ----------------------------------------------------------------------------------- - # This lane creates the release branch for a new hotfix release. - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane new_hotfix_release [skip_confirm:] [version:] - # - # Example: - # bundle exec fastlane new_hotfix_release version:10.6.1 - # bundle exec fastlane new_hotfix_release skip_confirm:true version:10.6.1 - ##################################################################################### - desc 'Creates a new hotfix branch for the given version:x.y.z. The branch will be cut from the tag x.y of the previous release' - lane :new_hotfix_release do | options | - prev_ver = ios_hotfix_prechecks(options) - ios_bump_version_hotfix(previous_version: prev_ver, version: options[:version]) - end - - ##################################################################################### - # finalize_hotfix_release - # ----------------------------------------------------------------------------------- - # This lane finalizes the hotfix branch. - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane finalize_hotfix_release [skip_confirm:] - # - # Example: - # bundle exec fastlane finalize_hotfix_release skip_confirm:true - ##################################################################################### - desc 'Performs the final checks and triggers a release build for the hotfix in the current branch' - lane :finalize_hotfix_release do |options| - ios_finalize_prechecks(options) - version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) - trigger_release_build(branch_to_build: "release/#{version}") - end - - ##################################################################################### - # finalize_release - # ----------------------------------------------------------------------------------- - # This lane finalize a release: updates store metadata, bump final version number, - # remove branch protection and close milestone, then trigger the final release on CI - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane finalize_release [skip_confirm:] [version:] - # - # Example: - # bundle exec fastlane finalize_release - # bundle exec fastlane finalize_release skip_confirm:true - ##################################################################################### - desc 'Trigger the final release build on CI' - lane :finalize_release do |options| - UI.user_error!('To finalize a hotfix, please use the finalize_hotfix_release lane instead') if ios_current_branch_is_hotfix - - ios_finalize_prechecks(options) - - UI.message('Checking app strings translation status...') - check_translation_progress( - glotpress_url: 'https://translate.wordpress.com/projects/simplenote/ios/', - abort_on_violations: false - ) - - UI.message("Checking release notes strings translation status...") - check_translation_progress( - glotpress_url: 'https://translate.wordpress.com/projects/simplenote/ios/release-notes/', - abort_on_violations: false - ) - - ios_download_strings_files_from_glotpress( - project_url: 'https://translate.wordpress.com/projects/simplenote/ios/', - locales: GLOTPRESS_TO_LPROJ_APP_LOCALE_CODES, - download_dir: File.join(PROJECT_ROOT_FOLDER, 'Simplenote') - ) - sanitize_appstore_keywords() - ios_lint_localizations(input_dir: 'Simplenote', allow_retry: true) - ios_bump_version_beta() - - # Wrap up - version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) - removebranchprotection(repository:GHHELPER_REPO, branch: "release/#{version}") - setfrozentag(repository:GHHELPER_REPO, milestone: version, freeze: false) - create_new_milestone(repository:GHHELPER_REPO) - close_milestone(repository:GHHELPER_REPO, milestone: version) - - # Start the build - trigger_release_build(branch_to_build: "release/#{version}") - end - - ##################################################################################### - # build_and_upload_beta_release - # ----------------------------------------------------------------------------------- - # This lane builds a beta release of the app and optionally uploads - # it for both internal and external distribution - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane build_and_upload_beta_release [skip_confirm:] - # [create_gh_release:] - # - # Example: - # bundle exec fastlane build_and_upload_beta_release - # bundle exec fastlane build_and_upload_beta_release skip_confirm:true - # bundle exec fastlane build_and_upload_beta_release create_gh_release:true - ##################################################################################### - desc "Builds and uploads a beta release for distribution" - lane :build_and_upload_beta_release do | options | - build_and_upload_release( - skip_confirm: options[:skip_confirm], - create_gh_release: options[:create_gh_release], - beta_release: true) - end - - ##################################################################################### - # build_and_upload_stable_release - # ----------------------------------------------------------------------------------- - # This lane builds a stable release of the app and optionally uploads - # it for distribution - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane build_and_upload_stable_release [skip_confirm:] - # [create_gh_release:] - # - # Example: - # bundle exec fastlane build_and_upload_stable_release - # bundle exec fastlane build_and_upload_stable_release skip_confirm:true - # bundle exec fastlane build_and_upload_stable_release create_gh_release:true - ##################################################################################### - desc "Builds and uploads a stable release for distribution" - lane :build_and_upload_stable_release do | options | - build_and_upload_release( - skip_confirm: options[:skip_confirm], - create_gh_release: options[:create_gh_release], - beta_release: false) - end - - ##################################################################################### - # build_and_upload_release - # ----------------------------------------------------------------------------------- - # This lane builds the app and uploads it for both internal and external distribution - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane build_and_upload_release [skip_confirm:] - # [create_gh_release:] [beta_release:] - # - # Example: - # bundle exec fastlane build_and_upload_release - # bundle exec fastlane build_and_upload_release skip_confirm:true - # bundle exec fastlane build_and_upload_release beta_release:true - ##################################################################################### - desc "Builds and uploads for distribution" - lane :build_and_upload_release do | options | - ios_build_prechecks(skip_confirm: options[:skip_confirm], - internal: false, - internal_on_single_version: options[:beta_release], - external: true) - - ios_build_preflight() - build_and_upload_internal(skip_prechecks: true, skip_confirm: options[:skip_confirm]) if options[:beta_release] - build_and_upload_to_app_store_connect(skip_prechecks: true, skip_confirm: options[:skip_confirm], - beta_release: options[:beta_release], create_release: options[:create_gh_release]) - end - - ##################################################################################### - # build_and_upload_internal - # ----------------------------------------------------------------------------------- - # This lane builds the app and upload it for internal testing - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane build_and_upload_internal [skip_confirm:] - # - # Example: - # bundle exec fastlane build_and_upload_internal - # bundle exec fastlane build_and_upload_internal skip_confirm:true - ##################################################################################### - desc "Builds and updates for distribution" - lane :build_and_upload_internal do | options | - ios_build_prechecks(skip_confirm: options[:skip_confirm], internal: false) unless (options[:skip_prechecks]) - ios_build_preflight() unless (options[:skip_prechecks]) - - internal_code_signing - - gym( - scheme: "Simplenote", - configuration: "Distribution Internal", - workspace: "Simplenote.xcworkspace", - export_method: "enterprise", - clean: true, - output_directory: OUTPUT_DIRECTORY_PATH, - export_team_id: ENV["INT_EXPORT_TEAM_ID"], - export_options: { - method: "enterprise", - provisioningProfiles: simplenote_provisioning_profiles( - root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Internal", - match_type: 'InHouse' - ) - } - ) - - File.rename(File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote.ipa'), File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote Internal.ipa')) - - appcenter_upload( - api_token: ENV["APPCENTER_API_TOKEN"], - owner_name: "automattic", - owner_type: "organization", - app_name: "Simplenote", - file: File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote Internal.ipa'), - notify_testers: false - ) - - sentry_upload_dsym( - auth_token: ENV["SENTRY_AUTH_TOKEN"], - org_slug: 'a8c', - project_slug: 'simplenote-ios', - dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH] - ) - end - - ##################################################################################### - # build_and_upload_to_app_store_connect - # ----------------------------------------------------------------------------------- - # This lane builds the app and upload it for external distribution - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane build_and_upload_to_app_store_connect [skip_confirm:] [create_release:] [beta_release:] - # - # Example: - # bundle exec fastlane build_and_upload_to_app_store_connect - # bundle exec fastlane build_and_upload_to_app_store_connect skip_confirm:true - ##################################################################################### - desc "Builds and uploads for distribution" - lane :build_and_upload_to_app_store_connect do | options | - ios_build_prechecks(skip_confirm: options[:skip_confirm], external: true) unless (options[:skip_prechecks]) - ios_build_preflight() unless (options[:skip_prechecks]) - - appstore_code_signing - - gym( - scheme: "Simplenote", - workspace: "Simplenote.xcworkspace", - configuration: "Distribution AppStore", - clean: true, - export_options: { - method: "app-store", - export_team_id: ENV["EXT_EXPORT_TEAM_ID"], - provisioningProfiles: simplenote_provisioning_profiles - } - ) - - testflight( - skip_waiting_for_build_processing: true, - api_key_path: APP_STORE_CONNECT_KEY_PATH - ) - - sh("rm ../Simplenote.ipa") - dSYM_PATH = File.dirname(Dir.pwd) + "/Simplenote.app.dSYM.zip" - - sentry_upload_dsym( - dsym_path: dSYM_PATH, - auth_token: ENV["SENTRY_AUTH_TOKEN"], - org_slug: 'a8c', - project_slug: 'simplenote-ios', - ) - - sh("rm #{dSYM_PATH}") - - if (options[:create_release]) - archive_zip_path = File.dirname(Dir.pwd) + "/Simplenote.xarchive.zip" - zip(path: lane_context[SharedValues::XCODEBUILD_ARCHIVE], output_path: archive_zip_path) - - version = options[:beta_release] ? ios_get_build_version : ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) - create_release(repository:GHHELPER_REPO, - version: version, - release_notes_file_path: File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'Resources', 'release_notes.txt'), - release_assets: archive_zip_path.to_s, - prerelease: options[:beta_release] - ) - - sh("rm #{archive_zip_path}") - end - end +platform :ios do # Upload the localized metadata (from `fastlane/metadata/`) to App Store Connect # # @option [Boolean] with_screenshots (default: false) If true, will also upload the latest screenshot files to ASC # desc 'Upload the localized metadata to App Store Connect, optionally including screenshots.' - lane :update_metadata_on_app_store_connect do |options| + lane :update_metadata_on_app_store_connect do |with_screenshots: false| # Skip screenshots by default. The naming is "with" to make it clear that # callers need to opt-in to adding screenshots. The naming of the deliver # (upload_to_app_store) parameter, on the other hand, uses the skip verb. - with_screenshots = options.fetch(:with_screenshots, false) skip_screenshots = !with_screenshots upload_to_app_store( app_identifier: APP_STORE_BUNDLE_IDENTIFIER, - app_version: ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH), + app_version: release_version_current, skip_binary_upload: true, screenshots_path: promo_screenshots_directory, skip_screenshots: skip_screenshots, @@ -525,36 +100,6 @@ end ) end - ##################################################################################### - # trigger_beta_build - # ----------------------------------------------------------------------------------- - # This lane triggers a beta build on CI - # ----------------------------------------------------------------------------------- - # @option [String] branch_to_build The name of the branch we want the CI to build, e.g. `release/19.3` - # - # Usage: - # bundle exec fastlane trigger_beta_build [branch_to_build:] - # - ##################################################################################### - lane :trigger_beta_build do |options| - trigger_buildkite_release_build(branch: options[:branch_to_build], beta: true) - end - - ##################################################################################### - # trigger_release_build - # ----------------------------------------------------------------------------------- - # This lane triggers a stable release build on CI - # ----------------------------------------------------------------------------------- - # @option [String] branch_to_build The name of the branch we want the CI to build, e.g. `release/19.3` - # - # Usage: - # bundle exec fastlane trigger_release_build [branch_to_build:] - # - ##################################################################################### - lane :trigger_release_build do |options| - trigger_buildkite_release_build(branch: options[:branch_to_build], beta: false) - end - ##################################################################################### # build_and_upload_installable_build # ----------------------------------------------------------------------------------- @@ -566,8 +111,8 @@ end # Example: # bundle exec fastlane build_and_upload_installable_build ##################################################################################### - desc "Builds and uploads an installable build" - lane :build_and_upload_installable_build do | options | + desc 'Builds and uploads an installable build' + lane :build_and_upload_installable_build do alpha_code_signing # Get the current build version, and update it if needed @@ -575,15 +120,15 @@ end apply_build_number(build_number) gym( - scheme: "Simplenote", - configuration: "Distribution Alpha", - workspace: "Simplenote.xcworkspace", - export_method: "enterprise", + scheme: 'Simplenote', + configuration: 'Distribution Alpha', + workspace: WORKSPACE, + export_method: 'enterprise', clean: true, output_directory: OUTPUT_DIRECTORY_PATH, - export_team_id: ENV["INT_EXPORT_TEAM_ID"], + export_team_id: TEAM_ID_ENTERPRISE, export_options: { - method: "enterprise", + method: 'enterprise', provisioningProfiles: simplenote_provisioning_profiles( root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Alpha", match_type: 'InHouse' @@ -594,17 +139,17 @@ end File.rename(File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote.ipa'), File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote Alpha.ipa')) appcenter_upload( - api_token: get_required_env("APPCENTER_API_TOKEN"), - owner_name: "automattic", - owner_type: "organization", - app_name: "Simplenote-Installable-Builds", + api_token: EnvManager.get_required_env!('APPCENTER_API_TOKEN'), + owner_name: 'automattic', + owner_type: 'organization', + app_name: 'Simplenote-Installable-Builds', file: File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote Alpha.ipa'), - destinations: "All-Users-of-Simplenote-Installable-Builds", + destinations: 'All-Users-of-Simplenote-Installable-Builds', notify_testers: false ) sentry_upload_dsym( - auth_token: get_required_env("SENTRY_AUTH_TOKEN"), + auth_token: EnvManager.get_required_env!('SENTRY_AUTH_TOKEN'), org_slug: 'a8c', project_slug: 'simplenote-ios', dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH] @@ -612,43 +157,30 @@ end return if ENV['BUILDKITE_PULL_REQUEST'].nil? - post_installable_build_pr_comment( - app_name: 'simplenote-ios', - build_number: build_number, - url_slug: 'Simplenote-Installable-Builds' - ) + post_installable_build_pr_comment end # Posts a comment on the current PR to inform where to download a given Installable Build that was just published to App Center. # # Use this only after `upload_to_app_center` as been called, as it announces how said App Center build can be installed. # - # @param [String] app_name The display name to use in the comment text to identify which app this Installable Build is about - # @param [String] build_number The App Center's build number for this build - # @param [String] url_slug The last component of the `install.appcenter.ms` URL used to install the app. Typically a sluggified version of the app name in App Center (e.g. `WPiOS-One-Offs`) - # # @called_by CI — especially, relies on `BUILDKITE_PULL_REQUEST` being defined # - def post_installable_build_pr_comment(app_name:, build_number:, url_slug:) - UI.message("Successfully built and uploaded installable build `#{build_number}` to App Center.") + def post_installable_build_pr_comment + pr = ENV.fetch('BUILDKITE_PULL_REQUEST', nil) - return if ENV['BUILDKITE_PULL_REQUEST'].nil? + return if pr.nil? - install_url = "https://install.appcenter.ms/orgs/automattic/apps/#{url_slug}/" - qr_code_url = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=#{CGI.escape(install_url)}&choe=UTF-8" - comment_body = <<~COMMENT_BODY - You can test the changes in #{app_name} from this Pull Request by:
    -
  • Clicking here or scanning the QR code below to access App Center
  • -
  • Then installing the build number #{build_number} on your iPhone
  • -
- - If you need access to App Center, please ask a maintainer to add you. - COMMENT_BODY + comment_body = prototype_build_details_comment( + app_display_name: 'Simplenote Prototype Build', + app_center_org_name: APPCENTER_OWNER_NAME, + fold: true + ) comment_on_pr( - project: 'automattic/simplenote-ios', - pr_number: Integer(ENV.fetch('BUILDKITE_PULL_REQUEST', nil)), - reuse_identifier: "installable-build-link--#{url_slug}", + project: GITHUB_REPO, + pr_number: Integer(pr), + reuse_identifier: 'installable-build-link--Simplenote-Installable-Builds', body: comment_body ) end @@ -663,7 +195,7 @@ end # On the other hand, right now Simplenote only needs one locale, so we might # as well keep using this on CI too. We still benefit from not having to # rebuild the app just to take the screenshots in light vs dark mode. - desc "Walk through the app taking screenshots." + desc 'Walk through the app taking screenshots.' lane :take_screenshots do |options| build_app_for_screenshots(options) @@ -677,22 +209,22 @@ end ) end - desc "Build the binaries to run to take the screenshots" + desc 'Build the binaries to run to take the screenshots' lane :build_app_for_screenshots do scan( workspace: workspace_path, scheme: screenshots_scheme, build_for_testing: true, - derived_data_path: derived_data_directory, + derived_data_path: derived_data_directory ) end - desc "Runs through the app taking screenshots, using a prebuilt binary" + desc 'Runs through the app taking screenshots, using a prebuilt binary' lane :take_screenshots_from_app do |options| - devices = (options[:devices] || screenshot_devices).split(',').flatten() + devices = (options[:devices] || screenshot_devices).split(',').flatten languages = [ - "en-US" + 'en-US' ] # Erase the Simulators between runs in order to get everything back to a @@ -738,20 +270,20 @@ end concurrent_simulators: false, # Allow the caller to invoke dark mode - dark_mode: options[:mode].to_s.downcase == "dark" + dark_mode: options[:mode].to_s.downcase == 'dark' ) end lane :create_promo_screenshots do |options| - unless Fastlane::Helper::GitHelper.has_git_lfs then - UI.user_error!("Git LFS not enabled – Unable to generate promo screenshots. Run `git lfs install && git lfs fetch && git lfs pull` to fix this.") + unless Fastlane::Helper::GitHelper.has_git_lfs + UI.user_error!('Git LFS not enabled – Unable to generate promo screenshots. Run `git lfs install && git lfs fetch && git lfs pull` to fix this.') end # This value is defined in style.css. It would be good if there was a way # to make it parametric, so that if we update the CSS we don't risk this # getting out of sync. - font_name = "SourceSansPro-Regular.ttf" - user_font_directory = File.join(Dir.home, "Library/Fonts") + font_name = 'SourceSansPro-Regular.ttf' + user_font_directory = File.join(Dir.home, 'Library/Fonts') user_font_path = File.join(user_font_directory, font_name) if File.exist?(user_font_path) UI.success("Custom font #{font_name} already installed locally.") @@ -763,13 +295,13 @@ end promo_screenshots( orig_folder: options[:source] || screenshots_directory, - metadata_folder: File.join(Dir.pwd, "metadata"), + metadata_folder: File.join(Dir.pwd, 'metadata'), output_folder: promo_screenshots_directory, force: options[:force] || true ) end - desc "Rebuild Screenshot Devices" + desc 'Rebuild Screenshot Devices' lane :rebuild_screenshot_devices do |options| require 'simctl' @@ -779,18 +311,19 @@ end device_names = (options[:devices] || screenshot_devices).split(',').flatten sim_version = options[:simulator_version] || simulator_version - SimCtl.list_devices.each { |device| + SimCtl.list_devices.each do |device| next unless device_names.include? device.name + UI.message("Deleting #{device.name} because it already exists.") device.delete - } + end - device_names.each { |device| + device_names.each do |device| runtime = SimCtl.runtime(name: "iOS #{sim_version}") devicetype = SimCtl.devicetype(name: device) SimCtl.create_device device, devicetype, runtime - } + end end ##################################################################################### @@ -812,12 +345,12 @@ end # Example: # bundle exec fastlane test device:"iPhone 14" ##################################################################################### - desc "Run Unit Tests" - lane :run_unit_tests do | options | + desc 'Run Unit Tests' + lane :run_unit_tests do |options| scan( workspace: workspace_name, scheme: 'Simplenote', - device: options[:device] || "iPhone 14", + device: options[:device] || 'iPhone 14', output_directory: OUTPUT_DIRECTORY_PATH, reset_simulator: true, result_bundle: true @@ -836,12 +369,12 @@ end # Example: # bundle exec fastlane run_ui_tests scheme:"SimplenoteUITests_Subset" device:"iPhone 14" test_account:"test.account@test.com" ##################################################################################### - desc "Run UI tests" - lane :run_ui_tests do | options | + desc 'Run UI tests' + lane :run_ui_tests do |options| scan( workspace: workspace_name, - scheme: options[:scheme] || "SimplenoteUITests_Subset", - device: options[:device] || "iPhone 14", + scheme: options[:scheme] || 'SimplenoteUITests_Subset', + device: options[:device] || 'iPhone 14', output_directory: OUTPUT_DIRECTORY_PATH, reset_simulator: true, xcargs: { UI_TEST_ACCOUNT: options[:test_account] || '' }, @@ -861,219 +394,198 @@ end # Example: # bundle exec fastlane pick_test_account_and_run_ui_tests device:"iPhone 14" ##################################################################################### - desc "Occupy a free test account and run UI tests with it" - lane :pick_test_account_and_run_ui_tests do | options | + desc 'Occupy a free test account and run UI tests with it' + lane :pick_test_account_and_run_ui_tests do |options| if is_ci sanitize_test_accounts find_free_test_account - run_ui_tests(options.merge({ test_account: get_required_env("UI_TESTS_ACCOUNT_EMAIL").sub("X", $used_test_account_index.to_s)})) + run_ui_tests(options.merge({ test_account: EnvManager.get_required_env!('UI_TESTS_ACCOUNT_EMAIL').sub('X', $used_test_account_index.to_s) })) deoccupy_test_account else - UI.user_error!("This lane should be run only from CI") + UI.user_error!('This lane should be run only from CI') end end +end - desc 'Registers a Device in the developer console' - lane :register_new_device do |options| - device_name = UI.input('Device Name: ') if options[:device_name].nil? - device_id = UI.input('Device ID: ') if options[:device_id].nil? - # Currently, Simplenote has an odd setup with a dedicated app id for development builds - all_bundle_ids = - simplenote_app_identifiers + - simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Development") - - UI.message "Registering #{device_name} with ID #{device_id} and registering it with any provisioning profiles associated with these bundle identifiers:" - all_bundle_ids.each do |identifier| - UI.message "\t#{identifier}" - end - - team_id = get_required_env('EXT_EXPORT_TEAM_ID') - - # Register the user's device - register_device( - name: device_name, - udid: device_id, - team_id: team_id, - api_key_path: APP_STORE_CONNECT_KEY_PATH - ) - - # We're about to use `add_development_certificates_to_provisioning_profiles` and `add_all_devices_to_provisioning_profiles`. - # These actions use Developer Portal APIs that don't yet support authentication via API key (-.-'). - # Let's preemptively ask for and set the email here to avoid being asked twice for it if not set. - - require 'credentials_manager' +######################################################################## +# Fastlane Match Code Signing Lanes +######################################################################## - # If Fastlane cannot instantiate a user, it will ask the caller for the email. - # Once we have it, we can set it as `FASTLANE_USER` in the environment (which has lifecycle limited to this call) so that the next commands will already have access to it. - # Note that if the user is already available to `AccountManager`, setting it in the environment is redundant, but Fastlane doesn't provide a way to check it so we have to do it anyway. - ENV['FASTLANE_USER'] = CredentialsManager::AccountManager.new.user +# Downloads all the required certificates and profiles for both production and internal distribution builds. +# Optionally, it can create any new necessary certificates or profiles. +# +# @option [Boolean] readonly (default: true) Whether to only fetch existing certificates and profiles, without generating new ones. +lane :update_certs_and_profiles do |options| + alpha_code_signing(options) + internal_code_signing(options) + appstore_code_signing(options) +end - # Add all development certificates to the provisioning profiles (just in case – this is an easy step to miss) - add_development_certificates_to_provisioning_profiles( - team_id: team_id, - app_identifier: all_bundle_ids - ) +# Downloads all the required certificates and profiles (using `match`) for the alpha builds in the enterprise account. +# Optionally, it can create any new necessary certificates or profiles. +# +# @option [Boolean] readonly (default: true) Whether to only fetch existing certificates and profiles, without generating new ones. +# +private_lane :alpha_code_signing do |options| + update_code_signing_enterprise( + app_identifiers: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Alpha"), + readonly: options.fetch(:readonly, true) + ) +end - # Add all devices to the provisioning profiles - add_all_devices_to_provisioning_profiles( - team_id: team_id, - app_identifier: all_bundle_ids - ) - end +# Downloads all the required certificates and profiles (using `match`) for the internal builds in the enterprise account. +# Optionally, it can create any new necessary certificates or profiles. +# +# @option [Boolean] readonly (default: true) Whether to only fetch existing certificates and profiles, without generating new ones. +# +private_lane :internal_code_signing do |options| + update_code_signing_enterprise( + app_identifiers: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Internal"), + readonly: options.fetch(:readonly, true) + ) end -######################################################################## -# Configure Lanes -######################################################################## - ##################################################################################### - # update_certs_and_profiles - # ----------------------------------------------------------------------------------- - # This lane downloads all the required certs and profiles and, - # if not run on CI it creates the missing ones. - # ----------------------------------------------------------------------------------- - # Usage: - # bundle exec fastlane update_certs_and_profiles - # - # Example: - # bundle exec fastlane update_certs_and_profiles - ##################################################################################### - lane :update_certs_and_profiles do | options | - alpha_code_signing - internal_code_signing - appstore_code_signing - end +# Downloads all the required certificates and profiles for the production build. +# Optionally, it can create any new necessary certificates or profiles. +# +# @option [Boolean] readonly (default: true) Whether to only fetch existing certificates and profiles, without generating new ones. +private_lane :appstore_code_signing do |options| + update_code_signing( + type: 'appstore', + team_id: TEAM_ID_APP_STORE_CONNECT, + readonly: options.fetch(:readonly, true), + app_identifiers: simplenote_app_identifiers, + api_key_path: APP_STORE_CONNECT_KEY_PATH, + template_name: 'NotationalFlow Keychain Access (Distribution)' + ) +end -######################################################################## -# Fastlane match code signing -######################################################################## +def update_code_signing_enterprise(readonly:, app_identifiers:) + if readonly + # In readonly mode, we can use the API key + api_key_path = APP_STORE_CONNECT_KEY_PATH + else + # The Enterprise account APIs do not support authentication via API key. + # If we want to modify data (readonly = false) we need to authenticate manually. + prompt_user_for_app_store_connect_credentials + # We also need to pass no API key path, otherwise Fastlane will give + # precedence to that authentication mode. + api_key_path = nil + end + + update_code_signing( + type: 'enterprise', + # Enterprise builds belong to the "internal" team + team_id: TEAM_ID_ENTERPRISE, + readonly: readonly, + app_identifiers: app_identifiers, + api_key_path: api_key_path + ) +end - private_lane :alpha_code_signing do |options| - match( - type: "enterprise", - team_id: get_required_env("INT_EXPORT_TEAM_ID"), - readonly: options[:readonly] || is_ci, - app_identifier: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Alpha") - ) - end +# rubocop:disable Metrics/ParameterLists +def update_code_signing(type:, team_id:, readonly:, app_identifiers:, api_key_path:, template_name: nil) + # NOTE: It might be neccessary to add `force: true` alongside `readonly: true` in order to regenerate some provisioning profiles. + # If this turns out to be a hard requirement, we should consider updating the method with logic to toggle the two setting based on whether we're fetching or renewing. + + # Fail early if secrets not available via `EnvManager.get_required_env!`. + # Otherwise, Fastlane will prompt to type them. + access_key = EnvManager.get_required_env!('MATCH_S3_ACCESS_KEY') + secret_access_key = EnvManager.get_required_env!('MATCH_S3_SECRET_ACCESS_KEY') + + match( + storage_mode: 's3', + s3_bucket: 'a8c-fastlane-match', + s3_region: 'us-east-2', + s3_access_key: access_key, + s3_secret_access_key: secret_access_key, + type: type, + team_id: team_id, + readonly: readonly, + app_identifier: app_identifiers, + api_key_path: api_key_path, + template_name: template_name + ) +end +# rubocop:enable Metrics/ParameterLists - private_lane :internal_code_signing do |options| - match( - type: "enterprise", - team_id: get_required_env("INT_EXPORT_TEAM_ID"), - readonly: options[:readonly] || is_ci, - app_identifier: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Internal") - ) - end +# Compiles the array of bundle identifiers for the different targets that +# make up the Simplenote app, to be used as the `app_identifier` parameter +# for the `match` action. +def simplenote_app_identifiers(root_bundle_id: APP_STORE_BUNDLE_IDENTIFIER) + extension_bundle_ids = + %w[Share Widgets Intents] + .map { |suffix| "#{root_bundle_id}.#{suffix}" } - private_lane :appstore_code_signing do |options| - match( - type: "appstore", - team_id: get_required_env("EXT_EXPORT_TEAM_ID"), - readonly: options[:readonly] || is_ci, - app_identifier: simplenote_app_identifiers, - template_name: "NotationalFlow Keychain Access (Distribution)", - ) - end + [root_bundle_id, *extension_bundle_ids] +end - # Compiles the array of bundle identifiers for the different targets that - # make up the Simplenote app, to be used as the `app_identifier` parameter - # for the `match` action. - def simplenote_app_identifiers(root_bundle_id: APP_STORE_BUNDLE_IDENTIFIER) - extension_bundle_ids = - %w[Share Widgets Intents] - .map { |suffix| "#{root_bundle_id}.#{suffix}" } +# Compiles the dictionary mapping of the bundle identifiers and provisioning +# profiles to be used in the `export_options > provisioningProfiles` +# parameter for the `build_app`/`gym` action. +def simplenote_provisioning_profiles(root_bundle_id: APP_STORE_BUNDLE_IDENTIFIER, match_type: 'AppStore') + # FIXME: replace the array below with the following call once established + # the impact of adding the mapping for the Widgets extension, which is + # something we haven't had up to this point. + # + # simplenote_app_identifiers(root_bundle_id: root_bundle_id) + [root_bundle_id, "#{root_bundle_id}.Share", "#{root_bundle_id}.Intents"] + .to_h { |key| [key, "match #{match_type} #{key}"] } +end - [root_bundle_id, *extension_bundle_ids] - end +def prompt_user_for_app_store_connect_credentials + require 'credentials_manager' - # Compiles the dictionary mapping of the bundle identifiers and provisioning - # profiles to be used in the `export_options > provisioningProfiles` - # parameter for the `build_app`/`gym` action. - def simplenote_provisioning_profiles(root_bundle_id: APP_STORE_BUNDLE_IDENTIFIER, match_type: 'AppStore') - # FIXME: replace the array below with the following call once established - # the impact of adding the mapping for the Widgets extension, which is - # something we haven't had up to this point. - # - # simplenote_app_identifiers(root_bundle_id: root_bundle_id) - [root_bundle_id, "#{root_bundle_id}.Share", "#{root_bundle_id}.Intents"] - .map { |key| [key, "match #{match_type} #{key}"] } - .to_h - end + # If Fastlane cannot instantiate a user, it will ask the caller for the email. + # Once we have it, we can set it as `FASTLANE_USER` in the environment (which has lifecycle limited to this call) so that the next commands will + # already have access to it. + # Note that if the user is already available to `AccountManager`, setting it in the environment is redundant, but Fastlane doesn't provide a way + # to check it so we have to do it anyway. + ENV['FASTLANE_USER'] = CredentialsManager::AccountManager.new.user +end ######################################################################## # Localization Lanes ######################################################################## -desc 'Updates the main `Localizable.strings` file — that will be imported by GlotPress' -lane :generate_strings_file_for_glotpress do - en_lproj_path = File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'en.lproj') - ios_generate_strings_file_from_code( - paths: ['Simplenote/', 'SimplenoteIntents/', 'SimplenoteShare/', - 'SimplenoteWidgets/', 'Pods/Simperium/Simperium/', - 'Pods/Simperium/Simperium-iOS'], - output_dir: en_lproj_path) - - git_commit( - path: en_lproj_path, - message: 'Freeze strings for localization', - allow_nothing_to_commit: true - ) -end - ######################################################################## # Helper Lanes ######################################################################## -# Triggers a Release Build on Buildkite -# -# @param [String] branch The branch to build -# @param [Boolean] beta Indicate if we should build a beta or regular release -# -def trigger_buildkite_release_build(branch:, beta:) - push_to_git_remote(tags: false) - buildkite_trigger_build( - buildkite_organization: 'automattic', - buildkite_pipeline: 'simplenote-ios', - branch: branch, - environment: { BETA_RELEASE: beta }, - pipeline_file: 'release-build.yml' - ) -end - -def fastlane_directory() - File.expand_path File.dirname(__FILE__) +def fastlane_directory + __dir__ end def derived_data_directory - File.join(fastlane_directory, "DerivedData") + File.join(fastlane_directory, 'DerivedData') end def workspace_name - "Simplenote.xcworkspace" + 'Simplenote.xcworkspace' end def screenshots_scheme - "SimplenoteScreenshots" + 'SimplenoteScreenshots' end def workspace_path File.join(fastlane_directory, "../#{workspace_name}") end -def screenshots_directory() - File.join(fastlane_directory, "screenshots") +def screenshots_directory + File.join(fastlane_directory, 'screenshots') end def promo_screenshots_directory - File.join(fastlane_directory, "promo_screenshots") + File.join(fastlane_directory, 'promo_screenshots') end -def screenshot_devices() +def screenshot_devices [ - "iPhone X", - "iPhone 8", - "iPad Pro (12.9-inch) (2nd generation)", - "iPad Pro (12.9-inch) (3rd generation)", + 'iPhone X', + 'iPhone 8', + 'iPad Pro (12.9-inch) (2nd generation)', + 'iPad Pro (12.9-inch) (3rd generation)' ] end @@ -1082,9 +594,10 @@ def simulator_version end def deoccupy_test_account - return if $used_test_account_index == nil + return if $used_test_account_index.nil? + UI.message("Freeing used test account #{$used_test_account_index}") - change_test_account_availability("free") + change_test_account_availability('free') $used_test_account_index = nil end @@ -1092,10 +605,10 @@ end # the account index, and values are either "free" or BUILDKITE_BUILD_NUMBER, in case if account # was occupied by a build. Returns the hash. # Example: -# get_test_accounts_hash +# fetch_test_accounts_hash # => {"0"=>"4079", "1"=>"free", "2"=>"free", "3"=>"free"} -def get_test_accounts_hash - uri = URI.parse(get_required_env("UI_TESTS_ACCOUNTS_JSON_URL")) +def fetch_test_accounts_hash + uri = URI.parse(EnvManager.get_required_env!('UI_TESTS_ACCOUNTS_JSON_URL')) response = Net::HTTP.get_response(uri) accounts_state = response.body.chomp JSON.parse(accounts_state) @@ -1104,15 +617,15 @@ end # Finds the index of first free account in accounts hash, and marks it as accupied by current build. # Crashed fastlane if free account was not found. def find_free_test_account - accounts_hash = get_test_accounts_hash - UI.message("Looking for a free test account...") + accounts_hash = fetch_test_accounts_hash + UI.message('Looking for a free test account...') UI.message("Accounts state: #{accounts_hash}") $used_test_account_index = accounts_hash.key('free') - UI.user_error!("Could not find free UI Test account. Quitting.") if $used_test_account_index.nil? + UI.user_error!('Could not find free UI Test account. Quitting.') if $used_test_account_index.nil? UI.message("Free account index: #{$used_test_account_index}") - change_test_account_availability("#{get_required_env("BUILDKITE_BUILD_NUMBER")}") + change_test_account_availability(ENV['BUILDKITE_BUILD_NUMBER'].to_s) end # Replace a value in a key which is equal to $used_test_account_index global variable @@ -1123,53 +636,52 @@ end # change_test_account_availability("4820") # => {"0"=>"4820", "1"=>"free", "2"=>"free", "3"=>"free"} def change_test_account_availability(availability) - accounts_hash = get_test_accounts_hash + accounts_hash = fetch_test_accounts_hash accounts_hash[$used_test_account_index.to_s] = availability submit_accounts_hash(accounts_hash) end def sanitize_test_accounts - accounts_hash = get_test_accounts_hash - UI.message("Sanitizing stale test accounts...") + accounts_hash = fetch_test_accounts_hash + UI.message('Sanitizing stale test accounts...') UI.message("Accounts before sanitizing: #{accounts_hash}") accounts_hash.each do |key, value| - next if value == "free" || is_job_running(value) - accounts_hash[key] = "free" + next if value == 'free' || job_running?(value) + + accounts_hash[key] = 'free' submit_accounts_hash(accounts_hash) end end -def is_job_running(job_number) - begin - client = Buildkit.new(token: get_required_env('BUILDKITE_TOKEN')) - build = client.build('automattic', 'simplenote-ios', job_number) +def job_running?(job_number) + client = Buildkit.new(token: EnvManager.get_required_env!('BUILDKITE_TOKEN')) + build = client.build('automattic', 'simplenote-ios', job_number) - state = build['state'] + state = build['state'] - UI.message("Status of Job Number #{job_number} is #{state}") + UI.message("Status of Job Number #{job_number} is #{state}") - build['state'] == 'running' - rescue - false - end + build['state'] == 'running' +rescue StandardError + false end def submit_accounts_hash(accounts_hash) UI.message("Writing new accounts state: #{accounts_hash}") - uri = URI.parse(get_required_env("UI_TESTS_ACCOUNTS_JSON_URL")) + uri = URI.parse(EnvManager.get_required_env!('UI_TESTS_ACCOUNTS_JSON_URL')) request = Net::HTTP::Post.new(uri) request.body = accounts_hash.to_json req_options = { - use_ssl: uri.scheme == "https", + use_ssl: uri.scheme == 'https' } - response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http| + Net::HTTP.start(uri.hostname, uri.port, req_options) do |http| http.request(request) end - UI.message("Accounts state after write: #{get_test_accounts_hash}") + UI.message("Accounts state after write: #{fetch_test_accounts_hash}") end def apply_build_number(build_number) @@ -1196,3 +708,66 @@ def generate_installable_build_number "#{branch}-#{commit}" end end + +# Returns the release version of the app in the format `1.2` or `1.2.3` if it is a hotfix +# +def release_version_current + current_version = VERSION_FORMATTER.parse(PUBLIC_VERSION_FILE.read_release_version) + VERSION_FORMATTER.release_version(current_version) +end + +# Returns the next release version of the app in the format `1.2` or `1.2.3` if it is a hotfix +# +def release_version_next + current_version = VERSION_FORMATTER.parse(PUBLIC_VERSION_FILE.read_release_version) + next_calculated_release_version = VERSION_CALCULATOR.next_release_version(version: current_version) + VERSION_FORMATTER.release_version(next_calculated_release_version) +end + +# Returns the current build code of the app +# +def build_code_current + # We use the four part (1.2.3.4) build code format, so the version calculator can be used to calculate the next four-part version + version = VERSION_FORMATTER.parse(PUBLIC_VERSION_FILE.read_build_code(attribute_name: 'VERSION_LONG')) + BUILD_CODE_FORMATTER.build_code(version: version) +end + +# Returns the build code of the app for the code freeze. +# It is the release version name with build number (last of the four components) set to 0. +# +def build_code_code_freeze + # We use the four part (1.2.3.4) build code format, so the version calculator can be used to calculate the next four-part version + release_version_current = VERSION_FORMATTER.parse(PUBLIC_VERSION_FILE.read_release_version) + build_code_code_freeze = VERSION_CALCULATOR.next_release_version(version: release_version_current) + BUILD_CODE_FORMATTER.build_code(version: build_code_code_freeze) +end + +# Returns the build code of the app for the code freeze. +# It is the release version name with build number (last of the four components) set to 0. +# +def build_code_hotfix(release_version:) + version = VERSION_FORMATTER.parse(release_version) + BUILD_CODE_FORMATTER.build_code(version: version) +end + +# Returns the next build code of the app +# +def build_code_next + # We use the four part (1.2.3.4) build code format, so the version calculator can be used to calculate the next four-part version + build_code_current = VERSION_FORMATTER.parse(PUBLIC_VERSION_FILE.read_build_code(attribute_name: 'VERSION_LONG')) + build_code_next = VERSION_CALCULATOR.next_build_number(version: build_code_current) + BUILD_CODE_FORMATTER.build_code(version: build_code_next) +end + +def release_branch_name(release_version: release_version_current) + "#{RELEASE_BRANCH_ROOT}#{release_version}" +end + +def ensure_git_branch_is_release_branch! + # Verify that the current branch is a release branch. + # Notice that `ensure_git_branch` expects a RegEx parameter. + # Also, ensure_git_branch will fail the lane if the branch doesn't match, hence the ! in the method name. + ensure_git_branch(branch: "^#{RELEASE_BRANCH_ROOT}") +end + +RELEASE_BRANCH_ROOT = 'release/' diff --git a/fastlane/Matchfile b/fastlane/Matchfile deleted file mode 100644 index 2ee895e90..000000000 --- a/fastlane/Matchfile +++ /dev/null @@ -1,10 +0,0 @@ -# This Matchfile has the shared properties used for all signing types - -# Store certs/profiles encrypted in Google Cloud -storage_mode('google_cloud') -google_cloud_bucket_name('a8c-fastlane-match') -# Interestingly, Using the path with '~/.configure/...' results in Fastlane -# failing to find the file. -SECRETS_ROOT = File.join(Dir.home, '.configure/simplenote-ios/secrets') -google_cloud_keys_file(File.join(SECRETS_ROOT, 'google_cloud_keys.json')) -api_key_path(File.join(SECRETS_ROOT, 'app_store_connect_fastlane_api_key.json')) diff --git a/fastlane/appstoreres/metadata/source/description.txt b/fastlane/appstoreres/metadata/source/description.txt index ef31ca421..737c98735 100644 --- a/fastlane/appstoreres/metadata/source/description.txt +++ b/fastlane/appstoreres/metadata/source/description.txt @@ -30,7 +30,7 @@ ORGANIZE AND SEARCH Privacy Policy: https://automattic.com/privacy/ Terms of Service: https://simplenote.com/terms/ -California Users Privacy Notice: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Privacy Notice for California Users: https://automattic.com/privacy/#us-privacy-laws -- diff --git a/fastlane/download_metadata.swift b/fastlane/download_metadata.swift deleted file mode 100755 index 78f14dfc2..000000000 --- a/fastlane/download_metadata.swift +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env swift - -import Foundation - -let glotPressTitleKey = "app_store_title" -let glotPressSubtitleKey = "app_store_subtitle" -let glotPressWhatsNewKey = "v4.49-whats-new" -let glotPressDescriptionKey = "app_store_desc" -let glotPressKeywordsKey = "app_store_keywords" - -let scriptURL = URL(fileURLWithPath: CommandLine.arguments[0], relativeTo: URL(fileURLWithPath: FileManager.default.currentDirectoryPath)) -let fastlaneDir = scriptURL.deletingLastPathComponent() -let baseFolder = fastlaneDir.appendingPathComponent("metadata").path - -// iTunes Connect language code: GlotPress code -let languages = [ - "ar-SA": "ar", - "de-DE": "de", - "default": "en-us", // Technically not a real GlotPress language - "en-US": "en-us", // Technically not a real GlotPress language - "es-ES": "es", - "fr-FR": "fr", - "id": "id", - "it": "it", - "ja": "ja", - "ko": "ko", - "nl-NL": "nl", - "pt-BR": "pt-br", - "ru": "ru", - "sv": "sv", - "tr": "tr", - "zh-Hans": "zh-cn", - "zh-Hant": "zh-tw", -] - -func downloadTranslation(languageCode: String, folderName: String) { - let languageCodeOverride = languageCode == "en-us" ? "es" : languageCode - let glotPressURL = "https://translate.wordpress.com/projects/simplenote%2Fios%2Frelease-notes/\(languageCodeOverride)/default/export-translations?format=json" - let requestURL: URL = URL(string: glotPressURL)! - let urlRequest: URLRequest = URLRequest(url: requestURL) - let session = URLSession.shared - - let sema = DispatchSemaphore( value: 0) - - print("Downloading Language: \(languageCode)") - - let task = session.dataTask(with: urlRequest) { - (data, response, error) -> Void in - - defer { - sema.signal() - } - - guard let data = data else { - print(" Invalid data downloaded.") - return - } - - guard let json = try? JSONSerialization.jsonObject(with: data, options: []), - let jsonDict = json as? [String: Any] else { - print(" JSON was not returned") - return - } - - var title: String? - var subtitle: String? - var whatsNew: String? - var keywords: String? - var storeDescription: String? - - jsonDict.forEach({ (key: String, value: Any) in - - guard let index = key.firstIndex(of: Character(UnicodeScalar(0004))) else { - return - } - - let keyFirstPart = String(key[.. -DELIVER_USER= - GHHELPER_ACCESS= APPCENTER_API_TOKEN= SENTRY_AUTH_TOKEN= - -CIRCLE_CI_AUTH_TOKEN= diff --git a/fastlane/example.env b/fastlane/example.env new file mode 100644 index 000000000..4d561bbe6 --- /dev/null +++ b/fastlane/example.env @@ -0,0 +1,17 @@ +# You don't need to fill in these values for Fastlane to run. +# +# However, if a lane requires some of these environment values and they are not set here, it will fail. +GITHUB_TOKEN= +BUILDKITE_TOKEN= + +MATCH_S3_ACCESS_KEY= +MATCH_S3_SECRET_ACCESS_KEY= + +APP_STORE_CONNECT_API_KEY_KEY_ID= +APP_STORE_CONNECT_API_KEY_ISSUER_ID= +APP_STORE_CONNECT_API_KEY_KEY= + +APPCENTER_API_TOKEN= +SENTRY_AUTH_TOKEN= + +UI_TESTS_ACCOUNT_EMAIL= diff --git a/fastlane/lanes/build.rb b/fastlane/lanes/build.rb new file mode 100644 index 000000000..62b357c0e --- /dev/null +++ b/fastlane/lanes/build.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +APP_STORE_CONNECT_OUTPUT_NAME = 'Simplenote-AppStore' +XCARCHIVE_PATH = File.join(OUTPUT_DIRECTORY_PATH, "#{APP_STORE_CONNECT_OUTPUT_NAME}.xcarchive") +XCARCHIVE_ZIP_PATH = File.join(OUTPUT_DIRECTORY_PATH, "#{APP_STORE_CONNECT_OUTPUT_NAME}.xcarchive.zip") + +lane :build_for_app_store_connect do |fetch_code_signing: true| + appstore_code_signing if fetch_code_signing + + build_app( + scheme: 'Simplenote', + workspace: WORKSPACE, + configuration: 'Distribution AppStore', + clean: true, + export_method: 'app-store', + # The options below might seem redundant but are currently all necessary to have predictable artifact paths to use in other lanes. + # + # - archive_path sets the full path for the xcarchive. + # - output_directory and output_name set the path and basename for the ipa and dSYM. + # + # We could have used 'build_path: OUTPUT_DIRECTORY_PATH' for the xcarchive... + # ...but doing so would append a timestamp and unnecessarily complicate other logic to get the path + archive_path: XCARCHIVE_PATH, + output_directory: OUTPUT_DIRECTORY_PATH, + output_name: APP_STORE_CONNECT_OUTPUT_NAME + ) + + # It's convenient to have a ZIP available for things like CI uploads + UI.message("Zipping #{XCARCHIVE_PATH} to #{XCARCHIVE_ZIP_PATH}...") + zip(path: XCARCHIVE_PATH, output_path: XCARCHIVE_ZIP_PATH) +end + +lane :upload_to_app_store_connect do |beta_release:, skip_prechecks: false, create_release: false| + sentry_check_cli_installed unless skip_prechecks + + ipa_path = File.join(OUTPUT_DIRECTORY_PATH, "#{APP_STORE_CONNECT_OUTPUT_NAME}.ipa") + UI.user_error!("Could not find ipa at #{ipa_path}!") unless File.exist?(ipa_path) + + dsym_path = File.join(OUTPUT_DIRECTORY_PATH, "#{APP_STORE_CONNECT_OUTPUT_NAME}.app.dSYM.zip") + UI.user_error!("Could not find dSYM at #{dsym_path}!") unless File.exist?(ipa_path) + + UI.important("Uploading ipa at #{ipa_path} to TestFlight...") + upload_to_testflight( + ipa: ipa_path, + api_key_path: APP_STORE_CONNECT_KEY_PATH, + skip_waiting_for_build_processing: false, + distribute_external: true, + changelog: File.read(RELEASE_NOTES_SOURCE_PATH), + reject_build_waiting_for_review: true, + groups: ['Internal A8C Beta Testers', 'External Beta Testers'] + ) + + UI.important("Uploading dSYM at #{dsym_path} to Sentry...") + sentry_upload_dsym( + dsym_path: dsym_path, + auth_token: EnvManager.get_required_env!('SENTRY_AUTH_TOKEN'), + org_slug: 'a8c', + project_slug: 'simplenote-ios' + ) + + next unless create_release + + version = beta_release ? build_code_current : release_version_current + create_github_release( + repository: GITHUB_REPO, + version: version, + release_notes_file_path: RELEASE_NOTES_SOURCE_PATH, + release_assets: XCARCHIVE_ZIP_PATH.to_s, + prerelease: beta_release + ) +end diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb new file mode 100644 index 000000000..dd1023890 --- /dev/null +++ b/fastlane/lanes/localization.rb @@ -0,0 +1,233 @@ +# frozen_string_literal: true + +# List of locales used for the app strings (GlotPress code => `*.lproj` folder name`) +# +# TODO: Replace with `LocaleHelper` once provided by release toolkit (https://github.com/wordpress-mobile/release-toolkit/pull/296) +GLOTPRESS_TO_LPROJ_APP_LOCALE_CODES = { + 'ar' => 'ar', # Arabic + 'cy' => 'cy', # Welsh + 'de' => 'de', # German + 'el' => 'el', # Greek + 'es' => 'es', # Spanish + 'fa' => 'fa', # Persian + 'fr' => 'fr', # French + 'he' => 'he', # Hebrew + 'id' => 'id', # Indonesian + 'it' => 'it', # Italian + 'ja' => 'ja', # Japanese + 'ko' => 'ko', # Korean + 'nl' => 'nl', # Dutch + 'pt-br' => 'pt-BR', # Portuguese (Brazil) + 'ru' => 'ru', # Russian + 'sv' => 'sv', # Swedish + 'tr' => 'tr', # Turkish + 'zh-cn' => 'zh-Hans-CN', # Chinese (China) + 'zh-tw' => 'zh-Hant-TW' # Chinese (Taiwan) +}.freeze + +# Mapping of all locales which can be used for AppStore metadata (Glotpress code => AppStore Connect code) +# +# TODO: Replace with `LocaleHelper` once provided by release toolkit (https://github.com/wordpress-mobile/release-toolkit/pull/296) +GLOTPRESS_TO_ASC_METADATA_LOCALE_CODES = { + 'ar' => 'ar-SA', + 'de' => 'de-DE', + 'es' => 'es-ES', + 'fr' => 'fr-FR', + 'he' => 'he', + 'id' => 'id', + 'it' => 'it', + 'ja' => 'ja', + 'ko' => 'ko', + 'nl' => 'nl-NL', + 'pt-br' => 'pt-BR', + 'ru' => 'ru', + 'sv' => 'sv', + 'tr' => 'tr', + 'zh-cn' => 'zh-Hans', + 'zh-tw' => 'zh-Hant' +}.freeze + +platform :ios do + desc 'Updates the main `Localizable.strings` file — that will be imported by GlotPress' + lane :generate_strings_file_for_glotpress do + en_lproj_path = File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'en.lproj') + ios_generate_strings_file_from_code( + paths: [ + 'Simplenote/', + 'SimplenoteIntents/', + 'SimplenoteShare/', + 'SimplenoteWidgets/', + 'Pods/Simperium/Simperium/', + 'Pods/Simperium/Simperium-iOS' + ], + output_dir: en_lproj_path + ) + + git_commit( + path: en_lproj_path, + message: 'Freeze strings for localization', + allow_nothing_to_commit: true + ) + end + + desc 'Updates the localization source POT file with the latest metadata for App Store Connect.' + lane :update_appstore_strings do |version: release_version_current| + files = { + whats_new: RELEASE_NOTES_SOURCE_PATH, + app_store_subtitle: File.join(STORE_METADATA_DEFAULT_LOCALE_FOLDER, 'subtitle.txt'), + app_store_desc: File.join(STORE_METADATA_DEFAULT_LOCALE_FOLDER, 'description.txt'), + app_store_keywords: File.join(STORE_METADATA_DEFAULT_LOCALE_FOLDER, 'keywords.txt') + } + + ios_update_metadata_source( + po_file_path: File.join(APP_RESOURCES_DIR, 'AppStoreStrings.pot'), + source_files: files, + release_version: version + ) + end + + lane :download_localized_strings_and_metadata_from_glotpress do + download_localized_strings_from_glotpress + download_localized_metadata_from_glotpress + end + + lane :download_localized_strings_from_glotpress do + parent_dir_for_lprojs = File.join(PROJECT_ROOT_FOLDER, 'Simplenote') + ios_download_strings_files_from_glotpress( + project_url: GLOTPRESS_APP_STRINGS_PROJECT_URL, + locales: GLOTPRESS_TO_LPROJ_APP_LOCALE_CODES, + download_dir: parent_dir_for_lprojs + ) + git_commit( + path: File.join(parent_dir_for_lprojs, '*.lproj', 'Localizable.strings'), + message: 'Update app translations – `Localizable.strings`', + allow_nothing_to_commit: true + ) + end + + lane :download_localized_metadata_from_glotpress do + # FIXME: Replace this with a call to the future replacement of `gp_downloadmetadata` once it's implemented in the release-toolkit (see paaHJt-31O-p2). + target_files = { + "v#{release_version_current}-whats-new": { desc: 'release_notes.txt', max_size: 4000 }, + app_store_name: { desc: 'name.txt', max_size: 30 }, + app_store_subtitle: { desc: 'subtitle.txt', max_size: 30 }, + app_store_desc: { desc: 'description.txt', max_size: 4000 }, + app_store_keywords: { desc: 'keywords.txt', max_size: 100 } + } + gp_downloadmetadata( + project_url: GLOTPRESS_STORE_METADATA_PROJECT_URL, + target_files: target_files, + locales: GLOTPRESS_TO_ASC_METADATA_LOCALE_CODES, + download_path: STORE_METADATA_FOLDER + ) + + files_to_commit = [File.join(STORE_METADATA_FOLDER, '**', '*.txt')] + + ensure_default_metadata_are_not_overridden(metadata_files_hash: target_files) + + files_to_commit.append(*generate_gitkeep_for_empty_locale_folders) + + git_add(path: files_to_commit, shell_escape: false) + git_commit( + path: files_to_commit, + message: 'Update App Store metadata translations', + allow_nothing_to_commit: true + ) + + sanitize_appstore_keywords + end + + # TODO: This ought to be part of the localized metadata download action, or of Fastlane itself. + desc 'Updates the files with the localized keywords values for App Store Connect to match the 100 characters requirement' + lane :sanitize_appstore_keywords do + Dir[File.join(STORE_METADATA_FOLDER, '**')].each do |locale_dir| + keywords_path = File.join(locale_dir, 'keywords.txt') + + unless File.exist?(keywords_path) + UI.important "Could not find keywords file in #{locale_dir}. Skipping..." + next + end + + keywords = File.read(keywords_path) + app_store_connect_keywords_length_limit = 100 + + if keywords.length <= app_store_connect_keywords_length_limit + UI.success "#{keywords_path} has less than #{app_store_connect_keywords_length_limit} characters." + next + end + + UI.important "#{keywords_path} has more than #{app_store_connect_keywords_length_limit} characters. Trimming it..." + + english_comma = ',' + arabic_comma = '،' + locale_code = File.basename(locale_dir) + keywords = keywords.gsub(arabic_comma, english_comma) if locale_code == 'ar-SA' + + keywords = keywords.split(english_comma)[0...-1].join(english_comma) until keywords.length <= app_store_connect_keywords_length_limit + + File.write(keywords_path, keywords) + + git_commit( + path: keywords_path, + message: "Trim #{locale_code} keywords to be less than #{app_store_connect_keywords_length_limit} characters", + allow_nothing_to_commit: false + ) + end + end + + lane :lint_localizations do + ios_lint_localizations(input_dir: APP_RESOURCES_DIR, allow_retry: true) + end + + lane :check_translation_progress_all do + check_translation_progress_strings + check_translation_progress_release_notes + end + + lane :check_translation_progress_strings do + UI.message('Checking app strings translation status...') + check_translation_progress( + glotpress_url: GLOTPRESS_APP_STRINGS_PROJECT_URL, + abort_on_violations: false + ) + end + + lane :check_translation_progress_release_notes do + UI.message('Checking release notes strings translation status...') + check_translation_progress( + glotpress_url: GLOTPRESS_STORE_METADATA_PROJECT_URL, + abort_on_violations: false + ) + end +end + +# Ensure that none of the `.txt` files in `en-US` would accidentally override our originals in `default` +def ensure_default_metadata_are_not_overridden(metadata_files_hash:) + metadata_files_hash.values.map { |t| t[:desc] }.each do |file| + en_file_path = File.join(STORE_METADATA_FOLDER, 'en-US', file) + + override_not_allowed_message = <<~MSG + File `#{en_file_path}` would override the same one in `#{STORE_METADATA_FOLDER}/default`. + `default/` is the source of truth and we cannot allow it to change unintentionally. + Delete `#{en_file_path}`, ensure the version in `default/` has the expected original copy, and try again. + MSG + UI.user_error!(override_not_allowed_message) if File.exist?(en_file_path) + end +end + +# Ensure even empty locale folders have an empty `.gitkeep` file. +# This way, if we don't have any translation ready for those locales, we'll still have the folders in Git for clarity. +def generate_gitkeep_for_empty_locale_folders + gitkeeps = [] + + GLOTPRESS_TO_ASC_METADATA_LOCALE_CODES.each_value do |locale| + gitkeep = File.join(STORE_METADATA_FOLDER, locale, '.gitkeep') + next if File.exist?(gitkeep) + + FileUtils.mkdir_p(File.dirname(gitkeep)) + FileUtils.touch(gitkeep) + gitkeeps.append(gitkeep) + end + + gitkeeps +end diff --git a/fastlane/lanes/release.rb b/fastlane/lanes/release.rb new file mode 100644 index 000000000..676fc8f58 --- /dev/null +++ b/fastlane/lanes/release.rb @@ -0,0 +1,452 @@ +# frozen_string_literal: true + +# Lanes related to the Release Process (Code Freeze, Betas, Final Build, App Store Submission…) + +platform :ios do + lane :start_code_freeze do |skip_confirm: false| + ensure_git_status_clean + + Fastlane::Helper::GitHelper.checkout_and_pull(DEFAULT_BRANCH) + + computed_release_branch_name = release_branch_name(release_version: release_version_next) + + message = <<~MESSAGE + Code Freeze: + - New release branch from #{DEFAULT_BRANCH}: #{computed_release_branch_name} + + - Current release version and build code: #{release_version_current} (#{build_code_current}). + - New release version and build code: #{release_version_next} (#{build_code_code_freeze}). + MESSAGE + + UI.important(message) + + UI.user_error!('Aborted by user request') unless skip_confirm || UI.confirm('Do you want to continue?') + + UI.message 'Creating release branch...' + Fastlane::Helper::GitHelper.create_branch(computed_release_branch_name, from: DEFAULT_BRANCH) + UI.success "Done! New release branch is: #{git_branch}" + + UI.message 'Bumping release version and build code...' + PUBLIC_VERSION_FILE.write( + version_short: release_version_next, + version_long: build_code_code_freeze + ) + UI.success "Done! New release version: #{release_version_current}. New build code: #{build_code_current}." + + commit_version_and_build_files + + new_version = release_version_current + + # Delete all release notes metadata, including the source of truth. + # We'll generate a new source of truth next, and the localized versions will be re-downloaded once translated on GlotPress. + # It's important we delete them, otherwise we risk using old release notes for locales that won't get translated in time for the release finalization. + delete_all_metadata_release_notes + + changelog_path = File.join(PROJECT_ROOT_FOLDER, 'RELEASE-NOTES.txt') + extract_release_notes_for_version( + version: new_version, + release_notes_file_path: changelog_path, + extracted_notes_file_path: RELEASE_NOTES_SOURCE_PATH + ) + # Add a new section to the changelog for the version _after_ the one we are code freezing + ios_update_release_notes( + new_version: new_version, + release_notes_file_path: changelog_path + ) + + UI.important('Pushing changes to remote, configuring the release on GitHub, and triggering the beta build...') + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless skip_confirm || UI.confirm('Do you want to continue?') + + push_to_git_remote( + tags: false, + set_upstream: is_ci == false # only set upstream when running locally, useless in transient CI builds + ) + + copy_branch_protection( + repository: GITHUB_REPO, + from_branch: DEFAULT_BRANCH, + to_branch: computed_release_branch_name + ) + + freeze_milestone_and_move_assigned_prs_to_next_milestone( + milestone_to_freeze: new_version, + next_milestone: release_version_next + ) + + check_pods_references + + next unless is_ci + + message = <<~MESSAGE + Code freeze started successfully. + + Next steps: + + - Checkout `#{release_branch_name}` branch locally + - Update Pods and release notes if needed + - Finalize the code freeze + MESSAGE + buildkite_annotate(context: 'code-freeze-success', style: 'success', message: message) + end + + lane :complete_code_freeze do |skip_confirm: false| + ensure_git_branch_is_release_branch! + ensure_git_status_clean + + version = release_version_current + + UI.important("Completing code freeze for: #{version}") + + UI.user_error!('Aborted by user request') unless skip_confirm || UI.confirm('Do you want to continue?') + + generate_strings_file_for_glotpress + + update_appstore_strings + + unless skip_confirm || UI.confirm('Ready to push changes to remote and trigger the beta build?') + UI.message("Terminating as requested. Don't forget to run the remainder of this automation manually.") + next + end + + push_to_git_remote(tags: false) + + trigger_beta_build(branch_to_build: release_branch_name(release_version: version)) + + pr_url = create_backmerge_pr! + + message = <<~MESSAGE + Code freeze completed successfully. Next, review and merge the [integration PR](#{pr_url}). + MESSAGE + buildkite_annotate(context: 'code-freeze-completed', style: 'success', message: message) if is_ci + UI.success(message) + end + + lane :new_beta_release do |skip_confirm: false| + ensure_git_status_clean + ensure_git_branch_is_release_branch! + + new_build_code = build_code_next + UI.important <<~MESSAGE + New beta: + - Current build code: #{build_code_current} + - New build code: #{new_build_code} + MESSAGE + + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless skip_confirm || UI.confirm('Do you want to continue?') + + download_localized_strings_and_metadata_from_glotpress + + lint_localizations + + UI.message "Bumping build code to #{new_build_code}..." + PUBLIC_VERSION_FILE.write( + version_long: new_build_code + ) + commit_version_and_build_files + # Uses build_code_current let user double-check result. + UI.success "Done! Release version: #{release_version_current}. New build code: #{build_code_current}." + + UI.important('Pushing changes to remote and triggering the beta build...') + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless skip_confirm || UI.confirm('Do you want to continue?') + + push_to_git_remote(tags: false) + + trigger_beta_build(branch_to_build: release_branch_name) + + pr_url = create_backmerge_pr! + + message = <<~MESSAGE + New beta triggered successfully. Next, review and merge the [integration PR](#{pr_url}). + MESSAGE + buildkite_annotate(context: 'new-beta-completed', style: 'success', message: message) if is_ci + UI.success(message) + end + + desc 'Trigger the final release build on CI' + lane :finalize_release do |skip_confirm: false| + UI.user_error!('To finalize a hotfix, please use the finalize_hotfix_release lane instead') if release_is_hotfix? + + ensure_git_status_clean + ensure_git_branch_is_release_branch! + + check_translation_progress_all + + new_build_code = build_code_next + version = release_version_current + UI.important <<~MESSAGE + Finalizing release #{version}: + • Current build code: #{build_code_current} + • Final build code: #{new_build_code} + MESSAGE + + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless skip_confirm || UI.confirm('Do you want to continue?') + + download_localized_strings_and_metadata_from_glotpress + lint_localizations + + UI.message "Bumping build code to #{new_build_code}..." + PUBLIC_VERSION_FILE.write(version_long: new_build_code) + commit_version_and_build_files + # Uses build_code_current let user double-check result. + UI.success "Done! Release version: #{version}. Final build code: #{build_code_current}." + + UI.important('Will push changes to remote and trigger the release build.') + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless skip_confirm || UI.confirm('Do you want to continue?') + + push_to_git_remote(tags: false) + + trigger_release_build(branch_to_build: release_branch_name) + + create_backmerge_prs + + remove_branch_protection( + repository: GITHUB_REPO, + branch: release_branch_name + ) + + begin + set_milestone_frozen_marker( + repository: GITHUB_REPO, + milestone: version, + freeze: false + ) + + create_new_milestone(repository: GITHUB_REPO) + + close_milestone( + repository: GITHUB_REPO, + milestone: version + ) + rescue StandardError => e + report_milestone_error(error_title: "Error in milestone finalization process for `#{version}`: #{e.message}") + end + end + + desc 'Creates a new hotfix branch for the given version:x.y.z. The branch will be cut from the tag x.y of the previous release' + lane :new_hotfix_release do |version:, skip_confirm: false, skip_prechecks: false| + ensure_git_status_clean unless skip_prechecks + + parsed_version = VERSION_FORMATTER.parse(version) + build_code_hotfix = BUILD_CODE_FORMATTER.build_code(version: parsed_version) + previous_version = VERSION_FORMATTER.release_version(VERSION_CALCULATOR.previous_patch_version(version: parsed_version)) + + UI.important <<-MESSAGE + New hotfix version: #{version} + New build code: #{build_code_hotfix} + Branching from tag: #{previous_version} + MESSAGE + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless skip_confirm || UI.confirm('Do you want to continue?') + + UI.user_error!("Version #{version} already exists! Abort!") if git_tag_exists(tag: version) + UI.user_error!("No tag found for version #{previous_version}. A hotfix branch cannot be created.") unless git_tag_exists(tag: previous_version) + + UI.message('Creating hotfix branch...') + Fastlane::Helper::GitHelper.create_branch( + release_branch_name(release_version: version), + from: previous_version + ) + UI.success("Done! New hotfix branch is: #{git_branch}") + + UI.message('Bumping hotfix version and build code...') + VERSION_FILE.write( + version_short: version, + version_long: build_code_hotfix + ) + commit_version_bump + + unless skip_confirm || UI.confirm('Ready to push changes to remote?') + UI.message("Terminating as requested. Don't forget to run the remainder of this automation manually.") + next + end + + push_to_git_remote( + tags: false, + set_upstream: is_ci == false # only set upstream when running locally, useless in transient CI builds + ) + end + + desc 'Performs the final checks and triggers a release build for the hotfix in the current branch' + lane :finalize_hotfix_release do |skip_confirm: true, skip_prechecks: false| + unless skip_prechecks + ensure_git_branch_is_release_branch! + ensure_git_status_clean + end + + hotfix_version = release_version_current + + UI.important("Will triggrer hotfix build for version #{hotfix_version}") + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless skip_confirm || UI.confirm('Do you want to continue?') + + trigger_release_build(branch_to_build: release_branch_name(release_version: hotfix_version)) + + create_backmerge_prs + + begin + close_milestone( + repository: GITHUB_REPO, + milestone: hotfix_version + ) + rescue StandardError => e + report_milestone_error(error_title: "Error closing milestone `#{hotfix_version}`: #{e.message}") + end + end + + lane :trigger_beta_build do |branch_to_build:| + trigger_buildkite_release_build(branch: branch_to_build, beta: true) + end + + lane :trigger_release_build do |branch_to_build:| + trigger_buildkite_release_build(branch: branch_to_build, beta: false) + end +end + +def commit_version_and_build_files + git_commit( + path: [VERSION_FILE_PATH], + message: 'Bump version number', + allow_nothing_to_commit: false + ) +end + +def check_pods_references + # This will also print the result to STDOUT + result = ios_check_beta_deps(lockfile: File.join(PROJECT_ROOT_FOLDER, 'Podfile.lock')) + + style = result[:pods].nil? || result[:pods].empty? ? 'success' : 'warning' + message = "### Checking Internal Dependencies are all on a **stable** version\n\n#{result[:message]}" + buildkite_annotate(context: 'pods-check', style: style, message: message) if is_ci +end + +def trigger_buildkite_release_build(branch:, beta:) + build_url = buildkite_trigger_build( + buildkite_organization: BUILDKITE_ORGANIZATION, + buildkite_pipeline: BUILDKITE_PIPELINE, + branch: branch, + environment: { BETA_RELEASE: beta }, + pipeline_file: 'release-build.yml' + ) + + return unless is_ci + + message = "This build triggered #{build_url} on #{branch}." + buildkite_annotate(style: 'info', context: 'trigger-release-build', message: message) +end + +def create_backmerge_pr! + pr_urls = create_backmerge_prs + + return pr_urls unless pr_urls.length > 1 + + backmerge_error_message = UI.user_error! <<~ERROR + Unexpectedly opened more than one backmerge pull request. URLs: + #{pr_urls.map { |url| "- #{url}" }.join("\n")} + ERROR + buildkite_annotate(style: 'error', context: 'error-creating-backmerge', message: backmerge_error_message) if is_ci + UI.user_error!(backmerge_error_message) +end + +# Notice the plural in the name. +# The action this method calls may create multiple backmerge PRs, depending on how many release branches with version greater than the source are in the remote. +def create_backmerge_prs + version = release_version_current + + create_release_backmerge_pull_request( + repository: GITHUB_REPO, + source_branch: release_branch_name(release_version: version), + labels: ['Releases'], + milestone_title: release_version_next + ) +rescue StandardError => e + error_message = <<-MESSAGE + Error creating backmerge pull request: + + #{e.message} + + If this is not the first time you are running the release task, the backmerge PR for version `#{version}` might have already been created. + Please close any pre-existing backmerge PR for `#{version}`, delete the previous merge branch, then run the release automation again. + MESSAGE + + buildkite_annotate(style: 'error', context: 'error-creating-backmerge', message: error_message) if is_ci + + UI.user_error!(error_message) +end + +def freeze_milestone_and_move_assigned_prs_to_next_milestone( + milestone_to_freeze:, + next_milestone:, + github_repository: GITHUB_REPO +) + # Notice that the order of execution is important here and should not be changed. + # + # First, we move the PR from milestone_to_freeze to next_milestone. + # Then, we update milestone_to_freeze's tile with the frozen marker (traditionally ❄️ ) + # + # If the order were to be reversed, the PRs lookup for milestone_to_freeze would yeld no value. + # That's because the lookup uses the milestone title, which would no longer be milestone_to_freeze, but milestone_to_freeze + the frozen marker. + begin + # Move PRs to next milestone + moved_prs = update_assigned_milestone( + repository: github_repository, + from_milestone: milestone_to_freeze, + to_milestone: next_milestone, + comment: "Version `#{milestone_to_freeze}` has entered code-freeze. The milestone of this PR has been updated to `#{next_milestone}`." + ) + + # Add ❄️ marker to milestone title to indicate we entered code-freeze + set_milestone_frozen_marker( + repository: github_repository, + milestone: milestone_to_freeze + ) + rescue StandardError => e + moved_prs = [] + + report_milestone_error(error_title: "Error during milestone `#{milestone_to_freeze}` freezing and PRs milestone updating process: #{e.message}") + end + + UI.message("Moved the following PRs to milestone #{next_milestone}: #{moved_prs.join(', ')}") + + return unless is_ci + + moved_prs_info = if moved_prs.empty? + "No open PRs were targeting `#{milestone_to_freeze}` at the time of code-freeze." + else + "#{moved_prs.count} PRs targeting `#{milestone_to_freeze}` were still open at the time of code-freeze. They have been moved to `#{next_milestone}`:\n" \ + + moved_prs.map { |pr_num| "[##{pr_num}](https://github.com/#{GITHUB_REPO}/pull/#{pr_num})" }.join(', ') + end + + buildkite_annotate( + style: moved_prs.empty? ? 'success' : 'warning', + context: 'code-freeze-milestone-updates', + message: moved_prs_info + ) +end + +def report_milestone_error(error_title:) + error_message = <<-MESSAGE + #{error_title} + - If this is not the first time you are running the release task (e.g. retrying because it failed on first attempt), the milestone might have already been closed and this error is expected. + - Otherwise, please investigate the error. + MESSAGE + + UI.error(error_message) + + buildkite_annotate(style: 'warning', context: 'error-with-milestone', message: error_message) if is_ci +end + +def delete_all_metadata_release_notes(store_metadata_folder: STORE_METADATA_FOLDER) + files = Dir.glob(File.join(store_metadata_folder, '**', 'release_notes.txt')) + files.each { |path| File.delete(path) } + git_add(path: files) + git_commit( + path: files, + message: 'Delete previous version release notes before code freeze', + # Even if no locale was translated in the previous cycle, default/release_notes.txt should always be present, and therefore deleted at this stage. + allow_nothing_to_commit: false + ) +end + +def release_is_hotfix? + VERSION_CALCULATOR.release_is_hotfix?( + version: VERSION_FORMATTER.parse(VERSION_FILE.read_release_version) + ) +end diff --git a/fastlane/lib/env_manager.rb b/fastlane/lib/env_manager.rb new file mode 100644 index 000000000..d1b8705cd --- /dev/null +++ b/fastlane/lib/env_manager.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'dotenv' +# TODO: It would be nice to decouple this from Fastlane. +# To give a good UX in the current use case, however, it's best to access the Fastlane UI methods directly. +require 'fastlane' + +# Manages loading of environment variables from a .env and accessing them in a user-friendly way. +class EnvManager + @env_path = nil + @env_example_path = nil + @print_error_lambda = nil + + # Set up by loading the .env file with the given name. + # + # TODO: We could go one step and guess the name based on the repo URL. + def self.set_up( + env_file_name:, + env_file_folder: File.join(Dir.home, '.a8c-apps'), + example_env_file_path: 'fastlane/example.env', + print_error_lambda: ->(message) { FastlaneCore::UI.user_error!(message) } + ) + @env_path = File.join(env_file_folder, env_file_name) + @env_example_path = example_env_file_path + @print_error_lambda = print_error_lambda + + # We don't check for @env_path to exist here + Dotenv.load(@env_path) + end + + # Use this instead of getting values from `ENV` directly. It will throw an error if the requested value is missing or empty. + def self.get_required_env!(key) + unless ENV.key?(key) + message = "Environment variable '#{key}' is not set." + + if running_on_ci? + @print_error_lambda.call(message) + elsif File.exist?(@env_path) + @print_error_lambda.call("#{message} Consider adding it to #{@env_path}.") + else + env_file_dir = File.dirname(@env_path) + env_file_name = File.basename(@env_path) + + @print_error_lambda.call <<~MSG + #{env_file_name} not found in #{env_file_dir} while looking for env var #{key}. + + Please copy #{@env_example_path} to #{@env_path} and fill in the value for #{key}. + + mkdir -p #{env_file_dir} && cp #{@env_example_path} #{@env_path} + MSG + end + end + + value = ENV.fetch(key) + + UI.user_error!("Env var for key #{key} is set but empty. Please set a value for #{key}.") if value.to_s.empty? + + value + end + + # Use this to ensure all env vars a lane requires are set. + # + # The best place to call this is at the start of a lane, to fail early. + def self.require_env_vars!(*keys) + keys.each { |key| get_required_env!(key) } + end + + def self.running_on_ci? + ENV['CI'] == 'true' + end +end diff --git a/fastlane/metadata/ar-SA/.gitkeep b/fastlane/metadata/ar-SA/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/ar-SA/description.txt b/fastlane/metadata/ar-SA/description.txt index dc6b27caa..a5234d0b2 100644 --- a/fastlane/metadata/ar-SA/description.txt +++ b/fastlane/metadata/ar-SA/description.txt @@ -1,13 +1,13 @@ -إنَّ برنامج Simplenote من ضمن الطرق السهلة لتدوين الملحوظات، وإنشاء قوائم المهام، وتسجيل الأفكار، والمزيد. افتح البرنامج، ثم دوِّن بعض الأفكار، وهذا كل شيء. عندما تصبح مجموعتك كبيرة، حافظ على تنظيمها باستخدام الوسوم والدبابيس، واعثر على ما تحتاج إليه باستخدام البحث الفوري. نظرًا إلى أنَّ برنامج Simplenote سيقوم بالمزامنة عبر أجهزتك مجانًا، فإنَّ ملحوظاتك ستظل معك في جميع الأوقات. +إن برنامج Simplenote من ضمن الطرق السهلة لتدوين الملحوظات، وإنشاء قوائم المهام، وتسجيل الأفكار، والمزيد. افتح البرنامج، ثم دوِّن بعض الأفكار، وهذا كل شيء. عندما تصبح مجموعتك كبيرة، حافظ على تنظيمها باستخدام الوسوم والدبابيس، واعثر على ما تحتاج إليه باستخدام البحث الفوري. نظرًا إلى أن برنامج Simplenote سيقوم بالمزامنة عبر أجهزتك مجانًا، فإن ملحوظاتك ستظل معك في جميع الأوقات. -- استمتع بتجربة تدوين ملحوظات بسيطة -- قم بمزامنة كل شيء عبر جميع أجهزتك -- تعاون وشارك -- ابقَ منظمًا باستخدام الوسوم -- قم بتسجيل الدخول باستخدام بريدك الإلكتروني أو حسابك على ووردبريس.كوم +- تجربة تدوين بسيطة للملحوظات +- مزامنة كل شيء عبر جميع أجهزتك +- التعاون والمشاركة +- الحفاظ على تنظيم مجموعتك باستخدام الوسوم +- تسجيل الدخول باستخدام بريدك الإلكتروني أو حسابك على ووردبريس.كوم -وقم بالمزامنة بثقة -- يمكنك المزامنة تلقائيًا بسلاسة عبر أي حاسوب أو هاتف أو جهاز لوحي. +المزامنة بكل ثقة +- يمكنك المزامنة بسهولة تلقائيًا عبر أي حاسوب أو هاتف أو جهاز لوحي. - انسخ كل شيء احتياطيًا وقم بمزامنته أثناء تدوين الملحوظات، لكيلا تفقد محتواك أبدًا. التعاون والمشاركة @@ -24,14 +24,14 @@ - اختر ترتيب فرز ملحوظاتك ووسومك. - ثبِّت الملحوظات التي تستخدمها أكثر من غيرها. - حرِّر الوسوم مباشرةً عن طريق تسميتها وترتيبها مجددًا. -- احمِ محتواك بقفل رمز المرور. +- حافظ على محتواك باستخدام تأمين رمز المرور. -- سياسة الخصوصية: https://automattic.com/privacy/ -شروط الخدمة: https://simplenote.com/terms/ -إشعار الخصوصية الخاص بالمستخدمين في كاليفورنيا: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +شروط الخصوصية: https://simplenote.com/terms/ +إشعار الخصوصية الخاص بالمستخدمين في كاليفورنيا: https://automattic.com/privacy/#us-privacy-laws -- -تفضَّل بزيارة simplenote.com لتنزيل Simplenote لأجهزتك الأخرى. +تفضَّل بزيارة simplenote.com لتنزيل برنامج Simplenote لأجهزتك الأخرى. diff --git a/fastlane/metadata/ar-SA/keywords.txt b/fastlane/metadata/ar-SA/keywords.txt index 7609cc905..66c12533b 100644 --- a/fastlane/metadata/ar-SA/keywords.txt +++ b/fastlane/metadata/ar-SA/keywords.txt @@ -1 +1 @@ -ملحوظات,ملحوظة,مهام مصغرة,صحيفة,مذكرات,مزامنة,قائمة مهام,مفكرة,بسيط,مفكرة,وسم قائمة مهام,كتابة,مذكرة \ No newline at end of file +ملحوظات,ملحوظة,مهام مصغرة,صحيفة,مذكرات,مزامنة,قائمة مهام,مفكرة,بسيط,مفكرة,وسم قائمة مهام,كتابة \ No newline at end of file diff --git a/fastlane/metadata/ar-SA/release_notes.txt b/fastlane/metadata/ar-SA/release_notes.txt index a0b103b47..23cd4de4b 100644 --- a/fastlane/metadata/ar-SA/release_notes.txt +++ b/fastlane/metadata/ar-SA/release_notes.txt @@ -1,2 +1,4 @@ -- إضافة لون الخلفية إلى مكوّن التعليمات البرمجية في معاينة الوضع الداكن -- إضافة مربع جانبي كبير للملحوظات +- تم تحديث الأيقونات للعمل مع أنماط iOS 18 +- إضافة خيار تسجيل الدخول البديل باستخدام اسم المستخدم وكلمة المرور لتسجيل الدخول +- تم تحديث رابط إشعار الخصوصية الخاص بالمستخدمين في كاليفورنيا + diff --git a/fastlane/metadata/ar-SA/subtitle.txt b/fastlane/metadata/ar-SA/subtitle.txt index bc3f1692e..42feadee3 100644 --- a/fastlane/metadata/ar-SA/subtitle.txt +++ b/fastlane/metadata/ar-SA/subtitle.txt @@ -1 +1 @@ -ملاحظات بسيطة ومهام ومذكرات \ No newline at end of file +ملاحظات بسيطة ومهام ومذكرات diff --git a/fastlane/metadata/de-DE/.gitkeep b/fastlane/metadata/de-DE/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/de-DE/description.txt b/fastlane/metadata/de-DE/description.txt index d199909b0..b670e15a3 100644 --- a/fastlane/metadata/de-DE/description.txt +++ b/fastlane/metadata/de-DE/description.txt @@ -1,36 +1,36 @@ -Mit Simplenote kannst du ganz einfach Notizen,Aufgabenlisten, Ideen und mehr festhalten. Einfach aufrufen, Gedanken notieren – fertig. Mit Schlagwörtern und Pins behältst du den Überblick über deine wachsende Sammlung und mit der Sofortsuche findest du das, was du brauchst. Da Simplenote kostenlos mit deinen Geräten synchronisiert wird, hast du deine Notizen jederzeit zur Hand. +Mit Simplenote kannst du ganz einfach Notizen, Aufgabenlisten, Ideen und mehr festhalten. Einfach aufrufen, Gedanken notieren – fertig. Mit Schlagwörtern und Pins behältst du den Überblick über deine wachsende Sammlung und mit der Sofortsuche findest du das, was du brauchst. Da Simplenote kostenlos mit deinen Geräten synchronisiert wird, hast du deine Notizen jederzeit zur Hand. - Eine einfache Möglichkeit, Notizen zu erstellen - Alle Inhalte über alle deine Geräte hinweg synchronisieren - Zusammenarbeiten und Teilen -- Mit Schlagwörtern den Überblick behalten -- Mit E-Mail-Adresse oder WordPress.com-Konto anmelden +- Mit Schlagwörtern den Überblick behalten +- Mit deiner E-Mail-Adresse oder deinem WordPress.com-Konto anmelden -ZUVERLÄSSIGES SYNCHRONISIEREN -- Inhalte automatisch und problemlos mit jedem Computer, Smartphone oder Tablet synchronisieren -- Alle Notizen sichern und synchronisieren, damit keine Inhalte verloren gehen +ZUVERLÄSSIG SYNCHRONISIEREN +- Inhalte automatisch und problemlos mit jedem Computer, Smartphone oder Tablet synchronisieren. +- Alle Notizen sichern und synchronisieren, damit keine Inhalte verloren gehen. ZUSAMMENARBEITEN UND TEILEN -- Zusammenarbeiten und abstimmen: Teile Ideen mit einem Kollegen oder schreibe mit dem Mitbewohner eine Einkaufsliste. -- Auswählen, ob Inhalte im Web veröffentlicht werden sollen, und einen Link mit den gewünschten Personen teilen -- WordPress.com-Konto verbinden und Notizen direkt auf einer WordPress-Website veröffentlichen -- Notizen schnell und einfach mit Drittanbieter-Apps teilen - -ORGANISEREN UND SUCHEN -- Behalte mit Schlagwörtern den Überblick und suche und sortiere damit Inhalte. -- Mit hervorgehobenen Stichwörtern sofort das finden, wonach du suchst -- Mit Markdown Formatierungen hinzufügen -- Aufgabenlisten erstellen -- Die Sortierreihenfolge deiner Notizen und Schlagwörter auswählen -- Die am häufigsten verwendeten Notizen anheften -- Schlagwörter direkt bearbeiten, indem du sie umbenennst oder neu anordnest -- Inhalte mit Zugangscode-Sperre schützen +- Zusammenarbeiten und abstimmen: Ideen mit einem Kollegen teilen oder mit dem Mitbewohner eine Einkaufsliste schreiben. +- Auswählen, ob Inhalte im Web veröffentlicht werden sollen, und einen Link mit den gewünschten Personen teilen. +- WordPress.com-Konto verbinden und Notizen direkt auf einer WordPress-Website veröffentlichen. +- Notizen schnell und einfach mit Drittanbieter-Apps teilen. + +ORGANISIEREN UND SUCHEN +- Mit Schlagwörtern den Überblick behalten und Inhalte damit suchen und sortieren. +- Mit hervorgehobenen Stichwörtern sofort das finden, wonach du suchst. +- Mit Markdown Formatierungen hinzufügen. +- Aufgabenlisten erstellen. +- Die Sortierreihenfolge deiner Notizen und Schlagwörter auswählen. +- Die am häufigsten verwendeten Notizen anheften. +- Schlagwörter direkt bearbeiten, indem du sie umbenennst oder neu anordnest. +- Inhalte mit Zugangscode-Sperre schützen. -- Datenschutzerklärung: https://automattic.com/privacy/ Geschäftsbedingungen: https://simplenote.com/terms/ -Datenschutzhinweis für Benutzer in Kalifornien: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Datenschutzhinweise für Benutzer in Kalifornien: https://automattic.com/privacy/#us-privacy-laws -- diff --git a/fastlane/metadata/de-DE/keywords.txt b/fastlane/metadata/de-DE/keywords.txt index 9c13b0740..a7a4661c2 100644 --- a/fastlane/metadata/de-DE/keywords.txt +++ b/fastlane/metadata/de-DE/keywords.txt @@ -1 +1 @@ -Notizen,Markdown,Tagebuch,synchronisieren,To-do-Liste,Cloud,Notizbuch,Schlagwort,Schreiben,Memo \ No newline at end of file +Notizen,Markdown,Tagebuch,synchronisieren,To-do-Liste,Cloud,Notizbuch,Schlagwort,Schreiben,Memo diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt index 4ecb01e49..28aa27984 100644 --- a/fastlane/metadata/de-DE/release_notes.txt +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -1,2 +1,4 @@ -- Hintergrundfarbe zum Codeblock in der Vorschau im dunklen Modus hinzugefügt -- Widget für große Notizen hinzugefügt +- Aktualisierte Icons, die mit den neuen iOS 18-Stilen funktionieren +- Fallback-Anmeldung mit Option für den Benutzernamen und das Passwort zur Anmeldung hinzufügen +- Aktualisierter Link zu den Datenschutzhinweisen für Benutzer in Kalifornien + diff --git a/fastlane/metadata/de-DE/subtitle.txt b/fastlane/metadata/de-DE/subtitle.txt index a8c452986..47531bbcd 100644 --- a/fastlane/metadata/de-DE/subtitle.txt +++ b/fastlane/metadata/de-DE/subtitle.txt @@ -1 +1 @@ -Notizen, To-do-Listen, Memos \ No newline at end of file +Notizen, To-do-Listen, Memos diff --git a/fastlane/metadata/default/description.txt b/fastlane/metadata/default/description.txt index ef31ca421..16942a1a6 100644 --- a/fastlane/metadata/default/description.txt +++ b/fastlane/metadata/default/description.txt @@ -30,7 +30,7 @@ ORGANIZE AND SEARCH Privacy Policy: https://automattic.com/privacy/ Terms of Service: https://simplenote.com/terms/ -California Users Privacy Notice: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +California Users Privacy Notice: https://automattic.com/privacy/#us-privacy-laws -- diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index e54b3a729..9922f088e 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,2 +1,4 @@ -- Add background color to code block in dark mode preview -- Add large note widget +- Updated icons to work with new iOS 18 styles +- Add fall back login with username and password option to login +- Updated link to privacy notice for California users + diff --git a/fastlane/metadata/default/support_url.txt b/fastlane/metadata/default/support_url.txt new file mode 100644 index 000000000..c1424b760 --- /dev/null +++ b/fastlane/metadata/default/support_url.txt @@ -0,0 +1 @@ +https://simplenote.com/help/ diff --git a/fastlane/metadata/en-US/description.txt b/fastlane/metadata/en-US/description.txt deleted file mode 100644 index ef31ca421..000000000 --- a/fastlane/metadata/en-US/description.txt +++ /dev/null @@ -1,37 +0,0 @@ -Simplenote is an easy way to take notes, create to-do lists, capture ideas, and more. Open it, jot down some thoughts, and you're done. As your collection grows, stay organized with tags and pins, and find what you need with instant search. Since Simplenote will sync across your devices for free, your notes are with you at all times. - -- A simple, note taking experience -- Sync everything across all your devices -- Collaborate and share -- Stay organized with tags -- Log in with your email or WordPress.com account - -SYNC WITH CONFIDENCE -- Automatically sync seamlessly across any computer, phone or tablet. -- Back up and sync everything as you take notes, so you never lose your content. - -COLLABORATE AND SHARE -- Collaborate and work together -- share ideas with a colleague, or write a grocery list with your roommate. -- Choose whether to publish your content to the web, and share a link with whoever you want. -- Publish directly to a WordPress site by connecting your WordPress.com account. -- Quickly and easily share with third-party apps. - -ORGANIZE AND SEARCH -- Stay organized with tags, and use them for quick searching and sorting. -- Instantly find what you’re looking for with keyword highlighting. -- Use markdown to add formatting. -- Create to-do lists. -- Choose the sorting order of your notes and tags. -- Pin the notes that you use the most. -- Edit tags directly by renaming and reordering. -- Protect your content with a passcode lock. - --- - -Privacy Policy: https://automattic.com/privacy/ -Terms of Service: https://simplenote.com/terms/ -California Users Privacy Notice: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa - --- - -Visit simplenote.com to download Simplenote for your other devices. diff --git a/fastlane/metadata/en-US/keywords.txt b/fastlane/metadata/en-US/keywords.txt deleted file mode 100644 index 363d879cb..000000000 --- a/fastlane/metadata/en-US/keywords.txt +++ /dev/null @@ -1 +0,0 @@ -notes,note,markdown,journal,sync,to-do,list,cloud,notebook,simple,notepad,tag,todo,writing,memo \ No newline at end of file diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt deleted file mode 100644 index e54b3a729..000000000 --- a/fastlane/metadata/en-US/release_notes.txt +++ /dev/null @@ -1,2 +0,0 @@ -- Add background color to code block in dark mode preview -- Add large note widget diff --git a/fastlane/metadata/en-US/subtitle.txt b/fastlane/metadata/en-US/subtitle.txt deleted file mode 100644 index 308038419..000000000 --- a/fastlane/metadata/en-US/subtitle.txt +++ /dev/null @@ -1 +0,0 @@ -Simple notes, todos and memos \ No newline at end of file diff --git a/fastlane/metadata/es-ES/.gitkeep b/fastlane/metadata/es-ES/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/es-ES/description.txt b/fastlane/metadata/es-ES/description.txt index df6438d0b..9c80f4ae8 100644 --- a/fastlane/metadata/es-ES/description.txt +++ b/fastlane/metadata/es-ES/description.txt @@ -1,22 +1,22 @@ Simplenote es una forma fácil de tomar notas, crear listas de tareas, capturar ideas y mucho más. Abre la aplicación, escribe algunas líneas y listo. A medida que tu colección crezca, mantén el orden con etiquetas y chinchetas, y encuentra lo que necesites con la búsqueda instantánea. Como Simplenote se sincronizará gratuitamente con tus dispositivos, tus notas estarán contigo en todo momento. -- Una experiencia sencilla para tomar notas -- Sincroniza todos tus dispositivos -- Colabora y comparte -- Organízate con las etiquetas -- Inicia sesión con tu correo electrónico o cuenta WordPress.com - -SINCRONIZA CON CONFIANZA -- Sincroniza fácilmente cualquier ordenador, teléfono móvil o tableta. +- Una experiencia sencilla de toma de notas +- Sincroniza tus datos en todos tus dispositivos +- Colabora y comparte contenido +- Mantén el orden con etiquetas +- Accede con tu correo electrónico o con tu cuenta de WordPress.com + +SINCRONIZAR CON CONFIANZA +- Tus datos se sincronizan automáticamente sin problemas en cualquier ordenador, teléfono o tableta. - Haz una copia de seguridad y sincronízalo todo mientras tomas notas, para que nunca pierdas tu contenido. -COLABORA Y COMPARTE -- Colabora y trabaja en equipo: comparte ideas con un compañero de trabajo o escribe la lista de la compra con tu compañero de piso. +COLABORAR Y COMPARTIR +- Colabora y trabaja en equipo: comparte ideas con un compañero de trabajo o crea una lista de la compra con tu compañero de piso. - Decide si quieres publicar tu contenido en la Web y comparte un enlace con quien tú quieras. - Publica contenido directamente en un sitio de WordPress conectando tu cuenta de WordPress.com. - Comparte contenido rápida y fácilmente con aplicaciones de terceros. -ORGANIZA Y BUSCA +ORGANIZAR Y BUSCAR - Mantén el orden aplicando etiquetas y úsalas para buscar y clasificar información rápidamente. - Encuentra de inmediato lo que buscas con el resaltado de palabras clave. - Utiliza el marcado para añadir formato. @@ -30,8 +30,8 @@ ORGANIZA Y BUSCA Política de privacidad: https://automattic.com/privacy/ Condiciones del servicio: https://simplenote.com/terms/ -Aviso de privacidad para usuarios de California: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Aviso de privacidad dirigido a los usuarios de California: https://automattic.com/privacy/#us-privacy-laws -- -Visita simplenote.com para descargar Simplenote en otros dispositivos. +Ve a simplenote.com y descarga Simplenote en el resto de tus dispositivos. diff --git a/fastlane/metadata/es-ES/keywords.txt b/fastlane/metadata/es-ES/keywords.txt index 6eb80d7c9..02d3ddcda 100644 --- a/fastlane/metadata/es-ES/keywords.txt +++ b/fastlane/metadata/es-ES/keywords.txt @@ -1 +1 @@ -notas,nota,markdown,diario,sincronizar,lista,nube,simple,bloc,etiqueta,tarea,escribir,recordatorio \ No newline at end of file +notas,nota,markdown,diario,sincronizar,lista,nube,simple,bloc,etiqueta,tarea,escribir,recordatorio diff --git a/fastlane/metadata/es-ES/release_notes.txt b/fastlane/metadata/es-ES/release_notes.txt index 6e18aea31..8ce8f1923 100644 --- a/fastlane/metadata/es-ES/release_notes.txt +++ b/fastlane/metadata/es-ES/release_notes.txt @@ -1,2 +1,4 @@ -- Añadir color de fondo al bloque de código en la vista previa del modo oscuro -- Añadir widget grande de notas +- Se han actualizado los iconos para que funcionen con los nuevos estilos de iOS 18 +- Añade la opción de inicio de sesión «fall back» con nombre de usuario y contraseña para iniciar sesión +- Se ha actualizado el enlace que lleva al aviso de privacidad para usuarios de California + diff --git a/fastlane/metadata/es-ES/subtitle.txt b/fastlane/metadata/es-ES/subtitle.txt index bfd5a25df..4ef53e063 100644 --- a/fastlane/metadata/es-ES/subtitle.txt +++ b/fastlane/metadata/es-ES/subtitle.txt @@ -1 +1 @@ -Notas, tareas y recordatorios \ No newline at end of file +Notas, tareas y recordatorios diff --git a/fastlane/metadata/fr-FR/.gitkeep b/fastlane/metadata/fr-FR/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/fr-FR/description.txt b/fastlane/metadata/fr-FR/description.txt index 86d417d77..c24bbbe26 100644 --- a/fastlane/metadata/fr-FR/description.txt +++ b/fastlane/metadata/fr-FR/description.txt @@ -1,37 +1,37 @@ -Simplenote est un outil facile pour prendre des notes, créer des listes de choses à faire, capturer des idées, etc. Il vous suffit de l’ouvrir, d’entrer vos réflexions, et le tour est joué. Au fur et à mesure que votre ensemble de notes augmente, vous pouvez les organiser à l’aide d’étiquettes et d’épingles, et trouver ce dont vous avez besoin par le biais de recherches instantanées. Simplenote assurant gratuitement la synchronisation sur tous vos appareils, vous disposez de vos notes à tout moment. +Simplenote est un outil facile à utiliser pour prendre des notes, créer des listes de choses à faire, capturer des idées, etc. Il vous suffit de l’ouvrir, d’entrer vos réflexions, et le tour est joué. Au fur et à mesure que votre ensemble de notes augmente, vous pouvez les organiser à l’aide d’étiquettes et d’épingles, et trouver ce dont vous avez besoin par le biais de recherches instantanées. Puisque Simplenote assure gratuitement la synchronisation sur tous vos appareils, vous disposez de vos notes à tout moment. -- Une expérience de prise de note simple -- Une synchronisation complète sur tous vos appareils -- Collaboration et partage -- Organisation à l’aide d’étiquettes -- Connexion par adresse e-mail ou compte WordPress.com +- Une expérience de prise de notes simple +- Tout synchroniser sur tous vos appareils +- Collaborer et partager +- Rester organisé avec des étiquettes +- Se connecter avec une adresse e-mail ou un compte WordPress.com -SYNCHRONISATION EN TOUTE CONFIANCE -- Synchronisation automatique transparente sur n’importe quel ordinateur, téléphone ou tablette. +SYNCHRONISER EN TOUTE CONFIANCE +- Synchronisez votre travail automatiquement et en toute fluidité sur n’importe quel ordinateur, téléphone ou tablette. - Sauvegardez et synchronisez tout lors de votre prise de notes, afin de ne perdre aucun contenu. -COLLABORATION ET PARTAGE +COLLABORER ET PARTAGER - Collaborez, que ce soit pour partager vos idées avec un collègue ou rédiger la liste des courses avec votre colocataire. - Décidez si vous souhaitez publier votre contenu sur le Web et partagez un lien avec les personnes de votre choix. - Publiez votre contenu directement sur un site WordPress en vous connectant à votre compte WordPress.com. - Partagez facilement et rapidement avec des applications tierces. -ORGANISATION ET RECHERCHE +ORGANISER ET RECHERCHER - Organisez-vous à l’aide d’étiquettes et utilisez-les pour trier et rechercher rapidement ce dont vous avez besoin. -- Trouvez instantanément ce que vous recherchez grâce à la mise en surbrillance de mots clés. +- Trouvez instantanément ce que vous recherchez grâce à la mise en surbrillance de mots-clés. - Utilisez Markdown pour ajouter une mise en forme. - Créez des listes de choses à faire. - Choisissez l’ordre de tri de vos notes et étiquettes. - Épinglez les notes que vous utilisez le plus. - Modifiez directement les étiquettes en les renommant et les réorganisant. -- Protégez votre contenu à l’aide d’un verrou avec code d’authentification. +- Protégez votre contenu avec un code d’accès. -- Politique de confidentialité : https://automattic.com/privacy/ Conditions d’utilisation : https://simplenote.com/terms/ -Avis de confidentialité pour les utilisateurs californiens : https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Avis de confidentialité pour les utilisateurs californiens : https://automattic.com/privacy/#us-privacy-laws -- -Visitez simplenote.com afin de télécharger Simplenote pour vos autres appareils. +Accédez à simplenote.com afin de télécharger Simplenote pour vos autres appareils. diff --git a/fastlane/metadata/fr-FR/keywords.txt b/fastlane/metadata/fr-FR/keywords.txt index e1f13d298..0af652e03 100644 --- a/fastlane/metadata/fr-FR/keywords.txt +++ b/fastlane/metadata/fr-FR/keywords.txt @@ -1 +1 @@ -notes,note,markdown,journal,synchronisation,todo,liste,cloud,notebook,simple,notepad,écrire \ No newline at end of file +notes,note,markdown,journal,synchronisation,todo,liste,cloud,notebook,simple,notepad,écrire diff --git a/fastlane/metadata/fr-FR/release_notes.txt b/fastlane/metadata/fr-FR/release_notes.txt index 0a225f22e..49965f950 100644 --- a/fastlane/metadata/fr-FR/release_notes.txt +++ b/fastlane/metadata/fr-FR/release_notes.txt @@ -1,2 +1,4 @@ -- Ajouter une couleur d’arrière-plan au bloc de code dans l’aperçu en mode sombre -- Ajouter un grand widget de remarque +- Mise à jour des icônes pour qu’elles fonctionnent avec les nouveaux styles d’iOS 18 +- Ajout d’une connexion de secours avec identifiant et mot de passe en option +- Mise à jour du lien vers l’avis de confidentialité pour les utilisateurs californiens + diff --git a/fastlane/metadata/fr-FR/subtitle.txt b/fastlane/metadata/fr-FR/subtitle.txt index 966fe68d2..200b92876 100644 --- a/fastlane/metadata/fr-FR/subtitle.txt +++ b/fastlane/metadata/fr-FR/subtitle.txt @@ -1 +1 @@ -Notes, tâches et mémos simples \ No newline at end of file +Notes, tâches et mémos simples diff --git a/fastlane/metadata/he/.gitkeep b/fastlane/metadata/he/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/he/description.txt b/fastlane/metadata/he/description.txt new file mode 100644 index 000000000..e82aa4b33 --- /dev/null +++ b/fastlane/metadata/he/description.txt @@ -0,0 +1,37 @@ +האפליקציה Simplenote היא דרך קלה לרשום פתקים, ליצור רשימת משימות, לתעד רעיונות ועוד. פותחים את האפליקציה, רושמים כמה מחשבות, וזה הכול. כשיש לך הרבה פריטים באוסף, אפשר לשמור על הסדר באמצעות תגיות ונעצים ולמצוא את מה שנדרש לך בחיפוש מיידי. מאחר שהאפליקציה של Simplenote מסתנכרנת בין כל המכשירים שלך בחינם, הפתקים שלך תמיד יהיו זמינים לשימושך בכל עת. + +- לרשום פתקים בקלות +- לסכרן את התוכן כולו בין כל המכשירים שלך +- לשתף תוכן ולשתף פעולה +- להקפיד על ארגון בעזרת תגיות +- להתחבר באמצעות האימייל שלך או החשבון שלך ב-WordPress.com + +לסנכרן בביטחון +- האפליקציה מסתנכרנת באופן אוטומטי בין כל המחשבים, הטלפונים או הטאבלטים. +- ניתן לגבות ולסנכרן את כל התוכן במהלך הכתיבה של הפתקים ולא לאבד תוכן לעולם. + +לשתף תוכן ולשתף פעולה +- אפשר לשתף פעולה ולעבוד יחד – לחלוק רעיונות עם עמיתים או לכתוב רשימת קניות עם השותפים לדירה. +- ניתן לבחור אם ברצונך לפרסם את התוכן שיצרת ברשת או לשתף קישור עם אנשים מסוימים לבחירתך. +- ניתן לפרסם את התוכן ישירות לאתר שלך ב-WordPress.com באמצעות התחברות לחשבון שלך ב-WordPress.com. +- אפשר לשתף את התוכן במהירות ובקלות באפליקציות של צד שלישי. + +ארגון וחיפוש +- אפשר להקפיד על ארגון בעזרת תגיות ולהשתמש בהן לחיפוש ולמיון מהירים. +- ניתן למצוא כל מה שברצונך לחפש באופן מיידי בעזרת הדגשה של מילות מפתח. +- ניתן להשתמש ב-Markdown כדי להוסיף עיצוב. +- ליצור רשימת משימות. +- לבחור את סדר התצוגה של הפתקים והתגיות שלך. +- לנעוץ את הפתקים שנמצאים בשימוש נפוץ. +- לערוך תגיות באמצעות שינוי השם או הסדר. +- להגן על התוכן שלך באמצעות נעילה של קוד סיסמה. + +-- + +מדיניות פרטיות: https://automattic.com/privacy/ +‎תנאי השירות: https://simplenote.com/terms/ +‎הודעת פרטיות למשתמשים מקליפורניה: https://automattic.com/privacy/#us-privacy-laws + +-- + +ניתן לעבור אל simplenote.com כדי להוריד את Simplenote לשאר המכשירים שלך. diff --git a/fastlane/metadata/he/keywords.txt b/fastlane/metadata/he/keywords.txt new file mode 100644 index 000000000..a2e547a5d --- /dev/null +++ b/fastlane/metadata/he/keywords.txt @@ -0,0 +1 @@ +פתקים,פתק,סימון,markdown,יומן,סנכרון,לביצוע,רשימה,ענן,מחברת,פשוט,notepad,תג,כתיבה,תזכורת diff --git a/fastlane/metadata/he/release_notes.txt b/fastlane/metadata/he/release_notes.txt new file mode 100644 index 000000000..13c0d2da8 --- /dev/null +++ b/fastlane/metadata/he/release_notes.txt @@ -0,0 +1,4 @@ +- עדכנו את הסמלים כדי להתאים אותם לסגנון של iOS 18 +- הוספנו ניסיון חוזר להתחברות באמצעות שם משתמש וסיסמה בכניסה +- עדכנו את הקישור להצהרת הפרטיות עבור משתמשים בקליפורניה + diff --git a/fastlane/metadata/he/subtitle.txt b/fastlane/metadata/he/subtitle.txt new file mode 100644 index 000000000..8d04c0521 --- /dev/null +++ b/fastlane/metadata/he/subtitle.txt @@ -0,0 +1 @@ +פתקים, רשימות לביצוע ותזכורות diff --git a/fastlane/metadata/id/.gitkeep b/fastlane/metadata/id/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/id/description.txt b/fastlane/metadata/id/description.txt index 4d51008f2..d9ae41eeb 100644 --- a/fastlane/metadata/id/description.txt +++ b/fastlane/metadata/id/description.txt @@ -1,36 +1,36 @@ Simplenote adalah cara yang mudah untuk menulis catatan, membuat daftar hal yang harus dilakukan, mengembangkan ide, dan banyak lagi. Buka, catat beberapa ide, dan selesai. Seiring dengan berkembangnya koleksi Anda, tetap rapi dengan tag dan fitur sematkan, serta temukan hal yang Anda butuhkan dengan pencarian instan. Karena Simplenote akan tersinkron dengan seluruh perangkat secara gratis, catatan akan terus bersama Anda kapan saja. -- Pengalaman mencatat yang sederhana -- Menyinkronkan semua catatan di berbagai perangkat Anda -- Berkolaborasi dan berbagi +- Pengalaman mencatat yang simpel +- Sinkronkan semua catatan di seluruh perangkat +- Lakukan kolaborasi dan bagikan - Tetap rapi dengan tag -- Masuk dengan email atau akun WordPress.com Anda +- Login dengan email atau akun WordPress.com Anda -SINKRONISASI ANDAL -- Menyinkronkan dengan lancar di semua jenis komputer, ponsel atau tablet secara otomatis. +SINKRONKAN DENGAN PERCAYA DIRI +- Sinkronkan dengan mulus secara otomatis di seluruh komputer, ponsel, atau tablet. - Cadangkan dan sinkronkan segala hal selagi Anda mencatat, sehingga Anda tidak akan kehilangan isi catatan. -KOLABORASI DAN BERBAGI -- Lakukan kolaborasi dan bekerja bersama -- bagikan ide dengan rekan kerja, atau tulis daftar belanja bersama teman sekamar. +LAKUKAN KOLABORASI DAN BAGIKAN +- Lakukan kolaborasi dan bekerja bersama -- bagikan ide dengan rekan kerja, atau tulis daftar belanja dengan rekan sekamar Anda. - Pilih untuk memublikasikan konten Anda ke web, dan bagikan tautan ke siapa pun yang Anda mau. - Publikasikan secara langsung ke situs WordPress dengan menyambungkan akun WordPress.com Anda. - Bagikan dengan aplikasi pihak ketiga secara mudah dan cepat. -ATUR DAN CARI -- Tetap rapi dengan tag; gunakan untuk pencarian dan penyortiran cepat. +RAPIKAN DAN CARI +- Tetap rapi dengan tag, dan gunakan untuk pencarian dan penyortiran cepat. - Temukan apa yang Anda cari secara instan dengan penyorotan kata kunci. - Gunakan markdown untuk menambahkan pemformatan. - Buat daftar hal yang harus dilakukan. - Pilih urutan penyortiran catatan dan tag. - Sematkan catatan yang paling sering Anda gunakan. - Sunting tag secara langsung dengan menamai dan mengurutkan ulang. -- Lindungi konten Anda dengan kunci kode sandi. +- Lindungi isi catatan Anda dengan kunci kode. -- Kebijakan Privasi: https://automattic.com/privacy/ Ketentuan Layanan: https://simplenote.com/terms/ -Pemberitahuan Privasi Pengguna California: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Pemberitahuan Privasi Pengguna di California: https://automattic.com/privacy/#us-privacy-laws -- diff --git a/fastlane/metadata/id/keywords.txt b/fastlane/metadata/id/keywords.txt index 0ed58918b..6cea874d3 100644 --- a/fastlane/metadata/id/keywords.txt +++ b/fastlane/metadata/id/keywords.txt @@ -1 +1 @@ -catatan,teks,markdown,jurnal,sinkronisasi,daftar,tugas,cloud,notebook,simpel,notepad,tag,tugas \ No newline at end of file +catatan,teks,markdown,jurnal,sinkronisasi,daftar,tugas,cloud,notebook,simpel,notepad,tag,tugas diff --git a/fastlane/metadata/id/release_notes.txt b/fastlane/metadata/id/release_notes.txt index ffcec52c7..7e2398135 100644 --- a/fastlane/metadata/id/release_notes.txt +++ b/fastlane/metadata/id/release_notes.txt @@ -1,2 +1,4 @@ -- Menambahkan warna latar belakang ke blok kode dalam pratinjau mode gelap -- Menambahkan widget catatan besar +- Perbarui ikon untuk dapat bekerja dengan penataan iOS 18 yang baru +- Tambahkan login sementara dengan menggunakan nama pengguna dan kata sandi +- Tautan diperbarui untuk pemberitahuan privasi untuk pengguna di California + diff --git a/fastlane/metadata/id/subtitle.txt b/fastlane/metadata/id/subtitle.txt index e4fd67b3c..a26e9331e 100644 --- a/fastlane/metadata/id/subtitle.txt +++ b/fastlane/metadata/id/subtitle.txt @@ -1 +1 @@ -Catatan, tugas, dan memo \ No newline at end of file +Catatan, tugas, dan memo diff --git a/fastlane/metadata/it/.gitkeep b/fastlane/metadata/it/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/it/description.txt b/fastlane/metadata/it/description.txt index fdcb2d48f..c94debb63 100644 --- a/fastlane/metadata/it/description.txt +++ b/fastlane/metadata/it/description.txt @@ -1,7 +1,7 @@ Con Simplenote puoi prendere appunti in modo semplice, creare elenchi di cose da fare, registrare le tue idee e molto altro. Aprilo, annota alcune idee e il gioco è fatto. Mentre la tua raccolta cresce, organizzala con tag e spilli e cerca quello di cui hai bisogno con la funzione di ricerca istantanea. I tuoi appunti saranno sempre con te poiché Simplenote si sincronizzerà gratuitamente su tutti i tuoi dispositivi. - Vivi un'esperienza semplice per prendere appunti -- Sincronizza di ogni cosa su tutti i tuoi dispositivi +- Sincronizza ogni cosa su tutti i tuoi dispositivi - Collabora e condividi - Rimani organizzato con i tag - Accedi con la tua e-mail o con il tuo account WordPress.com @@ -30,8 +30,8 @@ ORGANIZZA E CERCA Politica sulla privacy: https://automattic.com/privacy/ Termini di servizio: https://simplenote.com/terms/ -Nota sulla privacy per utenti in California: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Nota sulla privacy per utenti in California: https://automattic.com/privacy/#us-privacy-laws -- -Visita simplenote.com per scaricare Simplenote per altri dispositivi. +Visita simplenote.com e scarica Simplenote sugli altri dispositivi. diff --git a/fastlane/metadata/it/keywords.txt b/fastlane/metadata/it/keywords.txt index 8f861a182..f0ac1ba23 100644 --- a/fastlane/metadata/it/keywords.txt +++ b/fastlane/metadata/it/keywords.txt @@ -1 +1 @@ -appunti,appunto,markdown,diario,sincronizzazione,checklist,cloud,blocco,semplice,notepad,tag \ No newline at end of file +appunti,appunto,markdown,diario,sincronizzazione,checklist,cloud,blocco,semplice,notepad,tag diff --git a/fastlane/metadata/it/release_notes.txt b/fastlane/metadata/it/release_notes.txt index 77de6ddc6..24546a851 100644 --- a/fastlane/metadata/it/release_notes.txt +++ b/fastlane/metadata/it/release_notes.txt @@ -1,2 +1,4 @@ -- Aggiungere il colore di sfondo al blocco di codice nell'anteprima in modalità scura -- Aggiungere widget nota di grandi dimensioni +- Aggiornato le icone per renderle compatibili con i nuovi stili di iOS 18 +- Aggiunto un accesso di ripiego con opzioni di nome utente e password per l'accesso +- Aggiornato il link alla nota sulla privacy per utenti in California + diff --git a/fastlane/metadata/it/subtitle.txt b/fastlane/metadata/it/subtitle.txt index f5c046a0e..fd9ca3b1c 100644 --- a/fastlane/metadata/it/subtitle.txt +++ b/fastlane/metadata/it/subtitle.txt @@ -1 +1 @@ -Appunti, checklist e memo \ No newline at end of file +Appunti, checklist e memo diff --git a/fastlane/metadata/ja/.gitkeep b/fastlane/metadata/ja/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/ja/description.txt b/fastlane/metadata/ja/description.txt index 60fc83dbd..794aec289 100644 --- a/fastlane/metadata/ja/description.txt +++ b/fastlane/metadata/ja/description.txt @@ -1,37 +1,37 @@ -Simplenote では、メモ書き、ToDo リストの作成、アイデアのメモなどを簡単に行うことができます。 開いたら、あとは思い付いたことを書き込むだけ。 メモが増えても、タグやピンを使ってわかりやすくまとめることができます。クイック検索を使えば必要なものが見つかります。 メモは使用しているデバイスに無料で同期されるため、いつでも確認できます。 +Simplenote では、メモ書き、ToDo リストの作成、アイデアのメモなどを簡単に行うことができます。 開いたら、あとは思い付いたことを書き込むだけ。 メモが増えても、タグやピンを使ってわかりやすくまとめることができます。クイック検索を使えば必要なものが見つかります。 メモは使用している端末に無料で同期されるため、いつでも確認できます。 -- 簡単なメモ作り -- すべてのデバイスですべてを同期 -- コラボレーションと共有 -- タグで見やすく整理 -- メールまたは WordPress.com アカウントでログイン +- 手軽にメモできる +- すべての内容をすべての端末と同期 +- メモを共同編集し共有する +- タグを使ってまとめられる +- メールアドレスまたは WordPress.com アカウントでログイン -秘密を保持しながら同期 -- コンピューター、携帯電話、タブレット間でシームレスに自動同期 +確実に同期 +- あらゆる PC、スマホ、タブレットともシームレスに自動で同期します。 - 使用中にバックアップと同期を実行するため、データを紛失することはありません。 -コラボレーションと共有 -- コラボレーションして共に作業する -- 同僚とアイデアを共有したり、ルームメイトと買い物リストを作成したりできます。 +共同編集機能と共有機能 +- 複数で作業できる共同編集機能 - アイデアを仲間とシェアしたり、ルームメイトと一緒に買い物リストを作成したりできます。 - コンテンツのウェブ公開と、特定の相手とのリンクのシェアから選択できます。 - WordPress アカウントと連携させることで、WordPress サイトに直接公開できます。 - サードパーティ製アプリとすばやく簡単に共有できます。 整理と検索 -- タグを使用して見やすく整理し、タグからすばやく検索、並べ替えを行えます。 +- ノートにタグ付けしてわかりやすく整理。タグを使ってすばやく検索や分類ができます。 - キーワードのハイライト表示で、探している部分がすばやく見つかります。 - マークダウン機能による書式設定。 - やることリストの作成。 - ノートとタグのソート順序を選択。 - よく使うノートをピンで固定。 - 名前の編集と順序の並べ替えでタグを直接編集。 -- パスコードロックでコンテンツを保護。 +- パスコードによるロック機能でコンテンツを保護。 -- -プライバシーポリシー : https://automattic.com/privacy/ -利用規約 : https://simplenote.com/terms/ -カリフォルニア州のユーザーへの新しいプライバシー通知 : https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +プライバシーポリシー: https://automattic.com/privacy/ +利用規約: https://simplenote.com/terms/ +カリフォルニア州のユーザーへのプライバシー通知: https://automattic.com/privacy/#us-privacy-laws -- -simplenote.com にアクセスして他のデバイスに Simplenote をダウンロード。 +simplenote.com にアクセスして、他の端末用の Simplenote をダウンロードしてください。 diff --git a/fastlane/metadata/ja/keywords.txt b/fastlane/metadata/ja/keywords.txt index a0474d2aa..4e483358d 100644 --- a/fastlane/metadata/ja/keywords.txt +++ b/fastlane/metadata/ja/keywords.txt @@ -1 +1 @@ -注意,メモ,Markdown,日記,同期,ToDo,リスト,クラウド,ノートブック,シンプル,メモ帳,タグ,ToDo,投稿,メモ \ No newline at end of file +注意,メモ,Markdown,日記,同期,ToDo,リスト,クラウド,ノートブック,シンプル,メモ帳,タグ,ToDo,投稿,メモ diff --git a/fastlane/metadata/ja/release_notes.txt b/fastlane/metadata/ja/release_notes.txt index 2a7b7876e..c69992b82 100644 --- a/fastlane/metadata/ja/release_notes.txt +++ b/fastlane/metadata/ja/release_notes.txt @@ -1,2 +1,4 @@ -- ダークモードプレビューでコードブロックに背景色を追加します -- 大きいメモウィジェットを追加します +- 新しい iOS 18 のスタイルに対応するようにアイコンを更新しました +- ログインにユーザー名とパスワードのオプションを使用するフォールバックログインを追加します +- カリフォルニア州のユーザーへのプライバシー通知のリンクを更新しました + diff --git a/fastlane/metadata/ja/subtitle.txt b/fastlane/metadata/ja/subtitle.txt index cf70022b3..4302d53c8 100644 --- a/fastlane/metadata/ja/subtitle.txt +++ b/fastlane/metadata/ja/subtitle.txt @@ -1 +1 @@ -ノート、ToDo リスト、メモをよりシンプルに \ No newline at end of file +ノート、ToDo リスト、メモをよりシンプルに diff --git a/fastlane/metadata/ko/.gitkeep b/fastlane/metadata/ko/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/ko/description.txt b/fastlane/metadata/ko/description.txt index 5c1b76e84..5caf0a28a 100644 --- a/fastlane/metadata/ko/description.txt +++ b/fastlane/metadata/ko/description.txt @@ -1,37 +1,37 @@ -Simplenote로 손쉽게 메모를 작성하고 할 일 목록을 만들고 아이디어를 보관할 수 있습니다. 메모를 열고 생각나는 아이디어를 적으면 끝입니다. 컬렉션의 내용이 다양해지면 태그와 핀으로 내용을 정리하고 빠른 검색 기능으로 필요한 내용을 찾을 수 있습니다. Simplenote는 여러 기기에서 무료로 동기화되기 때문에 내가 작성한 메모를 언제든지 확인할 수 있습니다. +Simplenote로 손쉽게 메모를 작성하고 할 일 목록을 만들고 아이디어를 보관할 수 있습니다. 생각이 떠오르는 대로 적어 놓기만 하면 됩니다. 컬렉션의 내용이 다양해지면 태그와 핀으로 내용을 정리하고 빠른 검색 기능으로 필요한 내용을 찾을 수 있습니다. Simplenote가 사용하는 기기 전체에서 무료로 동기화되므로 항상 메모가 유지됩니다. -- 간편한 메모 작성 환경 -- 모든 기기의 모든 콘텐츠 동기화 -- 협업 및 공유 +- 간단한 메모 작성 환경 +- 모든 기기에서 모든 콘텐츠 동기화 +- 공동 작업 및 공유 - 태그로 깔끔하게 정리 - 이메일 또는 워드프레스닷컴 계정으로 로그인 -원활한 동기화 -- 컴퓨터, 휴대폰 또는 태블릿에서 자동으로 문제없이 동기화됩니다. +자신 있게 동기화 +- 모든 컴퓨터, 휴대폰 또는 태블릿에서 자동으로 원활하게 동기화됩니다. - 메모 작성과 동시에 모든 내용이 백업 및 동기화되므로 손실되는 내용이 없습니다. -협업 및 공유 +공동 작업 및 공유 - 공동으로 협력하여 작업하세요. 동료와 아이디어를 공유하거나 룸메이트와 함께 장보기 목록을 작성해 보세요. - 콘텐츠의 웹 공개 여부를 결정하고 원하는 사람과 링크를 공유하세요. - 워드프레스닷컴 계정에 연결하여 워드프레스 사이트에 바로 공개하세요. -- 타사 앱과 빠르고 쉽게 공유할 수 있습니다. +- 서드파티 앱과 빠르고 쉽게 공유하세요. 정리 및 검색 - 태그로 깔끔하게 정리하여 빠르게 검색하고 정렬해 보세요. -- 키워드 강조 표시로 원하는 항목을 즉시 찾을 수 있습니다. +- 키워드 강조 표시로 원하는 항목을 즉시 찾으세요. - 마크다운을 사용하여 서식을 추가하세요. - 할 일 목록을 만드세요. - 메모 및 태그 정렬 순서를 선택하세요. - 가장 많이 사용하는 메모를 고정하세요. - 이름을 바꾸고 순서를 변경하여 직접 태그를 편집하세요. -- 패스코드 잠금으로 콘텐츠를 보호하세요. +- 암호 잠금으로 콘텐츠를 보호하세요. ---- +-- 개인정보 취급방침: https://automattic.com/privacy/ -서비스 약관: https://simplenote.com/terms/ -캘리포니아 사용자 개인정보 고지사항: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +https://simplenote.com/terms/ +캘리포니아 사용자 개인정보보호 고지: https://automattic.com/privacy/#us-privacy-laws -- -simplenote.com을 방문하여 다른 기기에서 사용할 Simplenote를 다운로드하세요. +simplenote.com으로 이동하여 다른 기기에 Simplenote를 다운로드하세요. diff --git a/fastlane/metadata/ko/keywords.txt b/fastlane/metadata/ko/keywords.txt index a6d41c5db..b42d39249 100644 --- a/fastlane/metadata/ko/keywords.txt +++ b/fastlane/metadata/ko/keywords.txt @@ -1 +1 @@ -기록,노트,마크다운,일기,동기화,할일목록,목록,클라우드,공책,심플,메모장,태그,할일,글쓰기,메모 \ No newline at end of file +기록,노트,마크다운,일기,동기화,할일목록,목록,클라우드,공책,심플,메모장,태그,할일,글쓰기,메모 diff --git a/fastlane/metadata/ko/release_notes.txt b/fastlane/metadata/ko/release_notes.txt index 97763a1f0..6346e49cf 100644 --- a/fastlane/metadata/ko/release_notes.txt +++ b/fastlane/metadata/ko/release_notes.txt @@ -1,2 +1,4 @@ -- 어둡게 모드 미리보기의 코드 블록에 배경 색상 추가 -- 큰 메모 위젯 추가 +- 새로운 iOS 18 스타일에서 지원하도록 아이콘이 업데이트됨 +- 사용자명과 비밀번호로 로그인할 수 있는 대체 로그인 옵션 추가 +- 캘리포니아 사용자를 위한 개인정보 취급방침 링크가 업데이트됨 + diff --git a/fastlane/metadata/ko/subtitle.txt b/fastlane/metadata/ko/subtitle.txt index e8ad5d866..433047e7c 100644 --- a/fastlane/metadata/ko/subtitle.txt +++ b/fastlane/metadata/ko/subtitle.txt @@ -1 +1 @@ -간단한 기록, 할 일 목록, 메모 \ No newline at end of file +간단한 기록, 할 일 목록, 메모 diff --git a/fastlane/metadata/nl-NL/.gitkeep b/fastlane/metadata/nl-NL/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/nl-NL/description.txt b/fastlane/metadata/nl-NL/description.txt index 03c6474cb..7377109f8 100644 --- a/fastlane/metadata/nl-NL/description.txt +++ b/fastlane/metadata/nl-NL/description.txt @@ -1,13 +1,13 @@ Simplenote is een eenvoudige manier om aantekeningen te maken, takenlijsten op te stellen, ideeën vast te leggen en meer. Open Simplenote, schrijf je gedachten op en je bent klaar. Naarmate je verzameling groeit, kan je je opmerkingen geordend houden met tags en pins en zoeken naar wat je nodig hebt. Simplenote synchroniseert gratis met al je toestellen, waardoor je je opmerkingen altijd bij je hebt. -- Een fijne ervaring voor notities maken -- Synchroniseer alles op al je apparaten -- Samenwerken en delen -- Overzicht houden met tags -- Log in met je e-mail of WordPress.com-account +- Een eenvoudige manier om aantekeningen te maken +- Synchroniseer alles op al je toestellen +- Werk samen en deel +- Houd het overzicht met tags +- Log in met je e-mail of WordPress.com account VOL VERTROUWEN SYNCHRONISEREN -- Automatisch zonder problemen synchroniseren op elke computer, telefoon of tablet. +- Synchroniseer automatisch naadloos op computer, telefoon of tablet. - Maak back-ups en synchroniseer terwijl je opmerkingen maakt, zodat je je inhoud nooit kwijtraakt. SAMENWERKEN EN DELEN @@ -24,14 +24,14 @@ ORGANISEREN EN ZOEKEN - Kies de sorteervolgorde voor je aantekeningen en tags. - Pin de aantekeningen die je het meest gebruikt. - Bewerk tags direct door ze een nieuwe naam te geven en de volgorde te wijzigen. -- Bescherm je content met een pincodevergrendeling. +- Bescherm je inhoud met een pincodevergrendeling. -- Privacybeleid: https://automattic.com/privacy/ -Servicevoorwaarden: https://simplenote.com/terms/ -Privacyverklaring voor gebruikers in Californië: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Terms of service: https://simplenote.com/terms/ +Privacyverklaring voor gebruikers in Californië: https://automattic.com/privacy/#us-privacy-laws -- -Ga naar simplenote.com om Simplenote voor je andere apparaten te downloaden. +Ga naar simplenote.com om Simplenote te downloaden voor je andere apparaten. diff --git a/fastlane/metadata/nl-NL/keywords.txt b/fastlane/metadata/nl-NL/keywords.txt index 1103f8e33..aec8c9704 100644 --- a/fastlane/metadata/nl-NL/keywords.txt +++ b/fastlane/metadata/nl-NL/keywords.txt @@ -1 +1 @@ -notities,tekst,markdown,dagboek,synchroniseren,to-do,cloud,eenvoudig,tag,schrijven,memo \ No newline at end of file +notities,tekst,markdown,dagboek,synchroniseren,to-do,cloud,eenvoudig,tag,schrijven,memo diff --git a/fastlane/metadata/nl-NL/release_notes.txt b/fastlane/metadata/nl-NL/release_notes.txt index 8030c55f6..fb5ce0f87 100644 --- a/fastlane/metadata/nl-NL/release_notes.txt +++ b/fastlane/metadata/nl-NL/release_notes.txt @@ -1,2 +1,4 @@ -- Een achtergrondkleur toegevoegd aan het codeblok in de voorbeeldweergave van de donkere modus -- Een grote notitie-widget toegevoegd +- Pictogrammen bijgewerkt om te werken met nieuwe iOS 18-stijlen +-Back-up login toevoegen met gebruikersnaam en wachtwoordoptie om in te loggen +-Bijgewerkt link naar privacyverklaring voor gebruikers in Californië + diff --git a/fastlane/metadata/nl-NL/subtitle.txt b/fastlane/metadata/nl-NL/subtitle.txt index aa0c49ad1..910b1630c 100644 --- a/fastlane/metadata/nl-NL/subtitle.txt +++ b/fastlane/metadata/nl-NL/subtitle.txt @@ -1 +1 @@ -Notities, taken en memo's \ No newline at end of file +Notities, taken en memo's diff --git a/fastlane/metadata/pt-BR/.gitkeep b/fastlane/metadata/pt-BR/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/pt-BR/description.txt b/fastlane/metadata/pt-BR/description.txt index 2202b3e17..28f5f2f9f 100644 --- a/fastlane/metadata/pt-BR/description.txt +++ b/fastlane/metadata/pt-BR/description.txt @@ -1,36 +1,36 @@ -O Simplenote é uma maneira fácil de fazer anotações, criar listas de tarefas, captar ideias e muito mais. Abra-o, escreva o que quiser e pronto. Conforme a sua coleção aumenta, organize-a usando tags e pins, e encontre o que precisa com a pesquisa instantânea. Como o Simplenote será sincronizado em todos os seus dispositivos gratuitamente, suas anotações o acompanharão o tempo todo. +O Simplenote é uma maneira fácil de fazer anotações, criar listas de tarefas, captar ideias e muito mais. Abra-o, escreva o que quiser e pronto. Conforme sua coleção cresce, mantenha tudo no seu devido lugar com tags e anotações fixadas para encontrar o que você precisa com uma pesquisa instantânea. Como o Simplenote é sincronizado em todos os seus dispositivos gratuitamente, suas anotações acompanham você o tempo todo. -- Faça anotações de forma simples. -- Sincronize tudo em todos os seus dispositivos. -- Colabore e compartilhe. -- Mantenha seu conteúdo organizado com tags. -- Faça login com seu e-mail ou sua conta do WordPress.com. +- Desfrute de uma experiência simples de anotação +- Sincronize tudo em todos os seus dispositivos +- Colabore e compartilhe +- Mantenha tudo organizado com as tags +- Faça login com seu e-mail ou conta do WordPress.com SINCRONIZE COM CONFIANÇA -- Sincronize seu conteúdo automaticamente em qualquer computador, celular ou tablet. -- Faça backup e sincronize tudo enquanto faz suas anotações, para não perder nenhum conteúdo. +- Sincronize suas anotações de forma ininterrupta e automática em computadores, celulares e tablets. +- Faça backup e sincronize tudo enquanto faz suas anotações para não perder nenhum conteúdo. COLABORE E COMPARTILHE -- Colabore e trabalhe com outras pessoas: compartilhe ideias com um amigo ou faça uma lista de compras com seu colega de quarto. -- Escolha se quer ou não publicar o seu conteúdo na Web e compartilhe um link com quem você desejar. +- Colabore e trabalhe em conjunto: compartilhe suas ideias com um colega de trabalho, ou escreva uma lista de compras junto com seu colega de quarto. +- Decida se quer publicar o conteúdo na Web e compartilhe um link com quem você quiser. - Publique diretamente em um site do WordPress ao conectar-se a sua conta do WordPress.com. - Compartilhe de forma rápida e simples com aplicativos de terceiros. ORGANIZE E PESQUISE -- Mantenha seu conteúdo organizado com tags e use-as para classificar e pesquisar de forma rápida. +- Organize seu conteúdo com as tags e as use para pesquisas e classificações rápidas. - Encontre o que está procurando, de forma instantânea, com o destaque de palavras-chave. - Use o markdown para adicionar formatação. - Crie listas de tarefas. - Selecione a ordem de classificação de suas anotações e tags. - Marque as anotações que mais usa. - Edite as tags diretamente, renomeando-as e reorganizando-as. -- Proteja seu conteúdo usando o bloqueio com senha. +- Proteja o seu conteúdo com um bloqueio por senha. -- Política de privacidade: https://automattic.com/privacy/ Termos de serviço: https://simplenote.com/terms/ -Aviso de privacidade para usuários residentes da Califórnia: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Aviso de privacidade para usuários residentes da Califórnia: https://automattic.com/privacy/#us-privacy-laws -- diff --git a/fastlane/metadata/pt-BR/keywords.txt b/fastlane/metadata/pt-BR/keywords.txt index c045440a7..a4752ea11 100644 --- a/fastlane/metadata/pt-BR/keywords.txt +++ b/fastlane/metadata/pt-BR/keywords.txt @@ -1 +1 @@ -anotações,anotaçãp,markdown,diário,sincronização,tarefas,lista,nuvem,bloco de notas,simples,tag \ No newline at end of file +anotações,anotaçãp,markdown,diário,sincronização,tarefas,lista,nuvem,bloco de notas,simples,tag diff --git a/fastlane/metadata/pt-BR/release_notes.txt b/fastlane/metadata/pt-BR/release_notes.txt index e33553de7..e0ed42b0d 100644 --- a/fastlane/metadata/pt-BR/release_notes.txt +++ b/fastlane/metadata/pt-BR/release_notes.txt @@ -1,2 +1,4 @@ -- Adição de cor de fundo ao bloco de código na visualização do modo escuro -- Adição do widget de anotação grande +- Ícones atualizados para funcionar com novos estilos do iOS 18 +- Adicionamos uma alternativa de login com opção de nome de usuário e senha +- Link do aviso de privacidade para usuários residentes da Califórnia atualizado + diff --git a/fastlane/metadata/pt-BR/subtitle.txt b/fastlane/metadata/pt-BR/subtitle.txt index 20066af09..d0e6dd18e 100644 --- a/fastlane/metadata/pt-BR/subtitle.txt +++ b/fastlane/metadata/pt-BR/subtitle.txt @@ -1 +1 @@ -Anotações, tarefas e recados \ No newline at end of file +Anotações, tarefas e recados diff --git a/fastlane/metadata/ru/.gitkeep b/fastlane/metadata/ru/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/ru/description.txt b/fastlane/metadata/ru/description.txt index f12d5f60f..e11422b84 100644 --- a/fastlane/metadata/ru/description.txt +++ b/fastlane/metadata/ru/description.txt @@ -1,37 +1,37 @@ Simplenote — это простой способ делать заметки, создавать списки дел, записывать идеи и так далее. Откройте его, запишите свои мысли, и готово. Чтобы быстро находить заметки, используйте теги и булавки, а также функцию быстрого поиска. Simplenote бесплатно синхронизирует заметки на ваших устройствах — у вас всегда будет доступ к ним. -- Простой удобный способ делать заметки -- Синхронизируйте на всех своих устройствах -- Совместное редактирование и обмен -- Упорядочивание заметок с помощью меток -- Вход с помощью эл. почты или учетной записи WordPress.com - -НАДЕЖНАЯ СИНХРОНИЗАЦИЯ -- Автоматическая синхронизация на компьютере, телефоне или планшете. -- Резервное копирование и синхронизация по мере ввода заметки, вы никогда ничего не потеряете. - -СОВМЕСТНАЯ РАБОТА И ОБМЕН -- Работайте совместно, делитесь идеями с коллегами, составляйте список покупок вместе с друзьями. -- Публикуйте материалы в Интернете и делитесь ссылками с кем-угодно. +— Простой и удобный способ делать заметки. +— Синхронизируйте на всех своих устройствах. +— Работайте совместно и делитесь. +— Упорядочивайте при помощи меток. +— Входите с учётной записью электронной почты или WordPress.com. + +УВЕРЕННАЯ СИНХРОНИЗАЦИЯ +— Автоматическая синхронизация на компьютере, телефоне или планшете. +— Резервное копирование и синхронизация по мере ввода заметки: вы никогда ничего не потеряете. + +СОВМЕСТНАЯ РАБОТА И ОБМЕН ИНФОРМАЦИЕЙ +— Работайте совместно: делитесь идеями с коллегами или составляйте списки покупок вместе с партнёром. +— Публикуйте материалы в Интернете и делитесь ссылками с кем угодно. - Публикуйте прямо на сайте WordPress, подключив свою учетную запись WordPress.com. -- Быстро и легко переносите заметки в другие приложения. +— Быстро и легко переносите заметки в другие приложения. ОРГАНИЗАЦИЯ И ПОИСК -- Используйте метки для быстрого поиска и сортировки. -- Мгновенно находите то, что нужно, с помощью выделения ключевых слов. +— Используйте метки для быстрого поиска и сортировки. +— Мгновенно находите то, что нужно, выделяя ключевые слова. - Используйте разметку для форматирования. - Создавайте списки дел. - Выбирайте порядок сортировки заметок и тегов. - Прикалывайте булавками самые популярные заметки. -- Редактируйте теги: меняйте их имена и порядок расположения. -- Защищайте заметки с помощью пароля. +— Редактируйте теги: меняйте их имена и порядок расположения. +— Защищайте заметки с помощью пароля. -- Политика конфиденциальности: https://automattic.com/privacy/ Условия предоставления услуг: https://simplenote.com/terms/ -Заявление о защите конфиденциальности пользователей из Калифорнии: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Уведомление о конфиденциальности для жителей Калифорнии: https://automattic.com/privacy/#us-privacy-laws -- -Перейдите на simplenote.com, чтобы загрузить Simplenote на другие устройства. +Посетите страницу simplenote.com, чтобы загрузить Simplenote для других устройств. diff --git a/fastlane/metadata/ru/keywords.txt b/fastlane/metadata/ru/keywords.txt index 1c57c3a06..2e8ac572b 100644 --- a/fastlane/metadata/ru/keywords.txt +++ b/fastlane/metadata/ru/keywords.txt @@ -1 +1 @@ -примечания, заметка, разметка, журнал, синхронизация, список дел, список, облако, записная книжка \ No newline at end of file +примечания, заметка, разметка, журнал, синхронизация, список дел, список, облако, записная книжка diff --git a/fastlane/metadata/ru/release_notes.txt b/fastlane/metadata/ru/release_notes.txt index f778cd935..58e305475 100644 --- a/fastlane/metadata/ru/release_notes.txt +++ b/fastlane/metadata/ru/release_notes.txt @@ -1,2 +1,4 @@ -— В предварительный просмотр блока "Код" в тёмном режиме добавлен цвет фона. -— Добавлен большой виджет заметок. +— Значки обновлены в соответствии со стилями новой iOS 18 +— Добавлена опция резервного входа с именем пользователя и паролем +— Обновлена ссылка на заявление о защите конфиденциальности для пользователей из Калифорнии + diff --git a/fastlane/metadata/ru/subtitle.txt b/fastlane/metadata/ru/subtitle.txt index 9b88ecae7..bc41693eb 100644 --- a/fastlane/metadata/ru/subtitle.txt +++ b/fastlane/metadata/ru/subtitle.txt @@ -1 +1 @@ -Примечания, списки, заметки \ No newline at end of file +Примечания, списки, заметки diff --git a/fastlane/metadata/sv/.gitkeep b/fastlane/metadata/sv/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/sv/description.txt b/fastlane/metadata/sv/description.txt index 56bdef89b..50e37c979 100644 --- a/fastlane/metadata/sv/description.txt +++ b/fastlane/metadata/sv/description.txt @@ -1,37 +1,37 @@ -Simplenote är ett enkelt sätt att göra anteckningar, skapa att-göra-listor, skriva ner idéer med mera. Öppna appen, skriv ner några tankar och du är klar. Allteftersom din samling växer kan du hålla dig organiserad med hjälp av etiketter och stift samt hitta vad du behöver med omedelbara sökningar. Eftersom Simplenote synkroniseras kostnadsfritt på alla dina enheter kan du alltd komma åt dina anteckningar. +Simplenote är ett enkelt sätt att göra anteckningar, skapa att-göra-listor, skriva ner idéer med mera. Öppna appen, skriv ner några tankar och du är klar. Allteftersom din samling växer kan du hålla dig organiserad med hjälp av etiketter och stift samt hitta vad du behöver med omedelbara sökningar. Eftersom Simplenote synkroniseras kostnadsfritt på alla dina enheter kan du alltid komma åt dina anteckningar. - En enkel anteckningsupplevelse -- Synkronisera allt över dina enheter +- Synkronisera allt på alla dina enheter - Samarbeta och dela -- Var organiserad med etiketter -- Logga in på din e-post eller ditt WordPress.com-konto +- Håll ordning och reda med etiketter +- Logga in med din e-postadress eller ditt WordPress.com-konto -SYNKRONISERA TRYGGT -- Synkronisera automatiskt och sömlöst mellan alla datorer, telefoner och surfplattor. +SYNKRONISERA MED SJÄLVFÖRTROENDE +- Synkronisera automatiskt och smidigt på dator, telefon eller surfplatta. - Säkerhetskopiera och synkronisera allt när du gör anteckningar, så att du aldrig förlorar ditt innehåll SAMARBETA OCH DELA -Samarbeta och arbeta tillsammans – dela idéer med en kollega eller skriv en inköpslista med din rumskamrat. +- Samarbeta och arbeta tillsammans – dela idéer med en kollega eller skriv en handlingslista tillsammans med din rumskamrat. - Välj om du vill publicera ditt innehåll på webben och dela länkar med vem du vill. - Publicera direkt till en WordPress-webbplats genom att ansluta ditt WordPress.com-konto. - Dela snabbt och enkelt med tredjepartsappar. ORGANISERA OCH SÖK -- Håll dig organiserad med etiketter och använd dem för snabb sökning och sortering. +- Håll ordning och reda med etiketter och använd dem för snabb sökning och sortering. - Hitta vad du letar efter omedelbart med nyckelordsmarkering. - Använd markdown för att lägga till formatering. - Skapa att göra-listor. - Välj sorteringsordningen för dina anteckningar och etiketter. - Fäst de anteckningar som du använder mest. - Redigera etiketter direkt genom att byta namn eller ändra ordning på dem. -Skydda ditt innehåll med ett lösenordslås. +- Skydda ditt innehåll med ett lösenordslås. -– +-- -Sekretesspolicy: https://automattic.com/privacy/ +Integritetspolicy: https://automattic.com/privacy/ Användarvillkor: https://simplenote.com/terms/ -Sekretessmeddelande för användare i Kalifornien: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Integritetsmeddelande för användare i Kalifornien: https://automattic.com/privacy/#us-privacy-laws -– +-- Besök simplenote.com för att ladda ner Simplenote till dina andra enheter. diff --git a/fastlane/metadata/sv/keywords.txt b/fastlane/metadata/sv/keywords.txt index 67b3d33c4..e61ccd39b 100644 --- a/fastlane/metadata/sv/keywords.txt +++ b/fastlane/metadata/sv/keywords.txt @@ -1 +1 @@ -примечания,заметка,разметка,журнал,синхронизация,список дел,список,облако,записная книжка,простой \ No newline at end of file +примечания,заметка,разметка,журнал,синхронизация,список дел,список,облако,записная книжка,простой diff --git a/fastlane/metadata/sv/release_notes.txt b/fastlane/metadata/sv/release_notes.txt index c3eb68060..20714a737 100644 --- a/fastlane/metadata/sv/release_notes.txt +++ b/fastlane/metadata/sv/release_notes.txt @@ -1,2 +1,4 @@ -- Lägg till bakgrundsfärg till kodblocket vid förhandsgranskning i mörkt läge -- Lägg till widget för stora anteckningar +- Uppdaterade ikoner som fungerar med nya iOS 18-stilar +- Lägg till fallback-inloggning med alternativ för användarnamn och lösenord för att logga in +- Uppdaterad länk till integritetsmeddelande för användare i Kalifornien + diff --git a/fastlane/metadata/sv/subtitle.txt b/fastlane/metadata/sv/subtitle.txt index 9620ac9ce..46bfed826 100644 --- a/fastlane/metadata/sv/subtitle.txt +++ b/fastlane/metadata/sv/subtitle.txt @@ -1 +1 @@ -Enkla anteckningar och memon \ No newline at end of file +Enkla anteckningar och memon diff --git a/fastlane/metadata/tr/.gitkeep b/fastlane/metadata/tr/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/tr/description.txt b/fastlane/metadata/tr/description.txt index af4cbaf09..3e4dc9007 100644 --- a/fastlane/metadata/tr/description.txt +++ b/fastlane/metadata/tr/description.txt @@ -1,37 +1,37 @@ Simplenote not tutmanın, yapılacaklar listesi oluşturmanın, fikirlerinizi kaydetmenin ve daha fazlasını yapmanın kolay bir yoludur. Uygulamayı açıp düşüncelerinizi yazmanız yeterlidir. Koleksiyonunuz büyüdükçe etiketler ve iğnelerle düzeninizi koruyun. Anında arama özelliğiyle ihtiyacınız olanları bulun. Simplenote, cihazlarınızda ücretsiz olarak eşitlendiğinden notlarınız her zaman sizinledir. -- Basit bir not alma deneyimi -- Tüm cihazlarınızdaki her şeyi eşitleyin -- İşbirliği yapın ve paylaşın -- Etiketler sayesinde düzenli kalın -- E-postanızla veya WordPress.com hesabınızla oturum açın - -GÜVENLE SENKRONİZE EDİN -- Herhangi bir bilgisayar, telefon veya tablette otomatik olarak sorunsuz senkronizasyon yapın. +- Basit bir not tutma deneyimi +- Her şeyi tüm cihazlarınızda eşitleyin +- İş birliği yapın ve paylaşın +- Etiketlerle düzeninizi koruyun +- E-postanız veya WordPress.com hesabınızla giriş yapın + +GÜVENLE EŞİTLEYİN +- İstediğiniz bilgisayarda, telefonda veya tablette sorunsuzca otomatik eşitleme yapın. - İçeriğinizi hiçbir zaman kaybetmemek için not tutarken her şeyi yedekleyin ve eşitleyin. -İŞBİRLİĞİ YAPIN VE PAYLAŞIN -- İşbirliği yapın ve birlikte çalışın - fikirlerinizi iş arkadaşınızla paylaşın veya oda arkadaşınızla bir alışveriş listesi yazın. +İŞ BİRLİĞİ YAPIN VE PAYLAŞIN +- İş birliği yapın ve birlikte çalışın: Fikirlerinizi bir iş arkadaşınızla paylaşın veya ev arkadaşınızla alışveriş listesi yapın. - İçeriğinizi web'de yayımlayıp yayımlamayacağınızı seçin ve istediğiniz kişilerle bağlantı paylaşın. - WordPress.com hesabınızı bağlayarak içeriğinizi doğrudan bir WordPress sitesinde yayımlayın. - Üçüncü taraf uygulamalarıyla hızla ve kolayca paylaşım yapın. DÜZENLEYİN VE ARAYIN -- Etiketler sayesinde düzenli kalın ve bunları kullanarak hızlı şekilde arama ve sıralama yapın. +- Etiketlerle düzeninizi koruyun. Bunları hızlı arama ve sıralama için kullanın. - Anahtar sözcük vurgulama ile aradığınız her şeyi anında bulun. - Biçimlendirme eklemek için Markdown'ı kullanın. - Yapılacaklar listesi oluşturun. - Notlarınızın ve etiketlerinizin nasıl sıralanacağını seçin. - En çok kullandığınız notları sabitleyin. - Etiketleri düzenleyerek yeniden adlandırın ve yeniden sıralayın. -- Bir parola kilidi ile içeriğinizi koruyun. +- İçeriğinizi parola kilidiyle koruyun. -- Gizlilik Politikası: https://automattic.com/privacy/ -Hizmet Şartları: https://simplenote.com/terms/ -Kaliforniya'daki Kullanıcılar için Gizlilik Bildirimi: https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +Hizmet Koşulları: https://simplenote.com/terms/ +Kaliforniya'daki Kullanıcılar İçin Gizlilik Bildirimi: https://automattic.com/privacy/#us-privacy-laws -- -Diğer cihazlarınız için Simplenote'u indirmek üzere simplenote.com adresini ziyaret edin. +simplenote.com adresine giderek diğer cihazlarınıza da Simplenote uygulamasını indirin. diff --git a/fastlane/metadata/tr/keywords.txt b/fastlane/metadata/tr/keywords.txt index 8130ed4e3..18fda9249 100644 --- a/fastlane/metadata/tr/keywords.txt +++ b/fastlane/metadata/tr/keywords.txt @@ -1 +1 @@ -not,planlama,günlük,eşitle,yapılacaklar,bulut,defter,basit,not defteri,etiket,işler,yazı,hatırlatma \ No newline at end of file +not,planlama,günlük,eşitle,yapılacaklar,bulut,defter,basit,not defteri,etiket,işler,yazı,hatırlatma diff --git a/fastlane/metadata/tr/release_notes.txt b/fastlane/metadata/tr/release_notes.txt index fffd73b49..58a4fdad0 100644 --- a/fastlane/metadata/tr/release_notes.txt +++ b/fastlane/metadata/tr/release_notes.txt @@ -1,2 +1,4 @@ -- Karanlık mod önizlemesinde kod blokuna arka plan rengi ekleyin -- Büyük not bileşeni ekleyin +- Yeni iOS 18 stilleriyle çalışması için simgeler güncellendi +- Oturum açmak için kullanıcı ve şifre seçeneği içeren ikinci oturum açma eklendi +- Kaliforniya'daki kullanıcılar için gizlilik bildirimine giden bağlantı güncellendi + diff --git a/fastlane/metadata/tr/subtitle.txt b/fastlane/metadata/tr/subtitle.txt index ca9514cd0..7f78f2803 100644 --- a/fastlane/metadata/tr/subtitle.txt +++ b/fastlane/metadata/tr/subtitle.txt @@ -1 +1 @@ -Basit not ve listeler \ No newline at end of file +Basit not ve listeler diff --git a/fastlane/metadata/zh-Hans/.gitkeep b/fastlane/metadata/zh-Hans/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/zh-Hans/description.txt b/fastlane/metadata/zh-Hans/description.txt index 0792a09af..062d8895e 100644 --- a/fastlane/metadata/zh-Hans/description.txt +++ b/fastlane/metadata/zh-Hans/description.txt @@ -1,23 +1,23 @@ Simplenote 可帮助您轻松记录笔记、创建任务清单、捕捉想法等。 只需打开并写下一些想法即可。 随着合集增多,您可以使用标签和固定按钮对其进行整理,通过快速搜索找到您需要的内容。 因为 Simplenote 会在您的设备之间免费同步,所以您可以随时随地访问您的笔记。 -- 简单易用的笔记体验 -- 在所有设备之间同步一切内容 -- 协作和共享 +- 轻松的笔记记录体验 +- 在所有设备之间同步所有内容 +- 协作与共享 - 使用标签保持整洁有序 -- 使用电子邮件地址或 WordPress.com 帐户登录 +- 使用您的电子邮件或 WordPress.com 账户登录 -安心同步 -- 自动无缝地跨任何电脑、手机或平板电脑同步。 +同步无忧 +- 自动在所有计算机、手机或平板电脑之间无缝同步。 - 在您记录笔记时备份并同步所有内容,因此您永远不会丢失内容。 -协作和共享 -- 协作与合作 - 与同事分享想法,或者写一份需要与室友一同采购的杂货清单。 +协作与共享 +- 协作与合作:与同事分享想法或与室友一起写购物清单。 - 选择是否在网络上发布您的内容,并与您的理想对象共享链接。 - 连接至您的 WordPress.com 帐户,可直接在 WordPress 站点上发布。 - 快速轻松地与第三方应用共享。 -整理和搜索 -- 使用标签保持整洁有序,还可以通过标签快速搜索和分类。 +整理与搜索 +- 使用标签保持整洁有序,并通过标签快速搜索和分类。 - 通过高亮显示关键字,快速找到您所需的内容。 - 使用 Markdown 添加格式。 - 创建任务清单。 @@ -30,8 +30,8 @@ Simplenote 可帮助您轻松记录笔记、创建任务清单、捕捉想法等 隐私政策:https://automattic.com/privacy/ 服务条款:https://simplenote.com/terms/ -加州用户隐私声明:https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +加州用户隐私声明:https://automattic.com/privacy/#us-privacy-laws -- -访问 simplenote.com,下载适用于您的其他设备的 Simplenote。 +访问 simplenote.com,将 Simplenote 下载至您的其他设备上。 diff --git a/fastlane/metadata/zh-Hans/keywords.txt b/fastlane/metadata/zh-Hans/keywords.txt index 968552717..4577ded2b 100644 --- a/fastlane/metadata/zh-Hans/keywords.txt +++ b/fastlane/metadata/zh-Hans/keywords.txt @@ -1 +1 @@ -备注、文本、markdown、日志、日记、同步、待办事项、云、笔记本、简单、记事本、标记、待办事项、写作、备忘录 \ No newline at end of file +备注、文本、markdown、日志、日记、同步、待办事项、云、笔记本、简单、记事本、标记、待办事项、写作、备忘录 diff --git a/fastlane/metadata/zh-Hans/release_notes.txt b/fastlane/metadata/zh-Hans/release_notes.txt index d0b4d810c..51bd95f3d 100644 --- a/fastlane/metadata/zh-Hans/release_notes.txt +++ b/fastlane/metadata/zh-Hans/release_notes.txt @@ -1,2 +1,4 @@ -- 在深色模式预览中向代码区块添加了背景颜色 -- 添加大的笔记小工具 +- 更新了图标,使其与 iOS 18 的新样式相匹配 +- 增加了使用用户名和密码进行回退登录的登录选项 +- 更新了面向加州用户的隐私声明的链接 + diff --git a/fastlane/metadata/zh-Hans/subtitle.txt b/fastlane/metadata/zh-Hans/subtitle.txt index e2f931aec..5907655e2 100644 --- a/fastlane/metadata/zh-Hans/subtitle.txt +++ b/fastlane/metadata/zh-Hans/subtitle.txt @@ -1 +1 @@ -简明的备注、待办事项和备忘录 \ No newline at end of file +简明的备注、待办事项和备忘录 diff --git a/fastlane/metadata/zh-Hant/.gitkeep b/fastlane/metadata/zh-Hant/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/zh-Hant/description.txt b/fastlane/metadata/zh-Hant/description.txt index fd89291dd..9c8742ea0 100644 --- a/fastlane/metadata/zh-Hant/description.txt +++ b/fastlane/metadata/zh-Hant/description.txt @@ -1,22 +1,22 @@ -Simplenote 讓你輕鬆做筆記、建立待辦事項清單、記下想法等等。 開啟程式、寫下想法,這樣就大功告成囉。 隨著收集的資料越來越多,標籤和釘選功能可讓你保持井井有條,你也可以使用即時搜尋功能尋找所需資料。 Simplenote 提供免費跨裝置同步,讓你隨時隨地將筆記帶在身邊。 +Simplenote 讓你輕鬆做筆記、建立待辦事項清單、記下想法等等。 開啟程式、記下想法,大功告成。 隨著收集的資料越來越多,標籤和釘選功能可讓你保持井井有條,你也可以使用即時搜尋功能尋找所需資料。 Simplenote 提供免費跨裝置同步功能,讓你隨時隨地將筆記帶在身邊。 -- 輕鬆做筆記 -- 在所有裝置同步全部內容 -- 協同合作並彼此分享 -- 使用標籤整理筆記 -- 使用你的電子郵件地址或 WordPress.com 帳號登入 +- 享受輕鬆做筆記的體驗 +- 所有內容會同步到所有裝置 +- 協作與共用 +- 使用標籤統整資料 +- 使用電子郵件或 WordPress.com 帳號即可登入 -放心同步筆記 -- 順利將筆記自動同步至任何電腦、手機或平板電腦。 +放心同步 +- 順利自動同步到任何電腦、手機或平板電腦。 - 做筆記的當下同時備份和同步,讓你永遠不會遺失內容。 -協同合作並彼此分享 +協作與共用 - 共同作業,與同事分享想法,或與室友合寫購物清單。 - 能夠選擇是否將筆記內容發佈到網路,並與任何人分享連結。 - 連結 WordPress.com 帳號即可將筆記直接發表至 WordPress 網站。 - 輕鬆快速與第三方應用程式共享筆記。 -整理及搜尋 +整理和搜尋 - 使用標籤即可讓一切保持井井有條,還能快速搜尋和排序。 - 輸入關鍵字可立即找到所需內容。 - 使用 Markdown 即可新增格式。 @@ -24,14 +24,14 @@ Simplenote 讓你輕鬆做筆記、建立待辦事項清單、記下想法等等 - 為筆記和標籤選擇排序順序。 - 釘選最常用的筆記。 - 重新命名和重新排序即可直接編輯標籤。 -- 透過密碼鎖定保護你的內容。 +- 使用密碼鎖定保護你的內容。 -- 隱私權政策:https://automattic.com/privacy/ 服務條款:https://simplenote.com/terms/ -加州使用者隱私權聲明:https://wp.me/Pe4R-d/#california-consumer-privacy-act-ccpa +加州使用者的隱私權聲明:https://automattic.com/privacy/#us-privacy-laws -- -若要下載 Simplenote 到其他裝置,請前往 simplenote.com。 +如需為其他裝置下載 Simplenote,請造訪 simplenote.com。 diff --git a/fastlane/metadata/zh-Hant/keywords.txt b/fastlane/metadata/zh-Hant/keywords.txt index 2c5acd0a8..676a44305 100644 --- a/fastlane/metadata/zh-Hant/keywords.txt +++ b/fastlane/metadata/zh-Hant/keywords.txt @@ -1 +1 @@ -記事, 筆記, markdown, 日誌, 同步, 待辦事項, 清單, 雲端, 筆記本, 簡單, 記事本, 標籤, 待辦事項, 待處理, 備忘錄 \ No newline at end of file +記事, 筆記, markdown, 日誌, 同步, 待辦事項, 清單, 雲端, 筆記本, 簡單, 記事本, 標籤, 待辦事項, 待處理, 備忘錄 diff --git a/fastlane/metadata/zh-Hant/release_notes.txt b/fastlane/metadata/zh-Hant/release_notes.txt index b2ac39008..3d39bc13c 100644 --- a/fastlane/metadata/zh-Hant/release_notes.txt +++ b/fastlane/metadata/zh-Hant/release_notes.txt @@ -1,2 +1,4 @@ -- 在深色模式預覽中為程式碼區塊新增了背景顏色 -- 新增了大型筆記的小工具 +- 更新圖示以搭配新的 iOS 18 樣式 +- 新增可透過使用者名稱和密碼選項登入的備用登入方式 +- 更新了加州使用者隱私權聲明的連結 + diff --git a/fastlane/metadata/zh-Hant/subtitle.txt b/fastlane/metadata/zh-Hant/subtitle.txt index 12e876a79..e8a28a8cc 100644 --- a/fastlane/metadata/zh-Hant/subtitle.txt +++ b/fastlane/metadata/zh-Hant/subtitle.txt @@ -1 +1 @@ -簡單好用的筆記、待辦事項和備忘錄 \ No newline at end of file +簡單好用的筆記、待辦事項和備忘錄