From 8b109fe2601ddf7be0dfc91441580d0df154f11a Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 18 Nov 2022 11:03:53 -0700 Subject: [PATCH 001/547] Add rubocop.yml --- .rubocop.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..ee10b62d1 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,25 @@ +AllCops: + Exclude: + - DerivedData/**/* + - Pods/**/* + - vendor/**/* + NewCops: enable + SuggestExtensions: + rubocop-rake: false + +Layout/LineLength: + Exclude: + - fastlane/Fastfile + +Metrics/BlockLength: + Exclude: + - fastlane/*file + +Metrics/BlockNesting: + Exclude: + - fastlane/*file + +Naming/FileName: + Exclude: + - fastlane/*file + - '**/*.podspec' From 860f6acb393c6df82093ba406771ec8317f340b0 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 18 Nov 2022 11:04:06 -0700 Subject: [PATCH 002/547] Remove unneeded TravisCI code --- Rakefile | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Rakefile b/Rakefile index 5f337f755..bcbb3302a 100644 --- a/Rakefile +++ b/Rakefile @@ -239,16 +239,6 @@ task :xcode => [:dependencies] do sh "open #{XCODE_WORKSPACE}" end -def fold(label, &block) - puts "travis_fold:start:#{label}" if is_travis? - yield - puts "travis_fold:end:#{label}" if is_travis? -end - -def is_travis? - return ENV["TRAVIS"] != nil -end - def pod(args) args = %w[bundle exec pod] + args sh(*args) From fff6a3b217ad72457c7dde2a5de1254819f69baf Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 18 Nov 2022 11:12:40 -0700 Subject: [PATCH 003/547] Apply rubocop fixes --- Podfile | 8 +- Rakefile | 162 ++++++------ fastlane/Deliverfile | 12 +- fastlane/Fastfile | 579 ++++++++++++++++++++++--------------------- 4 files changed, 385 insertions(+), 376 deletions(-) diff --git a/Podfile b/Podfile index 020fd582d..61f839a42 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 @@ -46,7 +45,6 @@ abstract_target 'Automattic' do end end - # Post Install # post_install do |installer| diff --git a/Rakefile b/Rakefile index bcbb3302a..58c2655be 100644 --- a/Rakefile +++ b/Rakefile @@ -1,61 +1,64 @@ -SWIFTLINT_VERSION="0.41.0" -XCODE_WORKSPACE="Simplenote.xcworkspace" -XCODE_SCHEME="Simplenote" -XCODE_CONFIGURATION="Debug" +# frozen_string_literal: true + +require 'English' +SWIFTLINT_VERSION = '0.41.0' +XCODE_WORKSPACE = 'Simplenote.xcworkspace' +XCODE_SCHEME = 'Simplenote' +XCODE_CONFIGURATION = 'Debug' require 'fileutils' require 'tmpdir' require 'rake/clean' require 'yaml' require 'digest' -PROJECT_DIR = File.expand_path(File.dirname(__FILE__)) +PROJECT_DIR = __dir__ 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 lint: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 check --path=${BUNDLE_PATH:-vendor/bundle} > /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 --path=${BUNDLE_PATH:-vendor/bundle}' 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,42 +66,41 @@ 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 + dependency_failed('SwiftLint') + Rake::Task['dependencies:lint:install'].invoke end end task :install do - fold("install.swiftlint") 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.exists?(zipfile) + if File.exist?(zipfile) extracted_dir = "#{tmpdir}/swiftlint-#{SWIFTLINT_VERSION}" sh "unzip #{zipfile} -d #{extracted_dir}" FileUtils.mkdir_p("#{swiftlint_path}/bin") @@ -107,8 +109,8 @@ namespace :dependencies do 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) + sh 'git submodule --quiet update --init --recursive' + FileUtils.rm_f(swiftlint_path) FileUtils.mkdir_p(swiftlint_path) sh "make prefix_install PREFIX='#{swiftlint_path}'" end @@ -116,51 +118,51 @@ namespace :dependencies do end end end - CLOBBER << "vendor/swiftlint" + 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" - xcodebuild(:build, "OTHER_SWIFT_FLAGS='-Xfrontend -debug-time-compilation -Xfrontend -debug-time-expression-type-checking'") +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 +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 + desc 'Automatically corrects style errors where possible' + task autocorrect: %w[dependencies:lint:check] do swiftlint %w[autocorrect] end end @@ -168,32 +170,34 @@ end namespace :git do hooks = %w[pre-commit 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,12 +221,10 @@ namespace :git do end namespace :git do - task :pre_commit => %[dependencies:lint:check] do - begin - swiftlint %w[lint --quiet --strict] - rescue - exit $?.exitstatus - end + task pre_commit: %(dependencies:lint:check) do + swiftlint %w[lint --quiet --strict] + rescue StandardError + exit $CHILD_STATUS.exitstatus end task :post_merge do @@ -234,8 +236,8 @@ 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 @@ -249,14 +251,14 @@ 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.safe_load(File.read('Podfile.lock'))['PODFILE CHECKSUM'] podfile_checksum == lockfile_checksum end def swiftlint_path - "#{PROJECT_DIR}/vendor/swiftlint" + "#{PROJECT_DIR}/vendor/swiftlint" end def swiftlint(args) @@ -265,25 +267,28 @@ def swiftlint(args) end def swiftlint_bin - "#{swiftlint_path}/bin/swiftlint" + "#{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) + (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(' ') + unless ENV['verbose'] + cmd += ' | bundle exec xcpretty -f `bundle exec xcpretty-travis-formatter` && exit ${PIPESTATUS[0]}' + end sh(cmd) end @@ -294,19 +299,20 @@ 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 diff --git a/fastlane/Deliverfile b/fastlane/Deliverfile index a27802c01..980b37e51 100644 --- a/fastlane/Deliverfile +++ b/fastlane/Deliverfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # The Deliverfile allows you to store various App Store Connect metadata # For more information, check out the docs # https://docs.fastlane.tools/actions/deliver/ @@ -7,15 +9,15 @@ ######################################## # This folder has to include one folder for each language -screenshots_path "./screenshots/" -app_identifier "com.codality.NotationalFlow" +screenshots_path './screenshots/' +app_identifier 'com.codality.NotationalFlow' # Make sure to update these keys for a new version -app_version "4.48" +app_version '4.48' privacy_url({ - 'default' => 'https://simplenote.com/privacy/', -}) + 'default' => 'https://simplenote.com/privacy/' + }) copyright('2021 Automattic') diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c682b4d87..8018e7a8a 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,12 +1,12 @@ +# 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? USER_ENV_FILE_PATH = File.join(Dir.home, '.simplenoteios-env.default') PROJECT_ENV_FILE_PATH = File.join( @@ -23,48 +23,48 @@ before_all do UI.user_error!("~/.simplenoteios-env.default not found: Please copy env/user.env-example to #{USER_ENV_FILE_PATH} and fill in the values") end unless File.file?(PROJECT_ENV_FILE_PATH) - UI.user_error!("project.env not found: Make sure your configuration is up to date with `rake dependencies`") + UI.user_error!('project.env not found: Make sure your configuration is up to date with `rake dependencies`') end 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['FL_RELEASE_TOOLKIT_DEFAULT_BRANCH'] = 'trunk' -REPOSITORY_NAME="simplenote-ios" -APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow'.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?") + ######################################################################## + # 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.fetch(GHHELPER_REPO = 'Automattic/simplenote-ios', nil) + 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['FL_RELEASE_TOOLKIT_DEFAULT_BRANCH'] = 'trunk' + REPOSITORY_NAME = 'simplenote-ios' + APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow' + + # 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.fetch(key, nil) end - ENV[key] -end -######################################################################## -# Release Lanes -######################################################################## + ######################################################################## + # Release Lanes + ######################################################################## ##################################################################################### # code_freeze # ----------------------------------------------------------------------------------- @@ -77,18 +77,18 @@ end # 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 | + 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() + ios_bump_version_release + new_version = ios_get_app_version 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')) + 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) - setbranchprotection(repository:GHHELPER_REPO, branch: "release/#{new_version}") - setfrozentag(repository:GHHELPER_REPO, milestone: new_version) + 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}") @@ -106,20 +106,20 @@ end # 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") + 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") + 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", + po_file_path: "#{prj_folder}/Simplenote/Resources/AppStoreStrings.pot", source_files: files, release_version: options[:version] ) @@ -127,12 +127,12 @@ 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" + 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| + Dir['./metadata/**'].each do |locale_dir| keywords_path = File.join(locale_dir, 'keywords.txt') - unless File.exists?(keywords_path) + unless File.exist?(keywords_path) UI.message "Could not find keywords file in #{locale_dir}. Skipping." next end @@ -175,8 +175,8 @@ end # 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 | + 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) @@ -198,7 +198,7 @@ end # 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 | + lane :new_hotfix_release do |options| prev_ver = ios_hotfix_prechecks(options) ios_bump_version_hotfix(previous_version: prev_ver, version: options[:version]) end @@ -236,7 +236,9 @@ end ##################################################################################### 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 + if ios_current_branch_is_hotfix + UI.user_error!('To finalize a hotfix, please use the finalize_hotfix_release lane instead') + end ios_finalize_prechecks(options) @@ -246,23 +248,23 @@ end abort_on_violations: false ) - UI.message("Checking release notes strings translation status...") + 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_update_metadata(options) - sanitize_appstore_keywords() + sanitize_appstore_keywords ios_lint_localizations(input_dir: 'Simplenote', allow_retry: true) - ios_bump_version_beta() + ios_bump_version_beta # Wrap up - version = ios_get_app_version() - 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) + version = ios_get_app_version + 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}") @@ -283,12 +285,13 @@ end # 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 | + 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) + beta_release: true + ) end ##################################################################################### @@ -306,12 +309,13 @@ end # 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 | + 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) + beta_release: false + ) end ##################################################################################### @@ -328,17 +332,17 @@ end # 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 | + 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) + internal: false, + internal_on_single_version: options[:beta_release], + external: true) - ios_build_preflight() + 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]) + beta_release: options[:beta_release], create_release: options[:create_gh_release]) end ##################################################################################### @@ -353,23 +357,23 @@ end # 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]) + 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", + 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_team_id: ENV.fetch('INT_EXPORT_TEAM_ID', nil), export_options: { - method: "enterprise", + method: 'enterprise', provisioningProfiles: simplenote_provisioning_profiles( root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Internal", match_type: 'InHouse' @@ -380,16 +384,16 @@ end 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", + api_token: ENV.fetch('APPCENTER_API_TOKEN', nil), + 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"], + auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), org_slug: 'a8c', project_slug: 'simplenote-ios', dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH] @@ -408,21 +412,21 @@ end # 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]) + 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", + scheme: 'Simplenote', + workspace: 'Simplenote.xcworkspace', + configuration: 'Distribution AppStore', clean: true, export_options: { - method: "app-store", - export_team_id: ENV["EXT_EXPORT_TEAM_ID"], + method: 'app-store', + export_team_id: ENV.fetch('EXT_EXPORT_TEAM_ID', nil), provisioningProfiles: simplenote_provisioning_profiles } ) @@ -432,29 +436,28 @@ end api_key_path: File.join(Dir.home, '.configure', 'simplenote-ios', 'secrets', 'app_store_connect_fastlane_api_key.json') ) - sh("rm ../Simplenote.ipa") - dSYM_PATH = File.dirname(Dir.pwd) + "/Simplenote.app.dSYM.zip" + 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"], + auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), org_slug: 'a8c', - project_slug: 'simplenote-ios', + project_slug: 'simplenote-ios' ) sh("rm #{dSYM_PATH}") - if (options[:create_release]) - archive_zip_path = File.dirname(Dir.pwd) + "/Simplenote.xarchive.zip" + 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 - 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] - ) + 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 @@ -501,8 +504,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 |_options| alpha_code_signing # Get the current build version, and update it if needed @@ -510,15 +513,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: 'Simplenote.xcworkspace', + export_method: 'enterprise', clean: true, output_directory: OUTPUT_DIRECTORY_PATH, - export_team_id: ENV["INT_EXPORT_TEAM_ID"], + export_team_id: ENV.fetch('INT_EXPORT_TEAM_ID', nil), export_options: { - method: "enterprise", + method: 'enterprise', provisioningProfiles: simplenote_provisioning_profiles( root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Alpha", match_type: 'InHouse' @@ -529,17 +532,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: 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: get_required_env('SENTRY_AUTH_TOKEN'), org_slug: 'a8c', project_slug: 'simplenote-ios', dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH] @@ -598,7 +601,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) @@ -612,22 +615,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 @@ -673,22 +676,22 @@ 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.exists?(user_font_path) + if File.exist?(user_font_path) UI.success("Custom font #{font_name} already installed locally.") else UI.message("Installing #{font_name} at #{user_font_path}.") @@ -698,13 +701,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' @@ -714,18 +717,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 ##################################################################################### @@ -747,12 +751,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 @@ -771,12 +775,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] || '' }, @@ -796,15 +800,15 @@ 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: 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 @@ -812,112 +816,111 @@ 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 +##################################################################################### +# 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 - lane :upload_screenshots do |options| - # This uses the following env vars from the user env: - # DELIVER_USER - # DELIVER_APP_IDENTIFIER - # FASTLANE_PASSWORD - # - # and from the project env: - # FASTLANE_ITC_TEAM_ID - upload_to_app_store( - edit_live: false, - use_live_version: false, - screenshots_path: promo_screenshots_directory, - skip_binary_upload: true, - skip_metadata: true, - skip_app_version_update: true, - - # Prechecks - run_precheck_before_submit: false, - precheck_include_in_app_purchases: false, - - # Delete all the previous screenshots on ASC - overwrite_screenshots: true, - # Set this to true to skip the interactive confirmation of the - # screenshots that will be uploaded. Please, though, don't do it, unless - # you disable the overwrite option above. If we loose the screenshot that - # are on ASC, we might not be able to recover them, unless we checkout - # whichever version made them and generate them again. - force: false, - submit_for_review: false - ) - end +lane :upload_screenshots do |_options| + # This uses the following env vars from the user env: + # DELIVER_USER + # DELIVER_APP_IDENTIFIER + # FASTLANE_PASSWORD + # + # and from the project env: + # FASTLANE_ITC_TEAM_ID + upload_to_app_store( + edit_live: false, + use_live_version: false, + screenshots_path: promo_screenshots_directory, + skip_binary_upload: true, + skip_metadata: true, + skip_app_version_update: true, + + # Prechecks + run_precheck_before_submit: false, + precheck_include_in_app_purchases: false, + + # Delete all the previous screenshots on ASC + overwrite_screenshots: true, + # Set this to true to skip the interactive confirmation of the + # screenshots that will be uploaded. Please, though, don't do it, unless + # you disable the overwrite option above. If we loose the screenshot that + # are on ASC, we might not be able to recover them, unless we checkout + # whichever version made them and generate them again. + force: false, + submit_for_review: false + ) +end ######################################################################## # Fastlane match code signing ######################################################################## - 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 +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 - 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 +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 - 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 +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 - # 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 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}" } - [root_bundle_id, *extension_bundle_ids] - end + [root_bundle_id, *extension_bundle_ids] +end - # 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 +# 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 ######################################################################## # Localization Lanes @@ -930,7 +933,8 @@ lane :generate_strings_file_for_glotpress do paths: ['Simplenote/', 'SimplenoteIntents/', 'SimplenoteShare/', 'SimplenoteWidgets/', 'Pods/Simperium/Simperium/', 'Pods/Simperium/Simperium-iOS'], - output_dir: en_lproj_path) + output_dir: en_lproj_path + ) git_commit( path: en_lproj_path, @@ -958,40 +962,40 @@ def trigger_buildkite_release_build(branch:, beta:) ) 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 @@ -1000,9 +1004,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 @@ -1013,7 +1018,7 @@ end # get_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")) + uri = URI.parse(get_required_env('UI_TESTS_ACCOUNTS_JSON_URL')) response = Net::HTTP.get_response(uri) accounts_state = response.body.chomp JSON.parse(accounts_state) @@ -1023,14 +1028,14 @@ end # 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...") + 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(get_required_env('BUILDKITE_BUILD_NUMBER').to_s) end # Replace a value in a key which is equal to $used_test_account_index global variable @@ -1048,39 +1053,38 @@ end def sanitize_test_accounts accounts_hash = get_test_accounts_hash - UI.message("Sanitizing stale test accounts...") + 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' || is_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) + client = Buildkit.new(token: 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(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| @@ -1114,4 +1118,3 @@ def generate_installable_build_number "#{branch}-#{commit}" end end - From 39a67046e4f31e194d3e49d2bd75268ecf12f15f Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 18 Nov 2022 12:03:10 -0700 Subject: [PATCH 004/547] Update Podfile.lock --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index 87d192485..cdc28e38d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -65,6 +65,6 @@ SPEC CHECKSUMS: WordPress-Ratings-iOS: 9f83dbba6e728c5121b1fd21b5683cf2fd120646 ZIPFoundation: 063163dc828bf699c5be160eb4f58f676322d94f -PODFILE CHECKSUM: 9cc274c6be26f2c5d638648574ddf65553445e98 +PODFILE CHECKSUM: 8d8834b02ffb09014570072f31b06679ab03843c COCOAPODS: 1.11.3 From 38fceee8467d7629c48025acc7ece1c9955a77c1 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 22:27:45 -0800 Subject: [PATCH 005/547] Fix Rubocop violations --- Scripts/extract_release_notes.rb | 31 ++++++++++++++++--------------- Scripts/update-translations.rb | 28 ++++++++++++++-------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Scripts/extract_release_notes.rb b/Scripts/extract_release_notes.rb index c2e5649cf..bd3e2b040 100644 --- a/Scripts/extract_release_notes.rb +++ b/Scripts/extract_release_notes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Parses the release notes file to extract the current version information and # puts it to STDOUT. # @@ -56,31 +58,30 @@ def replace_pr_number_with_markdown_link(string) # 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 +index += 1 until lines[index].start_with? '-----' -lines[(index+1)...].each do |line| +lines[(index + 1)...].each do |line| break if line.strip == '' + release_lines.push line end -formatted_lines = release_lines. - map { |l| l.gsub(/- /, '- ') } +formatted_lines = release_lines + .map { |l| l.gsub(/- /, '- ') } case mode when :strip_pr_links - formatted_lines = formatted_lines. - map { |l| l.gsub(/ \#\d*$/, '') } + 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) } + # 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 diff --git a/Scripts/update-translations.rb b/Scripts/update-translations.rb index 8576e863f..b146020de 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,34 +48,34 @@ '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?("/*")) 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 +if ARGV.count.positive? + ARGV.each do |key| unless local = ALL_LANGS[key] puts "Unknown language #{key}" exit 1 @@ -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" From 5aff4a27418ae6380ede0b749a7dbe9851056815 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 22:29:19 -0800 Subject: [PATCH 006/547] Updated Rubocop from `1.38.0` to `1.60.2` --- Gemfile.lock | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c8611365f..b3ea524ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -242,8 +242,9 @@ GEM concurrent-ruby (~> 1.0) java-properties (0.3.0) jmespath (1.6.2) - json (2.6.3) + json (2.7.1) jwt (2.7.1) + language_server-protocol (3.17.0.3) mini_magick (4.12.0) mini_mime (1.1.5) minitest (5.20.0) @@ -263,9 +264,10 @@ GEM options (2.3.2) optparse (0.1.1) os (1.1.4) - parallel (1.23.0) - parser (3.1.2.1) + parallel (1.24.0) + parser (3.3.0.5) ast (~> 2.4.1) + racc plist (3.7.0) progress_bar (1.3.3) highline (>= 1.6, < 3) @@ -277,7 +279,7 @@ GEM rake-compiler (1.2.5) rake rchardet (1.8.0) - regexp_parser (2.6.0) + regexp_parser (2.9.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -286,20 +288,21 @@ GEM rexml (3.2.6) rmagick (3.2.0) rouge (2.0.7) - rubocop (1.38.0) + rubocop (1.60.2) 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) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.23.0, < 2.0) + rubocop-ast (>= 1.30.0, < 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.30.0) + parser (>= 3.2.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) From c1527fd95493e2dabb404856cf4557d035f24e21 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 22:30:01 -0800 Subject: [PATCH 007/547] Fix Rubocop violations --- Rakefile | 2 +- Scripts/extract_release_notes.rb | 2 +- Scripts/update-translations.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 58c2655be..a0fda4754 100644 --- a/Rakefile +++ b/Rakefile @@ -252,7 +252,7 @@ end def podfile_locked? podfile_checksum = Digest::SHA1.file('Podfile') - lockfile_checksum = YAML.safe_load(File.read('Podfile.lock'))['PODFILE CHECKSUM'] + lockfile_checksum = YAML.safe_load_file('Podfile.lock')['PODFILE CHECKSUM'] podfile_checksum == lockfile_checksum end diff --git a/Scripts/extract_release_notes.rb b/Scripts/extract_release_notes.rb index bd3e2b040..2cfd40ae5 100644 --- a/Scripts/extract_release_notes.rb +++ b/Scripts/extract_release_notes.rb @@ -67,7 +67,7 @@ def replace_pr_number_with_markdown_link(string) end formatted_lines = release_lines - .map { |l| l.gsub(/- /, '- ') } + .map { |l| l.gsub('- ', '- ') } case mode when :strip_pr_links diff --git a/Scripts/update-translations.rb b/Scripts/update-translations.rb index b146020de..2a3b11a30 100755 --- a/Scripts/update-translations.rb +++ b/Scripts/update-translations.rb @@ -53,7 +53,7 @@ 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 @@ -76,7 +76,7 @@ def copy_comment(f, trans_strings, value) langs = {} if ARGV.count.positive? ARGV.each do |key| - unless local = ALL_LANGS[key] + unless (local = ALL_LANGS[key]) puts "Unknown language #{key}" exit 1 end From a15e5d22d216e41b22583777cdb3e5ce79235de1 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 22:43:48 -0800 Subject: [PATCH 008/547] Update method indentation --- fastlane/Fastfile | 100 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 35ae7035a..a8aa0c71b 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -93,6 +93,7 @@ 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 +end ######################################################################## # Release Lanes @@ -116,12 +117,20 @@ def get_required_env(key) 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')) + 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) - setbranchprotection(repository: GHHELPER_REPO, branch: "release/#{new_version}") - setfrozentag(repository: GHHELPER_REPO, milestone: new_version) + 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}") @@ -212,7 +221,10 @@ def get_required_env(key) 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_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}") @@ -233,7 +245,10 @@ def get_required_env(key) 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]) + ios_bump_version_hotfix( + previous_version: prev_ver, + version: options[:version] + ) end ##################################################################################### @@ -292,16 +307,29 @@ def get_required_env(key) 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) + 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) + 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}") @@ -371,15 +399,24 @@ def get_required_env(key) ##################################################################################### 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_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]) + 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 ##################################################################################### @@ -396,7 +433,10 @@ def get_required_env(key) ##################################################################################### 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_prechecks( + skip_confirm: options[:skip_confirm], + internal: false + ) unless options[:skip_prechecks] ios_build_preflight unless options[:skip_prechecks] internal_code_signing @@ -451,7 +491,10 @@ def get_required_env(key) ##################################################################################### 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_prechecks( + skip_confirm: options[:skip_confirm], + external: true + ) unless options[:skip_prechecks] ios_build_preflight unless options[:skip_prechecks] appstore_code_signing @@ -490,7 +533,8 @@ def get_required_env(key) 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, + 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, @@ -538,7 +582,10 @@ def get_required_env(key) # ##################################################################################### lane :trigger_beta_build do |options| - trigger_buildkite_release_build(branch: options[:branch_to_build], beta: true) + trigger_buildkite_release_build( + branch: options[:branch_to_build], + beta: true + ) end ##################################################################################### @@ -553,7 +600,10 @@ def get_required_env(key) # ##################################################################################### lane :trigger_release_build do |options| - trigger_buildkite_release_build(branch: options[:branch_to_build], beta: false) + trigger_buildkite_release_build( + branch: options[:branch_to_build], + beta: false + ) end ##################################################################################### From 6bde715607693c183e325d091a005d06043691f6 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 22:46:27 -0800 Subject: [PATCH 009/547] Replace `GHHELPER_REPO` with `GITHUB_TOKEN` --- fastlane/Fastfile | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a8aa0c71b..0c0fc06fd 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -49,9 +49,11 @@ platform :ios do VERSION_FILE_PATH = File.join(PROJECT_ROOT_FOLDER, 'config', 'Version.Public.xcconfig') OUTPUT_DIRECTORY_PATH = File.join(PROJECT_ROOT_FOLDER, 'build', 'results') + GITHUB_TOKEN = 'Automattic/simplenote-ios' + Dotenv.load(USER_ENV_FILE_PATH) Dotenv.load(PROJECT_ENV_FILE_PATH) - ENV.fetch(GHHELPER_REPO = 'Automattic/simplenote-ios', nil) + ENV['PROJECT_NAME'] = 'Simplenote' ENV['PROJECT_ROOT_FOLDER'] = PROJECT_ROOT_FOLDER ENV['APP_STORE_STRINGS_FILE_NAME'] = 'AppStoreStrings.pot' @@ -124,11 +126,11 @@ end ) ios_update_release_notes(new_version: new_version) setbranchprotection( - repository: GHHELPER_REPO, + repository: GITHUB_TOKEN, branch: "release/#{new_version}" ) setfrozentag( - repository: GHHELPER_REPO, + repository: GITHUB_TOKEN, milestone: new_version ) @@ -317,17 +319,17 @@ end # Wrap up version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) removebranchprotection( - repository: GHHELPER_REPO, + repository: GITHUB_TOKEN, branch: "release/#{version}" ) setfrozentag( - repository: GHHELPER_REPO, + repository: GITHUB_TOKEN, milestone: version, freeze: false ) - create_new_milestone(repository: GHHELPER_REPO) + create_new_milestone(repository: GITHUB_TOKEN) close_milestone( - repository: GHHELPER_REPO, + repository: GITHUB_TOKEN, milestone: version ) @@ -534,7 +536,7 @@ end version = options[:beta_release] ? ios_get_build_version : ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) create_release( - repository: GHHELPER_REPO, + repository: GITHUB_TOKEN, version: version, release_notes_file_path: File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'Resources', 'release_notes.txt'), release_assets: archive_zip_path.to_s, From 260c2d47ff6f0513fb429e4e15ebec5d6dc2667f Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 22:53:20 -0800 Subject: [PATCH 010/547] Update `rubocop.yml` to current standards --- .rubocop.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ee10b62d1..fdb0d8582 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,19 +7,17 @@ AllCops: SuggestExtensions: rubocop-rake: false +Metrics/MethodLength: + Max: 30 + Layout/LineLength: - Exclude: - - fastlane/Fastfile + Max: 180 Metrics/BlockLength: Exclude: - fastlane/*file + - Podfile + - Rakefile -Metrics/BlockNesting: - Exclude: - - fastlane/*file - -Naming/FileName: - Exclude: - - fastlane/*file - - '**/*.podspec' +Style/HashSyntax: + EnforcedShorthandSyntax: never From 9b003751ff32cd38b0c80c27c1a85ffe2256310a Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:00:46 -0800 Subject: [PATCH 011/547] Move constants and fix `GITHUB_TOKEN/REPO` error --- fastlane/Fastfile | 56 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0c0fc06fd..88d33d5db 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -41,27 +41,12 @@ 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') - - GITHUB_TOKEN = 'Automattic/simplenote-ios' - - Dotenv.load(USER_ENV_FILE_PATH) - Dotenv.load(PROJECT_ENV_FILE_PATH) - - 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['FL_RELEASE_TOOLKIT_DEFAULT_BRANCH'] = 'trunk' - REPOSITORY_NAME = 'simplenote-ios' - APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow' +# Constants +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') +GITHUB_REPO = 'Automattic/simplenote-ios' +APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow' # List of locales used for the app strings (GlotPress code => `*.lproj` folder name`) # @@ -89,6 +74,19 @@ GLOTPRESS_TO_LPROJ_APP_LOCALE_CODES = { 'zh-tw' => 'zh-Hant-TW' # Chinese (Taiwan) }.freeze +######################################################################## +# Environment +######################################################################## +Dotenv.load(USER_ENV_FILE_PATH) +Dotenv.load(PROJECT_ENV_FILE_PATH) + +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['FL_RELEASE_TOOLKIT_DEFAULT_BRANCH'] = 'trunk' + # 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) @@ -97,6 +95,8 @@ def get_required_env(key) end end +platform :ios do + ######################################################################## # Release Lanes ######################################################################## @@ -126,11 +126,11 @@ end ) ios_update_release_notes(new_version: new_version) setbranchprotection( - repository: GITHUB_TOKEN, + repository: GITHUB_REPO, branch: "release/#{new_version}" ) setfrozentag( - repository: GITHUB_TOKEN, + repository: GITHUB_REPO, milestone: new_version ) @@ -319,17 +319,17 @@ end # Wrap up version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) removebranchprotection( - repository: GITHUB_TOKEN, + repository: GITHUB_REPO, branch: "release/#{version}" ) setfrozentag( - repository: GITHUB_TOKEN, + repository: GITHUB_REPO, milestone: version, freeze: false ) - create_new_milestone(repository: GITHUB_TOKEN) + create_new_milestone(repository: GITHUB_REPO) close_milestone( - repository: GITHUB_TOKEN, + repository: GITHUB_REPO, milestone: version ) @@ -536,7 +536,7 @@ end version = options[:beta_release] ? ios_get_build_version : ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) create_release( - repository: GITHUB_TOKEN, + repository: GITHUB_REPO, version: version, release_notes_file_path: File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'Resources', 'release_notes.txt'), release_assets: archive_zip_path.to_s, From 34bbf4cebf05496b07f0fdff1c1e4835ccfe188d Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:04:42 -0800 Subject: [PATCH 012/547] Fix Rubocop violations --- fastlane/Fastfile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 88d33d5db..a6f538660 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -96,7 +96,6 @@ def get_required_env(key) end platform :ios do - ######################################################################## # Release Lanes ######################################################################## @@ -519,16 +518,16 @@ platform :ios do ) sh('rm ../Simplenote.ipa') - dSYM_PATH = "#{File.dirname(Dir.pwd)}/Simplenote.app.dSYM.zip" + dsym_path = "#{File.dirname(Dir.pwd)}/Simplenote.app.dSYM.zip" sentry_upload_dsym( - dsym_path: dSYM_PATH, + dsym_path: dsym_path, auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), org_slug: 'a8c', project_slug: 'simplenote-ios' ) - sh("rm #{dSYM_PATH}") + sh("rm #{dsym_path}") if options[:create_release] archive_zip_path = "#{File.dirname(Dir.pwd)}/Simplenote.xarchive.zip" @@ -678,7 +677,8 @@ platform :ios do # # @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`) + # @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 # @@ -959,7 +959,8 @@ platform :ios do # 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. + # 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 # Add all development certificates to the provisioning profiles (just in case – this is an easy step to miss) @@ -1189,14 +1190,14 @@ def sanitize_test_accounts UI.message("Accounts before sanitizing: #{accounts_hash}") accounts_hash.each do |key, value| - next if value == 'free' || is_job_running(value) + next if value == 'free' || job_running?(value) accounts_hash[key] = 'free' submit_accounts_hash(accounts_hash) end end -def is_job_running(job_number) +def job_running?(job_number) client = Buildkit.new(token: get_required_env('BUILDKITE_TOKEN')) build = client.build('automattic', 'simplenote-ios', job_number) From 82c0a3aba949c823800a4d509e0c9f198d0425d4 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:09:25 -0800 Subject: [PATCH 013/547] Fix Rubocop violations --- Rakefile | 4 +--- fastlane/Fastfile | 55 +++++++++++++++++++++++------------------------ 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/Rakefile b/Rakefile index a0fda4754..62c5ad1bf 100644 --- a/Rakefile +++ b/Rakefile @@ -286,9 +286,7 @@ def xcodebuild(*build_cmds) cmd += " -configuration #{xcode_configuration}" cmd += ' ' cmd += build_cmds.map(&:to_s).join(' ') - unless ENV['verbose'] - cmd += ' | bundle exec xcpretty -f `bundle exec xcpretty-travis-formatter` && exit ${PIPESTATUS[0]}' - end + cmd += ' | bundle exec xcpretty -f `bundle exec xcpretty-travis-formatter` && exit ${PIPESTATUS[0]}' unless ENV['verbose'] sh(cmd) end diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a6f538660..c7981c699 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -23,16 +23,14 @@ before_all do # 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: + ~/.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} + \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 + UI.user_error!('project.env not found: Make sure your configuration is up to date with `rake dependencies`') unless File.file?(PROJECT_ENV_FILE_PATH) setup_ci end @@ -90,9 +88,8 @@ ENV['FL_RELEASE_TOOLKIT_DEFAULT_BRANCH'] = 'trunk' # 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 + UI.user_error!("Environment variable '#{key}' is not set. Have you setup #{USER_ENV_FILE_PATH} correctly?") unless ENV.key?(key) + ENV.fetch(key) end platform :ios do @@ -113,7 +110,7 @@ platform :ios do ##################################################################################### desc 'Creates a new release branch from the current trunk' lane :code_freeze do |options| - old_version = ios_codefreeze_prechecks(options) + ios_codefreeze_prechecks(options) ios_bump_version_release new_version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) @@ -194,9 +191,7 @@ platform :ios do 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 + keywords = keywords.split(english_comma)[0...-1].join(english_comma) until keywords.length <= app_store_connect_keywords_length_limit File.write(keywords_path, keywords) end @@ -285,9 +280,7 @@ platform :ios do ##################################################################################### desc 'Trigger the final release build on CI' lane :finalize_release do |options| - if ios_current_branch_is_hotfix - UI.user_error!('To finalize a hotfix, please use the finalize_hotfix_release lane instead') - end + 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) @@ -408,10 +401,12 @@ platform :ios do ) ios_build_preflight - build_and_upload_internal( - skip_prechecks: true, - skip_confirm: options[:skip_confirm] - ) if options[:beta_release] + if options[:beta_release] + build_and_upload_internal( + skip_prechecks: true, + skip_confirm: options[:skip_confirm] + ) + end build_and_upload_to_app_store_connect( skip_prechecks: true, skip_confirm: options[:skip_confirm], @@ -434,10 +429,12 @@ platform :ios do ##################################################################################### 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] + unless options[:skip_prechecks] + ios_build_prechecks( + skip_confirm: options[:skip_confirm], + internal: false + ) + end ios_build_preflight unless options[:skip_prechecks] internal_code_signing @@ -492,10 +489,12 @@ platform :ios do ##################################################################################### 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] + unless options[:skip_prechecks] + ios_build_prechecks( + skip_confirm: options[:skip_confirm], + external: true + ) + end ios_build_preflight unless options[:skip_prechecks] appstore_code_signing @@ -1220,7 +1219,7 @@ def submit_accounts_hash(accounts_hash) 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 From 0ed00e9856a85c4d55b3045f7a510b8619015175 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:12:21 -0800 Subject: [PATCH 014/547] Use `get_required_env` for required tokens --- fastlane/Fastfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c7981c699..4de77f5b1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -459,7 +459,7 @@ platform :ios do File.rename(File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote.ipa'), File.join(OUTPUT_DIRECTORY_PATH, 'Simplenote Internal.ipa')) appcenter_upload( - api_token: ENV.fetch('APPCENTER_API_TOKEN', nil), + api_token: get_required_env('APPCENTER_API_TOKEN'), owner_name: 'automattic', owner_type: 'organization', app_name: 'Simplenote', @@ -468,7 +468,7 @@ platform :ios do ) sentry_upload_dsym( - auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), + auth_token: get_required_env('SENTRY_API_TOKEN'), org_slug: 'a8c', project_slug: 'simplenote-ios', dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH] @@ -521,7 +521,7 @@ platform :ios do sentry_upload_dsym( dsym_path: dsym_path, - auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), + auth_token: get_required_env('SENTRY_AUTH_TOKEN'), org_slug: 'a8c', project_slug: 'simplenote-ios' ) From 28a387eec99ff40d148615589632921c689009d2 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:13:15 -0800 Subject: [PATCH 015/547] Update Podfile.lock --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index e590a298d..e7d572490 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -47,6 +47,6 @@ SPEC CHECKSUMS: WordPress-Ratings-iOS: 9f83dbba6e728c5121b1fd21b5683cf2fd120646 ZIPFoundation: 063163dc828bf699c5be160eb4f58f676322d94f -PODFILE CHECKSUM: 0663a4b2a1777f0957be7a40d3b2f7bf92a7fb64 +PODFILE CHECKSUM: 0310d57af0efbf50954ff46d578bc70b455981c4 COCOAPODS: 1.14.1 From 57711bfcb08741d7d9abf5dc2253dac1959abb17 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:24:47 -0800 Subject: [PATCH 016/547] Re-add `fold` method to `Rakefile` --- Rakefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Rakefile b/Rakefile index 62c5ad1bf..cfb63df35 100644 --- a/Rakefile +++ b/Rakefile @@ -241,6 +241,11 @@ task xcode: [:dependencies] do sh "open #{XCODE_WORKSPACE}" end +def fold(label) + puts "--- #{label}" if ENV['BUILDKITE'] + yield +end + def pod(args) args = %w[bundle exec pod] + args sh(*args) From 132aac134a9bb33a14770f4de0f4f5573c0e3cd2 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:34:55 -0800 Subject: [PATCH 017/547] Fix Rubocop violation --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index cfb63df35..5e64fa6f5 100644 --- a/Rakefile +++ b/Rakefile @@ -318,7 +318,7 @@ def check_dependencies_hook ENV['DRY_RUN'] = '1' begin Rake::Task['dependencies'].invoke - rescue Exception => e + rescue StandardError => e puts e.message exit 1 end From 2119e165f9b2317ac1cd3dc1a28de187e98c3840 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 6 Feb 2024 23:46:56 -0800 Subject: [PATCH 018/547] Fix Rubocop violations --- Scripts/update-translations.rb | 8 ++++---- fastlane/Fastfile | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Scripts/update-translations.rb b/Scripts/update-translations.rb index 2a3b11a30..7f8d70e42 100755 --- a/Scripts/update-translations.rb +++ b/Scripts/update-translations.rb @@ -55,19 +55,19 @@ def copy_header(target_file, trans_strings) trans_strings.each_line do |line| unless line.start_with?('/*') target_file.write("\n") - return + break end target_file.write(line) end end -def copy_comment(f, trans_strings, value) +def copy_comment(file, trans_strings, value) prev_line = '' trans_strings.each_line do |line| if line.include?(value) - f.write(prev_line) - return + file.write(prev_line) + break end prev_line = line end diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4de77f5b1..c795cb164 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1147,9 +1147,9 @@ 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 +# test_accounts_hash # => {"0"=>"4079", "1"=>"free", "2"=>"free", "3"=>"free"} -def get_test_accounts_hash +def test_accounts_hash uri = URI.parse(get_required_env('UI_TESTS_ACCOUNTS_JSON_URL')) response = Net::HTTP.get_response(uri) accounts_state = response.body.chomp @@ -1159,7 +1159,7 @@ 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 + accounts_hash = test_accounts_hash UI.message('Looking for a free test account...') UI.message("Accounts state: #{accounts_hash}") @@ -1178,13 +1178,13 @@ 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 = 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 + accounts_hash = test_accounts_hash UI.message('Sanitizing stale test accounts...') UI.message("Accounts before sanitizing: #{accounts_hash}") @@ -1223,7 +1223,7 @@ def submit_accounts_hash(accounts_hash) http.request(request) end - UI.message("Accounts state after write: #{get_test_accounts_hash}") + UI.message("Accounts state after write: #{test_accounts_hash}") end def apply_build_number(build_number) From 356bdec3dcad3b58f116c72bb336f1e79824c4b1 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 00:00:39 -0800 Subject: [PATCH 019/547] Allow `used_test_account_index` global variable --- .rubocop.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index fdb0d8582..acff6190c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,3 +21,8 @@ Metrics/BlockLength: 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 From 42cc23a8b2193f83135349ae24682856e33a7c3d Mon Sep 17 00:00:00 2001 From: Spencer Transier <17955542+spencertransier@users.noreply.github.com> Date: Wed, 7 Feb 2024 07:57:04 -0800 Subject: [PATCH 020/547] Use File.join Co-authored-by: Olivier Halligon --- fastlane/Fastfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c795cb164..b38226edd 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -149,17 +149,17 @@ platform :ios do 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') + source_metadata_folder = File.join(prj_folder, 'fastlane', 'appstoreres', 'metadata', 'source') files = { - whats_new: File.join(prj_folder, '/Simplenote/Resources/release_notes.txt'), + 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", + po_file_path: File.join(prj_folder, 'Simplenote', 'Resources', 'AppStoreStrings.pot'), source_files: files, release_version: options[:version] ) @@ -517,7 +517,7 @@ platform :ios do ) sh('rm ../Simplenote.ipa') - dsym_path = "#{File.dirname(Dir.pwd)}/Simplenote.app.dSYM.zip" + dsym_path = File.join(File.dirname(Dir.pwd), 'Simplenote.app.dSYM.zip') sentry_upload_dsym( dsym_path: dsym_path, @@ -529,7 +529,7 @@ platform :ios do sh("rm #{dsym_path}") if options[:create_release] - archive_zip_path = "#{File.dirname(Dir.pwd)}/Simplenote.xarchive.zip" + archive_zip_path = File.join(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) From 6dd4a85208e229ebbb5678f6b936fd97478c56aa Mon Sep 17 00:00:00 2001 From: Spencer Transier <17955542+spencertransier@users.noreply.github.com> Date: Wed, 7 Feb 2024 07:57:49 -0800 Subject: [PATCH 021/547] Remove unused `|_options|` Co-authored-by: Olivier Halligon --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index b38226edd..bb3c40d71 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -618,7 +618,7 @@ platform :ios do # bundle exec fastlane build_and_upload_installable_build ##################################################################################### desc 'Builds and uploads an installable build' - lane :build_and_upload_installable_build do |_options| + lane :build_and_upload_installable_build do alpha_code_signing # Get the current build version, and update it if needed @@ -991,7 +991,7 @@ end # Example: # bundle exec fastlane update_certs_and_profiles ##################################################################################### -lane :update_certs_and_profiles do |_options| +lane :update_certs_and_profiles do alpha_code_signing internal_code_signing appstore_code_signing From 345b6fdf9e41f0cac847995fd019211b4f004a4d Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 16:00:59 -0800 Subject: [PATCH 022/547] Group `require`s and constants together --- Rakefile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index 5e64fa6f5..0629c9cb5 100644 --- a/Rakefile +++ b/Rakefile @@ -1,16 +1,17 @@ # frozen_string_literal: true require 'English' -SWIFTLINT_VERSION = '0.41.0' -XCODE_WORKSPACE = 'Simplenote.xcworkspace' -XCODE_SCHEME = 'Simplenote' -XCODE_CONFIGURATION = 'Debug' - require 'fileutils' require 'tmpdir' require 'rake/clean' require 'yaml' require 'digest' + +# Constants +SWIFTLINT_VERSION = '0.41.0' +XCODE_WORKSPACE = 'Simplenote.xcworkspace' +XCODE_SCHEME = 'Simplenote' +XCODE_CONFIGURATION = 'Debug' PROJECT_DIR = __dir__ task default: %w[test] From 0a760bd4f8d0d4787f1cbc523f1de13e8c0d3abd Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 16:03:37 -0800 Subject: [PATCH 023/547] Updated `remove_branch_protection` name --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index bb3c40d71..973b81312 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -310,7 +310,7 @@ platform :ios do # Wrap up version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) - removebranchprotection( + remove_branch_protection( repository: GITHUB_REPO, branch: "release/#{version}" ) From 8a8f5271a85d33a38b5e6c5190a94063ca1f7f55 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 16:05:28 -0800 Subject: [PATCH 024/547] Update `setbranchprotection` to `copy_branch_protection` --- fastlane/Fastfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 973b81312..e4ba57f98 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -45,6 +45,7 @@ VERSION_FILE_PATH = File.join(PROJECT_ROOT_FOLDER, 'config', 'Version.Public.xcc OUTPUT_DIRECTORY_PATH = File.join(PROJECT_ROOT_FOLDER, 'build', 'results') GITHUB_REPO = 'Automattic/simplenote-ios' APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow' +DEFAULT_BRANCH = 'trunk' # List of locales used for the app strings (GlotPress code => `*.lproj` folder name`) # @@ -121,9 +122,10 @@ platform :ios do extracted_notes_file_path: File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'Resources', 'release_notes.txt') ) ios_update_release_notes(new_version: new_version) - setbranchprotection( + copy_branch_protection( repository: GITHUB_REPO, - branch: "release/#{new_version}" + from_branch: DEFAULT_BRANCH, + to_branch: "release/#{new_version}" ) setfrozentag( repository: GITHUB_REPO, From d67dce51a4a8328ee079d3d2825e971378fe484a Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 16:07:18 -0800 Subject: [PATCH 025/547] Update Release Toolkit to `9.4.0` and Fastlane to `2.219.0` --- Gemfile.lock | 95 +++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b3ea524ef..dddca5485 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.1.1) + activesupport (7.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -13,7 +13,7 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) @@ -21,25 +21,25 @@ GEM artifactory (3.0.15) 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.888.0) + aws-sdk-core (3.191.1) + 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-sdk-kms (1.77.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.136.0) - aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-s3 (1.143.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.1) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - base64 (0.1.1) - bigdecimal (3.1.4) + base64 (0.2.0) + bigdecimal (3.1.6) buildkit (1.5.0) sawyer (>= 0.6) chroma (0.2.0) @@ -85,22 +85,21 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) 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) + drb (2.2.0) ruby2_keywords emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.104.0) + excon (0.109.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -129,8 +128,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.7) - fastlane (2.216.0) + fastimage (2.3.0) + fastlane (2.219.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -149,6 +148,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,7 +157,7 @@ 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) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) @@ -173,7 +173,7 @@ GEM fastlane-plugin-appcenter (1.11.1) fastlane-plugin-sentry (1.14.0) os (~> 1.1, >= 1.1.4) - fastlane-plugin-wpmreleasetoolkit (9.1.0) + fastlane-plugin-wpmreleasetoolkit (9.4.0) activesupport (>= 6.1.7.1) buildkit (~> 1.5) chroma (= 0.2.0) @@ -182,7 +182,7 @@ GEM git (~> 1.3) google-cloud-storage (~> 1.31) java-properties (~> 0.3.0) - nokogiri (~> 1.11) + nokogiri (~> 1.11, < 1.16) octokit (~> 6.1) parallel (~> 1.14) plist (~> 3.1) @@ -194,12 +194,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 +207,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.6.1) + 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-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) @@ -247,36 +246,36 @@ GEM language_server-protocol (3.17.0.3) mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.20.0) + minitest (5.22.2) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) - mutex_m (0.1.2) + multipart-post (2.4.0) + 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) + nokogiri (1.15.5-arm64-darwin) racc (~> 1.4) octokit (6.1.1) faraday (>= 1, < 3) sawyer (~> 0.9) options (2.3.2) - optparse (0.1.1) + optparse (0.4.0) os (1.1.4) parallel (1.24.0) parser (3.3.0.5) ast (~> 2.4.1) racc - plist (3.7.0) + plist (3.7.1) progress_bar (1.3.3) highline (>= 1.6, < 3) options (~> 2.3.0) public_suffix (4.0.7) - racc (1.7.1) + racc (1.7.3) rainbow (3.1.1) - rake (13.0.6) - rake-compiler (1.2.5) + rake (13.1.0) + rake-compiler (1.2.7) rake rchardet (1.8.0) regexp_parser (2.9.0) @@ -322,7 +321,7 @@ GEM 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) @@ -330,13 +329,9 @@ GEM 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) From 026291a474a6688590b72f95387b8ad015af6ec8 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 16:10:35 -0800 Subject: [PATCH 026/547] Fix comment indenting --- Scripts/extract_release_notes.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Scripts/extract_release_notes.rb b/Scripts/extract_release_notes.rb index 2cfd40ae5..cae2662fe 100644 --- a/Scripts/extract_release_notes.rb +++ b/Scripts/extract_release_notes.rb @@ -74,14 +74,14 @@ def replace_pr_number_with_markdown_link(string) 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) } + # 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. + formatted_lines = formatted_lines + .map { |l| replace_pr_number_with_markdown_link(l) } end # It would be good to either add overriding of the file where the parsed From 33022a2625a261bcdbe3f1e4f6c4e8c17a4e1dc1 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 16:13:00 -0800 Subject: [PATCH 027/547] Use `get_required_env` for env vars --- fastlane/Fastfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index e4ba57f98..b28fe1b9d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -448,7 +448,7 @@ platform :ios do export_method: 'enterprise', clean: true, output_directory: OUTPUT_DIRECTORY_PATH, - export_team_id: ENV.fetch('INT_EXPORT_TEAM_ID', nil), + export_team_id: get_required_env('INT_EXPORT_TEAM_ID'), export_options: { method: 'enterprise', provisioningProfiles: simplenote_provisioning_profiles( @@ -508,7 +508,7 @@ platform :ios do clean: true, export_options: { method: 'app-store', - export_team_id: ENV.fetch('EXT_EXPORT_TEAM_ID', nil), + export_team_id: get_required_env('EXT_EXPORT_TEAM_ID'), provisioningProfiles: simplenote_provisioning_profiles } ) @@ -634,7 +634,7 @@ platform :ios do export_method: 'enterprise', clean: true, output_directory: OUTPUT_DIRECTORY_PATH, - export_team_id: ENV.fetch('INT_EXPORT_TEAM_ID', nil), + export_team_id: get_required_env('INT_EXPORT_TEAM_ID'), export_options: { method: 'enterprise', provisioningProfiles: simplenote_provisioning_profiles( From 2177a071036217143f6e4672ae8b7f0de3d808db Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 7 Feb 2024 16:44:41 -0800 Subject: [PATCH 028/547] Fix parenthesis --- fastlane/Fastfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0be2a106a..ef7798874 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -133,9 +133,9 @@ platform :ios do ) generate_strings_file_for_glotpress - + push_to_git_remote(tags: false) - + trigger_beta_build(branch_to_build: "release/#{new_version}") end @@ -534,7 +534,7 @@ platform :ios do sh("rm #{dsym_path}") if options[:create_release] - archive_zip_path = File.join(File.dirname(Dir.pwd)), 'Simplenote.xarchive.zip') + archive_zip_path = File.join(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) From 7102a2fe61d24528e9c5841ea60e602de0c237aa Mon Sep 17 00:00:00 2001 From: Spencer Transier <17955542+spencertransier@users.noreply.github.com> Date: Thu, 8 Feb 2024 12:19:52 -0800 Subject: [PATCH 029/547] Move `ios_build_preflight` to existing unless block Co-authored-by: Olivier Halligon --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ef7798874..bbe15ca5a 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -499,8 +499,8 @@ platform :ios do skip_confirm: options[:skip_confirm], external: true ) + ios_build_preflight end - ios_build_preflight unless options[:skip_prechecks] appstore_code_signing From ee82f371b523fd9c094494acee7c53860a076adc Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Thu, 8 Feb 2024 16:59:25 -0800 Subject: [PATCH 030/547] Move `push_to_git_remote` before GitHub configuration --- fastlane/Fastfile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index bbe15ca5a..583b5ec1c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -122,6 +122,14 @@ platform :ios do extracted_notes_file_path: File.join(PROJECT_ROOT_FOLDER, 'Simplenote', 'Resources', 'release_notes.txt') ) ios_update_release_notes(new_version: new_version) + + generate_strings_file_for_glotpress + + UI.important('Pushing changes to remote and configuring the release on GitHub') + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") unless options[:skip_confirm] || UI.confirm('Do you want to continue?') + + push_to_git_remote(tags: false) + copy_branch_protection( repository: GITHUB_REPO, from_branch: DEFAULT_BRANCH, @@ -132,10 +140,6 @@ platform :ios do milestone: new_version ) - generate_strings_file_for_glotpress - - push_to_git_remote(tags: false) - trigger_beta_build(branch_to_build: "release/#{new_version}") end From 47a2fc04cf66e703137e82cad3600d113f027a59 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Thu, 8 Feb 2024 17:01:56 -0800 Subject: [PATCH 031/547] Update UI message --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 583b5ec1c..e15fb046e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -125,7 +125,7 @@ platform :ios do generate_strings_file_for_glotpress - UI.important('Pushing changes to remote and configuring the release on GitHub') + 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 options[:skip_confirm] || UI.confirm('Do you want to continue?') push_to_git_remote(tags: false) From f0a4aa05ab1d9d4d45b57461949133330137de99 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 16:37:07 +0100 Subject: [PATCH 032/547] Add Dangermattic configuration --- .github/workflows/run-danger.yml | 13 ++++++++++ Dangerfile | 42 ++++++++++++++++++++++++++++++++ Gemfile | 2 +- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/run-danger.yml create mode 100644 Dangerfile diff --git a/.github/workflows/run-danger.yml b/.github/workflows/run-danger.yml new file mode 100644 index 000000000..856ab8cea --- /dev/null +++ b/.github/workflows/run-danger.yml @@ -0,0 +1,13 @@ +name: ☢️ Danger + +on: + pull_request: + types: [opened, reopened, ready_for_review, synchronize, edited, labeled, unlabeled, milestoned, demilestoned] + +jobs: + dangermattic: + # runs on draft PRs only for opened / synchronize events + if: ${{ (github.event.pull_request.draft == false) || (github.event.pull_request.draft == true && contains(fromJSON('["opened", "synchronize"]'), github.event.action)) }} + uses: Automattic/dangermattic/.github/workflows/reusable-run-danger.yml@v1.0.0 + secrets: + github-token: ${{ secrets.DANGERMATTIC_GITHUB_TOKEN }} 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..b78fff34a 100644 --- a/Gemfile +++ b/Gemfile @@ -4,11 +4,11 @@ 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-sentry', '~> 1.6' gem 'fastlane-plugin-wpmreleasetoolkit', '~> 9.1' -gem 'rubocop', '~> 1.38' group :screenshots, optional: true do gem 'rmagick', '~> 3.2.0' From e22bdfadf3fc8e79987500a63020d9a656b8b07c Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 16:51:55 +0100 Subject: [PATCH 033/547] Run bundle install --- Gemfile.lock | 76 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c8611365f..a17c35f6a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,6 +44,10 @@ GEM sawyer (>= 0.6) chroma (0.2.0) claide (1.1.0) + claide-plugins (0.9.2) + cork + nap + open4 (~> 1.3) cocoapods (1.14.1) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) @@ -87,6 +91,44 @@ GEM highline (~> 2.0.0) concurrent-ruby (1.2.2) connection_pool (2.4.1) + cork (0.3.0) + colored2 (~> 3.1) + danger (9.4.3) + 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) + no_proxy_fix + octokit (>= 4.0) + terminal-table (>= 1, < 4) + danger-dangermattic (1.0.0) + danger (~> 9.4) + danger-junit (~> 1.0) + danger-plugin-api (~> 1.0) + danger-rubocop (~> 0.12) + danger-swiftlint (~> 0.35) + danger-xcode_summary (~> 1.0) + rubocop (~> 1.60) + danger-junit (1.0.2) + danger (> 2.0) + ox (~> 2.0) + danger-plugin-api (1.0.0) + danger (> 2.0) + danger-rubocop (0.12.0) + danger + rubocop (~> 1.0) + danger-swiftlint (0.35.0) + danger + rake (> 10) + thor (~> 1.0.0) + danger-xcode_summary (1.3.0) + danger-plugin-api (~> 1.0) + xcresult (~> 0.2) declarative (0.0.20) diffy (3.4.2) digest-crc (0.6.5) @@ -119,6 +161,8 @@ 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) @@ -244,6 +288,11 @@ GEM jmespath (1.6.2) json (2.6.3) jwt (2.7.1) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.3) mini_magick (4.12.0) mini_mime (1.1.5) minitest (5.20.0) @@ -255,17 +304,21 @@ GEM nap (1.1.0) naturally (2.2.1) netrc (0.11.0) + no_proxy_fix (0.1.2) nokogiri (1.15.4-arm64-darwin) 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) os (1.1.4) + ox (2.14.17) parallel (1.23.0) - parser (3.1.2.1) + parser (3.3.0.5) ast (~> 2.4.1) + racc plist (3.7.0) progress_bar (1.3.3) highline (>= 1.6, < 3) @@ -277,7 +330,7 @@ GEM rake-compiler (1.2.5) rake rchardet (1.8.0) - regexp_parser (2.6.0) + regexp_parser (2.9.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -286,20 +339,21 @@ GEM rexml (3.2.6) rmagick (3.2.0) rouge (2.0.7) - rubocop (1.38.0) + rubocop (1.60.2) 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) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.23.0, < 2.0) + rubocop-ast (>= 1.30.0, < 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.30.0) + parser (>= 3.2.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) @@ -317,6 +371,7 @@ GEM terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) + thor (1.0.1) trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) @@ -344,6 +399,7 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) + xcresult (0.2.1) PLATFORMS arm64-darwin-21 @@ -352,12 +408,12 @@ PLATFORMS DEPENDENCIES cocoapods (~> 1.14) + danger-dangermattic (~> 1.0) fastlane (~> 2) fastlane-plugin-appcenter (~> 1.11) fastlane-plugin-sentry (~> 1.6) fastlane-plugin-wpmreleasetoolkit (~> 9.1) rmagick (~> 3.2.0) - rubocop (~> 1.38) BUNDLED WITH 2.4.13 From 2b45b7f9402da68f1f6a673c029adaf5f2789cb4 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 16:52:26 +0100 Subject: [PATCH 034/547] Add RuboCop config --- .rubocop.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..ad146a0a1 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,18 @@ +AllCops: + NewCops: enable + SuggestExtensions: + rubocop-rake: false + +Metrics/MethodLength: + Max: 30 + +Layout/LineLength: + Max: 180 + +Metrics/BlockLength: + Exclude: + - fastlane/Fastfile + - Rakefile + +Style/HashSyntax: + EnforcedShorthandSyntax: never From 5dec4c9786a19a1dfd78ba1a2a5817f581f531e6 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 16:53:40 +0100 Subject: [PATCH 035/547] Run RuboCop -a --- Podfile | 6 +- Rakefile | 148 ++++---- Scripts/extract_release_notes.rb | 29 +- Scripts/update-translations.rb | 24 +- fastlane/Fastfile | 561 +++++++++++++++---------------- 5 files changed, 378 insertions(+), 390 deletions(-) diff --git a/Podfile b/Podfile index ef7e409e8..d9e7ff13e 100644 --- a/Podfile +++ b/Podfile @@ -1,8 +1,6 @@ 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 +11,6 @@ workspace 'Simplenote.xcworkspace' # Main # abstract_target 'Automattic' do - # Main Target # target 'Simplenote' do @@ -44,7 +41,6 @@ abstract_target 'Automattic' do end end - # Post Install # post_install do |installer| diff --git a/Rakefile b/Rakefile index e27dc8665..556dd6342 100644 --- a/Rakefile +++ b/Rakefile @@ -1,61 +1,61 @@ -SWIFTLINT_VERSION="0.41.0" -XCODE_WORKSPACE="Simplenote.xcworkspace" -XCODE_SCHEME="Simplenote" -XCODE_CONFIGURATION="Debug" +SWIFTLINT_VERSION = '0.41.0' +XCODE_WORKSPACE = 'Simplenote.xcworkspace' +XCODE_SCHEME = 'Simplenote' +XCODE_CONFIGURATION = 'Debug' require 'fileutils' require 'tmpdir' require 'rake/clean' require 'yaml' require 'digest' -PROJECT_DIR = File.expand_path(File.dirname(__FILE__)) +PROJECT_DIR = __dir__ 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 lint: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 check --path=${BUNDLE_PATH:-vendor/bundle} > /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 --path=${BUNDLE_PATH:-vendor/bundle}' 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,36 +63,35 @@ 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 + dependency_failed('SwiftLint') + Rake::Task['dependencies:lint:install'].invoke end end task :install do - fold("install.swiftlint") do + fold('install.swiftlint') do puts "Installing SwiftLint #{SWIFTLINT_VERSION} into #{swiftlint_path}" Dir.mktmpdir do |tmpdir| # Try first using a binary release @@ -107,7 +106,7 @@ namespace :dependencies do 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" + 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}'" @@ -116,51 +115,50 @@ namespace :dependencies do end end end - CLOBBER << "vendor/swiftlint" + 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 +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 + desc 'Automatically corrects style errors where possible' + task autocorrect: %w[dependencies:lint:check] do swiftlint %w[autocorrect] end end @@ -168,7 +166,7 @@ end namespace :git do hooks = %w[pre-commit post-checkout post-merge] - desc "Install git hooks" + desc 'Install git hooks' task :install_hooks do hooks.each do |hook| target = hook_target(hook) @@ -177,16 +175,17 @@ namespace :git do next if File.symlink?(target) and File.readlink(target) == source next if File.file?(target) and 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) @@ -194,6 +193,7 @@ namespace :git do backup = hook_backup(hook) next unless File.symlink?(target) and File.readlink(target) == source + puts "Removing hook for #{hook}" File.unlink(target) if File.exist?(backup) @@ -217,12 +217,10 @@ namespace :git do end namespace :git do - task :pre_commit => %[dependencies:lint:check] do - begin - swiftlint %w[lint --quiet --strict] - rescue - exit $?.exitstatus - end + task pre_commit: %(dependencies:lint:check) do + swiftlint %w[lint --quiet --strict] + rescue StandardError + exit $?.exitstatus end task :post_merge do @@ -234,19 +232,19 @@ 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) +def fold(label) puts "travis_fold:start:#{label}" if is_travis? yield puts "travis_fold:end:#{label}" if is_travis? end def is_travis? - return ENV["TRAVIS"] != nil + !ENV['TRAVIS'].nil? end def pod(args) @@ -259,14 +257,14 @@ 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" + "#{PROJECT_DIR}/vendor/swiftlint" end def swiftlint(args) @@ -275,25 +273,26 @@ def swiftlint(args) end def swiftlint_bin - "#{swiftlint_path}/bin/swiftlint" + "#{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) + (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,19 +303,20 @@ 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 diff --git a/Scripts/extract_release_notes.rb b/Scripts/extract_release_notes.rb index c2e5649cf..52a29db5a 100644 --- a/Scripts/extract_release_notes.rb +++ b/Scripts/extract_release_notes.rb @@ -56,31 +56,30 @@ def replace_pr_number_with_markdown_link(string) # 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 +index += 1 until lines[index].start_with? '-----' -lines[(index+1)...].each do |line| +lines[(index + 1)...].each do |line| break if line.strip == '' + release_lines.push line end -formatted_lines = release_lines. - map { |l| l.gsub(/- /, '- ') } +formatted_lines = release_lines + .map { |l| l.gsub('- ', '- ') } case mode when :strip_pr_links - formatted_lines = formatted_lines. - map { |l| l.gsub(/ \#\d*$/, '') } + 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) } + # 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 diff --git a/Scripts/update-translations.rb b/Scripts/update-translations.rb index 8576e863f..425d9e71d 100755 --- a/Scripts/update-translations.rb +++ b/Scripts/update-translations.rb @@ -1,8 +1,6 @@ #!/usr/bin/env ruby -# encoding: utf-8 - # 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 +23,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,28 +46,28 @@ '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) } 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 @@ -86,11 +84,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/fastlane/Fastfile b/fastlane/Fastfile index a4a88625d..554351855 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -4,9 +4,7 @@ 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? USER_ENV_FILE_PATH = File.join(Dir.home, '.simplenoteios-env.default') PROJECT_ENV_FILE_PATH = File.join( @@ -23,82 +21,78 @@ before_all do # 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: + ~/.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} + \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 + UI.user_error!('project.env not found: Make sure your configuration is up to date with `rake dependencies`') unless File.file?(PROJECT_ENV_FILE_PATH) 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['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?") + ######################################################################## + # 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.fetch(GHHELPER_REPO = 'Automattic/simplenote-ios', nil) + 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['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) + UI.user_error!("Environment variable '#{key}' is not set. Have you setup #{USER_ENV_FILE_PATH} correctly?") unless ENV.key?(key) + ENV.fetch(key, nil) end - ENV[key] -end -######################################################################## -# Release Lanes -######################################################################## + ######################################################################## + # Release Lanes + ######################################################################## ##################################################################################### # code_freeze # ----------------------------------------------------------------------------------- @@ -111,20 +105,20 @@ end # 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 | + 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')) + 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) + 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}") @@ -142,20 +136,20 @@ end # 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") + 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") + 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", + po_file_path: prj_folder + '/Simplenote/Resources/AppStoreStrings.pot', source_files: files, release_version: options[:version] ) @@ -163,9 +157,9 @@ 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" + 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| + Dir['./metadata/**'].each do |locale_dir| keywords_path = File.join(locale_dir, 'keywords.txt') unless File.exist?(keywords_path) @@ -187,9 +181,7 @@ end 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 + keywords = keywords.split(english_comma)[0...-1].join(english_comma) until keywords.length <= app_store_connect_keywords_length_limit File.write(keywords_path, keywords) end @@ -211,8 +203,8 @@ end # 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 | + 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) @@ -234,7 +226,7 @@ end # 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 | + lane :new_hotfix_release do |options| prev_ver = ios_hotfix_prechecks(options) ios_bump_version_hotfix(previous_version: prev_ver, version: options[:version]) end @@ -282,7 +274,7 @@ end abort_on_violations: false ) - UI.message("Checking release notes strings translation status...") + 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 @@ -293,16 +285,16 @@ end locales: GLOTPRESS_TO_LPROJ_APP_LOCALE_CODES, download_dir: File.join(PROJECT_ROOT_FOLDER, 'Simplenote') ) - sanitize_appstore_keywords() + sanitize_appstore_keywords ios_lint_localizations(input_dir: 'Simplenote', allow_retry: true) - ios_bump_version_beta() + 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) + 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}") @@ -323,12 +315,13 @@ end # 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 | + 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) + beta_release: true + ) end ##################################################################################### @@ -346,12 +339,13 @@ end # 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 | + 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) + beta_release: false + ) end ##################################################################################### @@ -368,17 +362,17 @@ end # 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 | + 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) + internal: false, + internal_on_single_version: options[:beta_release], + external: true) - ios_build_preflight() + 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]) + beta_release: options[:beta_release], create_release: options[:create_gh_release]) end ##################################################################################### @@ -393,23 +387,23 @@ end # 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]) + 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", + 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_team_id: ENV.fetch('INT_EXPORT_TEAM_ID', nil), export_options: { - method: "enterprise", + method: 'enterprise', provisioningProfiles: simplenote_provisioning_profiles( root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Internal", match_type: 'InHouse' @@ -420,16 +414,16 @@ end 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", + api_token: ENV.fetch('APPCENTER_API_TOKEN', nil), + 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"], + auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), org_slug: 'a8c', project_slug: 'simplenote-ios', dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH] @@ -448,21 +442,21 @@ end # 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]) + 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", + scheme: 'Simplenote', + workspace: 'Simplenote.xcworkspace', + configuration: 'Distribution AppStore', clean: true, export_options: { - method: "app-store", - export_team_id: ENV["EXT_EXPORT_TEAM_ID"], + method: 'app-store', + export_team_id: ENV.fetch('EXT_EXPORT_TEAM_ID', nil), provisioningProfiles: simplenote_provisioning_profiles } ) @@ -472,29 +466,28 @@ end api_key_path: APP_STORE_CONNECT_KEY_PATH ) - sh("rm ../Simplenote.ipa") - dSYM_PATH = File.dirname(Dir.pwd) + "/Simplenote.app.dSYM.zip" + 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"], + auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), org_slug: 'a8c', - project_slug: 'simplenote-ios', + project_slug: 'simplenote-ios' ) sh("rm #{dSYM_PATH}") - if (options[:create_release]) - archive_zip_path = File.dirname(Dir.pwd) + "/Simplenote.xarchive.zip" + 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] - ) + 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 @@ -566,8 +559,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 |_options| alpha_code_signing # Get the current build version, and update it if needed @@ -575,15 +568,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: 'Simplenote.xcworkspace', + export_method: 'enterprise', clean: true, output_directory: OUTPUT_DIRECTORY_PATH, - export_team_id: ENV["INT_EXPORT_TEAM_ID"], + export_team_id: ENV.fetch('INT_EXPORT_TEAM_ID', nil), export_options: { - method: "enterprise", + method: 'enterprise', provisioningProfiles: simplenote_provisioning_profiles( root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Alpha", match_type: 'InHouse' @@ -594,17 +587,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: 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: get_required_env('SENTRY_AUTH_TOKEN'), org_slug: 'a8c', project_slug: 'simplenote-ios', dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH] @@ -663,7 +656,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 +670,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 +731,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 +756,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 +772,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 +806,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 +830,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,15 +855,15 @@ 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: 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 @@ -925,80 +919,80 @@ 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 +##################################################################################### +# 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 ######################################################################## # Fastlane match code signing ######################################################################## - 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 +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 - 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 +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 - 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 +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 - # 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 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}" } - [root_bundle_id, *extension_bundle_ids] - end + [root_bundle_id, *extension_bundle_ids] +end - # 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 +# 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 ######################################################################## # Localization Lanes @@ -1011,7 +1005,8 @@ lane :generate_strings_file_for_glotpress do paths: ['Simplenote/', 'SimplenoteIntents/', 'SimplenoteShare/', 'SimplenoteWidgets/', 'Pods/Simperium/Simperium/', 'Pods/Simperium/Simperium-iOS'], - output_dir: en_lproj_path) + output_dir: en_lproj_path + ) git_commit( path: en_lproj_path, @@ -1040,40 +1035,40 @@ def trigger_buildkite_release_build(branch:, beta:) ) 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 +1077,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 @@ -1095,7 +1091,7 @@ end # get_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")) + uri = URI.parse(get_required_env('UI_TESTS_ACCOUNTS_JSON_URL')) response = Net::HTTP.get_response(uri) accounts_state = response.body.chomp JSON.parse(accounts_state) @@ -1105,14 +1101,14 @@ end # 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...") + 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("#{get_required_env('BUILDKITE_BUILD_NUMBER')}") end # Replace a value in a key which is equal to $used_test_account_index global variable @@ -1130,39 +1126,38 @@ end def sanitize_test_accounts accounts_hash = get_test_accounts_hash - UI.message("Sanitizing stale test accounts...") + 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' || is_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) + client = Buildkit.new(token: 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(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| From 3041cf959d62c9594fa83df34adc9cdccf534285 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 16:54:00 +0100 Subject: [PATCH 036/547] Run RuboCop -A --- Podfile | 2 ++ Rakefile | 13 ++++++++----- Scripts/extract_release_notes.rb | 2 ++ Scripts/update-translations.rb | 10 ++++++---- fastlane/Fastfile | 19 ++++++++++--------- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Podfile b/Podfile index d9e7ff13e..d9dd0091f 100644 --- a/Podfile +++ b/Podfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://cdn.cocoapods.org/' raise 'Please run CocoaPods via `bundle exec`' unless %w[BUNDLE_BIN_PATH BUNDLE_GEMFILE].any? { |k| ENV.key?(k) } diff --git a/Rakefile b/Rakefile index 556dd6342..3f9ce58f7 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,6 @@ +# frozen_string_literal: true + +require 'English' SWIFTLINT_VERSION = '0.41.0' XCODE_WORKSPACE = 'Simplenote.xcworkspace' XCODE_SCHEME = 'Simplenote' @@ -107,7 +110,7 @@ namespace :dependencies do 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.rm_rf(swiftlint_path) FileUtils.mkdir_p(swiftlint_path) sh "make prefix_install PREFIX='#{swiftlint_path}'" end @@ -173,8 +176,8 @@ namespace :git do 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}" @@ -192,7 +195,7 @@ namespace :git do 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) @@ -220,7 +223,7 @@ namespace :git do task pre_commit: %(dependencies:lint:check) do swiftlint %w[lint --quiet --strict] rescue StandardError - exit $?.exitstatus + exit $CHILD_STATUS.exitstatus end task :post_merge do diff --git a/Scripts/extract_release_notes.rb b/Scripts/extract_release_notes.rb index 52a29db5a..2cfd40ae5 100644 --- a/Scripts/extract_release_notes.rb +++ b/Scripts/extract_release_notes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Parses the release notes file to extract the current version information and # puts it to STDOUT. # diff --git a/Scripts/update-translations.rb b/Scripts/update-translations.rb index 425d9e71d..2a3b11a30 100755 --- a/Scripts/update-translations.rb +++ b/Scripts/update-translations.rb @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# 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 # * Arabic @@ -47,7 +49,7 @@ 'tr' => 'tr', # Turkish 'zh-cn' => 'zh-Hans-CN', # Chinese (China) 'zh-tw' => 'zh-Hant-TW' # Chinese (Taiwan) -} +}.freeze def copy_header(target_file, trans_strings) trans_strings.each_line do |line| @@ -72,9 +74,9 @@ def copy_comment(f, trans_strings, value) 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 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 554351855..6386aa6c5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + default_platform(:ios) fastlane_require 'xcodeproj' fastlane_require 'dotenv' @@ -55,7 +57,7 @@ platform :ios do ENV['DOWNLOAD_METADATA'] = './fastlane/download_metadata.swift' ENV['FL_RELEASE_TOOLKIT_DEFAULT_BRANCH'] = 'trunk' REPOSITORY_NAME = 'simplenote-ios' - APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow'.freeze + APP_STORE_BUNDLE_IDENTIFIER = 'com.codality.NotationalFlow' # List of locales used for the app strings (GlotPress code => `*.lproj` folder name`) # @@ -107,7 +109,7 @@ platform :ios do ##################################################################################### desc 'Creates a new release branch from the current trunk' lane :code_freeze do |options| - old_version = ios_codefreeze_prechecks(options) + ios_codefreeze_prechecks(options) ios_bump_version_release new_version = ios_get_app_version(public_version_xcconfig_file: VERSION_FILE_PATH) @@ -149,7 +151,7 @@ platform :ios do } ios_update_metadata_source( - po_file_path: prj_folder + '/Simplenote/Resources/AppStoreStrings.pot', + po_file_path: "#{prj_folder}/Simplenote/Resources/AppStoreStrings.pot", source_files: files, release_version: options[:version] ) @@ -467,7 +469,7 @@ platform :ios do ) sh('rm ../Simplenote.ipa') - dSYM_PATH = File.dirname(Dir.pwd) + '/Simplenote.app.dSYM.zip' + dSYM_PATH = "#{File.dirname(Dir.pwd)}/Simplenote.app.dSYM.zip" sentry_upload_dsym( dsym_path: dSYM_PATH, @@ -479,7 +481,7 @@ platform :ios do sh("rm #{dSYM_PATH}") if options[:create_release] - archive_zip_path = File.dirname(Dir.pwd) + '/Simplenote.xarchive.zip' + 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) @@ -990,8 +992,7 @@ def simplenote_provisioning_profiles(root_bundle_id: APP_STORE_BUNDLE_IDENTIFIER # # 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 + .to_h { |key| [key, "match #{match_type} #{key}"] } end ######################################################################## @@ -1108,7 +1109,7 @@ def find_free_test_account 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(get_required_env('BUILDKITE_BUILD_NUMBER').to_s) end # Replace a value in a key which is equal to $used_test_account_index global variable @@ -1160,7 +1161,7 @@ def submit_accounts_hash(accounts_hash) 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 From 8976795822dc903f804dadaddc9f7d3620713219 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 19:19:27 +0100 Subject: [PATCH 037/547] Fix some RuboCop violations manually --- Rakefile | 8 ++++---- fastlane/Fastfile | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Rakefile b/Rakefile index 3f9ce58f7..3fc87114e 100644 --- a/Rakefile +++ b/Rakefile @@ -241,12 +241,12 @@ task xcode: [:dependencies] do end def fold(label) - puts "travis_fold:start:#{label}" if is_travis? + puts "travis_fold:start:#{label}" if travis? yield - puts "travis_fold:end:#{label}" if is_travis? + puts "travis_fold:end:#{label}" if travis? end -def is_travis? +def travis? !ENV['TRAVIS'].nil? end @@ -322,7 +322,7 @@ def check_dependencies_hook ENV['DRY_RUN'] = '1' begin Rake::Task['dependencies'].invoke - rescue Exception => e + rescue StandardError => e puts e.message exit 1 end diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 6386aa6c5..54a897b55 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -469,16 +469,16 @@ platform :ios do ) sh('rm ../Simplenote.ipa') - dSYM_PATH = "#{File.dirname(Dir.pwd)}/Simplenote.app.dSYM.zip" + dsym_path = "#{File.dirname(Dir.pwd)}/Simplenote.app.dSYM.zip" sentry_upload_dsym( - dsym_path: dSYM_PATH, + dsym_path: dsym_path, auth_token: ENV.fetch('SENTRY_AUTH_TOKEN', nil), org_slug: 'a8c', project_slug: 'simplenote-ios' ) - sh("rm #{dSYM_PATH}") + sh("rm #{dsym_path}") if options[:create_release] archive_zip_path = "#{File.dirname(Dir.pwd)}/Simplenote.xarchive.zip" @@ -1089,9 +1089,9 @@ 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 +def fetch_test_accounts_hash uri = URI.parse(get_required_env('UI_TESTS_ACCOUNTS_JSON_URL')) response = Net::HTTP.get_response(uri) accounts_state = response.body.chomp @@ -1101,7 +1101,7 @@ 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 + accounts_hash = fetch_test_accounts_hash UI.message('Looking for a free test account...') UI.message("Accounts state: #{accounts_hash}") @@ -1120,25 +1120,25 @@ 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 + 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) + next if value == 'free' || job_running?(value) accounts_hash[key] = 'free' submit_accounts_hash(accounts_hash) end end -def is_job_running(job_number) +def job_running?(job_number) client = Buildkit.new(token: get_required_env('BUILDKITE_TOKEN')) build = client.build('automattic', 'simplenote-ios', job_number) @@ -1165,7 +1165,7 @@ def submit_accounts_hash(accounts_hash) 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) From cded6382d05cfc49eac38cfbe8317f8d968c6444 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 19:19:43 +0100 Subject: [PATCH 038/547] Generate RuboCop TODO file --- .rubocop.yml | 2 ++ .rubocop_todo.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .rubocop_todo.yml diff --git a/.rubocop.yml b/.rubocop.yml index ad146a0a1..cc096ac7b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,5 @@ +inherit_from: .rubocop_todo.yml + AllCops: NewCops: enable SuggestExtensions: 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' From 9c4d8f04e3b9da2fca8c289c6057faa86a941411 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 20:04:19 +0100 Subject: [PATCH 039/547] Update Podfile.lock --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index e590a298d..e7d572490 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -47,6 +47,6 @@ SPEC CHECKSUMS: WordPress-Ratings-iOS: 9f83dbba6e728c5121b1fd21b5683cf2fd120646 ZIPFoundation: 063163dc828bf699c5be160eb4f58f676322d94f -PODFILE CHECKSUM: 0663a4b2a1777f0957be7a40d3b2f7bf92a7fb64 +PODFILE CHECKSUM: 0310d57af0efbf50954ff46d578bc70b455981c4 COCOAPODS: 1.14.1 From 7f20135d99fa7e9df8c5b162db55df1f77c4f8f3 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 20 Feb 2024 20:11:35 +0100 Subject: [PATCH 040/547] Add Ruby as platform for dependencies --- Gemfile.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile.lock b/Gemfile.lock index a17c35f6a..5999fee29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -405,6 +405,7 @@ PLATFORMS arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 + ruby DEPENDENCIES cocoapods (~> 1.14) From 9da2ad72e67160065ae3198ebcea8eb8b8b30360 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 26 Feb 2024 19:14:21 +0100 Subject: [PATCH 041/547] Configure SwiftLint on CI --- .buildkite/pipeline.yml | 14 +++++++++++++- .buildkite/release-build.yml | 2 +- .swiftlint.yml | 8 ++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 9acc5b91b..2105546bf 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -2,7 +2,7 @@ common_params: # Common plugin settings to use with the `plugins` key. - &common_plugins - - automattic/a8c-ci-toolkit#3.0.1 + - automattic/a8c-ci-toolkit#3.1.0 # Common environment values to use with the `env` key. - &common_env IMAGE_ID: xcode-15.1 @@ -20,6 +20,18 @@ steps: artifact_paths: - "build/results/*" + ################# + # Linters + ################# + - label: ":swift: SwiftLint" + command: run_swiftlint --strict + plugins: *common_plugins + notify: + - github_commit_status: + context: "SwiftLint" + agents: + queue: "default" + ################# # Create Installable Build ################# diff --git a/.buildkite/release-build.yml b/.buildkite/release-build.yml index 085d1bcf6..f728cdcb0 100644 --- a/.buildkite/release-build.yml +++ b/.buildkite/release-build.yml @@ -4,7 +4,7 @@ common_params: # Common plugin settings to use with the `plugins` key. - &common_plugins - - automattic/a8c-ci-toolkit#3.0.1 + - automattic/a8c-ci-toolkit#3.1.0 # Common environment values to use with the `env` key. - &common_env IMAGE_ID: xcode-15.1 diff --git a/.swiftlint.yml b/.swiftlint.yml index 604062415..cf03a5a91 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 @@ -38,6 +44,8 @@ only_rules: # Lines should not have trailing whitespace. - trailing_whitespace + - vertical_whitespace + - custom_rules # Rules configuration From 175001b698fe386f495b15eed21cad7db8a37b93 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 26 Feb 2024 19:48:58 +0100 Subject: [PATCH 042/547] Configure SwiftLint via Podfile --- Podfile | 14 ++++++++++++++ Podfile.lock | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Podfile b/Podfile index d9dd0091f..d6548d74f 100644 --- a/Podfile +++ b/Podfile @@ -43,6 +43,20 @@ 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 # post_install do |installer| diff --git a/Podfile.lock b/Podfile.lock index e7d572490..14f4c6907 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: 0310d57af0efbf50954ff46d578bc70b455981c4 +PODFILE CHECKSUM: 6fc187e283b4fb7f9f4a0ef73a27632724c90845 COCOAPODS: 1.14.1 From c3325747b2b22ee4c89a6f76af7685a1bce4e61b Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 26 Feb 2024 19:50:37 +0100 Subject: [PATCH 043/547] Configure SwiftLint as a Build phase, removing Rake config --- Rakefile | 79 +--------------------------- Scripts/swiftlint.sh | 54 ------------------- Simplenote.xcodeproj/project.pbxproj | 2 +- 3 files changed, 3 insertions(+), 132 deletions(-) delete mode 100755 Scripts/swiftlint.sh diff --git a/Rakefile b/Rakefile index 3fc87114e..090686846 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'English' -SWIFTLINT_VERSION = '0.41.0' XCODE_WORKSPACE = 'Simplenote.xcworkspace' XCODE_SCHEME = 'Simplenote' XCODE_CONFIGURATION = 'Debug' @@ -19,7 +18,7 @@ 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 @@ -84,42 +83,6 @@ namespace :dependencies do end 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.rm_rf(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' @@ -154,20 +117,8 @@ 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' task :install_hooks do @@ -220,12 +171,6 @@ namespace :git do end namespace :git do - task pre_commit: %(dependencies:lint:check) do - swiftlint %w[lint --quiet --strict] - rescue StandardError - exit $CHILD_STATUS.exitstatus - end - task :post_merge do check_dependencies_hook end @@ -266,26 +211,6 @@ def podfile_locked? 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 - (installed_version != SWIFTLINT_VERSION) -end - def xcodebuild(*build_cmds) cmd = 'xcodebuild' cmd += " -destination 'platform=iOS Simulator,name=iPhone 6s'" 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/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 95d1ed3ac..83c24fd3e 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -3230,7 +3230,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; From 4f8cbac8827ce6e5ad86a821f97dbc09007568d4 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 26 Feb 2024 19:51:12 +0100 Subject: [PATCH 044/547] Fix SwiftLint violations --- Simplenote/AuthenticationValidator.swift | 4 ---- Simplenote/Classes/ActivityType.swift | 1 - Simplenote/Classes/Bundle+Simplenote.swift | 2 -- Simplenote/Classes/CGRect+Simplenote.swift | 1 - Simplenote/Classes/ColorStudio.swift | 1 - .../Classes/DateFormatter+Simplenote.swift | 1 - Simplenote/Classes/EditorFactory.swift | 2 -- Simplenote/Classes/IndexPath+Simplenote.swift | 1 - Simplenote/Classes/InterlinkProcessor.swift | 6 ----- .../Classes/InterlinkResultsController.swift | 4 ---- .../Classes/InterlinkViewController.swift | 8 +------ Simplenote/Classes/KeyboardObservable.swift | 2 -- Simplenote/Classes/KeychainManager.swift | 5 ---- .../Classes/MagicLinkAuthenticator.swift | 2 -- Simplenote/Classes/MigrationsHandler.swift | 2 -- .../NSAttributedString+AuthError.swift | 3 --- .../NSAttributedString+Simplenote.swift | 1 - ...NSMutableAttributedString+Simplenote.swift | 1 - .../NSMutableParagraphStyle+Simplenote.swift | 1 - .../NSRegularExpression+Simplenote.swift | 2 -- .../Classes/NSSortDescriptor+Simplenote.swift | 1 - Simplenote/Classes/NSString+Simplenote.swift | 1 - Simplenote/Classes/NSURL+Extensions.swift | 1 - Simplenote/Classes/NSURL+Links.swift | 1 - .../Classes/NSUserActivity+Shortcuts.swift | 1 - .../Classes/NSUserActivity+Simplenote.swift | 2 -- Simplenote/Classes/Note+Properties.swift | 1 - Simplenote/Classes/NotesListController.swift | 7 ------ Simplenote/Classes/NotesListFilter.swift | 3 --- Simplenote/Classes/NotesListSection.swift | 1 - Simplenote/Classes/NotesListState.swift | 1 - .../Classes/OptionsViewController.swift | 13 ----------- Simplenote/Classes/PassthruView.swift | 1 - .../Classes/PopoverViewController.swift | 4 ---- .../Classes/SPAboutViewController.swift | 5 ---- Simplenote/Classes/SPAuthError.swift | 3 --- Simplenote/Classes/SPAuthHandler.swift | 6 ----- Simplenote/Classes/SPAuthViewController.swift | 20 ---------------- Simplenote/Classes/SPBlurEffectView.swift | 4 ---- Simplenote/Classes/SPContactsManager.swift | 4 ---- .../Classes/SPDefaultTableViewCell.swift | 2 -- .../Classes/SPDiagnosticsViewController.swift | 3 --- .../SPEditorTapRecognizerDelegate.swift | 2 -- Simplenote/Classes/SPFeedbackManager.swift | 2 -- Simplenote/Classes/SPLabel.swift | 2 -- ...PNoteEditorViewController+Extensions.swift | 23 ------------------- .../SPNoteListViewController+Extensions.swift | 15 ------------ Simplenote/Classes/SPNoteTableViewCell.swift | 11 --------- .../Classes/SPOnboardingViewController.swift | 8 ------- Simplenote/Classes/SPPlaceholderView.swift | 4 ---- Simplenote/Classes/SPRatingsPromptView.swift | 8 ------- Simplenote/Classes/SPSectionHeaderView.swift | 3 --- .../SPSettingsViewController+Extensions.swift | 3 --- Simplenote/Classes/SPSheetController.swift | 8 ------- Simplenote/Classes/SPSortBar.swift | 5 ---- .../Classes/SPSortOrderViewController.swift | 4 ---- Simplenote/Classes/SPSquaredButton.swift | 2 -- Simplenote/Classes/SPTagHeaderView.swift | 3 --- Simplenote/Classes/SPTagTableViewCell.swift | 7 ------ Simplenote/Classes/SPTextInputView.swift | 8 ------- .../Classes/SPThemeViewController.swift | 5 ---- Simplenote/Classes/SPUserInterface.swift | 3 --- .../Classes/SearchDisplayController.swift | 7 ------ Simplenote/Classes/ShortcutsHandler.swift | 1 - .../SignupVerificationViewController.swift | 1 - Simplenote/Classes/Simperium+Simplenote.swift | 3 --- .../SimplenoteActivityItemSource.swift | 1 - Simplenote/Classes/SimplenoteConstants.swift | 1 - Simplenote/Classes/SortMode.swift | 1 - .../SortModePickerViewController.swift | 2 -- Simplenote/Classes/String+Simplenote.swift | 2 -- Simplenote/Classes/SwitchTableViewCell.swift | 4 ---- Simplenote/Classes/TagView.swift | 8 ------- Simplenote/Classes/TagViewCell.swift | 4 ---- Simplenote/Classes/Theme.swift | 3 --- .../Classes/UIActivity+Simplenote.swift | 1 - .../Classes/UIAlertController+Helpers.swift | 1 - .../Classes/UIApplication+Simplenote.swift | 6 ++--- .../Classes/UIBarButtonItem+Appearance.swift | 1 - .../Classes/UIBezierPath+Simplenote.swift | 1 - .../Classes/UIBlurEffect+Simplenote.swift | 1 - Simplenote/Classes/UIColor+Helpers.swift | 2 -- Simplenote/Classes/UIColor+Studio.swift | 2 -- .../UIContextualAction+Simplenote.swift | 1 - Simplenote/Classes/UIDevice+Simplenote.swift | 1 - Simplenote/Classes/UIFont+Simplenote.swift | 4 ---- .../UIGestureRecognizer+Simplenote.swift | 1 - Simplenote/Classes/UIImage+Dynamic.swift | 2 -- Simplenote/Classes/UIImage+Simplenote.swift | 1 - Simplenote/Classes/UIImageName.swift | 2 -- .../UIKeyboardAppearance+Simplenote.swift | 1 - Simplenote/Classes/UIKitConstants.swift | 1 - .../Classes/UINavigationBar+Appearance.swift | 1 - .../Classes/UINavigationBar+Simplenote.swift | 1 - .../UINavigationController+Simplenote.swift | 1 - Simplenote/Classes/UIPasteboard+Note.swift | 1 - Simplenote/Classes/UIScreen+Simplenote.swift | 1 - .../Classes/UISearchBar+Simplenote.swift | 1 - .../Classes/UITableView+Simplenote.swift | 1 - .../Classes/UITableViewCell+Simplenote.swift | 1 - ...TableViewHeaderFooterView+Simplenote.swift | 1 - .../Classes/UITextView+Simplenote.swift | 6 ----- .../UITraitCollection+Simplenote.swift | 1 - Simplenote/Classes/UIView+Animations.swift | 1 - .../Classes/UIView+ImageRepresentation.swift | 1 - Simplenote/Classes/UIView+Simplenote.swift | 2 -- .../Classes/UIViewController+Helpers.swift | 1 - .../Classes/UIViewController+Simplenote.swift | 2 -- .../Classes/UserDefaults+Simplenote.swift | 2 -- Simplenote/Color+Simplenote.swift | 1 - .../Controllers/VersionsController.swift | 4 ---- Simplenote/Controllers/WidgetController.swift | 1 - Simplenote/CrashLogging.swift | 1 - Simplenote/EmailVerification.swift | 1 - .../NoteInformationViewController.swift | 1 - Simplenote/KeychainPasswordItem.swift | 1 - ...SAttributedStringToMarkdownConverter.swift | 2 -- Simplenote/NSObject+Helpers.swift | 1 - Simplenote/NSPredicate+Email.swift | 1 - Simplenote/Note+Links.swift | 1 - Simplenote/NoteMetrics.swift | 1 - Simplenote/Notice.swift | 2 +- Simplenote/NoticeView.swift | 1 - Simplenote/Options.swift | 7 ------ Simplenote/PopoverPresenter.swift | 6 ----- Simplenote/Preferences+IAP.swift | 1 - Simplenote/SPAppDelegate+Extensions.swift | 7 ------ Simplenote/SPCardPresentationController.swift | 2 -- Simplenote/SPEditorTextView+Simplenote.swift | 1 - Simplenote/SPHistoryVersion.swift | 1 - Simplenote/SPNoteHistoryController.swift | 1 - Simplenote/SPPrivacyViewController.swift | 4 ---- Simplenote/SPTextAttachment.swift | 3 --- Simplenote/SPTracker+Extensions.swift | 2 -- Simplenote/SearchQuery+Simplenote.swift | 1 - Simplenote/SeparatorsView.swift | 2 -- Simplenote/SpinnerViewController.swift | 2 -- Simplenote/StoreConstants.swift | 1 - Simplenote/StoreManager.swift | 10 -------- Simplenote/StoreProduct.swift | 1 - Simplenote/SubtitleTableViewCell.swift | 3 --- Simplenote/SustainerView.swift | 6 ----- Simplenote/TableHeaderViewCell.swift | 2 -- Simplenote/TagListViewController.swift | 3 --- .../NoteEditorTagListViewController.swift | 5 ---- ...teEditorTagSuggestionsViewController.swift | 4 ---- .../UIActivityViewController+Simplenote.swift | 1 - Simplenote/UIAlertController+Sustainer.swift | 2 -- Simplenote/Value1TableViewCell.swift | 3 --- .../NSExtensionContext+Simplenote.swift | 1 - ...NSURLSessionConfiguration+Extensions.swift | 1 - SimplenoteShare/Extractors/Extractor.swift | 1 - SimplenoteShare/Extractors/Extractors.swift | 1 - .../Extractors/PlainTextExtractor.swift | 1 - SimplenoteShare/Extractors/URLExtractor.swift | 4 ---- .../ExtensionPresentationAnimator.swift | 4 ---- .../ExtensionPresentationController.swift | 5 ---- .../ExtensionTransitioningManager.swift | 2 -- SimplenoteShare/Simperium/Note.swift | 2 -- SimplenoteShare/Simperium/Uploader.swift | 5 ---- SimplenoteShare/Themes/SPUserInterface.swift | 1 - .../SharePresentationController.swift | 3 --- .../ViewControllers/ShareViewController.swift | 7 ------ .../AuthenticationValidatorTests.swift | 3 --- SimplenoteTests/CGRectSimplenoteTests.swift | 1 - SimplenoteTests/Constants.swift | 1 - SimplenoteTests/EmailVerificationTests.swift | 1 - .../InterlinkResultsControllerTests.swift | 5 ---- SimplenoteTests/MockupStorage+Sample.swift | 1 - SimplenoteTests/MockupStorage.swift | 3 --- ...SMutableAttributedStringStylingTests.swift | 1 - SimplenoteTests/NSPredicateEmailTests.swift | 1 - SimplenoteTests/NSStringCondensingTests.swift | 1 - SimplenoteTests/NSStringSimplenoteTests.swift | 1 - SimplenoteTests/NoteBodyExcerptTests.swift | 1 - SimplenoteTests/NoteContentHelperTests.swift | 1 - SimplenoteTests/NoteContentPreviewTests.swift | 1 - SimplenoteTests/NoteLinkTests.swift | 1 - .../NotesListControllerTests.swift | 12 ---------- SimplenoteTests/OptionsTests.swift | 1 - SimplenoteTests/StringContentSliceTests.swift | 1 - SimplenoteTests/StringSimplenoteTests.swift | 1 - .../TagTextFieldInputValidatorTests.swift | 9 ++++---- SimplenoteTests/UIColorSimplenoteTests.swift | 2 -- SimplenoteTests/UIImageSimplenoteTests.swift | 1 - SimplenoteTests/UserDefaults+Tests.swift | 1 - SimplenoteUITests/NoteEditor.swift | 1 - SimplenoteWidgets/SimplenoteWidgets.swift | 1 - SimplenoteWidgets/Tools/WidgetsState.swift | 1 - SimplenoteWidgets/ViewModifiers/Filling.swift | 1 - .../Views/ListWidgetHeaderView.swift | 1 - SimplenoteWidgets/Views/ListWidgetView.swift | 1 - SimplenoteWidgets/Views/NoteListRow.swift | 1 - UITestsFoundation/PasscodeScreen.swift | 6 +---- 194 files changed, 10 insertions(+), 545 deletions(-) diff --git a/Simplenote/AuthenticationValidator.swift b/Simplenote/AuthenticationValidator.swift index 4b3642e40..b922fd899 100644 --- a/Simplenote/AuthenticationValidator.swift +++ b/Simplenote/AuthenticationValidator.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - AuthenticationValidator // struct AuthenticationValidator { @@ -13,7 +12,6 @@ struct AuthenticationValidator { /// private let strongPasswordLength = UInt(8) - /// Returns the Validation Result for a given Username /// func performUsernameValidation(username: String) -> Result { @@ -52,7 +50,6 @@ struct AuthenticationValidator { } } - // MARK: - Nested Types // extension AuthenticationValidator { @@ -71,7 +68,6 @@ extension AuthenticationValidator { } } - // MARK: - Validation Results: String Conversion // extension AuthenticationValidator.Result: CustomStringConvertible { diff --git a/Simplenote/Classes/ActivityType.swift b/Simplenote/Classes/ActivityType.swift index 88a5cc8d2..f115d11e6 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 { 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/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/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..38c97012a 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -24,7 +24,6 @@ struct MagicLinkAuthenticator { } } - // MARK: - [URLQueryItem] Helper // private extension Array where Element == URLQueryItem { @@ -42,7 +41,6 @@ private extension Array where Element == URLQueryItem { } } - // MARK: - Constants // private struct Constants { 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/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/SPAboutViewController.swift b/Simplenote/Classes/SPAboutViewController.swift index b3ce35f12..22798c719 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 { diff --git a/Simplenote/Classes/SPAuthError.swift b/Simplenote/Classes/SPAuthError.swift index 8f3dbdf64..21d3e61a3 100644 --- a/Simplenote/Classes/SPAuthError.swift +++ b/Simplenote/Classes/SPAuthError.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - SPAuthError // enum SPAuthError: Error { @@ -14,7 +13,6 @@ enum SPAuthError: Error { case unknown(statusCode: Int, response: String?, error: Error?) } - // MARK: - SPAuthError Convenience Initializers // extension SPAuthError { @@ -52,7 +50,6 @@ extension SPAuthError { } } - // MARK: - SPAuthError Public Methods // extension SPAuthError { diff --git a/Simplenote/Classes/SPAuthHandler.swift b/Simplenote/Classes/SPAuthHandler.swift index 6f655d441..bf0a85a4a 100644 --- a/Simplenote/Classes/SPAuthHandler.swift +++ b/Simplenote/Classes/SPAuthHandler.swift @@ -1,7 +1,6 @@ import Foundation import SafariServices - // MARK: - SPAuthHandler // class SPAuthHandler { @@ -10,7 +9,6 @@ class SPAuthHandler { /// private let simperiumService: SPAuthenticator - /// Designated Initializer. /// /// - Parameter simperiumService: Reference to a valid SPAuthenticator instance. @@ -19,8 +17,6 @@ class SPAuthHandler { self.simperiumService = simperiumService } - - /// Authenticates against the Simperium Backend. /// /// - Note: Errors are mapped into SPAuthError Instances @@ -39,7 +35,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,7 +53,6 @@ class SPAuthHandler { }) } - /// Registers a new user in the Simperium Backend. /// /// - Note: Errors are mapped into SPAuthError Instances diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index a293f0f16..f6e27223e 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import SafariServices - // MARK: - SPAuthViewController // class SPAuthViewController: UIViewController { @@ -166,15 +165,12 @@ class SPAuthViewController: UIViewController { /// 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) { @@ -183,7 +179,6 @@ class SPAuthViewController: UIViewController { super.init(nibName: nil, bundle: nil) } - // MARK: - Overridden Methods override func viewDidLoad() { @@ -213,7 +208,6 @@ class SPAuthViewController: UIViewController { } } - // MARK: - Actions // extension SPAuthViewController { @@ -225,7 +219,6 @@ extension SPAuthViewController { } } - // MARK: - Interface // private extension SPAuthViewController { @@ -276,7 +269,6 @@ private extension SPAuthViewController { } } - // MARK: - Actions // private extension SPAuthViewController { @@ -347,7 +339,6 @@ private extension SPAuthViewController { } } - // MARK: - Simperium Services // private extension SPAuthViewController { @@ -380,7 +371,6 @@ private extension SPAuthViewController { } } - // MARK: - Password Reset Flow // private extension SPAuthViewController { @@ -401,7 +391,6 @@ private extension SPAuthViewController { } } - // MARK: - Error Handling // private extension SPAuthViewController { @@ -502,7 +491,6 @@ private extension SPAuthViewController { } } - // MARK: - Warning Labels // private extension SPAuthViewController { @@ -536,7 +524,6 @@ private extension SPAuthViewController { } } - // MARK: - Validation // private extension SPAuthViewController { @@ -588,7 +575,6 @@ private extension SPAuthViewController { } } - // MARK: - UITextFieldDelegate Conformance // extension SPAuthViewController: SPTextInputViewDelegate { @@ -630,7 +616,6 @@ extension SPAuthViewController: SPTextInputViewDelegate { } } - // MARK: - AuthenticationMode: Signup / Login // struct AuthenticationMode { @@ -644,7 +629,6 @@ struct AuthenticationMode { let isPasswordHidden: Bool } - // MARK: - Default Operation Modes // extension AuthenticationMode { @@ -676,7 +660,6 @@ extension AuthenticationMode { } } - // MARK: - Authentication Strings // private enum AuthenticationStrings { @@ -702,7 +685,6 @@ private enum AuthenticationStrings { static let verificationSentTemplate = NSLocalizedString("We’ve sent a verification email to %1$@. Please check your inbox and follow the instructions.", comment: "Confirmation that an email has been sent") } - // MARK: - PasswordInsecure Alert Strings // private enum PasswordInsecureString { @@ -718,7 +700,6 @@ private enum PasswordInsecureString { ].joined(separator: .newline) } - // MARK: - Strings >> Authenticated Strings Convenience Properties // private extension AuthenticationStrings { @@ -738,7 +719,6 @@ private extension AuthenticationStrings { } } - // MARK: - Authentication Constants // private enum AuthenticationConstants { 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/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/SPNoteListViewController+Extensions.swift b/Simplenote/Classes/SPNoteListViewController+Extensions.swift index 08ee2a98e..8c0677a3c 100644 --- a/Simplenote/Classes/SPNoteListViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteListViewController+Extensions.swift @@ -149,7 +149,6 @@ extension SPNoteListViewController { } } - // MARK: - Internal Methods // extension SPNoteListViewController { @@ -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 { @@ -772,12 +766,10 @@ private extension SPNoteListViewController { } shareAction.accessibilityLabel = ActionTitle.share - return [trashAction, pinAction, copyAction, shareAction] } } - // MARK: - UIMenu // private extension SPNoteListViewController { @@ -826,7 +818,6 @@ private extension SPNoteListViewController { } } - // MARK: - Services // private extension SPNoteListViewController { @@ -893,7 +884,6 @@ private extension SPNoteListViewController { } } - // MARK: - Services (Internal) // extension SPNoteListViewController { @@ -908,7 +898,6 @@ extension SPNoteListViewController { } } - // MARK: - Keyboard Handling // extension SPNoteListViewController { @@ -957,7 +946,6 @@ extension SPNoteListViewController { } } - // MARK: - Search Action Handlers // extension SPNoteListViewController { @@ -981,7 +969,6 @@ extension SPNoteListViewController { } } - // MARK: - Keyboard // extension SPNoteListViewController { @@ -1047,7 +1034,6 @@ private extension SPNoteListViewController { } } - // MARK: - Private Types // private enum ActionTitle { @@ -1092,7 +1078,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/SPNoteTableViewCell.swift b/Simplenote/Classes/SPNoteTableViewCell.swift index d1cb75cff..1f9fde988 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 { @@ -355,7 +347,6 @@ extension SPNoteTableViewCell { } } - // MARK: - Cell Styles // private enum Style { @@ -445,8 +436,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..c80e9a0f5 100644 --- a/Simplenote/Classes/SPOnboardingViewController.swift +++ b/Simplenote/Classes/SPOnboardingViewController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import SafariServices - // MARK: - SPOnboardingViewController // class SPOnboardingViewController: UIViewController, SPAuthenticationInterface { @@ -43,7 +42,6 @@ class SPOnboardingViewController: UIViewController, SPAuthenticationInterface { /// var authenticator: SPAuthenticator? - // MARK: - Overriden Properties override var supportedInterfaceOrientations: UIInterfaceOrientationMask { @@ -54,7 +52,6 @@ class SPOnboardingViewController: UIViewController, SPAuthenticationInterface { return false } - // MARK: - Overridden Methods override func viewDidLoad() { @@ -74,7 +71,6 @@ class SPOnboardingViewController: UIViewController, SPAuthenticationInterface { } } - // MARK: - Private // private extension SPOnboardingViewController { @@ -141,7 +137,6 @@ private extension SPOnboardingViewController { } } - // MARK: - Actions // private extension SPOnboardingViewController { @@ -195,7 +190,6 @@ private extension SPOnboardingViewController { } } - // MARK: - Actions // private extension SPOnboardingViewController { @@ -219,7 +213,6 @@ private extension SPOnboardingViewController { } - // MARK: - Private Types // private struct OnboardingStrings { @@ -233,7 +226,6 @@ private struct OnboardingStrings { 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..596d03cb0 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -1,6 +1,5 @@ import UIKit - // MARK: - Subscriber UI // extension SPSettingsViewController { @@ -62,7 +61,6 @@ extension SPSettingsViewController { } } - // MARK: - Pin // extension SPSettingsViewController { @@ -237,7 +235,6 @@ private struct AccountDeletion { } } - // MARK: - RestorationAlert // struct RestorationAlert { 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/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..6ec70ace4 100644 --- a/Simplenote/Classes/ShortcutsHandler.swift +++ b/Simplenote/Classes/ShortcutsHandler.swift @@ -1,7 +1,6 @@ import Foundation import CoreSpotlight - // MARK: - AppDelegate Shortcuts Methods // class ShortcutsHandler: NSObject { 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..6e15cbcfd 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,7 +57,6 @@ extension Simperium { } } - // MARK: - Constants // extension Simperium { 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..f273e382e 100644 --- a/Simplenote/Classes/SimplenoteConstants.swift +++ b/Simplenote/Classes/SimplenoteConstants.swift @@ -1,6 +1,5 @@ import Foundation - // MARK: - Simplenote Constants! // @objcMembers 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..8867d4ad8 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 { @@ -19,7 +18,6 @@ extension UserDefaults { } } - // 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/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/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/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..ccea36890 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 { @@ -152,7 +147,6 @@ extension Options { } } - // MARK: - Private // private extension Options { @@ -179,7 +173,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..c670c5952 100644 --- a/Simplenote/Preferences+IAP.swift +++ b/Simplenote/Preferences+IAP.swift @@ -8,7 +8,6 @@ import Foundation - // MARK: - Preferences Extensions // extension Preferences { diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index dcd793697..87b556e46 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -1,7 +1,6 @@ import Foundation import WidgetKit - // MARK: - Initialization // extension SPAppDelegate { @@ -196,7 +195,6 @@ extension SPAppDelegate { } } - // MARK: - UIViewControllerRestoration // @objc @@ -259,7 +257,6 @@ extension SPAppDelegate: UIViewControllerRestoration { } } - // MARK: - SimperiumDelegate // extension SPAppDelegate: SimperiumDelegate { @@ -320,7 +317,6 @@ extension SPAppDelegate: SimperiumDelegate { } } - // MARK: - Passcode // extension SPAppDelegate { @@ -363,7 +359,6 @@ extension SPAppDelegate { } } - // MARK: - PinLockVerifyControllerDelegate // extension SPAppDelegate: PinLockVerifyControllerDelegate { @@ -419,7 +414,6 @@ private extension SPAppDelegate { } } - // MARK: - Magic Link authentication // extension SPAppDelegate { @@ -429,7 +423,6 @@ extension SPAppDelegate { } } - // MARK: - Scroll position cache // extension SPAppDelegate { 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/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/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..3ba92acc3 100644 --- a/Simplenote/StoreManager.swift +++ b/Simplenote/StoreManager.swift @@ -9,14 +9,12 @@ import Foundation import StoreKit - // MARK: - StoreError // enum StoreError: Error { case failedVerification } - // MARK: - StoreManager // @available(iOS 15, *) @@ -26,7 +24,6 @@ class StoreManager { // static let shared = StoreManager() - // MARK: - Aliases // typealias SubscriptionStatus = Product.SubscriptionInfo.Status @@ -53,14 +50,12 @@ class StoreManager { return subscriptionGroupStatus.isActive } - // MARK: - Deinit deinit { updateListenerTask?.cancel() } - // MARK: - Public API(s) /// Initialization involves three major steps: @@ -84,7 +79,6 @@ class StoreManager { } } - /// Purchases the specified Product (as long as we don't own it already?) /// func purchase(storeProduct: StoreProduct) { @@ -126,7 +120,6 @@ class StoreManager { } } - // MARK: - Private API(s) // @available(iOS 15, *) @@ -216,7 +209,6 @@ private extension StoreManager { } } - // MARK: - Private Helpers // @available(iOS 15, *) @@ -248,7 +240,6 @@ private extension StoreManager { } } - // MARK: - Simperium Kung Fu // @available(iOS 15, *) @@ -307,7 +298,6 @@ private extension StoreManager { } } - // MARK: - SubscriptionStatus Helpers // @available(iOS 15, *) 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/SustainerView.swift b/Simplenote/SustainerView.swift index 9f84c029f..4c325a921 100644 --- a/Simplenote/SustainerView.swift +++ b/Simplenote/SustainerView.swift @@ -1,7 +1,6 @@ import Foundation import UIKit - // MARK: - SustainerView // class SustainerView: UIView { @@ -53,7 +52,6 @@ class SustainerView: UIView { refreshInterface() } - // MARK: - Actions @IBAction @@ -62,7 +60,6 @@ class SustainerView: UIView { } } - // MARK: - Private API(s) // private extension SustainerView { @@ -79,7 +76,6 @@ private extension SustainerView { } } - // MARK: - Style // private struct Style { @@ -89,7 +85,6 @@ private struct Style { let backgroundColor: UIColor } - private extension Style { static var sustainer: Style { Style(title: NSLocalizedString("You are a Simplenote Sustainer", comment: "Current Sustainer Title"), @@ -106,7 +101,6 @@ private extension Style { } } - // MARK: - Metrics // private enum Metrics { 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..dee5f1339 100644 --- a/Simplenote/TagListViewController.swift +++ b/Simplenote/TagListViewController.swift @@ -79,7 +79,6 @@ final class TagListViewController: UIViewController { } } - // MARK: - Configuration // private extension TagListViewController { @@ -127,7 +126,6 @@ private extension TagListViewController { } } - // MARK: - Action Handlers // private extension TagListViewController { @@ -143,7 +141,6 @@ private extension TagListViewController { } } - // MARK: - Notifications // private extension TagListViewController { 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+Sustainer.swift b/Simplenote/UIAlertController+Sustainer.swift index 29f591679..9a8bbf904 100644 --- a/Simplenote/UIAlertController+Sustainer.swift +++ b/Simplenote/UIAlertController+Sustainer.swift @@ -8,7 +8,6 @@ import Foundation - // MARK: - UIAlertController+Sustainer // @available(iOS 15, *) @@ -39,7 +38,6 @@ extension UIAlertController { } } - // MARK: - Localization // private enum Localization { 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/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/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..743e918e6 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,7 +15,6 @@ class Uploader: NSObject { token = simperiumToken } - // MARK: - Public Methods func send(_ note: Note) { // Build the targetURL @@ -38,7 +36,6 @@ class Uploader: NSObject { } } - // MARK: - URLSessionDelegate // extension Uploader: URLSessionDelegate { @@ -54,7 +51,6 @@ extension Uploader: URLSessionDelegate { } } - // MARK: - URLSessionTaskDelegate // extension Uploader: URLSessionTaskDelegate { @@ -65,7 +61,6 @@ extension Uploader: URLSessionTaskDelegate { } } - // MARK: - Settings // private struct Settings { 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/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/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.. 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/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/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 ) From c67c63cbe2fff2f4464e833565c7f225a26d3439 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 26 Feb 2024 20:01:17 +0100 Subject: [PATCH 045/547] Remove Hound configuration --- .hound.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .hound.yml 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 From f63e68a4e11ce717f9a094b216cdea2473a0c6d9 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 26 Feb 2024 20:06:31 +0100 Subject: [PATCH 046/547] Update README file with SwiftLint instructions --- README.md | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) 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 From eb1a2620b43035d87fe17f23ed6f5ac0bacbd2a4 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 25 Mar 2024 15:54:52 -0600 Subject: [PATCH 047/547] Remove the sustainer banner from the settings view --- .../SPSettingsViewController+Extensions.swift | 5 ++- Simplenote/Classes/SPSettingsViewController.m | 31 +------------------ 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 596d03cb0..be35d7233 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -2,7 +2,10 @@ import UIKit // MARK: - Subscriber UI // -extension SPSettingsViewController { + +// 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 { private var isActiveSustainer: Bool { SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index b73535fe1..5b022a653 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -132,10 +132,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 @@ -180,46 +176,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 @@ -594,8 +566,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath break; } case SPOptionsViewSectionsSustainer: { - - [self restorePurchases]; + //TODO: Remove the sustainer case? break; } case SPOptionsViewSectionsAccount: { From aa143af8478a49d673f27dc2a21ebf8ad2a16fbc Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 25 Mar 2024 15:58:14 -0600 Subject: [PATCH 048/547] Dropped SPOptionsViewSectionsSustainer enum case for settings tableview --- Simplenote/Classes/SPSettingsViewController.m | 49 +++---------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 5b022a653..363ad47a6 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -36,18 +36,12 @@ typedef NS_ENUM(NSInteger, SPOptionsViewSections) { 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) { @@ -224,14 +218,6 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger return [self isPinLockEnabled] ? SPOptionsSecurityRowRowCount - rowsToRemove : disabledPinLockRows; } - case SPOptionsViewSectionsSustainer: { - return SPOptionsSustainerRowCount; - } - - case SPOptionsViewSectionsAccount: { - return SPOptionsAccountRowCount; - } - case SPOptionsViewSectionsDelete: { return SPOptionsDeleteRowCount; } @@ -270,9 +256,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); @@ -417,22 +400,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"); @@ -565,10 +534,6 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath break; - } case SPOptionsViewSectionsSustainer: { - //TODO: Remove the sustainer case? - break; - } case SPOptionsViewSectionsAccount: { switch (indexPath.row) { From d1300a8588d9c6378686a9b31084f0089c9e601b Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 25 Mar 2024 16:09:25 -0600 Subject: [PATCH 049/547] Removed sustainerUI from tag list view controller --- Simplenote/TagListViewController.swift | 48 ++------------------------ 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/Simplenote/TagListViewController.swift b/Simplenote/TagListViewController.swift index dee5f1339..0c880531b 100644 --- a/Simplenote/TagListViewController.swift +++ b/Simplenote/TagListViewController.swift @@ -49,7 +49,6 @@ final class TagListViewController: UIViewController { super.viewWillAppear(animated) startListeningToKeyboardNotifications() - refreshTableHeaderView() reloadTableView() startListeningForChanges() becomeFirstResponder() @@ -64,19 +63,6 @@ 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 @@ -93,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() { @@ -126,21 +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 { @@ -150,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() { @@ -183,13 +144,6 @@ private extension TagListViewController { func tagsSortOrderWasUpdated() { refreshSortDescriptorsAndPerformFetch() } - - @objc - func subscriptionStatusDidChange() { - DispatchQueue.main.async { - self.refreshTableHeaderView() - } - } } // MARK: - Style @@ -588,6 +542,8 @@ 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 { return From 65c43969e8cd47821bce64e393349253316e66ed Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 25 Mar 2024 16:12:16 -0600 Subject: [PATCH 050/547] Dropped uialertcontroller + sustainer extension --- Simplenote.xcodeproj/project.pbxproj | 6 +- .../SPSettingsViewController+Extensions.swift | 21 ------ Simplenote/UIAlertController+Sustainer.swift | 65 ------------------- 3 files changed, 2 insertions(+), 90 deletions(-) delete mode 100644 Simplenote/UIAlertController+Sustainer.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 83c24fd3e..209ff1b1b 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -237,7 +237,6 @@ 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 */; }; @@ -907,7 +906,6 @@ 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; }; @@ -1883,7 +1881,6 @@ B54D9C562909BA2600D0E0EC /* StoreManager.swift */, B549E24A290B3DD70072C3E8 /* StoreConstants.swift */, B54D9C582909CC4400D0E0EC /* StoreProduct.swift */, - B514678C291AC7790062736E /* UIAlertController+Sustainer.swift */, B5BADB7C2909D78B00275B29 /* TestConfiguration.storekit */, ); name = Store; @@ -3429,7 +3426,6 @@ 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 */, @@ -4382,6 +4378,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 +4409,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"; diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index be35d7233..50dcc70b4 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -11,17 +11,6 @@ fileprivate extension SPSettingsViewController { 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 - } - @objc func refreshTableHeaderView() { guard let headerView = tableView.tableHeaderView as? SustainerView else { @@ -35,16 +24,6 @@ fileprivate extension SPSettingsViewController { 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 { diff --git a/Simplenote/UIAlertController+Sustainer.swift b/Simplenote/UIAlertController+Sustainer.swift deleted file mode 100644 index 9a8bbf904..000000000 --- a/Simplenote/UIAlertController+Sustainer.swift +++ /dev/null @@ -1,65 +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) - } -} From bb686189762038c4fd4041519de417b90e842daf Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 25 Mar 2024 16:16:45 -0600 Subject: [PATCH 051/547] Renamed SustainerView to BannerView --- Simplenote.xcodeproj/project.pbxproj | 16 ++++++------- .../{SustainerView.swift => BannerView.swift} | 6 ++--- .../{SustainerView.xib => BannerView.xib} | 24 +++++++++---------- .../SPSettingsViewController+Extensions.swift | 2 +- Simplenote/TagListViewController.swift | 10 ++++---- 5 files changed, 29 insertions(+), 29 deletions(-) rename Simplenote/{SustainerView.swift => BannerView.swift} (96%) rename Simplenote/{SustainerView.xib => BannerView.xib} (95%) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 209ff1b1b..3de031937 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -410,8 +410,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 */; }; @@ -1106,8 +1106,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 = ""; }; @@ -2250,8 +2250,8 @@ B5F232B429084317006D8570 /* Sustainer */ = { isa = PBXGroup; children = ( - B5F232B529084336006D8570 /* SustainerView.swift */, - B5F232B929084526006D8570 /* SustainerView.xib */, + B5F232B529084336006D8570 /* BannerView.swift */, + B5F232B929084526006D8570 /* BannerView.xib */, ); name = Sustainer; sourceTree = ""; @@ -3063,7 +3063,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 */, @@ -3333,7 +3333,7 @@ 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 */, diff --git a/Simplenote/SustainerView.swift b/Simplenote/BannerView.swift similarity index 96% rename from Simplenote/SustainerView.swift rename to Simplenote/BannerView.swift index 4c325a921..67d9d875a 100644 --- a/Simplenote/SustainerView.swift +++ b/Simplenote/BannerView.swift @@ -1,9 +1,9 @@ import Foundation import UIKit -// MARK: - SustainerView +// MARK: - BannerView // -class SustainerView: UIView { +class BannerView: UIView { @IBOutlet private var backgroundView: UIView! @@ -62,7 +62,7 @@ class SustainerView: UIView { // MARK: - Private API(s) // -private extension SustainerView { +private extension BannerView { func refreshInterface() { let style = isActiveSustainer ? Style.sustainer : Style.notSubscriber diff --git a/Simplenote/SustainerView.xib b/Simplenote/BannerView.xib similarity index 95% rename from Simplenote/SustainerView.xib rename to Simplenote/BannerView.xib index f334fcbad..73d929362 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/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 50dcc70b4..d1718d92d 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -13,7 +13,7 @@ fileprivate extension SPSettingsViewController { @objc func refreshTableHeaderView() { - guard let headerView = tableView.tableHeaderView as? SustainerView else { + guard let headerView = tableView.tableHeaderView as? BannerView else { return } diff --git a/Simplenote/TagListViewController.swift b/Simplenote/TagListViewController.swift index 0c880531b..cb3e01e01 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 } @@ -545,7 +545,7 @@ private extension TagListViewController { // 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 } @@ -554,10 +554,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) { From 49eaac6a7733bc53a403894c5a53226713b6c0a0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 25 Mar 2024 16:24:41 -0600 Subject: [PATCH 052/547] Removed a bunch of sustainer information from banner view --- Simplenote/BannerView.swift | 15 ++++++--------- Simplenote/BannerView.xib | 2 +- .../SPSettingsViewController+Extensions.swift | 1 - 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Simplenote/BannerView.swift b/Simplenote/BannerView.swift index 67d9d875a..ec98db8cc 100644 --- a/Simplenote/BannerView.swift +++ b/Simplenote/BannerView.swift @@ -29,12 +29,6 @@ class BannerView: UIView { var onPress: (() -> Void)? - var isActiveSustainer = false { - didSet { - refreshInterface() - } - } - var preferredWidth: CGFloat? { didSet { guard let preferredWidth else { @@ -55,7 +49,7 @@ class BannerView: UIView { // MARK: - Actions @IBAction - func sustainerWasPresssed() { + func bannerWasPresssed() { onPress?() } } @@ -64,8 +58,10 @@ class BannerView: UIView { // 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 @@ -86,6 +82,7 @@ private struct Style { } 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"), diff --git a/Simplenote/BannerView.xib b/Simplenote/BannerView.xib index 73d929362..dc1d2af0e 100644 --- a/Simplenote/BannerView.xib +++ b/Simplenote/BannerView.xib @@ -13,7 +13,7 @@ - + diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index d1718d92d..3563e91cb 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -17,7 +17,6 @@ fileprivate extension SPSettingsViewController { return } - headerView.isActiveSustainer = isActiveSustainer headerView.preferredWidth = tableView.frame.width headerView.adjustSizeForCompressedLayout() From 2a986f6b1c36f3cca438307c4c4eeeea5af0d68f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 13:01:33 -0600 Subject: [PATCH 053/547] put account section rows back in settings --- Simplenote/Classes/SPSettingsViewController.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 363ad47a6..694e0ebf3 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -233,7 +233,11 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger case SPOptionsViewSectionsDebug: { return SPOptionsDebugRowCount; } - + + case SPOptionsViewSectionsAccount: { + return SPOptionsAccountRowCount; + } + default: break; } From d60e4dbe9f5bcc1d636a2f4916800b30968b6c35 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 13:11:13 -0600 Subject: [PATCH 054/547] Added basic app icon for simplenote sustainer --- Simplenote.xcodeproj/project.pbxproj | 5 +++++ ...145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png | Bin 0 -> 30714 bytes .../AppIcon-Sustainer.appiconset/Contents.json | 14 ++++++++++++++ Simplenote/Icons.xcassets/Contents.json | 6 +++--- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 3de031937..55b64266a 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -4368,6 +4368,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; @@ -4424,6 +4425,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; @@ -4537,6 +4539,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; @@ -4733,6 +4736,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; @@ -4844,6 +4848,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; diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png new file mode 100644 index 0000000000000000000000000000000000000000..f1180a775793ad8af364e765a94f8cbaa741528b GIT binary patch literal 30714 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�G`PjXErKEmYGAHzZq*N@FGa@Rg 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 literal 0 HcmV?d00001 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..d5647fa4c --- /dev/null +++ b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} 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 +} From 27640dfe75fb0fd498abe17bce836042eef4dff5 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 13:12:14 -0600 Subject: [PATCH 055/547] Added switch to enable sustainer icon --- Simplenote/Classes/SPSettingsViewController.m | 67 ++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 694e0ebf3..c9dc939ad 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -9,11 +9,13 @@ 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; @@ -30,18 +32,20 @@ @implementation SPSettingsViewController { #define kTagPasscode 5 #define kTagTimeout 6 #define kTagTouchID 7 +#define kTagSustainerIcon 8 typedef NS_ENUM(NSInteger, SPOptionsViewSections) { SPOptionsViewSectionsNotes = 0, SPOptionsViewSectionsTags = 1, SPOptionsViewSectionsAppearance = 2, - SPOptionsViewSectionsSecurity = 3, - SPOptionsViewSectionsAccount = 4, - SPOptionsViewSectionsDelete = 5, - SPOptionsViewSectionsAbout = 6, - SPOptionsViewSectionsHelp = 7, - SPOptionsViewSectionsDebug = 8, - SPOptionsViewSectionsCount = 9, + SPOptionsViewSectionsSustainer = 3, + SPOptionsViewSectionsSecurity = 4, + SPOptionsViewSectionsAccount = 5, + SPOptionsViewSectionsDelete = 6, + SPOptionsViewSectionsAbout = 7, + SPOptionsViewSectionsHelp = 8, + SPOptionsViewSectionsDebug = 9, + SPOptionsViewSectionsCount = 10, }; typedef NS_ENUM(NSInteger, SPOptionsAccountRow) { @@ -67,6 +71,12 @@ typedef NS_ENUM(NSInteger, SPOptionsAppearanceRow) { SPOptionsAppearanceRowCount = 1 }; +typedef NS_ENUM(NSInteger, SPOptionsSustainerRow) { + SPOptionsAccountSustainerIcon = 0, + SPOptionsAccountSustainerCount = 1 +}; + + typedef NS_ENUM(NSInteger, SPOptionsSecurityRow) { SPOptionsSecurityRowRowPasscode = 0, SPOptionsSecurityRowRowBiometry = 1, @@ -142,6 +152,11 @@ - (void)viewDidLoad action:@selector(touchIdSwitchDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + self.sustainerIconSwitch = [UISwitch new]; + [self.sustainerIconSwitch addTarget:self + action:@selector(sustainerSwitchDidChangeValue:) + forControlEvents:UIControlEventValueChanged]; + self.pinTimeoutPickerView = [UIPickerView new]; self.pinTimeoutPickerView.delegate = self; self.pinTimeoutPickerView.dataSource = self; @@ -211,7 +226,11 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger case SPOptionsViewSectionsAppearance: { return SPOptionsAppearanceRowCount; } - + + case SPOptionsViewSectionsSustainer: { + return SPOptionsAccountSustainerCount; + } + case SPOptionsViewSectionsSecurity: { int rowsToRemove = [self isBiometryAvailable] ? 0 : 1; int disabledPinLockRows = [self isBiometryAvailable] ? 2 : 1; @@ -257,6 +276,9 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte case SPOptionsViewSectionsAppearance: return NSLocalizedString(@"Appearance", nil); + case SPOptionsViewSectionsSustainer: + return NSLocalizedString(@"Sustainer Thank You", nil); + case SPOptionsViewSectionsSecurity: return NSLocalizedString(@"Security", nil); @@ -354,7 +376,26 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; } - + + case SPOptionsViewSectionsSustainer: { + switch (indexPath.row) { + 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: { @@ -700,6 +741,14 @@ - (void)touchIdSwitchDidChangeValue:(UISwitch *)sender } } +- (void)sustainerSwitchDidChangeValue:(UISwitch *)sender +{ + BOOL isOn = [(UISwitch *)sender isOn]; + + NSString *iconName = isOn ? SPSustainerAppIconName : nil; + [UIApplication.sharedApplication setAlternateIconName:iconName completionHandler:nil]; +} + - (UIAlertController*)pinLockRequiredAlert { NSString *alertTitleTemplate = NSLocalizedString(@"To enable %1$@, you must have a passcode setup first.", From de8a8c1b4ba928537f450809becbe00e4ad2cf30 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 13:46:07 -0600 Subject: [PATCH 056/547] Hide sustainer row if the user isn't a past or current sustainer --- .../SPSettingsViewController+Extensions.swift | 11 +++++--- Simplenote/Classes/SPSettingsViewController.m | 26 +++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 3563e91cb..7d68f16e3 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -7,10 +7,6 @@ import UIKit // 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 { - private var isActiveSustainer: Bool { - SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber - } - @objc func refreshTableHeaderView() { guard let headerView = tableView.tableHeaderView as? BannerView else { @@ -42,6 +38,13 @@ fileprivate extension SPSettingsViewController { } } +extension SPSettingsViewController { + @objc + var isActiveSustainer: Bool { + SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber + } +} + // MARK: - Pin // extension SPSettingsViewController { diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index c9dc939ad..d67e82c85 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -228,7 +228,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } case SPOptionsViewSectionsSustainer: { - return SPOptionsAccountSustainerCount; + return [self isActiveSustainer] ? SPOptionsAccountSustainerCount : 0; } case SPOptionsViewSectionsSecurity: { @@ -277,7 +277,10 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte return NSLocalizedString(@"Appearance", nil); case SPOptionsViewSectionsSustainer: - return NSLocalizedString(@"Sustainer Thank You", nil); + if ([self isActiveSustainer]) { + return NSLocalizedString(@"Sustainer Thank You", nil); + } + break; case SPOptionsViewSectionsSecurity: return NSLocalizedString(@"Security", nil); @@ -303,6 +306,25 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte return nil; } +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section +{ + switch(section) { + case SPOptionsViewSectionsSustainer: + return CGFLOAT_MIN; + default: + return UITableViewAutomaticDimension; + } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + switch(section) { + case SPOptionsViewSectionsSustainer: + return CGFLOAT_MIN; + default: + return UITableViewAutomaticDimension; + } +} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { From f50dae38458ad7ab275e7a22350b164f3aec6417 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 14:07:07 -0600 Subject: [PATCH 057/547] Fix spacing issue for when sustainer is not visible --- Simplenote/Classes/SPSettingsViewController.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index d67e82c85..f0a0eb165 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -10,6 +10,7 @@ NSString *const SPAlphabeticalTagSortPref = @"SPAlphabeticalTagSortPref"; NSString *const SPThemePref = @"SPThemePref"; NSString *const SPSustainerAppIconName = @"AppIcon-Sustainer"; +CGFloat const SPSettingsTableViewSpacing = 25.0; @interface SPSettingsViewController () @property (nonatomic, strong) UISwitch *condensedNoteListSwitch; @@ -186,6 +187,9 @@ - (void)viewDidLoad object:nil]; [self refreshThemeStyles]; + + self.tableView.sectionHeaderHeight = CGFLOAT_MIN; + self.tableView.sectionFooterHeight = CGFLOAT_MIN; } - (void)viewWillAppear:(BOOL)animated @@ -310,9 +314,9 @@ - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSIntege { switch(section) { case SPOptionsViewSectionsSustainer: - return CGFLOAT_MIN; + return [self isActiveSustainer] ? SPSettingsTableViewSpacing : CGFLOAT_MIN; default: - return UITableViewAutomaticDimension; + return SPSettingsTableViewSpacing; } } @@ -320,9 +324,9 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSIntege { switch(section) { case SPOptionsViewSectionsSustainer: - return CGFLOAT_MIN; + return [self isActiveSustainer] ? SPSettingsTableViewSpacing : CGFLOAT_MIN; default: - return UITableViewAutomaticDimension; + return SPSettingsTableViewSpacing; } } From 37d4e06d8753802735f6cc655da1949248e27927 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 14:29:20 -0600 Subject: [PATCH 058/547] Actually fix the sizing for the settings vc header and footer --- Simplenote/Classes/SPSettingsViewController.m | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index f0a0eb165..b4793d4f7 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -188,8 +188,8 @@ - (void)viewDidLoad [self refreshThemeStyles]; - self.tableView.sectionHeaderHeight = CGFLOAT_MIN; - self.tableView.sectionFooterHeight = CGFLOAT_MIN; +// self.tableView.sectionHeaderHeight = CGFLOAT_MIN; +// self.tableView.sectionFooterHeight = CGFLOAT_MIN; } - (void)viewWillAppear:(BOOL)animated @@ -314,9 +314,9 @@ - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSIntege { switch(section) { case SPOptionsViewSectionsSustainer: - return [self isActiveSustainer] ? SPSettingsTableViewSpacing : CGFLOAT_MIN; + return [self isActiveSustainer] ? UITableViewAutomaticDimension : CGFLOAT_MIN; default: - return SPSettingsTableViewSpacing; + return UITableViewAutomaticDimension; } } @@ -324,12 +324,22 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSIntege { switch(section) { case SPOptionsViewSectionsSustainer: - return [self isActiveSustainer] ? SPSettingsTableViewSpacing : CGFLOAT_MIN; + return [self isActiveSustainer] ? UITableViewAutomaticDimension : CGFLOAT_MIN; default: - return SPSettingsTableViewSpacing; + return UITableViewAutomaticDimension; } } +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section +{ + return nil; +} + +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section +{ + return nil; +} + - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; From d874afadf7a0c8c66e03c017fd068c0cf24c462f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 14:36:21 -0600 Subject: [PATCH 059/547] Added all sizes for sustainer icon --- .../Contents.json | 113 +++++++++++++++++- ...61bb8a.png => SN-Sustainer @1024x1024.png} | Bin .../SN-Sustainer @114x114.png | Bin 0 -> 8425 bytes .../SN-Sustainer @120x120 1.png | Bin 0 -> 8623 bytes .../SN-Sustainer @120x120.png | Bin 0 -> 8623 bytes .../SN-Sustainer @128x128 1.png | Bin 0 -> 8988 bytes .../SN-Sustainer @128x128.png | Bin 0 -> 8988 bytes .../SN-Sustainer @136x136.png | Bin 0 -> 9295 bytes .../SN-Sustainer @152x152.png | Bin 0 -> 9132 bytes .../SN-Sustainer @167x167.png | Bin 0 -> 8924 bytes .../SN-Sustainer @180x180.png | Bin 0 -> 10788 bytes .../SN-Sustainer @192x192.png | Bin 0 -> 10118 bytes .../SN-Sustainer @40x40.png | Bin 0 -> 4470 bytes .../SN-Sustainer @58x58.png | Bin 0 -> 5458 bytes .../SN-Sustainer @60x60.png | Bin 0 -> 5429 bytes .../SN-Sustainer @76x76.png | Bin 0 -> 6310 bytes .../SN-Sustainer @80x80.png | Bin 0 -> 6435 bytes .../SN-Sustainer @87x87.png | Bin 0 -> 6670 bytes 18 files changed, 112 insertions(+), 1 deletion(-) rename Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/{316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png => SN-Sustainer @1024x1024.png} (100%) create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @114x114.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120 1.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @120x120.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128 1.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @128x128.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @136x136.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @152x152.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @167x167.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @180x180.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @192x192.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @40x40.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @58x58.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @60x60.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @76x76.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @80x80.png create mode 100644 Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @87x87.png diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json index d5647fa4c..2bd75b76d 100644 --- a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json +++ b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/Contents.json @@ -1,10 +1,121 @@ { "images" : [ { - "filename" : "316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png", + "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" : { diff --git a/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png b/Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @1024x1024.png similarity index 100% rename from Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/316077145-3c0b8d73-fc79-4898-afb9-2c188061bb8a.png rename to Simplenote/Icons.xcassets/AppIcon-Sustainer.appiconset/SN-Sustainer @1024x1024.png 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 0000000000000000000000000000000000000000..df06a3cf72d33e071002cef006167ab618a5067d GIT binary patch literal 8425 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 ^`*S{eWVC4hc$b`yZVeItRCN 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..51db22c3c06c3d8709d0e43e525e91d4cf774e02 GIT binary patch literal 8623 zcmdUVc{tQj`}YhZTlSPCyKKdbG00f5FJX!G>c@A_Tu``0_J?|jeP=l*=|``pVp=MRy3IvO-oY*YXMfJRGG)qwc+ z-g_uWh~HI*vcrilAkIKT8Bp4Ne3tm*9mZ744uJrO5XTe%av&psbWer&2LQ4G$o9to zfGv>y*VqXt^h1Zp2^0tHF*6b$4E!#<;j5 z@mLRT2{9Qlad7|?0)@dL5^$ ^`*S{eWVC4hc$b`yZVeItRCN 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..40ff25e9028690e7ec7aa14147569c79e55aafef GIT binary patch literal 8988 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..40ff25e9028690e7ec7aa14147569c79e55aafef GIT binary patch literal 8988 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e1c62d36064bdce5c37257782cf219ef51617cda GIT binary patch literal 9295 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b604e577c48448167b4f12acfc149518ed08bf0f GIT binary patch literal 8924 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..364479440e54c3210e719c72f0b36aec810fba18 GIT binary patch literal 10788 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 P&x~qHT8hQ;mO=juxaS?g literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c291d51095ac160badd3b987d98198962c138a96 GIT binary patch literal 10118 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 NO*K8$5@qX<{{_bllaT-b literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4bce01ecaa1f82f1971d1a792087727f453025a5 GIT binary patch literal 4470 zcmdT|c~}$I7N4*vi--nMM6GE+jMgNRg(Yc-0>LIikOHzupq*qU5P>8lfq=W9sKpJ@ zq7{`Y7AvAck-F9`inUclk&0GSRIpec1ubfe@@5hOBSOD^@4pwm$=sZKe!p|>S?;|b zYePdmaj0|3B5Bou_9ZwmI)|ZB51=t@dplFoA?|0qX~%?$;Dz|Iy$xi zh7*W@C8mP@0R&gT+BgOPDPcn2cmaXZtAlb97y!mhL>~=-X&jG1$17JW0IOc?HuQ~s zgy@5^n%<#`3?w-P(y25g7L7w=FaRdVWOG3lm+3`f7zb=N09b9Z>f^jtMCkXe$fA#* zQe@db#tflMEG`4&vKSDkJwaeiPqza)Wvdp(XaT_933~{@SA{MBU{|7uh}K1m zgSj$Q3Jro)QiPU~qQ;m2UIrH(rXV_ql#!CG)N(U?$wmz>I>y9wGRdf-OY$X0i$h5P zDh)#NqJcD!%(o+vNIVTJ=Y|Pp_rlR%zT`xmPR*s$^?E%`&!VX`33LXB!=ZyrI+IC7 zHK^K5r4GuVDz$DV5;Kng(aJOmwN9Z@k}zIKs!G%OlF68%zK_XISk`Apou)}PT7hMB zBpFFTlsYY)L1WMd!=bC~11hz>(Lm!u&w$h@2pkloQ^@<5^$rS~84`wMD29MxGeg2q z7scX#b}}VpP(QW0z;rZTz43p?QST1 zwECt^t1y;=DMLJ1#+9oyXzBA6DNq7JS1S{E^nPJ~E-_7Qnsfmo)8X(ai?OD{kyJWP zY@~Q!n}-IA8=yh3MvG9XlKJoT9gjXx1?K@aB}S_phIEhs(jk1TNvR-<%49~MeZ^#O z+1^wT?J>|q^N#KS9-|kCnT17VfK(PE!rY*E7M$h)4zUVW$TQ6x1GkG_ANqB0Xsi&q zSXz_Qu=bg!ROt9j9(^$JK-64~0<8iFgYg-?5WH-leoVg^zPH&1UN{gCph{6|5Umz7 zfCkd&6J9n@KYB0&cp(mebrRQ@^caBxZGEE=Hfm+iiGjcXRkBJGu7+d?wsIo`CmN`2 zG!s3P8QcU8Xl!P#P@zQ&G!s|PxH5lNYMw||7`H$*q|qW+&-#+Fs9;Uow`ZCf&h*VQ z_C8mulI!%42JuTkQ_Ag~m0dctx~ zhz+vMzT)6R+kj-oYL;Oe0|J|&z0!d&9vwStk`@0yd5|SS$^-=F)6FY03kEI?D<)i0 z{$Y2+X$?(+i@iB@5XfRc9JUt~^k&G>)XCUXDd@?hve;6&Opbs)EC?}siGvSqqc=%f zDE)s-oGG2?@qx>yapx3^2kArX06{0v{VqYFgpvg+SsHeM=q*n8Ul-?)H2o{>H6?n# z{{LMNO(t;g_o#8+n=Wy%N`|hW@37`V-Ym8^2l1hLLS8VH#biTNDa?{lH*Da3A`!uR}z3{fXZ`CdSuIE25?hc9zxNq(F z>{6rP0t%I%g#@5mx*7Qf`18%C;yEJx|{--A7`<+CqUGDpx8^P$7CA{iI)3Rt+ z5AvXe)S#qUCpVJ>shK|B(Solct21t%4Gj7G;=v>K+dWdlLZ%U_?!E1}^-)a^Pv@)< zWqodNW9jd#j*V{2WtZ%Xyt~Kwf>#|`okgZRhucfq+8yo=&udzEH9imR$4n zu+MCg=T=Es@w{_CS)7Whot}G3a{smM7^@o&zyzXxV;cL#GL^pJ6favxtP^FPun-=h z&$wM{@$0Cq&3B_ty!|0ZXStkRo}aq>s|&1|$3d@06(3Hr-nV$qgYlHYDTh~&i_~tk z8nt}e$!!ANjlOf24!#`t|R z$wo?8DJpCo-!`$bYp*kd)Y{r=s9A2k`?^$-_#Hc|nb`7hiA2FTq03iD}~0w1_H0l=T5%f4vXJ-=#c(VTU`E~4-a z%idhHgyS3kzIv6;zOw(q@oKnO(%s~m>E4YbJsKx_ zPVBfw<>KFVmQL;2KFNP90XDz|2>;son5>(B`2@_0=-x~;_~cAzve!?O8a&BW_AZ^n zt`0kN_C=!CXK#!CT;K8fQ+La;nx=K^koGXacw*OCw>$o`q_$lZO(QP;=<#Jj_J+q# zS}lK3%xUD;b;i|jI&wV(9)2Zlr`rK5$Go*G*o?=&6}?rjl(o!Av4^DCyme5Jg#;OJiJ%-fq>s`B^Vh^H*M zw=LRWE7=q6ueono<~C}b%gW$ypR`ZrL~YkvUHic0_{17NZXOdj z*OJ|^>x0t648C<5taSKC{h9c zAW~`&8Sxkb@sm=Y9`BUWo74e0r4SGi^9u0cNg>5!C2$-S&45TxVo!l@(T4;+AnU~2 znGpgoSrh?>r-d@c86yx7Bpivx!%=u7Rtq5>V6YH~be!}wXYDzu8Q;z=nC8cyTR1Zo z83LJ5cmy1eLTDkdcsLG^fkS?pUT*=Xe&Le5_z;M^qUe!=B%M)#K;$wRZk_^9@+Lf$ z!!oAOIDUX}2#YIXf|!Tk!66F}P_#l=Og0}MVxcY8z=LCv7^bZyRuSy7(Do!dYY{m- zKnrUOH->8yM+Izyz+maj6(?>0N5J6+ zaOR2D^z_6k#HBEiA#)lPPv`K!(kC!j6n_B5W&4}MW`r}j6lwAU{dNLWfdn385!aL} zup1^J7E}CN+Z+rQp2!14jpoMTFbV(aJ9F4<6$uYSQ)IBpX%qp)h9Uq6q9%pHQBWk( z4eTo<0*^L3t)-`1x3K2D1_TogG#bUXwKpwb7%~D=oH87?PAi0 z8663g72na}X*j(b-sLebESOpXjjDVPgNXlmGd(D_3m`vG_6wU?^ zIV>&@;PXWWz(9(9l9bKX_ngasq)-AN>Lk24>0UMru=T}8FsA0PIVWZVi5w<}=gOr} z0ny6E5DC$2ZLt~8`OHX6NC3snj3+VpV1b58)U!mH|5G|O!DNWHKrV&H2Shz(4tiF}H8PBM9pJ`^1w-~_ne`7_uQrVWQ0 zD7rvQ7ANh$F3x#rW>z|B%HiMj|KA01!h{6;U(^!bPh8??94fei>BAaNF+rhCaDXWk zL&4IZC?uK!^`oJvP&x{SG{K;0el#Swp-&i+Ad@gO!zJD*!2=5*pv+-&i6>DL`H;BU ziSsfc1uu>SIndzLC6plNQ*|a1yvBMs*jTv*$b8tgmFfLVwR`)`S4z>VvM9o*c{jUA zYj`)ktJrqQtNJ0^{&Lu&!rBk9P1X+h-tjrZN>d&!D<_@jiq3ky2y?DEpw~3M(=&Ue zmk=;hOT<9E5=g>yQ{$?#!nO<}JD;t88Cgo#OG%ymlsdk*$8!88cC9e2qo(NrY^mEX z@9?jxMtAfzZa>N1xS+2(ZpA(7+xlI#C?)w0Ezm{=$@YBiLOpJHR=)`q!0D=$ZLN<(1BWv#LYHHcUSzQa)FCaZ7DmNKg0Y z;SG1|40Je5PP{qW^%n8tVYnj~Q}-P$U50s7i{9P!Mt?IlA4mcIK~ z0p0xL+Kh;`^-6N-I_)sMkR7Cta$AVIR(tfYt15^ERfR3v&@o>q%daZD@*WA>QTnyC z|5MIJA-Us`LS)aVO5Jkl;(M)QW!=ihnhC6iRXyt;qe>T5$1Us7TGUw_ZL9DWhz{cPbG*aMmLTra2V43&?v zMwf-_{60huqz;6&RE0Jr4lR|7s5!E?j@EOFJ@#1n6YWTxu7l~HWATN`d5yb72)G*F9=0=oJ%aC!Fg|E^PJ5tMp>_Ajox#N2aV2qmSnnFx zt-;=r;8Y_-VwGY8$vnn+v55;QFT;UO=o)1XCYhjQ#4a^&`oWgXLaX<3*0Qr^|*3KMY7xV-*=9+HLMzuiAf2y$hpN6-_ z>#kmnEr4;$yUfx#WKAo6_9l#F+eo!z^ggP|R$wvdun+b}+zI1^V;Nr7kN#NqRq9~+ zkL3elg+KT0D*5iv6Q9{zzQy~ctV1UfSxc&Vd(32SlV(bIPM^Z&fF<3fA^PuRR8~b_ zY(ubg3lEYm(gV4c51*eh?9o>G^`7;m3;bZa?nSUKw+dgkUN7C_OMKxGUEoxuvg~($ z2lkTC>HXJ+1fH=DK^wgseZLeUn{&km`I=J_6pE zn)HRdL-prv54RVzwqFKX+=`nhS?*1^ok(3fdy_xPMp`lHUpC4dh`U?AG3SQO!%Ks! zqe5{-26pa1&|CLK;m;lQJucmNLv~MB+qtHbdgsc1t7k0e_x|odmT$}0p6I7c z?^gjV)2Cm@9=~fXTDH*V#LK8o?puR5)~eW3CW&vEDeW5h3tld^Z&%PgmJ@{+KF}z! zvs4|4-9Y~I`Os=r)8?3q#c5Bn4a<9yZ+9m2vDA&69#;I#DSjLfzh;%Id)K9F*xQP< zXXc}!ezv^zvK+OH+I)C$clg!uf<6V`5JH$^R)xnFH>j*jZI@B28$;05Jlt%DE7C&l za!kqzN-pq7xSC~gAh_Ma`9wu}?MPWu(07e;EbaXhgW)AZcGkL&w$;@3dYni{@mMlN zjbZNnuYFpjk18F|SmGHw(xG;9@ndaEre;aIoRfdOAp^*2aN4398`qj5WH22-HJbg{3+@t2La5y7u*Ak*4(j!0G@Q!;!l1EjA zN0roGC#3_AN|4FaFTcXyDF2P~)s6n9hQ4@bxbFQG!k?S)VVwIhXLKCnpC&TD1r5nx zIiQ-LXA~cwlzxe3_7H!%E;}tgj%o1PV{7AE__ON%@I23DK3zUVKf1mk5)$J*Wq;mW z_LPB6lO5-{sWz=Y5vj(9rX}4Zl0IznQeEXRocpO5+GbJm&I@xPvGK3y(Zbq$%~ffJ zwLxXh$M^2X%j^!#bS`=xDLa~@b2a7F2=8dYk|*m3@wAM@8XRocR)%No#__!zexuT} z3|(4%YpIm-Z%TdDv!u`n;?hqkO0>w&6=@{=%)Sy4~M`tQc( zKN^d3#!A(Sj@XYDk4ilwCsdcYZm0H_w&o=}M#U>sK414JZ2?0q`{R#VB1xrJSf~`R zX>i2T!snx{+RB)3nG0QY`uA_CgQr2N>3gjrQZadpl8faST~7zQS8VqR!mnRy*w(94 zK}OiBM#^E2AG+J2QHg2KR$V!$e%U1&hStAsKai{N%D!rgR&?~xuQJF#_R{*enyJiZ o{MP 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 Jmer0!{|3Gu_sswR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9c8e76db866fc7a8d8af6141eacf5626181cdeb9 GIT binary patch literal 6310 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 J7iif8{s&khq^tk{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dbb03876644ab9ea4efbd784ab56c7ebf6bccc0c GIT binary patch literal 6435 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 Rhx4Bf0Bd1qUS{fj=s)8KAzJ_d literal 0 HcmV?d00001 From f857d6d151c7c9b317e898a1c94d3ce602d31edb Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 26 Mar 2024 15:01:24 -0600 Subject: [PATCH 060/547] Persist sustainer app icon setting --- .../Classes/SPSettingsViewController+Extensions.swift | 9 +++++++++ Simplenote/Classes/SPSettingsViewController.h | 1 + Simplenote/Classes/SPSettingsViewController.m | 11 ++--------- Simplenote/Classes/UserDefaults+Simplenote.swift | 1 + 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 7d68f16e3..1b9fb5eb3 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -43,6 +43,15 @@ extension SPSettingsViewController { var isActiveSustainer: Bool { SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber } + + @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 diff --git a/Simplenote/Classes/SPSettingsViewController.h b/Simplenote/Classes/SPSettingsViewController.h index f923770be..fcad4f121 100644 --- a/Simplenote/Classes/SPSettingsViewController.h +++ b/Simplenote/Classes/SPSettingsViewController.h @@ -11,3 +11,4 @@ @end extern NSString *const SPAlphabeticalTagSortPref; +extern NSString *const SPSustainerAppIconName; diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index b4793d4f7..92c52b0c7 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -155,8 +155,9 @@ - (void)viewDidLoad self.sustainerIconSwitch = [UISwitch new]; [self.sustainerIconSwitch addTarget:self - action:@selector(sustainerSwitchDidChangeValue:) + action:@selector(sustainerSwitchDidChangeValueWithSender:) forControlEvents:UIControlEventValueChanged]; + [self.sustainerIconSwitch setOn: [NSUserDefaults.standardUserDefaults boolForKey:@"useSustainerIcon"]]; self.pinTimeoutPickerView = [UIPickerView new]; self.pinTimeoutPickerView.delegate = self; @@ -777,14 +778,6 @@ - (void)touchIdSwitchDidChangeValue:(UISwitch *)sender } } -- (void)sustainerSwitchDidChangeValue:(UISwitch *)sender -{ - BOOL isOn = [(UISwitch *)sender isOn]; - - NSString *iconName = isOn ? SPSustainerAppIconName : nil; - [UIApplication.sharedApplication setAlternateIconName:iconName completionHandler:nil]; -} - - (UIAlertController*)pinLockRequiredAlert { NSString *alertTitleTemplate = NSLocalizedString(@"To enable %1$@, you must have a passcode setup first.", diff --git a/Simplenote/Classes/UserDefaults+Simplenote.swift b/Simplenote/Classes/UserDefaults+Simplenote.swift index 8867d4ad8..8590292f2 100644 --- a/Simplenote/Classes/UserDefaults+Simplenote.swift +++ b/Simplenote/Classes/UserDefaults+Simplenote.swift @@ -15,6 +15,7 @@ extension UserDefaults { case wordPressSessionKey = "SPAuthSessionKey" case useBiometryInsteadOfPin = "SimplenoteUseTouchID" case accountIsLoggedIn + case useSustainerIcon } } From 90009d833ce01300d8f8ae6d32d9b5283d3c6989 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 27 Mar 2024 10:51:22 -0600 Subject: [PATCH 061/547] Moved sustainer app icon row into the Appearance settings section --- Simplenote/Classes/SPSettingsViewController.m | 78 +++---------------- 1 file changed, 11 insertions(+), 67 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 92c52b0c7..e3c71fdfd 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -39,14 +39,13 @@ typedef NS_ENUM(NSInteger, SPOptionsViewSections) { SPOptionsViewSectionsNotes = 0, SPOptionsViewSectionsTags = 1, SPOptionsViewSectionsAppearance = 2, - SPOptionsViewSectionsSustainer = 3, - SPOptionsViewSectionsSecurity = 4, - SPOptionsViewSectionsAccount = 5, - SPOptionsViewSectionsDelete = 6, - SPOptionsViewSectionsAbout = 7, - SPOptionsViewSectionsHelp = 8, - SPOptionsViewSectionsDebug = 9, - SPOptionsViewSectionsCount = 10, + SPOptionsViewSectionsSecurity = 3, + SPOptionsViewSectionsAccount = 4, + SPOptionsViewSectionsDelete = 5, + SPOptionsViewSectionsAbout = 6, + SPOptionsViewSectionsHelp = 7, + SPOptionsViewSectionsDebug = 8, + SPOptionsViewSectionsCount = 9, }; typedef NS_ENUM(NSInteger, SPOptionsAccountRow) { @@ -69,15 +68,10 @@ typedef NS_ENUM(NSInteger, SPOptionsTagsRow) { typedef NS_ENUM(NSInteger, SPOptionsAppearanceRow) { SPOptionsPreferencesRowTheme = 0, - SPOptionsAppearanceRowCount = 1 + SPOptionsAccountSustainerIcon = 1, + SPOptionsAppearanceRowCount = 2 }; -typedef NS_ENUM(NSInteger, SPOptionsSustainerRow) { - SPOptionsAccountSustainerIcon = 0, - SPOptionsAccountSustainerCount = 1 -}; - - typedef NS_ENUM(NSInteger, SPOptionsSecurityRow) { SPOptionsSecurityRowRowPasscode = 0, SPOptionsSecurityRowRowBiometry = 1, @@ -229,11 +223,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } case SPOptionsViewSectionsAppearance: { - return SPOptionsAppearanceRowCount; - } - - case SPOptionsViewSectionsSustainer: { - return [self isActiveSustainer] ? SPOptionsAccountSustainerCount : 0; + return [self isActiveSustainer] ? SPOptionsAppearanceRowCount : SPOptionsAppearanceRowCount - 1; } case SPOptionsViewSectionsSecurity: { @@ -281,12 +271,6 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte case SPOptionsViewSectionsAppearance: return NSLocalizedString(@"Appearance", nil); - case SPOptionsViewSectionsSustainer: - if ([self isActiveSustainer]) { - return NSLocalizedString(@"Sustainer Thank You", nil); - } - break; - case SPOptionsViewSectionsSecurity: return NSLocalizedString(@"Security", nil); @@ -311,36 +295,6 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte return nil; } -- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section -{ - switch(section) { - case SPOptionsViewSectionsSustainer: - return [self isActiveSustainer] ? UITableViewAutomaticDimension : CGFLOAT_MIN; - default: - return UITableViewAutomaticDimension; - } -} - -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section -{ - switch(section) { - case SPOptionsViewSectionsSustainer: - return [self isActiveSustainer] ? UITableViewAutomaticDimension : CGFLOAT_MIN; - default: - return UITableViewAutomaticDimension; - } -} - -- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - return nil; -} - -- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section -{ - return nil; -} - - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; @@ -406,16 +360,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.tag = kTagTheme; break; } - - default: - break; - } - - break; - } - case SPOptionsViewSectionsSustainer: { - switch (indexPath.row) { case SPOptionsAccountSustainerIcon: { cell.textLabel.text = NSLocalizedString(@"Sustainer App Icon", @"Switch app icon"); cell.selectionStyle = UITableViewCellSelectionStyleNone; @@ -423,13 +368,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.accessoryView = self.sustainerIconSwitch; cell.tag = kTagSustainerIcon; break; - } default: break; } - + break; } From e4cb5b19533adab8582dd5896800832d13996ed0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 27 Mar 2024 10:53:00 -0600 Subject: [PATCH 062/547] Removed some unneeded commented out code --- Simplenote/Classes/SPSettingsViewController.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index e3c71fdfd..170227b68 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -182,9 +182,6 @@ - (void)viewDidLoad object:nil]; [self refreshThemeStyles]; - -// self.tableView.sectionHeaderHeight = CGFLOAT_MIN; -// self.tableView.sectionFooterHeight = CGFLOAT_MIN; } - (void)viewWillAppear:(BOOL)animated From b067188efd42b12c9fb24f413fbb50ad7c61b304 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 27 Mar 2024 10:53:55 -0600 Subject: [PATCH 063/547] Dropped unused row size constant --- Simplenote/Classes/SPSettingsViewController.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 170227b68..45c354625 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -10,7 +10,6 @@ NSString *const SPAlphabeticalTagSortPref = @"SPAlphabeticalTagSortPref"; NSString *const SPThemePref = @"SPThemePref"; NSString *const SPSustainerAppIconName = @"AppIcon-Sustainer"; -CGFloat const SPSettingsTableViewSpacing = 25.0; @interface SPSettingsViewController () @property (nonatomic, strong) UISwitch *condensedNoteListSwitch; From 13c911f3f562f9856566bee20a6328d69b0fbc66 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 27 Mar 2024 13:20:33 -0600 Subject: [PATCH 064/547] Make sure that updating the subscriptions doesnt remove sustainer status --- Simplenote/StoreManager.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Simplenote/StoreManager.swift b/Simplenote/StoreManager.swift index 3ba92acc3..e62c3d935 100644 --- a/Simplenote/StoreManager.swift +++ b/Simplenote/StoreManager.swift @@ -17,7 +17,6 @@ enum StoreError: Error { // MARK: - StoreManager // -@available(iOS 15, *) class StoreManager { // MARK: - Static @@ -122,7 +121,6 @@ class StoreManager { // MARK: - Private API(s) // -@available(iOS 15, *) private extension StoreManager { func listenForTransactions() -> Task { @@ -211,7 +209,6 @@ private extension StoreManager { // MARK: - Private Helpers // -@available(iOS 15, *) private extension StoreManager { func buildStoreProductMap(products: [Product]) -> [StoreProduct: Product] { @@ -242,7 +239,6 @@ private extension StoreManager { // MARK: - Simperium Kung Fu // -@available(iOS 15, *) private extension StoreManager { func refreshSimperiumPreferences(status: SubscriptionStatus?) { @@ -263,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() @@ -300,7 +292,6 @@ private extension StoreManager { // MARK: - SubscriptionStatus Helpers // -@available(iOS 15, *) private extension Product.SubscriptionInfo.Status { var isActive: Bool { From 9b417c9c783308799b16a22758785301fd4efa65 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 8 Apr 2024 15:19:40 -0600 Subject: [PATCH 065/547] Added was_sustainer value to preferences CD model --- Simplenote.xcodeproj/project.pbxproj | 2 + Simplenote/Preferences.h | 2 + Simplenote/Preferences.m | 2 + .../Simplenote 7.xcdatamodel/contents | 75 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 55b64266a..baa8eacea 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -1121,6 +1121,7 @@ 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 = ""; }; + BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Simplenote 7.xcdatamodel"; sourceTree = ""; }; BA18532726488DBC00D9A347 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.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 = ""; }; @@ -6183,6 +6184,7 @@ 467D9C5C1788A4FB00785EF3 /* Simplenote.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */, B54D9C542909B21700D0E0EC /* Simplenote 6.xcdatamodel */, B5526335238881EE009AB3B2 /* Simplenote 5.xcdatamodel */, B594E60C21508170001577EE /* Simplenote 4.xcdatamodel */, 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/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents b/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents new file mode 100644 index 000000000..bbe96025d --- /dev/null +++ b/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 65041400f99f6b8fc98026a00691a7b1a83a2de7 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 8 Apr 2024 15:22:54 -0600 Subject: [PATCH 066/547] Added migration to force update simperium preferences --- Simplenote/Classes/Simperium+Simplenote.swift | 1 + .../Classes/UserDefaults+Simplenote.swift | 1 + Simplenote/SPAppDelegate+Extensions.swift | 18 ++++++++++++++++++ Simplenote/SPAppDelegate.m | 2 ++ 4 files changed, 22 insertions(+) diff --git a/Simplenote/Classes/Simperium+Simplenote.swift b/Simplenote/Classes/Simperium+Simplenote.swift index 6e15cbcfd..8c1c9dc0e 100644 --- a/Simplenote/Classes/Simperium+Simplenote.swift +++ b/Simplenote/Classes/Simperium+Simplenote.swift @@ -61,4 +61,5 @@ extension Simperium { // extension Simperium { static let accountBucketName = "Account" + static let preferencesLastChangedSignatureKey = "lastChangeSignature-Preferences" } diff --git a/Simplenote/Classes/UserDefaults+Simplenote.swift b/Simplenote/Classes/UserDefaults+Simplenote.swift index 8590292f2..9bd5bfc25 100644 --- a/Simplenote/Classes/UserDefaults+Simplenote.swift +++ b/Simplenote/Classes/UserDefaults+Simplenote.swift @@ -16,6 +16,7 @@ extension UserDefaults { case useBiometryInsteadOfPin = "SimplenoteUseTouchID" case accountIsLoggedIn case useSustainerIcon + case hasMigratedSustainerPreferences } } diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 87b556e46..7918570c1 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -504,3 +504,21 @@ extension SPAppDelegate { WidgetController.syncWidgetDefaults(authenticated: authenticated, sortMode: sortMode) } } + +// MARK: - Sustainer migration +extension SPAppDelegate { + @objc + func migrateSimperiumPreferencesIfNeeded() { + guard UserDefaults.standard.bool(forKey: .firstLaunch) == false, + UserDefaults.standard.bool(forKey: .hasMigratedSustainerPreferences) == false else { + return + } + + UserDefaults.standard.removeObject(forKey: Simperium.preferencesLastChangedSignatureKey) + let prefs = simperium.preferencesObject() + prefs.ghostData = "" + simperium.saveWithoutSyncing() + + UserDefaults.standard.set(true, forKey: .hasMigratedSustainerPreferences) + } +} diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index 76c1e5afe..f19f8a2a7 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; } From e80a3092df9efef7a7731f6aa414acc81e7b4c6c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 8 Apr 2024 15:46:12 -0600 Subject: [PATCH 067/547] Updated data model version --- Simplenote.xcodeproj/project.pbxproj | 2 +- Simplenote/Simplenote.xcdatamodeld/.xccurrentversion | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index baa8eacea..0df97ec86 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -6192,7 +6192,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/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 From 396f117a7c4dccc2131c6cdff9ed057b4e289079 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 8 Apr 2024 15:48:19 -0600 Subject: [PATCH 068/547] Set hasMigratedSustainerPreferences to true on first launch --- Simplenote/SPAppDelegate+Extensions.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 7918570c1..5c98ff04b 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -509,8 +509,12 @@ extension SPAppDelegate { extension SPAppDelegate { @objc func migrateSimperiumPreferencesIfNeeded() { - guard UserDefaults.standard.bool(forKey: .firstLaunch) == false, - UserDefaults.standard.bool(forKey: .hasMigratedSustainerPreferences) == false else { + guard UserDefaults.standard.bool(forKey: .hasMigratedSustainerPreferences) == false else { + return + } + + guard UserDefaults.standard.bool(forKey: .firstLaunch) == false else { + UserDefaults.standard.set(true, forKey: .hasMigratedSustainerPreferences) return } From 43bac649e5b8a6ced461c587796e9814f1eb237c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 8 Apr 2024 15:50:00 -0600 Subject: [PATCH 069/547] Changed migration to use the options singleton checking first launch --- Simplenote/SPAppDelegate+Extensions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 5c98ff04b..83f216eef 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -513,7 +513,7 @@ extension SPAppDelegate { return } - guard UserDefaults.standard.bool(forKey: .firstLaunch) == false else { + guard Options.shared.firstLaunch == false else { UserDefaults.standard.set(true, forKey: .hasMigratedSustainerPreferences) return } From ca22321859b1036666c1979a230c7f6d8636f749 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 9 Apr 2024 14:27:26 -0600 Subject: [PATCH 070/547] Added logging to preferences migration --- Simplenote/SPAppDelegate+Extensions.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 83f216eef..4d3b48258 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -518,6 +518,7 @@ extension SPAppDelegate { return } + NSLog("Migrating Simperium Preferences object to include was_sustainer value") UserDefaults.standard.removeObject(forKey: Simperium.preferencesLastChangedSignatureKey) let prefs = simperium.preferencesObject() prefs.ghostData = "" From 8f44cf5a37a728670d8da737524d8d64a5b60496 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 9 Apr 2024 14:39:03 -0600 Subject: [PATCH 071/547] Fixed issue where the migration wouldn't run if it wasn't first launch --- Simplenote/SPAppDelegate+Extensions.swift | 4 ++-- Simplenote/SPAppDelegate.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 4d3b48258..942f6ac98 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -507,13 +507,13 @@ extension SPAppDelegate { // MARK: - Sustainer migration extension SPAppDelegate { - @objc + @objc func migrateSimperiumPreferencesIfNeeded() { guard UserDefaults.standard.bool(forKey: .hasMigratedSustainerPreferences) == false else { return } - guard Options.shared.firstLaunch == false else { + guard isFirstLaunch() == false else { UserDefaults.standard.set(true, forKey: .hasMigratedSustainerPreferences) return } diff --git a/Simplenote/SPAppDelegate.h b/Simplenote/SPAppDelegate.h index 41899d857..42aa15ef6 100644 --- a/Simplenote/SPAppDelegate.h +++ b/Simplenote/SPAppDelegate.h @@ -45,6 +45,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)save; - (void)logoutAndReset:(id)sender; +- (BOOL)isFirstLaunch; + (SPAppDelegate *)sharedDelegate; From 67dd0fa6af75466e806bcc1096e20aad7bbba0ee Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 9 Apr 2024 15:12:17 -0600 Subject: [PATCH 072/547] Removed default values from analytics enabled and was sustainer avoids over writing existing values when doing a fresh install --- .../Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents b/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents index bbe96025d..e915eb1f9 100644 --- a/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents +++ b/Simplenote/Simplenote.xcdatamodeld/Simplenote 7.xcdatamodel/contents @@ -40,14 +40,14 @@ - + - + From bbd457a800e767cc507067f50f312256ec84b04f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 9 Apr 2024 15:19:24 -0600 Subject: [PATCH 073/547] Updated settings VC to show sustainer app icon if user is/was sustainer --- Simplenote/Classes/SPSettingsViewController+Extensions.swift | 5 +++++ Simplenote/Classes/SPSettingsViewController.m | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 1b9fb5eb3..df9c32973 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -44,6 +44,11 @@ extension SPSettingsViewController { SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber } + @objc + var wasSustainer: Bool { + SPAppDelegate.shared().simperium.preferencesObject().was_sustainer == true + } + @objc func sustainerSwitchDidChangeValue(sender: UISwitch) { let isOn = sender.isOn diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 45c354625..cfd80cb91 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -219,7 +219,8 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } case SPOptionsViewSectionsAppearance: { - return [self isActiveSustainer] ? SPOptionsAppearanceRowCount : SPOptionsAppearanceRowCount - 1; + BOOL showSustainerSwitch = self.isActiveSustainer || self.wasSustainer; + return showSustainerSwitch ? SPOptionsAppearanceRowCount : SPOptionsAppearanceRowCount - 1; } case SPOptionsViewSectionsSecurity: { From 1d03d83718019a7303f02190be5aaa5454b04489 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 10 Apr 2024 13:51:55 -0600 Subject: [PATCH 074/547] Refactored checking if should show sustainer icon to single variable --- .../Classes/SPSettingsViewController+Extensions.swift | 10 +++------- Simplenote/Classes/SPSettingsViewController.m | 3 +-- Simplenote/Preferences+IAP.swift | 5 +++++ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index df9c32973..f5ea91e7c 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -40,13 +40,9 @@ fileprivate extension SPSettingsViewController { extension SPSettingsViewController { @objc - var isActiveSustainer: Bool { - SPAppDelegate.shared().simperium.preferencesObject().isActiveSubscriber - } - - @objc - var wasSustainer: Bool { - SPAppDelegate.shared().simperium.preferencesObject().was_sustainer == true + var showSustainerSwitch: Bool { + let preferences = SPAppDelegate.shared().simperium.preferencesObject() + return preferences.isActiveSubscriber || preferences.wasSustainer } @objc diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index cfd80cb91..61e6fab34 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -219,8 +219,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } case SPOptionsViewSectionsAppearance: { - BOOL showSustainerSwitch = self.isActiveSustainer || self.wasSustainer; - return showSustainerSwitch ? SPOptionsAppearanceRowCount : SPOptionsAppearanceRowCount - 1; + return self.showSustainerSwitch ? SPOptionsAppearanceRowCount : SPOptionsAppearanceRowCount - 1; } case SPOptionsViewSectionsSecurity: { diff --git a/Simplenote/Preferences+IAP.swift b/Simplenote/Preferences+IAP.swift index c670c5952..c3d869877 100644 --- a/Simplenote/Preferences+IAP.swift +++ b/Simplenote/Preferences+IAP.swift @@ -16,4 +16,9 @@ extension Preferences { var isActiveSubscriber: Bool { subscription_level == StoreConstants.activeSubscriptionLevel } + + @objc + var wasSustainer: Bool { + was_sustainer == true + } } From 97ee2b721bba1d6abdd082b67db8ccc7e826004d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 10 Apr 2024 16:01:32 -0600 Subject: [PATCH 075/547] Updated rake file to fix bundle install --path depreciation --- Rakefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 090686846..e58354a3d 100644 --- a/Rakefile +++ b/Rakefile @@ -4,6 +4,7 @@ require 'English' XCODE_WORKSPACE = 'Simplenote.xcworkspace' XCODE_SCHEME = 'Simplenote' XCODE_CONFIGURATION = 'Debug' +LOCAL_PATH = "vendor/bundle" require 'fileutils' require 'tmpdir' @@ -36,7 +37,8 @@ namespace :dependencies do 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 @@ -47,7 +49,7 @@ namespace :dependencies do task :install do fold('install.bundler') do - sh 'bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}' + sh 'bundle install --jobs=3 --retry=3' end end CLOBBER << 'vendor/bundle' From 65cf8208f3de118e788b27be94a7f9879bea95f3 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 10 Apr 2024 16:09:52 -0600 Subject: [PATCH 076/547] Fixed double quote formatting in rake file where not needed --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index e58354a3d..bd037a7b9 100644 --- a/Rakefile +++ b/Rakefile @@ -4,7 +4,7 @@ require 'English' XCODE_WORKSPACE = 'Simplenote.xcworkspace' XCODE_SCHEME = 'Simplenote' XCODE_CONFIGURATION = 'Debug' -LOCAL_PATH = "vendor/bundle" +LOCAL_PATH = 'vendor/bundle' require 'fileutils' require 'tmpdir' From b1fc06ac9fc9f5b01af8fe01fb7c70c1e7ec878d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 12 Apr 2024 10:20:36 -0600 Subject: [PATCH 077/547] Fixed an issue where note tableview cells highlighted before selection --- Simplenote/Classes/SPNoteTableViewCell.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Simplenote/Classes/SPNoteTableViewCell.swift b/Simplenote/Classes/SPNoteTableViewCell.swift index 1f9fde988..c12ab9d1e 100644 --- a/Simplenote/Classes/SPNoteTableViewCell.swift +++ b/Simplenote/Classes/SPNoteTableViewCell.swift @@ -345,6 +345,15 @@ 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 From ef50a0792bc762954959b053231c9fd9abc46161 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 12 Apr 2024 10:27:16 -0600 Subject: [PATCH 078/547] Updated release notes for PR1560 fixed note selection/highlighting issue --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index c705c09d6..188c15db1 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,6 @@ 4.51 ----- - +- Fixed issue where note cell appearance changes on press down instead of when the cell is selected 4.50 ----- From 37a087b76fe3f752cfa60c56c21e8d74f376b404 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 12 Apr 2024 17:32:40 -0700 Subject: [PATCH 079/547] Update Fastlane from `2.216.0` to `2.220.0` --- Gemfile.lock | 94 +++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5999fee29..2deddeaf8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,9 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml activesupport (7.1.1) base64 @@ -13,29 +15,29 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.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.911.0) + aws-sdk-core (3.191.6) + 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-sdk-kms (1.78.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.136.0) - aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-s3 (1.146.1) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.1) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.1.1) @@ -133,8 +135,7 @@ GEM 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 @@ -142,7 +143,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.104.0) + excon (0.110.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -173,15 +174,15 @@ GEM 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.220.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) @@ -193,6 +194,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) @@ -201,10 +203,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) @@ -213,7 +215,7 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-appcenter (1.11.1) fastlane-plugin-sentry (1.14.0) os (~> 1.1, >= 1.1.4) @@ -241,9 +243,9 @@ GEM git (1.18.0) 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) @@ -251,24 +253,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) @@ -286,8 +287,9 @@ GEM concurrent-ruby (~> 1.0) java-properties (0.3.0) jmespath (1.6.2) - json (2.6.3) - jwt (2.7.1) + json (2.7.2) + jwt (2.8.1) + base64 kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) @@ -298,12 +300,13 @@ GEM minitest (5.20.0) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) mutex_m (0.1.2) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) + nkf (0.2.0) no_proxy_fix (0.1.2) nokogiri (1.15.4-arm64-darwin) racc (~> 1.4) @@ -312,21 +315,21 @@ GEM sawyer (~> 0.9) open4 (1.3.4) options (2.3.2) - optparse (0.1.1) + optparse (0.4.0) os (1.1.4) ox (2.14.17) parallel (1.23.0) parser (3.3.0.5) ast (~> 2.4.1) racc - plist (3.7.0) + plist (3.7.1) progress_bar (1.3.3) highline (>= 1.6, < 3) options (~> 2.3.0) public_suffix (4.0.7) racc (1.7.1) rainbow (3.1.1) - rake (13.0.6) + rake (13.2.1) rake-compiler (1.2.5) rake rchardet (1.8.0) @@ -359,8 +362,8 @@ GEM 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) @@ -374,7 +377,7 @@ GEM thor (1.0.1) 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) @@ -382,13 +385,9 @@ GEM 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) @@ -405,7 +404,6 @@ PLATFORMS arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 - ruby DEPENDENCIES cocoapods (~> 1.14) From 2aaa5711e180dfc4a88f43e60a4f13f964c37b3e Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 12 Apr 2024 17:32:55 -0700 Subject: [PATCH 080/547] Update Matchfile for consistency --- fastlane/Matchfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fastlane/Matchfile b/fastlane/Matchfile index 2ee895e90..09ef3019e 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -1,10 +1,9 @@ +# frozen_string_literal: true + # 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')) +secrets_directory = File.join(Dir.home, '.configure', 'simplenote-ios', 'secrets') +google_cloud_keys_file(File.join(secrets_directory, 'google_cloud_keys.json')) From 27af395fbf574806b5253ca3cc705d62ff0d677a Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 12 Apr 2024 17:42:48 -0700 Subject: [PATCH 081/547] Remove unneeded Fastlane env examples --- fastlane/env/user.env-example | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fastlane/env/user.env-example b/fastlane/env/user.env-example index 763c920cc..254bd83be 100644 --- a/fastlane/env/user.env-example +++ b/fastlane/env/user.env-example @@ -1,8 +1,3 @@ -FASTLANE_USER= -DELIVER_USER= - GHHELPER_ACCESS= APPCENTER_API_TOKEN= SENTRY_AUTH_TOKEN= - -CIRCLE_CI_AUTH_TOKEN= From ad24b1234bda7ccb9d2da81adaf2ae51135492db Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 12 Apr 2024 23:11:33 -0700 Subject: [PATCH 082/547] Improve code signing lanes --- fastlane/Fastfile | 92 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 54a897b55..f794822a2 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -919,53 +919,84 @@ platform :ios do end ######################################################################## -# Configure Lanes +# Fastlane Match Code Signing 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 + +# Downloads all the required certificates and profiles for both production and internal distribution builds. +# Optionally, it can create any new necessary certificates or 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 +# @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 -######################################################################## -# Fastlane match code signing -######################################################################## - +# Downloads all the required certificates and profiles of the alpha enterprise 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 :alpha_code_signing do |options| + readonly = options.fetch(:readonly, true) + + 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 + 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") + app_identifier: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Alpha"), + readonly: readonly, + api_key_path: api_key_path ) end +# Downloads all the required certificates and profiles the internal enterprise 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 :internal_code_signing do |options| + readonly = options.fetch(:readonly, true) + + 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 + 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") + app_identifier: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Internal"), + readonly: readonly, + api_key_path: api_key_path ) 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| match( type: 'appstore', team_id: get_required_env('EXT_EXPORT_TEAM_ID'), - readonly: options[:readonly] || is_ci, + readonly: options.fetch(:readonly, true), app_identifier: simplenote_app_identifiers, template_name: 'NotationalFlow Keychain Access (Distribution)' ) @@ -1192,3 +1223,14 @@ def generate_installable_build_number "#{branch}-#{commit}" end end + +def prompt_user_for_app_store_connect_credentials + require 'credentials_manager' + + # 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 From 0660204cb7ea832b7399652e93ae7513918a0c81 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 12 Apr 2024 23:58:37 -0700 Subject: [PATCH 083/547] Move Matchfile contents into Fastfile --- fastlane/Fastfile | 70 +++++++++++++++++++++++++--------------------- fastlane/Matchfile | 9 ------ 2 files changed, 38 insertions(+), 41 deletions(-) delete mode 100644 fastlane/Matchfile diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f794822a2..d6a7aca4d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -932,13 +932,7 @@ lane :update_certs_and_profiles do |options| appstore_code_signing(options) end -# Downloads all the required certificates and profiles of the alpha enterprise 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 :alpha_code_signing do |options| - readonly = options.fetch(:readonly, true) - +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 @@ -951,40 +945,37 @@ private_lane :alpha_code_signing do |options| api_key_path = nil end - match( + update_code_signing( type: 'enterprise', + # Enterprise builds belong to the "internal" team team_id: get_required_env('INT_EXPORT_TEAM_ID'), - app_identifier: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Alpha"), readonly: readonly, + app_identifiers: app_identifiers, api_key_path: api_key_path ) end -# Downloads all the required certificates and profiles the internal enterprise build. +# 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 :internal_code_signing do |options| - readonly = options.fetch(:readonly, true) - - 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 +# +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 - match( - type: 'enterprise', - team_id: get_required_env('INT_EXPORT_TEAM_ID'), - app_identifier: simplenote_app_identifiers(root_bundle_id: "#{APP_STORE_BUNDLE_IDENTIFIER}.Internal"), - readonly: readonly, - api_key_path: api_key_path +# 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 @@ -993,12 +984,27 @@ end # # @option [Boolean] readonly (default: true) Whether to only fetch existing certificates and profiles, without generating new ones. private_lane :appstore_code_signing do |options| - match( + update_code_signing( type: 'appstore', team_id: get_required_env('EXT_EXPORT_TEAM_ID'), readonly: options.fetch(:readonly, true), app_identifier: simplenote_app_identifiers, - template_name: 'NotationalFlow Keychain Access (Distribution)' + api_key_path: APP_STORE_CONNECT_KEY_PATH + ) +end + +def update_code_signing(type:, team_id:, readonly:, app_identifiers:, api_key_path:) + # 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. + match( + storage_mode: 'google_cloud', + google_cloud_bucket_name: 'a8c-fastlane-match', + google_cloud_keys_file: File.join(SECRETS_ROOT, 'google_cloud_keys.json'), + type: type, + team_id: team_id, + readonly: readonly, + app_identifier: app_identifiers, + api_key_path: api_key_path ) end diff --git a/fastlane/Matchfile b/fastlane/Matchfile deleted file mode 100644 index 09ef3019e..000000000 --- a/fastlane/Matchfile +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -# 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') -secrets_directory = File.join(Dir.home, '.configure', 'simplenote-ios', 'secrets') -google_cloud_keys_file(File.join(secrets_directory, 'google_cloud_keys.json')) From a2ac7f570fc2df895223d21c22fbb511b397c162 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Sat, 13 Apr 2024 00:02:57 -0700 Subject: [PATCH 084/547] Fix parameter typo --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d6a7aca4d..6497f38c9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -988,7 +988,7 @@ private_lane :appstore_code_signing do |options| type: 'appstore', team_id: get_required_env('EXT_EXPORT_TEAM_ID'), readonly: options.fetch(:readonly, true), - app_identifier: simplenote_app_identifiers, + app_identifiers: simplenote_app_identifiers, api_key_path: APP_STORE_CONNECT_KEY_PATH ) end From 7c21397ed10d0f180cdd29be70656dfcd62a08a2 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Sat, 13 Apr 2024 00:07:16 -0700 Subject: [PATCH 085/547] Move prompt_user method --- fastlane/Fastfile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 6497f38c9..dd9d0d9a7 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1032,6 +1032,17 @@ def simplenote_provisioning_profiles(root_bundle_id: APP_STORE_BUNDLE_IDENTIFIER .to_h { |key| [key, "match #{match_type} #{key}"] } end +def prompt_user_for_app_store_connect_credentials + require 'credentials_manager' + + # 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 ######################################################################## @@ -1229,14 +1240,3 @@ def generate_installable_build_number "#{branch}-#{commit}" end end - -def prompt_user_for_app_store_connect_credentials - require 'credentials_manager' - - # 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 From 334c881ad0da7b42abd5353f64b23ca69336fbd5 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Sat, 13 Apr 2024 00:40:47 -0700 Subject: [PATCH 086/547] Move Match storage from Google Cloud to S3 --- fastlane/Fastfile | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index dd9d0d9a7..a8bfbfb29 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -996,10 +996,21 @@ end def update_code_signing(type:, team_id:, readonly:, app_identifiers:, api_key_path:) # 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 ENV.fetch with not default). + # Otherwise, Fastlane will prompt to type them. + access_key = ENV.fetch('MATCH_S3_ACCESS_KEY') + secret_access_key = ENV.fetch('MATCH_S3_SECRET_ACCESS_KEY') + # This ENV var is required but match has no input parameter for it, likely for security + password_key = 'MATCH_PASSWORD' + raise "Missing #{password_key} in the environment" unless ENV.key?(password_key) + match( - storage_mode: 'google_cloud', - google_cloud_bucket_name: 'a8c-fastlane-match', - google_cloud_keys_file: File.join(SECRETS_ROOT, 'google_cloud_keys.json'), + 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, From 5b1e2918377dbe4fb2d77626eea54035a91d4e9c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 15 Apr 2024 13:22:28 -0600 Subject: [PATCH 087/547] Added option in settings to enable indexing notes in spotlight --- Simplenote/Classes/SPSettingsViewController.m | 29 +++++++++++++++++-- .../Classes/UserDefaults+Simplenote.swift | 1 + Simplenote/Options.swift | 13 +++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 61e6fab34..d5e4989fa 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -19,6 +19,7 @@ @interface SPSettingsViewController () @property (nonatomic, strong) UITextField *pinTimeoutTextField; @property (nonatomic, strong) UIPickerView *pinTimeoutPickerView; @property (nonatomic, strong) UIToolbar *doneToolbar; +@property (nonatomic, strong) UISwitch *indexNotesSwitch; @end @implementation SPSettingsViewController { @@ -33,6 +34,7 @@ @implementation SPSettingsViewController { #define kTagTimeout 6 #define kTagTouchID 7 #define kTagSustainerIcon 8 +#define kTagIndexNotes 9 typedef NS_ENUM(NSInteger, SPOptionsViewSections) { SPOptionsViewSectionsNotes = 0, @@ -57,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) { @@ -141,6 +144,11 @@ - (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:) @@ -319,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; } @@ -697,6 +715,13 @@ - (void)condensedSwitchDidChangeValue:(UISwitch *)sender [SPTracker trackSettingsListCondensedEnabled:isOn]; } +- (void)indexNotesSwitchDidChangeValue:(UISwitch *)sender +{ + BOOL isOn = [(UISwitch *)sender isOn]; + + [[Options shared] setIndexNotesInSpotlight:isOn]; +} + - (void)tagSortSwitchDidChangeValue:(UISwitch *)sender { BOOL isOn = [(UISwitch *)sender isOn]; diff --git a/Simplenote/Classes/UserDefaults+Simplenote.swift b/Simplenote/Classes/UserDefaults+Simplenote.swift index 9bd5bfc25..be71a8855 100644 --- a/Simplenote/Classes/UserDefaults+Simplenote.swift +++ b/Simplenote/Classes/UserDefaults+Simplenote.swift @@ -17,6 +17,7 @@ extension UserDefaults { case accountIsLoggedIn case useSustainerIcon case hasMigratedSustainerPreferences + case indexNotesInSpotlight } } diff --git a/Simplenote/Options.swift b/Simplenote/Options.swift index ccea36890..8251594a9 100644 --- a/Simplenote/Options.swift +++ b/Simplenote/Options.swift @@ -145,6 +145,19 @@ 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 From c007d9c24a80a2da4400c3871902a9e4264e2d61 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 15 Apr 2024 13:42:28 -0600 Subject: [PATCH 088/547] Added ability to disable or enable spotlight indexing in preferences --- Simplenote/Classes/CSSearchable+Helpers.swift | 32 +++++++++++++++++++ Simplenote/Classes/SPSettingsViewController.m | 9 ++++++ Simplenote/SPAppDelegate.m | 10 ++---- Simplenote/Simplenote-Bridging-Header.h | 1 + 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Simplenote/Classes/CSSearchable+Helpers.swift b/Simplenote/Classes/CSSearchable+Helpers.swift index ef12bd6a2..8e9240acc 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] { + CSSearchableIndex.default().deleteSearchableNotes(deleted) + } + + if let notes = context.fetchObjects(forEntityName: "Note", with: NSPredicate(format: "deleted == NO")) as? [Note] { + CSSearchableIndex.default().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) } @@ -70,4 +95,11 @@ extension CSSearchableIndex { } } + @objc + func deleteSearchableNotes(in context: NSManagedObjectContext) { + if let notes = context.fetchAllObjects(forEntityName: "Note") as? [Note] { + + deleteSearchableNotes(notes) + } + } } diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index d5e4989fa..557af508e 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -720,6 +720,15 @@ - (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] deleteSearchableNotesIn:context]; + } } - (void)tagSortSwitchDidChangeValue:(UISwitch *)sender diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index f19f8a2a7..45b18e7bc 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -468,14 +468,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]; } 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 From 664aaae7051281ce2cedaf7b9eacbaff62224af7 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 15 Apr 2024 13:59:38 -0600 Subject: [PATCH 089/547] Added alert on deleting spotlight index that some history may remain --- Simplenote/Classes/CSSearchable+Helpers.swift | 8 -------- .../SPSettingsViewController+Extensions.swift | 15 +++++++++++++++ Simplenote/Classes/SPSettingsViewController.m | 5 ++++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Simplenote/Classes/CSSearchable+Helpers.swift b/Simplenote/Classes/CSSearchable+Helpers.swift index 8e9240acc..411de8728 100644 --- a/Simplenote/Classes/CSSearchable+Helpers.swift +++ b/Simplenote/Classes/CSSearchable+Helpers.swift @@ -94,12 +94,4 @@ extension CSSearchableIndex { } } } - - @objc - func deleteSearchableNotes(in context: NSManagedObjectContext) { - if let notes = context.fetchAllObjects(forEntityName: "Note") as? [Note] { - - deleteSearchableNotes(notes) - } - } } diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index f5ea91e7c..abd05fa21 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -206,6 +206,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 { diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index 557af508e..df788f9dd 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -727,7 +727,10 @@ - (void)indexNotesSwitchDidChangeValue:(UISwitch *)sender if (isOn) { [[CSSearchableIndex defaultSearchableIndex] indexSpotlightItemsIn:context]; } else { - [[CSSearchableIndex defaultSearchableIndex] deleteSearchableNotesIn:context]; + [[CSSearchableIndex defaultSearchableIndex] deleteAllSearchableItemsWithCompletionHandler:^(NSError * _Nullable error) { + + [self presentIndexRemovalAlert]; + }]; } } From dc1ac2cedae53e00c27171bbf2b3974cb3aff30a Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 15 Apr 2024 14:11:09 -0600 Subject: [PATCH 090/547] Updated release notes for PR1562 added toggle for indexing for spotlight --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 188c15db1..f4f964db3 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ 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 4.50 ----- From 494a646226046dd5e0fc3ad3906c5ca4ab322681 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 16 Apr 2024 12:42:38 -0600 Subject: [PATCH 091/547] Fix issue with cert provisioning --- Simplenote.xcodeproj/project.pbxproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 0df97ec86..56300cea5 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -2902,7 +2902,7 @@ BuildIndependentTargetsInParallel = YES; CLASSPREFIX = SP; LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1520; + LastUpgradeCheck = 1530; ORGANIZATIONNAME = Automattic; TargetAttributes = { 3F1BB4AD243199FF006D1A04 = { @@ -5239,11 +5239,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", @@ -5261,7 +5262,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"; From 428860fb5020f18b69e2b8e19aa64d3ed90b8a05 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 16 Apr 2024 12:43:00 -0600 Subject: [PATCH 092/547] Updated xcode settings to get rid of warnings --- Simplenote.xcodeproj/xcshareddata/xcschemes/Simplenote.xcscheme | 2 +- .../xcshareddata/xcschemes/SimplenoteIntents.xcscheme | 2 +- .../xcshareddata/xcschemes/SimplenoteScreenshots.xcscheme | 2 +- .../xcshareddata/xcschemes/SimplenoteShare.xcscheme | 2 +- .../xcshareddata/xcschemes/SimplenoteUITests.xcscheme | 2 +- .../xcshareddata/xcschemes/SimplenoteUITests_Subset.xcscheme | 2 +- .../xcshareddata/xcschemes/SimplenoteWidgetsExtension.xcscheme | 2 +- .../xcshareddata/xcschemes/UITestsFoundation.xcscheme | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) 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 @@ Date: Tue, 16 Apr 2024 14:02:56 -0600 Subject: [PATCH 093/547] Fixed an issue where the new note widget made a new note bypassing lock --- .../PinLock/PinLockVerifyController.swift | 19 +++++++++++++++++++ .../PinLock/PinLockViewController.swift | 2 +- Simplenote/SPAppDelegate+Extensions.swift | 14 +++++++++++++- Simplenote/SPAppDelegate.h | 2 +- Simplenote/SPAppDelegate.m | 6 +++++- Simplenote/URL+Simplenote.swift | 11 ++++++++++- 6 files changed, 49 insertions(+), 5 deletions(-) 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/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 942f6ac98..e33b1fa6c 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -150,8 +150,19 @@ extension SPAppDelegate { /// Opens editor with a new note /// + @objc func presentNewNoteEditor(animated: Bool = false) { - presentNote(nil, animated: animated) + if isPresentingPasscodeLock && SPPinLockManager.shared.isEnabled { + verifyController?.addOnSuccesBlock { + self.presentNote(nil, animated: animated) + } + } else { + presentNote(nil, animated: animated) + } + } + + var verifyController: PinLockVerifyController? { + (pinLockWindow?.rootViewController as? PinLockViewController)?.controller as? PinLockVerifyController } /// Opens a note with specified simperium key @@ -325,6 +336,7 @@ extension SPAppDelegate { @objc func showPasscodeLockIfNecessary() { guard SPPinLockManager.shared.isEnabled, !isPresentingPasscodeLock else { + verifyController?.removeSuccesBlocks() return } diff --git a/Simplenote/SPAppDelegate.h b/Simplenote/SPAppDelegate.h index 42aa15ef6..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; diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index f19f8a2a7..1693db6eb 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -512,7 +512,11 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDiction [self presentNote:newNote animated:NO]; } - + + if ([[components host] isEqualToString:@"widgetNew"]) { + [self presentNewNoteEditorWithAnimated: NO]; + } + return YES; } diff --git a/Simplenote/URL+Simplenote.swift b/Simplenote/URL+Simplenote.swift index 17e5287b4..42895a6fd 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 var 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" } From 6ec7e0bc7f1af2a0a79e1b742e4b28f3e00dba22 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 16 Apr 2024 14:10:59 -0600 Subject: [PATCH 094/547] Updated release notes PR1564 fixed new note widget ignoring pinlock --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 188c15db1..2f13d48e4 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ 4.51 ----- - Fixed issue where note cell appearance changes on press down instead of when the cell is selected +- Fixed an issue where using the new note widget with the lock screen could create empty notes while the app was locked 4.50 ----- From 0e71cf54b2cbd0efa09e6b99b7f24474a6913631 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 16 Apr 2024 13:37:15 -0700 Subject: [PATCH 095/547] Remove AppFile --- fastlane/Appfile | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 fastlane/Appfile 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 From 8eea4949be17fcf3c490ef2e5d5366b93d4180f3 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 16 Apr 2024 15:22:59 -0700 Subject: [PATCH 096/547] Apple Rubocop corrections --- Rakefile | 17 ++++++++--------- fastlane/Fastfile | 4 +++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Rakefile b/Rakefile index 6527830f4..f040dc5ff 100644 --- a/Rakefile +++ b/Rakefile @@ -1,11 +1,4 @@ -require 'English' -# Constants -SWIFTLINT_VERSION = '0.41.0' -PROJECT_DIR = __dir__ -XCODE_WORKSPACE = 'Simplenote.xcworkspace' -XCODE_SCHEME = 'Simplenote' -XCODE_CONFIGURATION = 'Debug' -LOCAL_PATH = 'vendor/bundle' +# frozen_string_literal: true require 'English' require 'fileutils' @@ -14,7 +7,13 @@ require 'rake/clean' require 'yaml' require 'digest' - +# 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] diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 13dc7ebfe..80bef9049 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -126,7 +126,9 @@ platform :ios do generate_strings_file_for_glotpress 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 options[:skip_confirm] || UI.confirm('Do you want to continue?') + unless options[:skip_confirm] || UI.confirm('Do you want to continue?') + UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") + end push_to_git_remote(tags: false) From 4a23e75e25f2e52d44e6789a90a4df48b64d8685 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 16 Apr 2024 16:52:26 -0700 Subject: [PATCH 097/547] Remove extra `MATCH_PASSWORD` check --- fastlane/Fastfile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 46a479c0f..818cf7f60 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1066,13 +1066,10 @@ def update_code_signing(type:, team_id:, readonly:, app_identifiers:, api_key_pa # 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 ENV.fetch with not default). + # Fail early if secrets not available via `get_required_env`. # Otherwise, Fastlane will prompt to type them. - access_key = ENV.fetch('MATCH_S3_ACCESS_KEY') - secret_access_key = ENV.fetch('MATCH_S3_SECRET_ACCESS_KEY') - # This ENV var is required but match has no input parameter for it, likely for security - password_key = 'MATCH_PASSWORD' - raise "Missing #{password_key} in the environment" unless ENV.key?(password_key) + access_key = get_required_env('MATCH_S3_ACCESS_KEY') + secret_access_key = get_required_env('MATCH_S3_SECRET_ACCESS_KEY') match( storage_mode: 's3', From 664a20c4c44905e1c82b7fbc654675deed9046ed Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 16 Apr 2024 16:54:56 -0700 Subject: [PATCH 098/547] Move the enterprise code signing method to for better organization --- fastlane/Fastfile | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 818cf7f60..425806748 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1001,29 +1001,6 @@ lane :update_certs_and_profiles do |options| appstore_code_signing(options) end -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: get_required_env('INT_EXPORT_TEAM_ID'), - readonly: readonly, - app_identifiers: app_identifiers, - api_key_path: api_key_path - ) -end - # 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. # @@ -1062,6 +1039,29 @@ private_lane :appstore_code_signing do |options| ) end +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: get_required_env('INT_EXPORT_TEAM_ID'), + readonly: readonly, + app_identifiers: app_identifiers, + api_key_path: api_key_path + ) +end + def update_code_signing(type:, team_id:, readonly:, app_identifiers:, api_key_path:) # 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. From 19fae80545d6def74aca1db364dece9ed957c949 Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Wed, 17 Apr 2024 11:01:54 +0200 Subject: [PATCH 099/547] Fix bundler setup to allow running danger on Linux --- .bundle/config | 1 + Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) 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/Gemfile.lock b/Gemfile.lock index cb2e42a85..8f14d3098 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -310,6 +310,8 @@ GEM no_proxy_fix (0.1.2) nokogiri (1.15.4-arm64-darwin) racc (~> 1.4) + nokogiri (1.15.4-x86_64-linux) + racc (~> 1.4) octokit (6.1.1) faraday (>= 1, < 3) sawyer (~> 0.9) @@ -404,6 +406,7 @@ PLATFORMS arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 + x86_64-linux DEPENDENCIES cocoapods (~> 1.14) From 1f365a88294bd79a00e49230b1ed815519f6aa7c Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 17 Apr 2024 04:16:23 -0700 Subject: [PATCH 100/547] Bump version number --- config/Version.Public.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/Version.Public.xcconfig b/config/Version.Public.xcconfig index 63c766f02..afdd640fc 100644 --- a/config/Version.Public.xcconfig +++ b/config/Version.Public.xcconfig @@ -1,4 +1,4 @@ -VERSION_SHORT=4.50 +VERSION_SHORT=4.51 // Public long version example: VERSION_LONG=4.8.1.1 -VERSION_LONG=4.50.0.1 +VERSION_LONG=4.51.0.0 From d3c5d5c8c124a8b4fe86509d7016fb322f73feea Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 17 Apr 2024 04:16:24 -0700 Subject: [PATCH 101/547] Update draft release notes for 4.51. --- Simplenote/Resources/release_notes.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Simplenote/Resources/release_notes.txt b/Simplenote/Resources/release_notes.txt index 81d1894ea..8f1de708c 100644 --- a/Simplenote/Resources/release_notes.txt +++ b/Simplenote/Resources/release_notes.txt @@ -1 +1,2 @@ -• Behind-the-scenes reliability improvements +- Fixed issue where note cell appearance changes on press down instead of when the cell is selected + From af7d860fe21f0b4f73bf11daac932b1df2bdc891 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 17 Apr 2024 04:16:24 -0700 Subject: [PATCH 102/547] Release Notes: add new section for next version (4.52) --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 188c15db1..5755b679c 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,3 +1,7 @@ +4.52 +----- + + 4.51 ----- - Fixed issue where note cell appearance changes on press down instead of when the cell is selected From 384d1923be51a5378371e454d4efbf9fe28bd486 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 17 Apr 2024 08:22:31 -0600 Subject: [PATCH 103/547] Removed unneeded calls to the CSSearchableIndex default --- Simplenote/Classes/CSSearchable+Helpers.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/CSSearchable+Helpers.swift b/Simplenote/Classes/CSSearchable+Helpers.swift index 411de8728..adb6f7433 100644 --- a/Simplenote/Classes/CSSearchable+Helpers.swift +++ b/Simplenote/Classes/CSSearchable+Helpers.swift @@ -41,11 +41,11 @@ extension CSSearchableIndex { context.perform { if let deleted = context.fetchObjects(forEntityName: "Note", with: NSPredicate(format: "deleted == YES")) as? [Note] { - CSSearchableIndex.default().deleteSearchableNotes(deleted) + self.deleteSearchableNotes(deleted) } if let notes = context.fetchObjects(forEntityName: "Note", with: NSPredicate(format: "deleted == NO")) as? [Note] { - CSSearchableIndex.default().indexSearchableNotes(notes) + self.indexSearchableNotes(notes) } } } From 2d1cd365132809be3f2c4846f3cfc00f901b1210 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 17 Apr 2024 16:18:26 -0700 Subject: [PATCH 104/547] Update release notes for 4.51 --- Simplenote/Resources/release_notes.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Simplenote/Resources/release_notes.txt b/Simplenote/Resources/release_notes.txt index 8f1de708c..37a8f4076 100644 --- a/Simplenote/Resources/release_notes.txt +++ b/Simplenote/Resources/release_notes.txt @@ -1,2 +1,3 @@ -- Fixed issue where note cell appearance changes on press down instead of when the cell is selected - +• Fixed issue where note cell appearance changes on press down instead of when the cell is selected +• Fixed an issue where using the new note widget with the lock screen could create empty notes while the app was locked +• Removed Sustainer plan information and upgrade UI From 4bc2ae889c3595fdee3d48234cf386ed0a3d75de Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 17 Apr 2024 16:18:42 -0700 Subject: [PATCH 105/547] Update metadata strings --- Simplenote/Resources/AppStoreStrings.pot | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Simplenote/Resources/AppStoreStrings.pot b/Simplenote/Resources/AppStoreStrings.pot index 9ab8e3981..65aa660df 100644 --- a/Simplenote/Resources/AppStoreStrings.pot +++ b/Simplenote/Resources/AppStoreStrings.pot @@ -66,8 +66,10 @@ 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.51-whats-new" msgid "" -"• Behind-the-scenes reliability improvements\n" +"• Fixed issue where note cell appearance changes on press down instead of when the cell is selected\n" +"• Fixed an issue where using the new note widget with the lock screen could create empty notes while the app was locked\n" +"• Removed Sustainer plan information and upgrade UI\n" msgstr "" From 4f5e1a3936c415769be60ec74a7a148335e97f5c Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 17 Apr 2024 17:15:16 -0700 Subject: [PATCH 106/547] Add template_name parameter to match call --- fastlane/Fastfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 425806748..2bca74ae3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1035,7 +1035,8 @@ private_lane :appstore_code_signing do |options| team_id: get_required_env('EXT_EXPORT_TEAM_ID'), readonly: options.fetch(:readonly, true), app_identifiers: simplenote_app_identifiers, - api_key_path: APP_STORE_CONNECT_KEY_PATH + api_key_path: APP_STORE_CONNECT_KEY_PATH, + template_name: 'NotationalFlow Keychain Access (Distribution)' ) end @@ -1062,7 +1063,8 @@ def update_code_signing_enterprise(readonly:, app_identifiers:) ) end -def update_code_signing(type:, team_id:, readonly:, app_identifiers:, api_key_path:) +# 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. @@ -1081,9 +1083,11 @@ def update_code_signing(type:, team_id:, readonly:, app_identifiers:, api_key_pa team_id: team_id, readonly: readonly, app_identifier: app_identifiers, - api_key_path: api_key_path + api_key_path: api_key_path, + template_name: template_name ) end +# rubocop:enable Metrics/ParameterLists # Compiles the array of bundle identifiers for the different targets that # make up the Simplenote app, to be used as the `app_identifier` parameter From 1e4252376ffb63b793995f5c9469a9c572fb8f2f Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 23 Apr 2024 06:00:01 -0700 Subject: [PATCH 107/547] Bump version number --- config/Version.Public.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/Version.Public.xcconfig b/config/Version.Public.xcconfig index afdd640fc..63d01d802 100644 --- a/config/Version.Public.xcconfig +++ b/config/Version.Public.xcconfig @@ -1,4 +1,4 @@ VERSION_SHORT=4.51 // Public long version example: VERSION_LONG=4.8.1.1 -VERSION_LONG=4.51.0.0 +VERSION_LONG=4.51.0.1 From 0a901e9214bac54851fc9d67050c4cc38f35ae56 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 23 Apr 2024 06:01:34 -0700 Subject: [PATCH 108/547] Updated strings for 4.51 --- Simplenote/ar.lproj/Localizable.strings | 2 +- Simplenote/de.lproj/Localizable.strings | 2 +- Simplenote/es.lproj/Localizable.strings | 2 +- Simplenote/fr.lproj/Localizable.strings | 2 +- Simplenote/he.lproj/Localizable.strings | 2 +- Simplenote/id.lproj/Localizable.strings | 2 +- Simplenote/it.lproj/Localizable.strings | 2 +- Simplenote/ja.lproj/Localizable.strings | 2 +- Simplenote/ko.lproj/Localizable.strings | 2 +- Simplenote/nl.lproj/Localizable.strings | 2 +- Simplenote/pt-BR.lproj/Localizable.strings | 2 +- Simplenote/ru.lproj/Localizable.strings | 2 +- Simplenote/sv.lproj/Localizable.strings | 2 +- Simplenote/tr.lproj/Localizable.strings | 2 +- Simplenote/zh-Hans-CN.lproj/Localizable.strings | 2 +- Simplenote/zh-Hant-TW.lproj/Localizable.strings | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Simplenote/ar.lproj/Localizable.strings b/Simplenote/ar.lproj/Localizable.strings index 85a9ae2f3..1465b5569 100644 --- a/Simplenote/ar.lproj/Localizable.strings +++ b/Simplenote/ar.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ar */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ شهريًا "; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/de.lproj/Localizable.strings b/Simplenote/de.lproj/Localizable.strings index f0b51ad56..516bb4fb2 100644 --- a/Simplenote/de.lproj/Localizable.strings +++ b/Simplenote/de.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: de */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ pro Monat"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/es.lproj/Localizable.strings b/Simplenote/es.lproj/Localizable.strings index 93ca839f3..de368529c 100644 --- a/Simplenote/es.lproj/Localizable.strings +++ b/Simplenote/es.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: es */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ al mes"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/fr.lproj/Localizable.strings b/Simplenote/fr.lproj/Localizable.strings index da1c0ef9b..733f34062 100644 --- a/Simplenote/fr.lproj/Localizable.strings +++ b/Simplenote/fr.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: fr */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ par mois"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/he.lproj/Localizable.strings b/Simplenote/he.lproj/Localizable.strings index 8c76cb5c1..efd46555d 100644 --- a/Simplenote/he.lproj/Localizable.strings +++ b/Simplenote/he.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: he_IL */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ לחודש"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/id.lproj/Localizable.strings b/Simplenote/id.lproj/Localizable.strings index 245e44f3b..530ea0e1d 100644 --- a/Simplenote/id.lproj/Localizable.strings +++ b/Simplenote/id.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: id */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ per Bulan"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/it.lproj/Localizable.strings b/Simplenote/it.lproj/Localizable.strings index bc600e39d..6dc597232 100644 --- a/Simplenote/it.lproj/Localizable.strings +++ b/Simplenote/it.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: it */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ al mese"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/ja.lproj/Localizable.strings b/Simplenote/ja.lproj/Localizable.strings index e1b84beb6..8a128bf25 100644 --- a/Simplenote/ja.lproj/Localizable.strings +++ b/Simplenote/ja.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ja_JP */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "月額 %@"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/ko.lproj/Localizable.strings b/Simplenote/ko.lproj/Localizable.strings index dd233661c..cf95dbc73 100644 --- a/Simplenote/ko.lproj/Localizable.strings +++ b/Simplenote/ko.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ko_KR */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "월 %@"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/nl.lproj/Localizable.strings b/Simplenote/nl.lproj/Localizable.strings index c0aca05a6..0bd2fe55e 100644 --- a/Simplenote/nl.lproj/Localizable.strings +++ b/Simplenote/nl.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: nl */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ Per maand"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/pt-BR.lproj/Localizable.strings b/Simplenote/pt-BR.lproj/Localizable.strings index 4f710de36..853469454 100644 --- a/Simplenote/pt-BR.lproj/Localizable.strings +++ b/Simplenote/pt-BR.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: pt_BR */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ por mês"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/ru.lproj/Localizable.strings b/Simplenote/ru.lproj/Localizable.strings index 8deef0e41..3e4d397f5 100644 --- a/Simplenote/ru.lproj/Localizable.strings +++ b/Simplenote/ru.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: ru */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ в месяц"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/sv.lproj/Localizable.strings b/Simplenote/sv.lproj/Localizable.strings index e39afe02e..f0f3dc15c 100644 --- a/Simplenote/sv.lproj/Localizable.strings +++ b/Simplenote/sv.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: sv_SE */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "%@ per månad"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/tr.lproj/Localizable.strings b/Simplenote/tr.lproj/Localizable.strings index 599617e24..3cd294523 100644 --- a/Simplenote/tr.lproj/Localizable.strings +++ b/Simplenote/tr.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: tr */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "Aylık %@"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/zh-Hans-CN.lproj/Localizable.strings b/Simplenote/zh-Hans-CN.lproj/Localizable.strings index ed6913856..853e9c036 100644 --- a/Simplenote/zh-Hans-CN.lproj/Localizable.strings +++ b/Simplenote/zh-Hans-CN.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: zh_CN */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "每月 %@"; /* Yearly Subscription Option. Please preserve the special marker! */ diff --git a/Simplenote/zh-Hant-TW.lproj/Localizable.strings b/Simplenote/zh-Hant-TW.lproj/Localizable.strings index f688d6846..b3bc6a284 100644 --- a/Simplenote/zh-Hant-TW.lproj/Localizable.strings +++ b/Simplenote/zh-Hant-TW.lproj/Localizable.strings @@ -3,7 +3,7 @@ /* Generator: GlotPress/2.4.0-alpha */ /* Language: zh_TW */ -/* No comment provided by engineer. */ +/* Monthly Subscription Option. Please preserve the special marker! */ "%@ per Month" = "每月 %@"; /* Yearly Subscription Option. Please preserve the special marker! */ From af4c607a60efc15ca21c0583eb99b4087237052e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 12:37:38 -0500 Subject: [PATCH 109/547] Display banner view on collaboration view announcing retirement --- Simplenote.xcodeproj/project.pbxproj | 4 +++ Simplenote/BannerView.swift | 36 +++++++++++-------- Simplenote/BannerView.xib | 8 +---- .../SPAddCollaboratorsViewController.m | 1 + .../SPAddCollaboratorsViewController.swift | 33 +++++++++++++++++ 5 files changed, 61 insertions(+), 21 deletions(-) create mode 100644 Simplenote/SPAddCollaboratorsViewController.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 56300cea5..b0a30bb67 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -518,6 +518,7 @@ 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 */; }; + BAC7264E2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.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 */; }; @@ -1181,6 +1182,7 @@ BAB576BD2670512C00B0C56F /* NoteWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteWidget.swift; sourceTree = ""; }; BAB6C04626BA4CAF007495C4 /* WidgetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetController.swift; sourceTree = ""; }; BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDefaults.swift; sourceTree = ""; }; + BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAddCollaboratorsViewController.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 = ""; }; @@ -2640,6 +2642,7 @@ E2F1497F179D8E1A00DC9690 /* Settings */, E25C39CE17A41F3400B2591A /* SPAddCollaboratorsViewController.h */, E25C39CF17A41F3400B2591A /* SPAddCollaboratorsViewController.m */, + BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift */, E2EAE38117C2C2D400268682 /* SPEntryListViewController.h */, E2EAE38217C2C2D400268682 /* SPEntryListViewController.m */, E215C790180B115C00AD36B5 /* SPNavigationController.h */, @@ -3394,6 +3397,7 @@ B531090823D0B685002B0998 /* SPSimpleTextPrintFormatter.swift in Sources */, B5CDE61F2150834C00C3FED4 /* Simperium+Simplenote.m in Sources */, A60A1A1E25655D840041701E /* ApplicationShortcutItemType.swift in Sources */, + BAC7264E2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift in Sources */, B51F6DD12460C3EE0074DDD9 /* AuthenticationValidator.swift in Sources */, B575736A232C567000443C2E /* UIImage+Dynamic.swift in Sources */, A60DF31025A4524100FDADF3 /* PinLockBaseController.swift in Sources */, diff --git a/Simplenote/BannerView.swift b/Simplenote/BannerView.swift index ec98db8cc..4ea2d33e6 100644 --- a/Simplenote/BannerView.swift +++ b/Simplenote/BannerView.swift @@ -27,7 +27,11 @@ class BannerView: UIView { } } - var onPress: (() -> Void)? + var onPress: (() -> Void)? { + didSet { + setupTapRecognizer() + } + } var preferredWidth: CGFloat? { didSet { @@ -48,17 +52,17 @@ class BannerView: UIView { // MARK: - Actions - @IBAction - func bannerWasPresssed() { + @objc + func bannerWasPressed() { onPress?() } -} -// MARK: - Private API(s) -// -private extension BannerView { + private func setupTapRecognizer() { + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(bannerWasPressed)) + backgroundView.addGestureRecognizer(tapRecognizer) + } - func refreshInterface(with style: Style? = nil) { + func refreshInterface(with style: BannerStyle? = nil) { guard let style else { return } @@ -74,28 +78,32 @@ private extension BannerView { // MARK: - Style // -private struct Style { +struct BannerStyle { let title: String let details: String let textColor: UIColor let backgroundColor: UIColor } -private extension Style { +extension BannerStyle { // 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"), + static var sustainer: BannerStyle { + BannerStyle(title: NSLocalizedString("You are a Simplenote Sustainer", comment: "Current Sustainer Title"), details: NSLocalizedString("Thank you for your continued support", comment: "Current Sustainer Details"), textColor: .white, backgroundColor: .simplenoteSustainerViewBackgroundColor) } - static var notSubscriber: Style { - Style(title: NSLocalizedString("Become a Simplenote Sustainer", comment: "Become a Sustainer Title"), + static var notSubscriber: BannerStyle { + BannerStyle(title: NSLocalizedString("Become a Simplenote Sustainer", comment: "Become a Sustainer Title"), details: NSLocalizedString("Support your favorite notes app to help unlock future features", comment: "Become a Sustainer Details"), textColor: .white, backgroundColor: .simplenoteBlue50Color) } + + static var collaborationRetirement: BannerStyle { + BannerStyle(title: NSLocalizedString("Collaboration Retirement", comment: "Title annoucning collaboration retirement"), details: NSLocalizedString("Collaboration is being retired and will be disabled for all users July 1st. For more details tap here", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) + } } // MARK: - Metrics diff --git a/Simplenote/BannerView.xib b/Simplenote/BannerView.xib index dc1d2af0e..f667c7744 100644 --- a/Simplenote/BannerView.xib +++ b/Simplenote/BannerView.xib @@ -10,12 +10,6 @@ - - - - - - @@ -90,10 +84,10 @@ - + diff --git a/Simplenote/Classes/SPAddCollaboratorsViewController.m b/Simplenote/Classes/SPAddCollaboratorsViewController.m index 97ec18462..23d4561d9 100644 --- a/Simplenote/Classes/SPAddCollaboratorsViewController.m +++ b/Simplenote/Classes/SPAddCollaboratorsViewController.m @@ -49,6 +49,7 @@ - (void)viewWillAppear:(BOOL)animated [primaryTableView reloadData]; [self.contactsManager requestAuthorizationIfNeededWithCompletion:nil]; + [self setupBannerView]; } - (void)viewDidAppear:(BOOL)animated diff --git a/Simplenote/SPAddCollaboratorsViewController.swift b/Simplenote/SPAddCollaboratorsViewController.swift new file mode 100644 index 000000000..b2e1899b2 --- /dev/null +++ b/Simplenote/SPAddCollaboratorsViewController.swift @@ -0,0 +1,33 @@ +// +// SPAddCollaboratorsViewController.swift +// Simplenote +// +// Created by Charlie Scheer on 4/24/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation +import UIKit + +extension SPAddCollaboratorsViewController { + @objc + func setupBannerView() { + let bannerView: BannerView = BannerView.instantiateFromNib() + + bannerView.onPress = { + print("# Click me") + } + + view.addSubview(bannerView) + + bannerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + bannerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + bannerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + bannerView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + bannerView.refreshInterface(with: .collaborationRetirement) + + } +} From 38d0f3ad8312c26528b8b012bff9fb2478618844 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 15:35:40 -0500 Subject: [PATCH 110/547] Added collaborator retirement banner announcement --- .../SPAddCollaboratorsViewController.m | 16 ++--- .../Classes/SPEntryListViewController.h | 4 +- .../Classes/SPEntryListViewController.m | 62 +++++++++---------- .../SPAddCollaboratorsViewController.swift | 19 +++++- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/Simplenote/Classes/SPAddCollaboratorsViewController.m b/Simplenote/Classes/SPAddCollaboratorsViewController.m index 23d4561d9..6e620202b 100644 --- a/Simplenote/Classes/SPAddCollaboratorsViewController.m +++ b/Simplenote/Classes/SPAddCollaboratorsViewController.m @@ -47,7 +47,7 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [primaryTableView reloadData]; + [self.primaryTableView reloadData]; [self.contactsManager requestAuthorizationIfNeededWithCompletion:nil]; [self setupBannerView]; } @@ -114,7 +114,7 @@ - (void)setupWithCollaborators:(NSArray *)collaborators self.dataSource = [[[merged allObjects] sortedArrayUsingSelector:@selector(compareName:)] mutableCopy]; - [primaryTableView reloadData]; + [self.primaryTableView reloadData]; } @@ -127,7 +127,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if ([tableView isEqual:primaryTableView]) { + if ([tableView isEqual:self.primaryTableView]) { return self.dataSource.count; } @@ -136,7 +136,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if ([tableView isEqual:primaryTableView]) { + if ([tableView isEqual:self.primaryTableView]) { return self.dataSource.count > 0 ? NSLocalizedString(@"Current Collaborators", nil) : nil; } @@ -145,7 +145,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { - if ([tableView isEqual:primaryTableView]) { + if ([tableView isEqual:self.primaryTableView]) { if (self.dataSource.count > 0) { return nil; } else { @@ -161,7 +161,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N SPEntryListCell *cell = (SPEntryListCell *)[super tableView:tableView cellForRowAtIndexPath:indexPath]; PersonTag *personTag; - if ([tableView isEqual:primaryTableView]) { + if ([tableView isEqual:self.primaryTableView]) { personTag = self.dataSource[indexPath.row]; } else { personTag = self.autoCompleteDataSource[indexPath.row]; @@ -192,7 +192,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.selected = NO; - if ([tableView isEqual:primaryTableView]) { + if ([tableView isEqual:self.primaryTableView]) { PersonTag *person = self.dataSource[indexPath.row]; person.active = !person.active; @@ -228,7 +228,7 @@ - (void)addPersonTag:(PersonTag *)person [self.dataSource addObject:person]; entryTextField.text = @""; - [primaryTableView reloadSections:[NSIndexSet indexSetWithIndex:0] + [self.primaryTableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [self updateAutoCompleteMatchesForString:nil]; diff --git a/Simplenote/Classes/SPEntryListViewController.h b/Simplenote/Classes/SPEntryListViewController.h index f3dcd95ec..c54fc8ac2 100644 --- a/Simplenote/Classes/SPEntryListViewController.h +++ b/Simplenote/Classes/SPEntryListViewController.h @@ -11,14 +11,14 @@ @interface SPEntryListViewController : UIViewController { - UITableView *primaryTableView; - UIView *entryFieldBackground; SPTextField *entryTextField; UIButton *entryFieldPlusButton; UITableView *autoCompleteTableView; } +@property (nonatomic, strong) UITableView *primaryTableView; +@property (nonatomic, strong) UIView *entryFieldBackground; @property (nonatomic, strong) NSMutableArray *dataSource; @property (nonatomic, strong) NSArray *autoCompleteDataSource; @property (nonatomic) BOOL showEntryFieldPlusButton; diff --git a/Simplenote/Classes/SPEntryListViewController.m b/Simplenote/Classes/SPEntryListViewController.m index 6f9f2adf7..2792e2804 100644 --- a/Simplenote/Classes/SPEntryListViewController.m +++ b/Simplenote/Classes/SPEntryListViewController.m @@ -32,7 +32,7 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [primaryTableView reloadData]; + [self.primaryTableView reloadData]; } - (void)setupViews { @@ -40,25 +40,25 @@ - (void)setupViews { // setup views CGFloat yOrigin = self.view.safeAreaInsets.top; - entryFieldBackground = [[UIView alloc] initWithFrame:CGRectMake(0, + self.entryFieldBackground = [[UIView alloc] initWithFrame:CGRectMake(0, yOrigin, self.view.frame.size.width, EntryListCellHeight)]; - entryFieldBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; - [self.view addSubview:entryFieldBackground]; - + self.entryFieldBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; + [self.view addSubview:self.entryFieldBackground]; + entryTextField = [[SPTextField alloc] initWithFrame:CGRectMake(EntryListTextFieldSidePadding, 0, - entryFieldBackground.frame.size.width - 2 * EntryListTextFieldSidePadding, - entryFieldBackground.frame.size.height)]; + self.entryFieldBackground.frame.size.width - 2 * EntryListTextFieldSidePadding, + self.entryFieldBackground.frame.size.height)]; entryTextField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; entryTextField.keyboardType = UIKeyboardTypeEmailAddress; entryTextField.keyboardAppearance = SPUserInterface.isDark ? UIKeyboardAppearanceDark : UIKeyboardAppearanceDefault; entryTextField.autocapitalizationType = UITextAutocapitalizationTypeNone; entryTextField.delegate = self; - [entryFieldBackground addSubview:entryTextField]; - + [self.entryFieldBackground addSubview:entryTextField]; + entryFieldPlusButton = [UIButton buttonWithType:UIButtonTypeCustom]; UIImage *pickerImage = [UIImage imageWithName:UIImageNameAdd]; [entryFieldPlusButton setImage:pickerImage forState:UIControlStateNormal]; @@ -70,17 +70,17 @@ - (void)setupViews { entryTextField.rightViewMode = UITextFieldViewModeAlways; - primaryTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, yOrigin + entryTextField.frame.size.height, + self.primaryTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, yOrigin + entryTextField.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - (yOrigin + entryTextField.frame.size.height)) style:UITableViewStyleGrouped]; - primaryTableView.rowHeight = EntryListCellHeight; - primaryTableView.delegate = self; - primaryTableView.dataSource = self; - primaryTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - [self.view addSubview:primaryTableView]; - + self.primaryTableView.rowHeight = EntryListCellHeight; + self.primaryTableView.delegate = self; + self.primaryTableView.dataSource = self; + self.primaryTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + [self.view addSubview:self.primaryTableView]; + - [primaryTableView registerClass:[SPEntryListCell class] + [self.primaryTableView registerClass:[SPEntryListCell class] forCellReuseIdentifier:cellIdentifier]; [autoCompleteTableView registerClass:[SPEntryListAutoCompleteCell class] @@ -124,7 +124,7 @@ - (void)applyDefaultStyle { self.view.backgroundColor = tableBackgroundColor; // entry field - entryFieldBackground.backgroundColor = tableBackgroundColor; + self.entryFieldBackground.backgroundColor = tableBackgroundColor; entryTextField.backgroundColor = [UIColor clearColor]; entryTextField.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; entryTextField.textColor = [UIColor simplenoteTextColor]; @@ -132,15 +132,15 @@ - (void)applyDefaultStyle { CALayer *entryFieldBorder = [[CALayer alloc] init]; entryFieldBorder.frame = CGRectMake(0, - entryFieldBackground.bounds.size.height - 1.0 / [[UIScreen mainScreen] scale], + self.entryFieldBackground.bounds.size.height - 1.0 / [[UIScreen mainScreen] scale], MAX(self.view.frame.size.width, self.view.frame.size.height), 1.0 / [[UIScreen mainScreen] scale]); entryFieldBorder.backgroundColor = tableSeparatorColor.CGColor; - [entryFieldBackground.layer addSublayer:entryFieldBorder]; - + [self.entryFieldBackground.layer addSublayer:entryFieldBorder]; + // tableview - primaryTableView.backgroundColor = [UIColor clearColor]; - primaryTableView.separatorColor = tableSeparatorColor; + self.primaryTableView.backgroundColor = [UIColor clearColor]; + self.primaryTableView.separatorColor = tableSeparatorColor; autoCompleteTableView.backgroundColor = backgroundColor; autoCompleteTableView.separatorColor = tableSeparatorColor; } @@ -169,7 +169,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if ([tableView isEqual:primaryTableView]) + if ([tableView isEqual:self.primaryTableView]) return 0; // this is implemented by subclassing else if ([tableView isEqual:autoCompleteTableView]) return _autoCompleteDataSource.count; @@ -192,8 +192,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N UITableViewCell *finalCell; - if ([tableView isEqual:primaryTableView]) { - + if ([tableView isEqual:self.primaryTableView]) { + SPEntryListCell *cell = (SPEntryListCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (!cell) { @@ -220,7 +220,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - return [tableView isEqual:primaryTableView] ? YES : NO; + return [tableView isEqual:self.primaryTableView] ? YES : NO; } - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { @@ -238,10 +238,10 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd if (editingStyle == UITableViewCellEditingStyleDelete) { [self removeItemFromDataSourceAtIndexPath:indexPath]; - [primaryTableView beginUpdates]; - [primaryTableView deleteRowsAtIndexPaths:@[indexPath] + [self.primaryTableView beginUpdates]; + [self.primaryTableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; - [primaryTableView endUpdates]; + [self.primaryTableView endUpdates]; } } - (void)removeItemFromDataSourceAtIndexPath:(NSIndexPath *)indexPath { @@ -295,7 +295,7 @@ - (void)updatedAutoCompleteMatches { - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { - if ([scrollView isEqual:primaryTableView]) + if ([scrollView isEqual:self.primaryTableView]) [entryTextField resignFirstResponder]; } diff --git a/Simplenote/SPAddCollaboratorsViewController.swift b/Simplenote/SPAddCollaboratorsViewController.swift index b2e1899b2..f0dba4f67 100644 --- a/Simplenote/SPAddCollaboratorsViewController.swift +++ b/Simplenote/SPAddCollaboratorsViewController.swift @@ -21,10 +21,27 @@ extension SPAddCollaboratorsViewController { view.addSubview(bannerView) bannerView.translatesAutoresizingMaskIntoConstraints = false + primaryTableView.translatesAutoresizingMaskIntoConstraints = false + entryFieldBackground.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ bannerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), bannerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - bannerView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + bannerView.topAnchor.constraint(equalTo: view.topAnchor), + bannerView.bottomAnchor.constraint(equalTo: entryFieldBackground.topAnchor) + ]) + + NSLayoutConstraint.activate([ + entryFieldBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor), + entryFieldBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor), + entryFieldBackground.heightAnchor.constraint(equalToConstant: 44) + ]) + + NSLayoutConstraint.activate([ + primaryTableView.topAnchor.constraint(equalTo: entryFieldBackground.bottomAnchor), + primaryTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + primaryTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + primaryTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) bannerView.refreshInterface(with: .collaborationRetirement) From 725b9fc4b9c3c86dc3fc4abd50af51c84aca2d60 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 15:43:16 -0500 Subject: [PATCH 111/547] Refactor creation and storage of banner view in collaboratorsVC --- .../Classes/SPAddCollaboratorsViewController.h | 2 ++ .../Classes/SPAddCollaboratorsViewController.m | 1 + Simplenote/Classes/SPEntryListViewController.h | 2 ++ Simplenote/Classes/SPEntryListViewController.m | 1 - Simplenote/SPAddCollaboratorsViewController.swift | 12 ++++++------ 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Simplenote/Classes/SPAddCollaboratorsViewController.h b/Simplenote/Classes/SPAddCollaboratorsViewController.h index 83c11be63..fe6ad6dce 100644 --- a/Simplenote/Classes/SPAddCollaboratorsViewController.h +++ b/Simplenote/Classes/SPAddCollaboratorsViewController.h @@ -5,6 +5,7 @@ NS_ASSUME_NONNULL_BEGIN @class SPAddCollaboratorsViewController; +@class BannerView; @protocol SPCollaboratorDelegate @required @@ -20,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SPAddCollaboratorsViewController : SPEntryListViewController +@property (nonatomic, strong) BannerView *bannerView; @property (nonatomic, nullable, weak) id collaboratorDelegate; - (void)setupWithCollaborators:(NSArray *)collaborators; diff --git a/Simplenote/Classes/SPAddCollaboratorsViewController.m b/Simplenote/Classes/SPAddCollaboratorsViewController.m index 6e620202b..18bc871f5 100644 --- a/Simplenote/Classes/SPAddCollaboratorsViewController.m +++ b/Simplenote/Classes/SPAddCollaboratorsViewController.m @@ -50,6 +50,7 @@ - (void)viewWillAppear:(BOOL)animated [self.primaryTableView reloadData]; [self.contactsManager requestAuthorizationIfNeededWithCompletion:nil]; [self setupBannerView]; + [self setupViewContraints]; } - (void)viewDidAppear:(BOOL)animated diff --git a/Simplenote/Classes/SPEntryListViewController.h b/Simplenote/Classes/SPEntryListViewController.h index c54fc8ac2..f4cc336a8 100644 --- a/Simplenote/Classes/SPEntryListViewController.h +++ b/Simplenote/Classes/SPEntryListViewController.h @@ -9,6 +9,8 @@ #import #import "SPTextField.h" +static CGFloat const EntryListCellHeight = 44; + @interface SPEntryListViewController : UIViewController { SPTextField *entryTextField; diff --git a/Simplenote/Classes/SPEntryListViewController.m b/Simplenote/Classes/SPEntryListViewController.m index 2792e2804..7ec1a1c86 100644 --- a/Simplenote/Classes/SPEntryListViewController.m +++ b/Simplenote/Classes/SPEntryListViewController.m @@ -14,7 +14,6 @@ static NSString *cellIdentifier = @"primaryCell"; static NSString *autoCompleteCellIdentifier = @"autoCompleteCell"; static CGFloat const EntryListTextFieldSidePadding = 15; -static CGFloat const EntryListCellHeight = 44; @implementation SPEntryListViewController diff --git a/Simplenote/SPAddCollaboratorsViewController.swift b/Simplenote/SPAddCollaboratorsViewController.swift index f0dba4f67..2d8153214 100644 --- a/Simplenote/SPAddCollaboratorsViewController.swift +++ b/Simplenote/SPAddCollaboratorsViewController.swift @@ -12,14 +12,17 @@ import UIKit extension SPAddCollaboratorsViewController { @objc func setupBannerView() { - let bannerView: BannerView = BannerView.instantiateFromNib() - + bannerView = BannerView.instantiateFromNib() + bannerView.refreshInterface(with: .collaborationRetirement) bannerView.onPress = { print("# Click me") } view.addSubview(bannerView) + } + @objc + func setupViewContraints() { bannerView.translatesAutoresizingMaskIntoConstraints = false primaryTableView.translatesAutoresizingMaskIntoConstraints = false entryFieldBackground.translatesAutoresizingMaskIntoConstraints = false @@ -34,7 +37,7 @@ extension SPAddCollaboratorsViewController { NSLayoutConstraint.activate([ entryFieldBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor), entryFieldBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor), - entryFieldBackground.heightAnchor.constraint(equalToConstant: 44) + entryFieldBackground.heightAnchor.constraint(equalToConstant: EntryListCellHeight) ]) NSLayoutConstraint.activate([ @@ -43,8 +46,5 @@ extension SPAddCollaboratorsViewController { primaryTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), primaryTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) - - bannerView.refreshInterface(with: .collaborationRetirement) - } } From 0a029e5727eb46f644db052139ad9b8aecabbbd0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 15:44:03 -0500 Subject: [PATCH 112/547] Added todo for collaborators banner view --- Simplenote/SPAddCollaboratorsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/SPAddCollaboratorsViewController.swift b/Simplenote/SPAddCollaboratorsViewController.swift index 2d8153214..23e14fe8b 100644 --- a/Simplenote/SPAddCollaboratorsViewController.swift +++ b/Simplenote/SPAddCollaboratorsViewController.swift @@ -15,7 +15,7 @@ extension SPAddCollaboratorsViewController { bannerView = BannerView.instantiateFromNib() bannerView.refreshInterface(with: .collaborationRetirement) bannerView.onPress = { - print("# Click me") + //TODO: load blog post for collaborator retirement details } view.addSubview(bannerView) From b5ff293fd2290877a3800e23623fe60795f7ee26 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 15:47:02 -0500 Subject: [PATCH 113/547] Moved setup of banner view tap recognizer to wakeFromNib --- Simplenote/BannerView.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Simplenote/BannerView.swift b/Simplenote/BannerView.swift index 4ea2d33e6..9d562a06d 100644 --- a/Simplenote/BannerView.swift +++ b/Simplenote/BannerView.swift @@ -27,11 +27,7 @@ class BannerView: UIView { } } - var onPress: (() -> Void)? { - didSet { - setupTapRecognizer() - } - } + var onPress: (() -> Void)? var preferredWidth: CGFloat? { didSet { @@ -48,6 +44,7 @@ class BannerView: UIView { override func awakeFromNib() { super.awakeFromNib() refreshInterface() + setupTapRecognizer() } // MARK: - Actions From fc7f5c598381c13d093df00ac95a084556cb48fb Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 15:49:52 -0500 Subject: [PATCH 114/547] Moved BannerView style to exist in the BannerView namespace --- Simplenote/BannerView.swift | 51 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/Simplenote/BannerView.swift b/Simplenote/BannerView.swift index 9d562a06d..2b5a6afcc 100644 --- a/Simplenote/BannerView.swift +++ b/Simplenote/BannerView.swift @@ -59,7 +59,7 @@ class BannerView: UIView { backgroundView.addGestureRecognizer(tapRecognizer) } - func refreshInterface(with style: BannerStyle? = nil) { + func refreshInterface(with style: BannerView.Style? = nil) { guard let style else { return } @@ -71,36 +71,35 @@ class BannerView: UIView { backgroundView.backgroundColor = style.backgroundColor backgroundView.layer.cornerRadius = Metrics.defaultCornerRadius } -} -// MARK: - Style -// -struct BannerStyle { - let title: String - let details: String - let textColor: UIColor - let backgroundColor: UIColor -} + // MARK: - Style + // + struct Style { + let title: String + let details: String + let textColor: UIColor + let backgroundColor: UIColor + + // 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"), + textColor: .white, + backgroundColor: .simplenoteSustainerViewBackgroundColor) + } -extension BannerStyle { - // Leaving these styles in cause we may want them back someday - static var sustainer: BannerStyle { - BannerStyle(title: NSLocalizedString("You are a Simplenote Sustainer", comment: "Current Sustainer Title"), - details: NSLocalizedString("Thank you for your continued support", comment: "Current Sustainer Details"), - textColor: .white, - backgroundColor: .simplenoteSustainerViewBackgroundColor) - } + static var notSubscriber: Style { + Style(title: NSLocalizedString("Become a Simplenote Sustainer", comment: "Become a Sustainer Title"), + details: NSLocalizedString("Support your favorite notes app to help unlock future features", comment: "Become a Sustainer Details"), + textColor: .white, + backgroundColor: .simplenoteBlue50Color) + } - static var notSubscriber: BannerStyle { - BannerStyle(title: NSLocalizedString("Become a Simplenote Sustainer", comment: "Become a Sustainer Title"), - details: NSLocalizedString("Support your favorite notes app to help unlock future features", comment: "Become a Sustainer Details"), - textColor: .white, - backgroundColor: .simplenoteBlue50Color) + static var collaborationRetirement: Style { + Style(title: NSLocalizedString("Collaboration Retirement", comment: "Title annoucning collaboration retirement"), details: NSLocalizedString("Collaboration is being retired and will be disabled for all users July 1st. For more details tap here", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) + } } - static var collaborationRetirement: BannerStyle { - BannerStyle(title: NSLocalizedString("Collaboration Retirement", comment: "Title annoucning collaboration retirement"), details: NSLocalizedString("Collaboration is being retired and will be disabled for all users July 1st. For more details tap here", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) - } } // MARK: - Metrics From e58f252af5bbdaac9a87bd0cc8ff00820bcf139d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 15:56:28 -0500 Subject: [PATCH 115/547] Added url to collaborator retirement post --- Simplenote/SPAddCollaboratorsViewController.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Simplenote/SPAddCollaboratorsViewController.swift b/Simplenote/SPAddCollaboratorsViewController.swift index 23e14fe8b..8b370606f 100644 --- a/Simplenote/SPAddCollaboratorsViewController.swift +++ b/Simplenote/SPAddCollaboratorsViewController.swift @@ -15,7 +15,11 @@ extension SPAddCollaboratorsViewController { bannerView = BannerView.instantiateFromNib() bannerView.refreshInterface(with: .collaborationRetirement) bannerView.onPress = { - //TODO: load blog post for collaborator retirement details + guard let url = URL(string: "https://simplenote.com/2024/05/01/collaboration-feature-retirement") else { + return + } + + UIApplication.shared.open(url) } view.addSubview(bannerView) From 1088ed4239b31b2472b0574f7412dd891dda2688 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Apr 2024 15:57:53 -0500 Subject: [PATCH 116/547] Updated banner view collaboration retirement message --- Simplenote/BannerView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/BannerView.swift b/Simplenote/BannerView.swift index 2b5a6afcc..0b772e9f7 100644 --- a/Simplenote/BannerView.swift +++ b/Simplenote/BannerView.swift @@ -96,7 +96,7 @@ class BannerView: UIView { } static var collaborationRetirement: Style { - Style(title: NSLocalizedString("Collaboration Retirement", comment: "Title annoucning collaboration retirement"), details: NSLocalizedString("Collaboration is being retired and will be disabled for all users July 1st. For more details tap here", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) + Style(title: NSLocalizedString("Collaboration Retirement", comment: "Title annoucning collaboration retirement"), details: NSLocalizedString("Collaboration is retiring on July 1st. For more details tap here.", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) } } From 50face7cb2296ac65f9dc2a3f876c0f80986f8e3 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 29 Apr 2024 11:52:45 -0600 Subject: [PATCH 117/547] Added privacy manifest details to Simplenote project --- Simplenote.xcodeproj/project.pbxproj | 12 +++++++ Simplenote/Resources/PrivacyInfo.xcprivacy | 33 +++++++++++++++++++ .../Supporting Files/PrivacyInfo.xcprivacy | 17 ++++++++++ .../Supporting Files/PrivacyInfo.xcprivacy | 17 ++++++++++ 4 files changed, 79 insertions(+) create mode 100644 Simplenote/Resources/PrivacyInfo.xcprivacy create mode 100644 SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy create mode 100644 SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 56300cea5..e6cf3d060 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -462,6 +462,9 @@ 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 */; }; 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 */; }; @@ -1145,6 +1148,9 @@ 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 = ""; }; BA6DA19026DB5F1B000464C8 /* URLComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponents.swift; sourceTree = ""; }; BA7071E426BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ListWidgetIntent.intentdefinition; sourceTree = ""; }; BA75D87D26C0843600883FFA /* Text+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+Simplenote.swift"; sourceTree = ""; }; @@ -1395,6 +1401,7 @@ BA8661D526B3A00B00466746 /* SimplenoteIntents-DistributionAlpha.entitlements */, BA8661D826B3A0B000466746 /* SimplenoteIntents-DistributionInternal.entitlements */, BA8661D726B3A0A000466746 /* SimplenoteIntents-Release.entitlements */, + BA69A51C2BE015BF0096E50F /* PrivacyInfo.xcprivacy */, ); path = "Supporting Files"; sourceTree = ""; @@ -2357,6 +2364,7 @@ 241709C7266829CD00F6E2B1 /* SimplenoteWidgetsExtension-DistributionAlpha.entitlements */, 241709D126682AED00F6E2B1 /* SimplenoteWidgetsExtension-DistributionInternal.entitlements */, 241709BB266827A800F6E2B1 /* SimplenoteWidgetsExtension-Release.entitlements */, + BA69A51A2BE015640096E50F /* PrivacyInfo.xcprivacy */, ); path = "Supporting Files"; sourceTree = ""; @@ -2693,6 +2701,7 @@ B5767F9022FDF2B900052D81 /* LaunchScreen.storyboard */, E280453E180DAE0200670073 /* Localizable.strings */, 467D9C5C1788A4FB00785EF3 /* Simplenote.xcdatamodeld */, + BA69A5182BE0127C0096E50F /* PrivacyInfo.xcprivacy */, ); path = Resources; sourceTree = ""; @@ -3049,6 +3058,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 */, @@ -3099,6 +3109,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BA69A51D2BE015BF0096E50F /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3106,6 +3117,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BA69A51B2BE015640096E50F /* PrivacyInfo.xcprivacy in Resources */, BAFA93E1265DCFCD0009DCFB /* Assets.xcassets in Resources */, BAFA93FA265DE8920009DCFB /* Images.xcassets in Resources */, ); diff --git a/Simplenote/Resources/PrivacyInfo.xcprivacy b/Simplenote/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..8ee414ce4 --- /dev/null +++ b/Simplenote/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,33 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + 3B52.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 7D9E.1 + + + + + diff --git a/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..dd6881b47 --- /dev/null +++ b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + + diff --git a/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..dd6881b47 --- /dev/null +++ b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + + From 6cc0da4ca9a4db5d70581f23fdb4d17e2ce25436 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 29 Apr 2024 12:06:25 -0600 Subject: [PATCH 118/547] Updated gem file --- Gemfile.lock | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8f14d3098..cddfae860 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM base64 nkf rexml - activesupport (7.1.3) + activesupport (7.1.3.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -24,25 +24,25 @@ GEM ast (2.4.2) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.911.0) - aws-sdk-core (3.191.6) + aws-partitions (1.921.0) + aws-sdk-core (3.193.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.78.0) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-kms (1.80.0) + aws-sdk-core (~> 3, >= 3.193.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.146.1) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-s3 (1.148.0) + aws-sdk-core (~> 3, >= 3.193.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) - bigdecimal (3.1.6) - buildkit (1.5.0) + bigdecimal (3.1.7) + buildkit (1.6.0) sawyer (>= 0.6) chroma (0.2.0) claide (1.1.0) @@ -137,8 +137,7 @@ GEM rake (>= 12.0.0, < 14.0.0) domain_name (0.6.20240107) dotenv (2.8.1) - drb (2.2.0) - ruby2_keywords + drb (2.2.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) @@ -283,7 +282,7 @@ GEM http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) java-properties (0.3.0) jmespath (1.6.2) @@ -297,7 +296,7 @@ GEM language_server-protocol (3.17.0.3) mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.22.2) + minitest (5.22.3) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.4.0) @@ -308,16 +307,16 @@ GEM netrc (0.11.0) nkf (0.2.0) no_proxy_fix (0.1.2) - nokogiri (1.15.4-arm64-darwin) + nokogiri (1.15.6-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.15.6-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.4.0) + optparse (0.5.0) os (1.1.4) ox (2.14.17) parallel (1.24.0) @@ -332,7 +331,7 @@ GEM racc (1.7.3) rainbow (3.1.1) rake (13.2.1) - rake-compiler (1.2.5) + rake-compiler (1.2.7) rake rchardet (1.8.0) regexp_parser (2.9.0) From 1f4fc831f1ae179fba50b1d8d7c570ff13d9508e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 29 Apr 2024 12:28:39 -0600 Subject: [PATCH 119/547] updated gemfile.lock --- Gemfile.lock | 46 +++++++++++++++------------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index cddfae860..1cd1c53e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,12 +50,12 @@ GEM cork nap open4 (~> 1.3) - cocoapods (1.14.1) + 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) @@ -68,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) @@ -79,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) @@ -108,29 +108,16 @@ GEM no_proxy_fix octokit (>= 4.0) terminal-table (>= 1, < 4) - danger-dangermattic (1.0.0) + danger-dangermattic (1.0.2) danger (~> 9.4) - danger-junit (~> 1.0) danger-plugin-api (~> 1.0) danger-rubocop (~> 0.12) - danger-swiftlint (~> 0.35) - danger-xcode_summary (~> 1.0) - rubocop (~> 1.60) - danger-junit (1.0.2) - danger (> 2.0) - ox (~> 2.0) + rubocop (~> 1.61) danger-plugin-api (1.0.0) danger (> 2.0) danger-rubocop (0.12.0) danger rubocop (~> 1.0) - danger-swiftlint (0.35.0) - danger - rake (> 10) - thor (~> 1.0.0) - danger-xcode_summary (1.3.0) - danger-plugin-api (~> 1.0) - xcresult (~> 0.2) declarative (0.0.20) diffy (3.4.2) digest-crc (0.6.5) @@ -216,7 +203,7 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-appcenter (1.11.1) - fastlane-plugin-sentry (1.14.0) + fastlane-plugin-sentry (1.22.0) os (~> 1.1, >= 1.1.4) fastlane-plugin-wpmreleasetoolkit (9.4.0) activesupport (>= 6.1.7.1) @@ -318,9 +305,8 @@ GEM options (2.3.2) optparse (0.5.0) os (1.1.4) - ox (2.14.17) parallel (1.24.0) - parser (3.3.0.5) + parser (3.3.1.0) ast (~> 2.4.1) racc plist (3.7.1) @@ -343,7 +329,7 @@ GEM rexml (3.2.6) rmagick (3.2.0) rouge (2.0.7) - rubocop (1.60.2) + rubocop (1.63.4) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -351,11 +337,11 @@ GEM rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.30.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) ruby-macho (2.5.1) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) @@ -375,13 +361,12 @@ GEM terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - thor (1.0.1) trailblazer-option (0.1.2) tty-cursor (0.7.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) @@ -399,7 +384,6 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) - xcresult (0.2.1) PLATFORMS arm64-darwin-21 @@ -417,4 +401,4 @@ DEPENDENCIES rmagick (~> 3.2.0) BUNDLED WITH - 2.4.13 + 2.5.4 From 8d9231941b4cc18fdacee95fc0e11aab0b4273ca Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 29 Apr 2024 12:35:20 -0600 Subject: [PATCH 120/547] updated gems --- Gemfile | 4 ++-- Gemfile.lock | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index b78fff34a..a2653bc3a 100644 --- a/Gemfile +++ b/Gemfile @@ -6,9 +6,9 @@ source 'https://rubygems.org' 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 'fastlane-plugin-wpmreleasetoolkit', '~> 11.0.1' group :screenshots, optional: true do gem 'rmagick', '~> 3.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 1cd1c53e0..5f2447ee7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -202,10 +202,10 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) - fastlane-plugin-appcenter (1.11.1) + fastlane-plugin-appcenter (2.1.2) fastlane-plugin-sentry (1.22.0) os (~> 1.1, >= 1.1.4) - fastlane-plugin-wpmreleasetoolkit (9.4.0) + fastlane-plugin-wpmreleasetoolkit (11.0.1) activesupport (>= 6.1.7.1) buildkit (~> 1.5) chroma (= 0.2.0) @@ -214,7 +214,7 @@ GEM git (~> 1.3) google-cloud-storage (~> 1.31) java-properties (~> 0.3.0) - nokogiri (~> 1.11, < 1.16) + nokogiri (~> 1.11, < 1.17) octokit (~> 6.1) parallel (~> 1.14) plist (~> 3.1) @@ -294,9 +294,9 @@ GEM netrc (0.11.0) nkf (0.2.0) no_proxy_fix (0.1.2) - nokogiri (1.15.6-arm64-darwin) + nokogiri (1.16.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.6-x86_64-linux) + nokogiri (1.16.4-x86_64-linux) racc (~> 1.4) octokit (6.1.1) faraday (>= 1, < 3) @@ -395,9 +395,9 @@ 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 (~> 11.0.1) rmagick (~> 3.2.0) BUNDLED WITH From bd5f56ea61e805e3d04fa10c10abcf7c98f4cb61 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 30 Apr 2024 10:03:47 -0600 Subject: [PATCH 121/547] Added privacy nutrition label info --- Simplenote/Resources/PrivacyInfo.xcprivacy | 63 +++++++++++++++++++ .../Supporting Files/PrivacyInfo.xcprivacy | 63 +++++++++++++++++++ .../Supporting Files/PrivacyInfo.xcprivacy | 63 +++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/Simplenote/Resources/PrivacyInfo.xcprivacy b/Simplenote/Resources/PrivacyInfo.xcprivacy index 8ee414ce4..3aa90c0cf 100644 --- a/Simplenote/Resources/PrivacyInfo.xcprivacy +++ b/Simplenote/Resources/PrivacyInfo.xcprivacy @@ -2,6 +2,69 @@ + 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 diff --git a/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy index dd6881b47..e56fe8190 100644 --- a/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy +++ b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy @@ -2,6 +2,69 @@ + 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 diff --git a/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy index dd6881b47..e56fe8190 100644 --- a/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy +++ b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy @@ -2,6 +2,69 @@ + 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 From 3fcefe72066019e4c03f3cef2da34402ec71b52e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 30 Apr 2024 10:28:45 -0600 Subject: [PATCH 122/547] Updated privacy info to mark collected data as linked to user --- Simplenote/Resources/PrivacyInfo.xcprivacy | 6 +- .../Supporting Files/PrivacyInfo.xcprivacy | 126 +++++++++--------- .../Supporting Files/PrivacyInfo.xcprivacy | 126 +++++++++--------- 3 files changed, 129 insertions(+), 129 deletions(-) diff --git a/Simplenote/Resources/PrivacyInfo.xcprivacy b/Simplenote/Resources/PrivacyInfo.xcprivacy index 3aa90c0cf..374b5efe9 100644 --- a/Simplenote/Resources/PrivacyInfo.xcprivacy +++ b/Simplenote/Resources/PrivacyInfo.xcprivacy @@ -8,7 +8,7 @@ NSPrivacyCollectedDataType NSPrivacyCollectedDataTypeOtherDiagnosticData NSPrivacyCollectedDataTypeLinked - + NSPrivacyCollectedDataTypeTracking NSPrivacyCollectedDataTypePurposes @@ -20,7 +20,7 @@ NSPrivacyCollectedDataType NSPrivacyCollectedDataTypePerformanceData NSPrivacyCollectedDataTypeLinked - + NSPrivacyCollectedDataTypeTracking NSPrivacyCollectedDataTypePurposes @@ -32,7 +32,7 @@ NSPrivacyCollectedDataType NSPrivacyCollectedDataTypeCrashData NSPrivacyCollectedDataTypeLinked - + NSPrivacyCollectedDataTypeTracking NSPrivacyCollectedDataTypePurposes diff --git a/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy index e56fe8190..321483656 100644 --- a/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy +++ b/SimplenoteIntents/Supporting Files/PrivacyInfo.xcprivacy @@ -2,69 +2,69 @@ - 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 - - - + 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 diff --git a/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy index e56fe8190..321483656 100644 --- a/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy +++ b/SimplenoteWidgets/Supporting Files/PrivacyInfo.xcprivacy @@ -2,69 +2,69 @@ - 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 - - - + 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 From 6260e3b07559adc2ab81bd5824351a240fb27d64 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 30 Apr 2024 10:37:15 -0600 Subject: [PATCH 123/547] Update Simplenote/BannerView.swift copy to make it the same as other apps Co-authored-by: Dan Roundhill --- Simplenote/BannerView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/BannerView.swift b/Simplenote/BannerView.swift index 0b772e9f7..d5e602cff 100644 --- a/Simplenote/BannerView.swift +++ b/Simplenote/BannerView.swift @@ -96,7 +96,7 @@ class BannerView: UIView { } static var collaborationRetirement: Style { - Style(title: NSLocalizedString("Collaboration Retirement", comment: "Title annoucning collaboration retirement"), details: NSLocalizedString("Collaboration is retiring on July 1st. For more details tap here.", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) + Style(title: NSLocalizedString("Collaboration Retirement", comment: "Title announcing collaboration retirement"), details: NSLocalizedString("Collaboration is retiring on July 1st, 2024. For more details, tap here.", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) } } From 7a3257e3a6c86bfd34d7dfb7f0e74d7b4e2cf77e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 30 Apr 2024 10:52:07 -0600 Subject: [PATCH 124/547] updated podfile.lock --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index 14f4c6907..bf8d40e2c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -53,4 +53,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 6fc187e283b4fb7f9f4a0ef73a27632724c90845 -COCOAPODS: 1.14.1 +COCOAPODS: 1.15.2 From fa11512ca8a5dfdd7ffe093695a859d0d598fc08 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 15:02:12 -0600 Subject: [PATCH 125/547] Drop registering NSUserActivity for shortcuts --- Simplenote/Classes/ShortcutsHandler.swift | 17 ----------------- Simplenote/SPAppDelegate+Extensions.swift | 1 - 2 files changed, 18 deletions(-) diff --git a/Simplenote/Classes/ShortcutsHandler.swift b/Simplenote/Classes/ShortcutsHandler.swift index 6ec70ace4..ae1a4d765 100644 --- a/Simplenote/Classes/ShortcutsHandler.swift +++ b/Simplenote/Classes/ShortcutsHandler.swift @@ -23,23 +23,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 diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index e33b1fa6c..f9bee8541 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -284,7 +284,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. From c6adbb45e72896878e5989a3cd3da060d398804e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 3 May 2024 14:35:27 -0600 Subject: [PATCH 126/547] Updated package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved index cd06d10ef..82138b2dd 100644 --- a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,12 +1,13 @@ { + "originHash" : "4c803bfc0eef41e640a11de86de8bd9cb6ec711e343dadfe7d14d904ae2a7283", "pins" : [ { "identity" : "automattic-tracks-ios", "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/Automattic-Tracks-iOS", "state" : { - "revision" : "b6979ef69b4b094c8809ba83661222dcd0d7667e", - "version" : "3.2.0" + "revision" : "bd981ad3a08c6af6113834eb62ee7466cb8aa71c", + "version" : "3.4.0" } }, { @@ -23,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/getsentry/sentry-cocoa", "state" : { - "revision" : "3b9a8e69ca296bd8cd0e317ad7a448e5daf4a342", - "version" : "8.18.0" + "revision" : "82af013792dca3784a2dc5e7f975159fb9d263b3", + "version" : "8.25.0" } }, { @@ -82,5 +83,5 @@ } } ], - "version" : 2 + "version" : 3 } From 50baa38cb80c54ffc96344aae99233ee0b386f95 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 15:02:59 -0600 Subject: [PATCH 127/547] Added shortcut intent files to simplenote and intent targets --- Simplenote.xcodeproj/project.pbxproj | 6 ++++++ .../ShortcutIntents.intentdefinition | 12 ++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 Simplenote/Supporting Files/ShortcutIntents.intentdefinition diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index c3ce139a8..7081a2014 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -436,6 +436,8 @@ 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 */; }; + BA289B582BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */; }; + BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */; }; 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 */; }; @@ -1127,6 +1129,7 @@ BA12B06C26B0D0150026F31D /* SPManagedObject+Widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SPManagedObject+Widget.swift"; sourceTree = ""; }; BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Simplenote 7.xcdatamodel"; sourceTree = ""; }; BA18532726488DBC00D9A347 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.swift; sourceTree = ""; }; + BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ShortcutIntents.intentdefinition; 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 = ""; }; BA3856CC2681715700F388CC /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; @@ -2618,6 +2621,7 @@ B5250A6B22B922F900AE7797 /* Simplenote-Internal.entitlements */, E29ADD4717848E8500E55842 /* main.m */, E29ADD4917848E8500E55842 /* Simplenote-Prefix.pch */, + BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */, ); path = "Supporting Files"; sourceTree = ""; @@ -3372,6 +3376,7 @@ B52BB74E22CFD1660042C162 /* SimplenoteActivityItemSource.swift in Sources */, B537730F252E14C600BC78C5 /* OptionsViewController.swift in Sources */, B56A696722F9D55F00B90398 /* UIView+Animations.swift in Sources */, + BA289B582BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, BA3856CD2681715700F388CC /* CoreDataManager.swift in Sources */, A60DF30825A44F0F00FDADF3 /* PinLockRemoveController.swift in Sources */, A694ABAB25D1549D00CC3A2D /* FileStorage.swift in Sources */, @@ -3728,6 +3733,7 @@ BAF8D51426AE172600CA9383 /* StorageSettings.swift in Sources */, BA86616326B35CF000466746 /* BuildConfiguration.swift in Sources */, BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */, + BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, BA86625D26B3B14900466746 /* SortMode.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAF4A9AA26DB138600C51C1D /* NoteContentHelper.swift in Sources */, diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition new file mode 100644 index 000000000..86b3c628c --- /dev/null +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -0,0 +1,12 @@ + + + + + INIntents + + + INEnums + + + + From 545a2e1880cf0ea32032458c001744321ccabd2e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 15:10:52 -0600 Subject: [PATCH 128/547] Refactored Widget intent handlers into their own class --- Simplenote.xcodeproj/project.pbxproj | 12 +++ SimplenoteIntents/IntentHandler.swift | 75 ++----------------- .../ListWidgetIntentHandler.swift | 45 +++++++++++ .../NoteWidgetIntentHandler.swift | 44 +++++++++++ 4 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 SimplenoteIntents/ListWidgetIntentHandler.swift create mode 100644 SimplenoteIntents/NoteWidgetIntentHandler.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 7081a2014..091438000 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -438,6 +438,10 @@ BA2015BB2B57384F005E59AA /* AutomatticTracks in Frameworks */ = {isa = PBXBuildFile; productRef = BA2015BA2B57384F005E59AA /* AutomatticTracks */; }; BA289B582BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */; }; BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */; }; + 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 */; }; 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 */; }; @@ -1130,6 +1134,8 @@ BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Simplenote 7.xcdatamodel"; sourceTree = ""; }; BA18532726488DBC00D9A347 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.swift; sourceTree = ""; }; BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ShortcutIntents.intentdefinition; 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 = ""; }; BA2D82C5261522F100A1695B /* PublishNoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishNoticePresenter.swift; sourceTree = ""; }; BA32A90E26B7469F00727247 /* WidgetError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetError.swift; sourceTree = ""; }; BA3856CC2681715700F388CC /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; @@ -2379,6 +2385,8 @@ children = ( 092FD78026D7BB72006BE8E2 /* Supporting Files */, BAB5762626703C8200B0C56F /* IntentHandler.swift */, + BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */, + BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */, BAF8D4AD26AE136D00CA9383 /* Simplenote-Intents-Bridging-Header.h */, ); path = SimplenoteIntents; @@ -3727,10 +3735,12 @@ files = ( BA86620E26B3A73D00466746 /* UserDefaults+Simplenote.swift in Sources */, BAF8D52826AE173D00CA9383 /* FileManager+Simplenote.swift in Sources */, + BA289B5F2BE43728000E6794 /* NoteWidgetIntentHandler.swift in Sources */, BAF8D50026AE171400CA9383 /* CoreDataManager.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 */, BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */, BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, @@ -3796,10 +3806,12 @@ 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 */, diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index d62dd97d0..1e53fb61f 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -2,76 +2,15 @@ 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() + 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/ListWidgetIntentHandler.swift b/SimplenoteIntents/ListWidgetIntentHandler.swift new file mode 100644 index 000000000..0cd9558d5 --- /dev/null +++ b/SimplenoteIntents/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 = WidgetCoreDataWrapper() + + 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/NoteWidgetIntentHandler.swift b/SimplenoteIntents/NoteWidgetIntentHandler.swift new file mode 100644 index 000000000..61d980050 --- /dev/null +++ b/SimplenoteIntents/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 = WidgetCoreDataWrapper() + + 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) + } +} From 220bd6f60306e9efa26d69406afc30015f39cb92 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 15:37:47 -0600 Subject: [PATCH 129/547] Added open new note intent and handler --- Simplenote.xcodeproj/project.pbxproj | 24 ++++-- Simplenote/Classes/ActivityType.swift | 1 + Simplenote/Classes/ShortcutsHandler.swift | 2 +- .../ShortcutIntents.intentdefinition | 75 +++++++++++++++++-- .../Supporting Files/Simplenote-Info.plist | 1 + .../ListWidgetIntentHandler.swift | 0 .../NoteWidgetIntentHandler.swift | 0 .../OpenNewNoteIntentHandler.swift | 15 ++++ SimplenoteIntents/IntentHandler.swift | 2 + SimplenoteIntents/Supporting Files/Info.plist | 1 + 10 files changed, 106 insertions(+), 15 deletions(-) rename SimplenoteIntents/{ => Intent Handlers}/ListWidgetIntentHandler.swift (100%) rename SimplenoteIntents/{ => Intent Handlers}/NoteWidgetIntentHandler.swift (100%) create mode 100644 SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 091438000..891e745ed 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -442,8 +442,9 @@ 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 */; }; + BA289B642BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */; }; + BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.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 */; }; @@ -513,7 +514,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 */; }; @@ -562,7 +562,6 @@ 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 */; }; D435D38FCA5863E8447D5C2C /* Pods_Automattic_SimplenoteShare.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 183BAB896957F07FDCAB6DF5 /* Pods_Automattic_SimplenoteShare.framework */; }; @@ -1136,6 +1135,7 @@ BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ShortcutIntents.intentdefinition; 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 = ""; }; BA2D82C5261522F100A1695B /* PublishNoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishNoticePresenter.swift; sourceTree = ""; }; BA32A90E26B7469F00727247 /* WidgetError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetError.swift; sourceTree = ""; }; BA3856CC2681715700F388CC /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; @@ -2319,6 +2319,16 @@ path = Providers; sourceTree = ""; }; + BA289B622BE43949000E6794 /* Intent Handlers */ = { + isa = PBXGroup; + children = ( + BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */, + BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */, + BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */, + ); + path = "Intent Handlers"; + sourceTree = ""; + }; BA3FB8CD25FEA09F00EA9A1B /* Notice */ = { isa = PBXGroup; children = ( @@ -2385,8 +2395,7 @@ children = ( 092FD78026D7BB72006BE8E2 /* Supporting Files */, BAB5762626703C8200B0C56F /* IntentHandler.swift */, - BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */, - BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */, + BA289B622BE43949000E6794 /* Intent Handlers */, BAF8D4AD26AE136D00CA9383 /* Simplenote-Intents-Bridging-Header.h */, ); path = SimplenoteIntents; @@ -3436,7 +3445,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 */, @@ -3554,7 +3562,6 @@ 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 */, @@ -3573,6 +3580,7 @@ B5AEC38423FAC5D600D24221 /* DateFormatter+Simplenote.swift in Sources */, B5DF734622A5713600602CE7 /* SortMode.swift in Sources */, BA55B05A25F067DF0042582B /* NoticePresenter.swift in Sources */, + BA289B642BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, 46A3C99D17DFA81A002865AE /* SPAddCollaboratorsViewController.m in Sources */, B52646AA22D3E04C00EBF299 /* UIViewController+Simplenote.swift in Sources */, B55E428C22A1A4550018C0CE /* SPSortOrderViewController.swift in Sources */, @@ -3743,6 +3751,7 @@ BA289B5C2BE4371A000E6794 /* ListWidgetIntentHandler.swift in Sources */, BA86616326B35CF000466746 /* BuildConfiguration.swift in Sources */, BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */, + BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, BA86625D26B3B14900466746 /* SortMode.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, @@ -3830,7 +3839,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 */, ); diff --git a/Simplenote/Classes/ActivityType.swift b/Simplenote/Classes/ActivityType.swift index f115d11e6..02270a00c 100644 --- a/Simplenote/Classes/ActivityType.swift +++ b/Simplenote/Classes/ActivityType.swift @@ -11,6 +11,7 @@ enum ActivityType: String { /// New Note Activity /// case newNote = "com.codality.NotationalFlow.newNote" + case newNoteShortcut = "SPOpenNewNoteIntent" /// Open a Note! /// diff --git a/Simplenote/Classes/ShortcutsHandler.swift b/Simplenote/Classes/ShortcutsHandler.swift index ae1a4d765..6ce9a5887 100644 --- a/Simplenote/Classes/ShortcutsHandler.swift +++ b/Simplenote/Classes/ShortcutsHandler.swift @@ -47,7 +47,7 @@ class ShortcutsHandler: NSObject { switch type { case .launch: break - case .newNote: + case .newNote, .newNoteShortcut: SPAppDelegate.shared().presentNewNoteEditor() case .openNote, .openSpotlightItem: presentNote(for: userActivity) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 86b3c628c..d8319b130 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -2,11 +2,74 @@ - INIntents - - - INEnums - - + INEnums + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + BxdWY5 + INIntentDefinitionSystemVersion + 23E214 + INIntentDefinitionToolsBuildVersion + 15E204a + INIntentDefinitionToolsVersion + 15.3 + INIntents + + + INIntentCategory + generic + INIntentClassPrefix + SP + INIntentConfigurable + + 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 + + + INTypes + diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index 895a310de..b437e5e1d 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -76,6 +76,7 @@ ListWidgetIntent NoteWidgetIntent + SPOpenNewNoteIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/SimplenoteIntents/ListWidgetIntentHandler.swift b/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift similarity index 100% rename from SimplenoteIntents/ListWidgetIntentHandler.swift rename to SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift diff --git a/SimplenoteIntents/NoteWidgetIntentHandler.swift b/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift similarity index 100% rename from SimplenoteIntents/NoteWidgetIntentHandler.swift rename to SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift diff --git a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift new file mode 100644 index 000000000..96775f6a2 --- /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, SPOpenNewNoteIntentHandling { + func handle(intent: SPOpenNewNoteIntent) async -> SPOpenNewNoteIntentResponse { + SPOpenNewNoteIntentResponse(code: .continueInApp, userActivity: nil) + } +} diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index 1e53fb61f..5beb00946 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -9,6 +9,8 @@ class IntentHandler: INExtension { return NoteWidgetIntentHandler() case is ListWidgetIntent: return ListWidgetIntentHandler() + case is SPOpenNewNoteIntent: + return OpenNewNoteIntentHandler() default: return self } diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index f632dc379..9c12557fb 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -32,6 +32,7 @@ ListWidgetIntent NoteWidgetIntent + SPOpenNewNoteIntent NSExtensionPointIdentifier From ed8495ce19e687f40f32d03f94c24b0aff5b6938 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 17:32:53 -0600 Subject: [PATCH 130/547] Added open note intent --- Simplenote.xcodeproj/project.pbxproj | 2 - Simplenote/Classes/ActivityType.swift | 1 + Simplenote/Classes/ShortcutsHandler.swift | 13 ++ .../ShortcutIntents.intentdefinition | 188 +++++++++++++++++- .../Supporting Files/Simplenote-Info.plist | 1 + .../OpenNewNoteIntentHandler.swift | 35 ++++ SimplenoteIntents/IntentHandler.swift | 2 + SimplenoteIntents/Supporting Files/Info.plist | 1 + 8 files changed, 240 insertions(+), 3 deletions(-) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 891e745ed..4a97afb8e 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -442,7 +442,6 @@ 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 */; }; - BA289B642BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */; }; BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */; }; BA2D82C6261522F100A1695B /* PublishNoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2D82C5261522F100A1695B /* PublishNoticePresenter.swift */; }; BA32A90F26B7469F00727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; @@ -3580,7 +3579,6 @@ B5AEC38423FAC5D600D24221 /* DateFormatter+Simplenote.swift in Sources */, B5DF734622A5713600602CE7 /* SortMode.swift in Sources */, BA55B05A25F067DF0042582B /* NoticePresenter.swift in Sources */, - BA289B642BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, 46A3C99D17DFA81A002865AE /* SPAddCollaboratorsViewController.m in Sources */, B52646AA22D3E04C00EBF299 /* UIViewController+Simplenote.swift in Sources */, B55E428C22A1A4550018C0CE /* SPSortOrderViewController.swift in Sources */, diff --git a/Simplenote/Classes/ActivityType.swift b/Simplenote/Classes/ActivityType.swift index 02270a00c..e3c066138 100644 --- a/Simplenote/Classes/ActivityType.swift +++ b/Simplenote/Classes/ActivityType.swift @@ -16,6 +16,7 @@ enum ActivityType: String { /// Open a Note! /// case openNote = "com.codality.NotationalFlow.openNote" + case openNoteShortcut = "SPOpenNoteIntent" /// Open an Item that was indexed by Spotlight /// diff --git a/Simplenote/Classes/ShortcutsHandler.swift b/Simplenote/Classes/ShortcutsHandler.swift index 6ce9a5887..5def63a65 100644 --- a/Simplenote/Classes/ShortcutsHandler.swift +++ b/Simplenote/Classes/ShortcutsHandler.swift @@ -1,5 +1,6 @@ import Foundation import CoreSpotlight +import Intents // MARK: - AppDelegate Shortcuts Methods // @@ -51,6 +52,8 @@ class ShortcutsHandler: NSObject { SPAppDelegate.shared().presentNewNoteEditor() case .openNote, .openSpotlightItem: presentNote(for: userActivity) + case .openNoteShortcut: + presentNote(for: userActivity.interaction) } return true @@ -155,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?["OpenNoteIntentHandlerIdentifierKey"] as? String else { + return + } + + SPAppDelegate.shared().presentNoteWithSimperiumKey(uniqueIdentifier) + } } diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index d8319b130..ba5aedc51 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -68,8 +68,194 @@ INIntentVerb Do + + INIntentCategory + information + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescription + Open Note in Simplenote + INIntentDescriptionID + sqM3pN + INIntentIneligibleForSuggestions + + 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 + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${note}’. + INIntentParameterPromptDialogFormatStringID + n3Yy85 + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${note}’? + INIntentParameterPromptDialogFormatStringID + v8y9Kh + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 2 + INIntentParameterType + Object + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Open Note + INIntentTitleID + IQnYW1 + INIntentType + Custom + INIntentVerb + Open + INTypes - + + + 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 + + + + diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index b437e5e1d..abcc93ef3 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -77,6 +77,7 @@ ListWidgetIntent NoteWidgetIntent SPOpenNewNoteIntent + SPOpenNoteIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift index 96775f6a2..f9cb5bbe3 100644 --- a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift @@ -13,3 +13,38 @@ class OpenNewNoteIntentHandler: NSObject, SPOpenNewNoteIntentHandling { SPOpenNewNoteIntentResponse(code: .continueInApp, userActivity: nil) } } + +class OpenNoteIntentHandler: NSObject, SPOpenNoteIntentHandling { + let coreDataWrapper = WidgetCoreDataWrapper() + + func resolveNote(for intent: SPOpenNoteIntent) async -> SPIntentNoteResolutionResult { + guard let identifier = intent.note?.identifier, + let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { + return SPIntentNoteResolutionResult.confirmationRequired(with: nil) + } + + return SPIntentNoteResolutionResult.success(with: SPIntentNote(identifier: note.simperiumKey, display: note.title)) + } + + func provideNoteOptionsCollection(for intent: SPOpenNoteIntent) async throws -> INObjectCollection { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + throw NSError(domain: "intents", code: 404) + } + + let intentNotes = notes.map({ + SPIntentNote(identifier: $0.simperiumKey, display: $0.title) + }) + return INObjectCollection(items: intentNotes) + } + + func handle(intent: SPOpenNoteIntent) async -> SPOpenNoteIntentResponse { + guard let identifier = intent.note?.identifier else { + return SPOpenNoteIntentResponse(code: .failure, userActivity: nil) + } + let activity = NSUserActivity(activityType: "SPOpenNoteIntent") + activity.userInfo = [Self.noteIdentifierKey: identifier] + return SPOpenNoteIntentResponse(code: .continueInApp, userActivity: activity) + } + + static let noteIdentifierKey = "OpenNoteIntentHandlerIdentifierKey" +} diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index 5beb00946..337138b1e 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -11,6 +11,8 @@ class IntentHandler: INExtension { return ListWidgetIntentHandler() case is SPOpenNewNoteIntent: return OpenNewNoteIntentHandler() + case is SPOpenNoteIntent: + return OpenNoteIntentHandler() default: return self } diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index 9c12557fb..5061d9aec 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -33,6 +33,7 @@ ListWidgetIntent NoteWidgetIntent SPOpenNewNoteIntent + SPOpenNoteIntent NSExtensionPointIdentifier From 7bab3bb70f44ac3a07f07b38b233f4d1645297ef Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 17:34:49 -0600 Subject: [PATCH 131/547] Added intents constants to save on duplication across targets --- Simplenote.xcodeproj/project.pbxproj | 6 ++++++ Simplenote/Classes/ShortcutsHandler.swift | 2 +- .../Intent Handlers/OpenNewNoteIntentHandler.swift | 4 +--- SimplenoteIntents/IntentsConstants.swift | 13 +++++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 SimplenoteIntents/IntentsConstants.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 4a97afb8e..1c53243c2 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -443,6 +443,8 @@ 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 */; }; BA2D82C6261522F100A1695B /* PublishNoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2D82C5261522F100A1695B /* PublishNoticePresenter.swift */; }; BA32A90F26B7469F00727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; BA32A91926B746A200727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; @@ -1135,6 +1137,7 @@ 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 = ""; }; BA2D82C5261522F100A1695B /* PublishNoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishNoticePresenter.swift; sourceTree = ""; }; BA32A90E26B7469F00727247 /* WidgetError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetError.swift; sourceTree = ""; }; BA3856CC2681715700F388CC /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; @@ -2394,6 +2397,7 @@ children = ( 092FD78026D7BB72006BE8E2 /* Supporting Files */, BAB5762626703C8200B0C56F /* IntentHandler.swift */, + BA289B702BE45A39000E6794 /* IntentsConstants.swift */, BA289B622BE43949000E6794 /* Intent Handlers */, BAF8D4AD26AE136D00CA9383 /* Simplenote-Intents-Bridging-Header.h */, ); @@ -3557,6 +3561,7 @@ 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 */, @@ -3748,6 +3753,7 @@ BAF8D51426AE172600CA9383 /* StorageSettings.swift in Sources */, BA289B5C2BE4371A000E6794 /* ListWidgetIntentHandler.swift in Sources */, BA86616326B35CF000466746 /* BuildConfiguration.swift in Sources */, + BA289B722BE45A39000E6794 /* IntentsConstants.swift in Sources */, BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */, BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, diff --git a/Simplenote/Classes/ShortcutsHandler.swift b/Simplenote/Classes/ShortcutsHandler.swift index 5def63a65..27b309d21 100644 --- a/Simplenote/Classes/ShortcutsHandler.swift +++ b/Simplenote/Classes/ShortcutsHandler.swift @@ -162,7 +162,7 @@ private extension ShortcutsHandler { func presentNote(for interaction: INInteraction?) { guard let interaction, let activity = interaction.intentResponse?.userActivity, - let uniqueIdentifier = activity.userInfo?["OpenNoteIntentHandlerIdentifierKey"] as? String else { + let uniqueIdentifier = activity.userInfo?[IntentsConstants.noteIdentifierKey] as? String else { return } diff --git a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift index f9cb5bbe3..a5a13de29 100644 --- a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift @@ -42,9 +42,7 @@ class OpenNoteIntentHandler: NSObject, SPOpenNoteIntentHandling { return SPOpenNoteIntentResponse(code: .failure, userActivity: nil) } let activity = NSUserActivity(activityType: "SPOpenNoteIntent") - activity.userInfo = [Self.noteIdentifierKey: identifier] + activity.userInfo = [IntentsConstants.noteIdentifierKey: identifier] return SPOpenNoteIntentResponse(code: .continueInApp, userActivity: activity) } - - static let noteIdentifierKey = "OpenNoteIntentHandlerIdentifierKey" } diff --git a/SimplenoteIntents/IntentsConstants.swift b/SimplenoteIntents/IntentsConstants.swift new file mode 100644 index 000000000..18e8bfdb8 --- /dev/null +++ b/SimplenoteIntents/IntentsConstants.swift @@ -0,0 +1,13 @@ +// +// 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" +} From 2894b96a510f097286cd87f7c74ecceccac22891 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 17:39:16 -0600 Subject: [PATCH 132/547] Dropped SP leading class name on new intent classes --- .../ShortcutIntents.intentdefinition | 6 +++++ .../Supporting Files/Simplenote-Info.plist | 4 ++-- .../OpenNewNoteIntentHandler.swift | 24 +++++++++---------- SimplenoteIntents/IntentHandler.swift | 4 ++-- SimplenoteIntents/Supporting Files/Info.plist | 4 ++-- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index ba5aedc51..646123ee2 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -19,6 +19,8 @@ INIntentCategory generic + INIntentClassName + OpenNewNoteIntent INIntentClassPrefix SP INIntentConfigurable @@ -71,6 +73,8 @@ INIntentCategory information + INIntentClassName + OpenNoteIntent INIntentClassPrefix SP INIntentConfigurable @@ -192,6 +196,8 @@ INTypes + INTypeClassName + IntentNote INTypeClassPrefix SP INTypeDisplayName diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index abcc93ef3..8d25ca1f2 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -76,8 +76,8 @@ ListWidgetIntent NoteWidgetIntent - SPOpenNewNoteIntent - SPOpenNoteIntent + OpenNewNoteIntent + OpenNoteIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift index a5a13de29..6dfcdf2a7 100644 --- a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift @@ -8,41 +8,41 @@ import Intents -class OpenNewNoteIntentHandler: NSObject, SPOpenNewNoteIntentHandling { - func handle(intent: SPOpenNewNoteIntent) async -> SPOpenNewNoteIntentResponse { - SPOpenNewNoteIntentResponse(code: .continueInApp, userActivity: nil) +class OpenNewNoteIntentHandler: NSObject, OpenNewNoteIntentHandling { + func handle(intent: OpenNewNoteIntent) async -> OpenNewNoteIntentResponse { + OpenNewNoteIntentResponse(code: .continueInApp, userActivity: nil) } } -class OpenNoteIntentHandler: NSObject, SPOpenNoteIntentHandling { +class OpenNoteIntentHandler: NSObject, OpenNoteIntentHandling { let coreDataWrapper = WidgetCoreDataWrapper() - func resolveNote(for intent: SPOpenNoteIntent) async -> SPIntentNoteResolutionResult { + func resolveNote(for intent: OpenNoteIntent) async -> IntentNoteResolutionResult { guard let identifier = intent.note?.identifier, let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { - return SPIntentNoteResolutionResult.confirmationRequired(with: nil) + return IntentNoteResolutionResult.confirmationRequired(with: nil) } - return SPIntentNoteResolutionResult.success(with: SPIntentNote(identifier: note.simperiumKey, display: note.title)) + return IntentNoteResolutionResult.success(with: IntentNote(identifier: note.simperiumKey, display: note.title)) } - func provideNoteOptionsCollection(for intent: SPOpenNoteIntent) async throws -> INObjectCollection { + func provideNoteOptionsCollection(for intent: OpenNoteIntent) async throws -> INObjectCollection { guard let notes = coreDataWrapper.resultsController()?.notes() else { throw NSError(domain: "intents", code: 404) } let intentNotes = notes.map({ - SPIntentNote(identifier: $0.simperiumKey, display: $0.title) + IntentNote(identifier: $0.simperiumKey, display: $0.title) }) return INObjectCollection(items: intentNotes) } - func handle(intent: SPOpenNoteIntent) async -> SPOpenNoteIntentResponse { + func handle(intent: OpenNoteIntent) async -> OpenNoteIntentResponse { guard let identifier = intent.note?.identifier else { - return SPOpenNoteIntentResponse(code: .failure, userActivity: nil) + return OpenNoteIntentResponse(code: .failure, userActivity: nil) } let activity = NSUserActivity(activityType: "SPOpenNoteIntent") activity.userInfo = [IntentsConstants.noteIdentifierKey: identifier] - return SPOpenNoteIntentResponse(code: .continueInApp, userActivity: activity) + return OpenNoteIntentResponse(code: .continueInApp, userActivity: activity) } } diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index 337138b1e..cb357191a 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -9,9 +9,9 @@ class IntentHandler: INExtension { return NoteWidgetIntentHandler() case is ListWidgetIntent: return ListWidgetIntentHandler() - case is SPOpenNewNoteIntent: + case is OpenNewNoteIntent: return OpenNewNoteIntentHandler() - case is SPOpenNoteIntent: + case is OpenNoteIntent: return OpenNoteIntentHandler() default: return self diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index 5061d9aec..a7c260143 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -32,8 +32,8 @@ ListWidgetIntent NoteWidgetIntent - SPOpenNewNoteIntent - SPOpenNoteIntent + OpenNewNoteIntent + OpenNoteIntent NSExtensionPointIdentifier From 721b42e7f04c8f78db8ca2be0e435992f29fc657 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 2 May 2024 17:41:59 -0600 Subject: [PATCH 133/547] Moved open note intent handler to its own file --- Simplenote.xcodeproj/project.pbxproj | 6 +++ .../OpenNewNoteIntentHandler.swift | 33 --------------- .../OpenNoteIntentHandler.swift | 42 +++++++++++++++++++ 3 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 1c53243c2..046f25fd1 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -445,6 +445,8 @@ 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 */; }; BA32A90F26B7469F00727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; BA32A91926B746A200727247 /* WidgetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32A90E26B7469F00727247 /* WidgetError.swift */; }; @@ -1138,6 +1140,7 @@ 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 = ""; }; BA3856CC2681715700F388CC /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; @@ -2327,6 +2330,7 @@ BA289B5D2BE43728000E6794 /* NoteWidgetIntentHandler.swift */, BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */, BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */, + BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */, ); path = "Intent Handlers"; sourceTree = ""; @@ -3760,9 +3764,11 @@ BA86625D26B3B14900466746 /* SortMode.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAF4A9AA26DB138600C51C1D /* NoteContentHelper.swift in Sources */, + BA289B782BE45BFB000E6794 /* ActivityType.swift in Sources */, BA7071E626BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition in Sources */, BA86622426B3AE4A00466746 /* WidgetResultsController.swift in Sources */, BABFFF2426CF9094003A4C25 /* WidgetDefaults.swift in Sources */, + BA289B762BE45BBB000E6794 /* OpenNoteIntentHandler.swift in Sources */, BAF8D46E26AE118800CA9383 /* SPCredentials.swift in Sources */, BAF8D5C526AE254100CA9383 /* Simplenote.xcdatamodeld in Sources */, BAB6C04426BA49F3007495C4 /* ContentSlice.swift in Sources */, diff --git a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift index 6dfcdf2a7..00bf44ac7 100644 --- a/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/OpenNewNoteIntentHandler.swift @@ -13,36 +13,3 @@ class OpenNewNoteIntentHandler: NSObject, OpenNewNoteIntentHandling { OpenNewNoteIntentResponse(code: .continueInApp, userActivity: nil) } } - -class OpenNoteIntentHandler: NSObject, OpenNoteIntentHandling { - let coreDataWrapper = WidgetCoreDataWrapper() - - func resolveNote(for intent: OpenNoteIntent) async -> IntentNoteResolutionResult { - guard let identifier = intent.note?.identifier, - let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { - return IntentNoteResolutionResult.confirmationRequired(with: nil) - } - - return IntentNoteResolutionResult.success(with: IntentNote(identifier: note.simperiumKey, display: note.title)) - } - - func provideNoteOptionsCollection(for intent: OpenNoteIntent) async throws -> INObjectCollection { - guard let notes = coreDataWrapper.resultsController()?.notes() else { - throw NSError(domain: "intents", code: 404) - } - - let intentNotes = notes.map({ - IntentNote(identifier: $0.simperiumKey, display: $0.title) - }) - 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: "SPOpenNoteIntent") - activity.userInfo = [IntentsConstants.noteIdentifierKey: identifier] - return OpenNoteIntentResponse(code: .continueInApp, userActivity: activity) - } -} diff --git a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift new file mode 100644 index 000000000..9ad6d1746 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift @@ -0,0 +1,42 @@ +// +// 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 = WidgetCoreDataWrapper() + + func resolveNote(for intent: OpenNoteIntent) async -> IntentNoteResolutionResult { + guard let identifier = intent.note?.identifier, + let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { + return IntentNoteResolutionResult.confirmationRequired(with: nil) + } + + return IntentNoteResolutionResult.success(with: IntentNote(identifier: note.simperiumKey, display: note.title)) + } + + func provideNoteOptionsCollection(for intent: OpenNoteIntent) async throws -> INObjectCollection { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + throw fatalError("Could not fetch notes") + } + + let intentNotes = notes.map({ + IntentNote(identifier: $0.simperiumKey, display: $0.title) + }) + 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) + } +} From 2cf7858b4f1ae6409baa548a99743465c8053100 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 3 May 2024 15:03:17 -0600 Subject: [PATCH 134/547] Renamed widgetsCoreDataWrapper to reflect it being used in intents --- .../Intent Handlers/ListWidgetIntentHandler.swift | 2 +- .../Intent Handlers/NoteWidgetIntentHandler.swift | 2 +- .../Intent Handlers/OpenNoteIntentHandler.swift | 2 +- SimplenoteWidgets/Providers/ListWidgetProvider.swift | 2 +- SimplenoteWidgets/Providers/NoteWidgetProvider.swift | 2 +- SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift b/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift index 0cd9558d5..99c95b427 100644 --- a/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/ListWidgetIntentHandler.swift @@ -9,7 +9,7 @@ import Intents class ListWidgetIntentHandler: NSObject, ListWidgetIntentHandling { - let coreDataWrapper = WidgetCoreDataWrapper() + let coreDataWrapper = ExtensionCoreDataWrapper() func provideTagOptionsCollection(for intent: ListWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { guard WidgetDefaults.shared.loggedIn else { diff --git a/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift b/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift index 61d980050..0d9d34f8e 100644 --- a/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/NoteWidgetIntentHandler.swift @@ -9,7 +9,7 @@ import Intents class NoteWidgetIntentHandler: NSObject, NoteWidgetIntentHandling { - let coreDataWrapper = WidgetCoreDataWrapper() + let coreDataWrapper = ExtensionCoreDataWrapper() func provideNoteOptionsCollection(for intent: NoteWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { guard WidgetDefaults.shared.loggedIn else { diff --git a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift index 9ad6d1746..f7984b4c5 100644 --- a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift @@ -9,7 +9,7 @@ import Intents class OpenNoteIntentHandler: NSObject, OpenNoteIntentHandling { - let coreDataWrapper = WidgetCoreDataWrapper() + let coreDataWrapper = ExtensionCoreDataWrapper() func resolveNote(for intent: OpenNoteIntent) async -> IntentNoteResolutionResult { guard let identifier = intent.note?.identifier, 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/Tools/WidgetCoreDataWrapper.swift b/SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift index 40d3048cf..8ef1bb4c6 100644 --- a/SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift +++ b/SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift @@ -1,6 +1,6 @@ import Foundation -class WidgetCoreDataWrapper { +class ExtensionCoreDataWrapper { private lazy var coreDataManager: CoreDataManager = { do { return try CoreDataManager(StorageSettings().sharedStorageURL, for: .widgets) @@ -9,7 +9,7 @@ class WidgetCoreDataWrapper { } }() - private lazy var widgetResultsController: WidgetResultsController = { + private lazy var extensionResultsController: WidgetResultsController = { WidgetResultsController(context: coreDataManager.managedObjectContext) }() @@ -17,6 +17,6 @@ class WidgetCoreDataWrapper { guard FileManager.default.fileExists(atPath: StorageSettings().sharedStorageURL.path) else { return nil } - return widgetResultsController + return extensionResultsController } } From 406ec4569450ec4028e01e4f25d4d2518568fd42 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 3 May 2024 15:04:05 -0600 Subject: [PATCH 135/547] Renamed widgetCoreDataWrapper file --- Simplenote.xcodeproj/project.pbxproj | 12 ++++++------ ...aWrapper.swift => ExtensionCoreDataWrapper.swift} | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename SimplenoteWidgets/Tools/{WidgetCoreDataWrapper.swift => ExtensionCoreDataWrapper.swift} (100%) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 046f25fd1..bb77f3519 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -565,8 +565,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 */; }; - 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 */; }; @@ -1218,7 +1218,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 = ""; }; @@ -2425,7 +2425,7 @@ BA88765026B79324001C9C9E /* DemoContent.swift */, BAADC8A326C634DB004CAAA9 /* WidgetConstants.swift */, BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */, - BAFFCEA526DDA9F6007F5EE3 /* WidgetCoreDataWrapper.swift */, + BAFFCEA526DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift */, BA524A0126DF1AE800DAC945 /* WidgetsState.swift */, ); path = Tools; @@ -3782,7 +3782,7 @@ BA86627226B3B1B700466746 /* NSSortDescriptor+Simplenote.swift in Sources */, BAADC8A526C634DB004CAAA9 /* WidgetConstants.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; @@ -3820,7 +3820,7 @@ 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 */, diff --git a/SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift b/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift similarity index 100% rename from SimplenoteWidgets/Tools/WidgetCoreDataWrapper.swift rename to SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift From 03819f916bc717d2f7565c31ea648526b7c151cf Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 3 May 2024 15:07:48 -0600 Subject: [PATCH 136/547] Added intents error enum --- Simplenote.xcodeproj/project.pbxproj | 4 +++ .../OpenNoteIntentHandler.swift | 2 +- SimplenoteIntents/IntentsError.swift | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 SimplenoteIntents/IntentsError.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index bb77f3519..83c4ebbe9 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -475,6 +475,7 @@ 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 */; }; @@ -1166,6 +1167,7 @@ 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 = ""; }; BA75D87D26C0843600883FFA /* Text+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+Simplenote.swift"; sourceTree = ""; }; @@ -2401,6 +2403,7 @@ children = ( 092FD78026D7BB72006BE8E2 /* Supporting Files */, BAB5762626703C8200B0C56F /* IntentHandler.swift */, + BA6D7B8A2BE588F0006AE368 /* IntentsError.swift */, BA289B702BE45A39000E6794 /* IntentsConstants.swift */, BA289B622BE43949000E6794 /* Intent Handlers */, BAF8D4AD26AE136D00CA9383 /* Simplenote-Intents-Bridging-Header.h */, @@ -3764,6 +3767,7 @@ BA86625D26B3B14900466746 /* SortMode.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAF4A9AA26DB138600C51C1D /* NoteContentHelper.swift in Sources */, + BA6D7B8B2BE588F0006AE368 /* IntentsError.swift in Sources */, BA289B782BE45BFB000E6794 /* ActivityType.swift in Sources */, BA7071E626BB68A300D5DFF0 /* ListWidgetIntent.intentdefinition in Sources */, BA86622426B3AE4A00466746 /* WidgetResultsController.swift in Sources */, diff --git a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift index f7984b4c5..5d678ee4a 100644 --- a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift @@ -22,7 +22,7 @@ class OpenNoteIntentHandler: NSObject, OpenNoteIntentHandling { func provideNoteOptionsCollection(for intent: OpenNoteIntent) async throws -> INObjectCollection { guard let notes = coreDataWrapper.resultsController()?.notes() else { - throw fatalError("Could not fetch notes") + throw IntentsError.couldNotFetchNotes } let intentNotes = notes.map({ diff --git a/SimplenoteIntents/IntentsError.swift b/SimplenoteIntents/IntentsError.swift new file mode 100644 index 000000000..b3c149d2b --- /dev/null +++ b/SimplenoteIntents/IntentsError.swift @@ -0,0 +1,27 @@ +// +// IntentsError.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/3/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation + +enum IntentsError: Error { + case couldNotFetchNotes + + var title: String { + switch self { + case .couldNotFetchNotes: + return NSLocalizedString("Could not fetch Notes", comment: "Note 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") + } + } +} From c60cea20d0ab03cf8cde9d83d2a50cd710035b25 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 3 May 2024 16:25:50 -0600 Subject: [PATCH 137/547] Revert "Updated package.resolved" This reverts commit c6adbb45e72896878e5989a3cd3da060d398804e. --- .../xcshareddata/swiftpm/Package.resolved | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved index 82138b2dd..cd06d10ef 100644 --- a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,13 +1,12 @@ { - "originHash" : "4c803bfc0eef41e640a11de86de8bd9cb6ec711e343dadfe7d14d904ae2a7283", "pins" : [ { "identity" : "automattic-tracks-ios", "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/Automattic-Tracks-iOS", "state" : { - "revision" : "bd981ad3a08c6af6113834eb62ee7466cb8aa71c", - "version" : "3.4.0" + "revision" : "b6979ef69b4b094c8809ba83661222dcd0d7667e", + "version" : "3.2.0" } }, { @@ -24,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/getsentry/sentry-cocoa", "state" : { - "revision" : "82af013792dca3784a2dc5e7f975159fb9d263b3", - "version" : "8.25.0" + "revision" : "3b9a8e69ca296bd8cd0e317ad7a448e5daf4a342", + "version" : "8.18.0" } }, { @@ -83,5 +82,5 @@ } } ], - "version" : 3 + "version" : 2 } From a5982b1c84dac7ed8cb5b310a0aa0b1efe72de28 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 7 May 2024 10:30:46 -0600 Subject: [PATCH 138/547] Added append note intent --- Simplenote.xcodeproj/project.pbxproj | 4 + .../ShortcutIntents.intentdefinition | 152 +++++++++++++++++- .../Supporting Files/Simplenote-Info.plist | 1 + .../AppendNoteIntentHandler.swift | 60 +++++++ SimplenoteIntents/IntentHandler.swift | 2 + SimplenoteIntents/Supporting Files/Info.plist | 1 + .../Tools/ExtensionCoreDataWrapper.swift | 5 + 7 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 83c4ebbe9..24bb23637 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -451,6 +451,7 @@ 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 */; }; + 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 */; }; @@ -1144,6 +1145,7 @@ 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 = ""; }; + 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 = ""; }; @@ -2333,6 +2335,7 @@ BA289B5A2BE4371A000E6794 /* ListWidgetIntentHandler.swift */, BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */, BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */, + BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */, ); path = "Intent Handlers"; sourceTree = ""; @@ -3769,6 +3772,7 @@ BAF4A9AA26DB138600C51C1D /* NoteContentHelper.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 */, BABFFF2426CF9094003A4C25 /* WidgetDefaults.swift in Sources */, diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 646123ee2..63acac2fc 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -138,13 +138,109 @@ 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 + + INIntentDescriptionID + EeqvcH + INIntentIneligibleForSuggestions + + 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 + INIntentParameterPromptDialogCustom INIntentParameterPromptDialogFormatString There are ${count} options matching ‘${note}’. INIntentParameterPromptDialogFormatStringID - n3Yy85 + 5Cplu9 INIntentParameterPromptDialogType DisambiguationIntroduction @@ -154,7 +250,7 @@ INIntentParameterPromptDialogFormatString Just to confirm, you wanted ‘${note}’? INIntentParameterPromptDialogFormatStringID - v8y9Kh + P8Wb3q INIntentParameterPromptDialogType Confirmation @@ -166,6 +262,52 @@ 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 @@ -184,13 +326,13 @@ INIntentTitle - Open Note + Append To Note INIntentTitleID - IQnYW1 + d6MjpX INIntentType Custom INIntentVerb - Open + Do INTypes diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index 8d25ca1f2..c70500170 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -74,6 +74,7 @@ This app does not require Photo Library access. NSUserActivityTypes + AppendNoteIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift new file mode 100644 index 000000000..09e82e788 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -0,0 +1,60 @@ +// +// AppendNoteIntentHandler.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/6/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() + + func resolveContent(for intent: AppendNoteIntent) async -> INStringResolutionResult { + guard let content = intent.content else { + return INStringResolutionResult.confirmationRequired(with: nil) + } + return INStringResolutionResult.success(with: content) + } + + func resolveNote(for intent: AppendNoteIntent) async -> IntentNoteResolutionResult { + guard let identifier = intent.note?.identifier, + let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { + return IntentNoteResolutionResult.confirmationRequired(with: nil) + } + + return IntentNoteResolutionResult.success(with: IntentNote(identifier: note.simperiumKey, display: note.title)) + } + + func provideNoteOptionsCollection(for intent: AppendNoteIntent) async throws -> INObjectCollection { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + throw IntentsError.couldNotFetchNotes + } + + let intentNotes = notes.map({ + IntentNote(identifier: $0.simperiumKey, display: $0.title) + }) + return INObjectCollection(items: intentNotes) + } + + func handle(intent: AppendNoteIntent) async -> AppendNoteIntentResponse { + guard let identifier = intent.note?.identifier, + let content = intent.content else { + return AppendNoteIntentResponse(code: .failure, userActivity: nil) + } + + guard let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { + return AppendNoteIntentResponse(code: .failure, userActivity: nil) + } + + do { + note.content? += " \n\n\(content)" + try coreDataWrapper.context().save() + } catch { + return AppendNoteIntentResponse(code: .failure, userActivity: nil) + } + + return AppendNoteIntentResponse(code: .success, userActivity: nil) + } +} diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index cb357191a..5cc6c985f 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -13,6 +13,8 @@ class IntentHandler: INExtension { return OpenNewNoteIntentHandler() case is OpenNoteIntent: return OpenNoteIntentHandler() + case is AppendNoteIntent: + return AppendNoteIntentHandler() default: return self } diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index a7c260143..43b7164eb 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -30,6 +30,7 @@ IntentsSupported + AppendNoteIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent diff --git a/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift b/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift index 8ef1bb4c6..777740db1 100644 --- a/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift +++ b/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift @@ -1,4 +1,5 @@ import Foundation +import CoreData class ExtensionCoreDataWrapper { private lazy var coreDataManager: CoreDataManager = { @@ -19,4 +20,8 @@ class ExtensionCoreDataWrapper { } return extensionResultsController } + + func context() -> NSManagedObjectContext { + coreDataManager.managedObjectContext + } } From c770e5caba99877d20b26999aa402497633fa894 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 7 May 2024 17:22:01 -0600 Subject: [PATCH 139/547] Updated append note intent to use simperium api to update notes --- Simplenote.xcodeproj/project.pbxproj | 10 +++++++ .../AppendNoteIntentHandler.swift | 17 +++++------- .../Simplenote-Intents-Bridging-Header.h | 1 + .../SimplenoteIntents-Debug.entitlements | 4 +++ ...noteIntents-DistributionAlpha.entitlements | 4 +++ ...eIntents-DistributionInternal.entitlements | 4 +++ .../SimplenoteIntents-Release.entitlements | 4 +++ SimplenoteWidgets/Models/Note+Widget.swift | 27 ++++++++++++++++--- .../Models/SPManagedObject+Widget.swift | 2 +- 9 files changed, 58 insertions(+), 15 deletions(-) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 24bb23637..eb034ce58 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -420,6 +420,8 @@ 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 */; }; @@ -451,6 +453,9 @@ 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 */; }; 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 */; }; @@ -3756,6 +3761,7 @@ files = ( BA86620E26B3A73D00466746 /* UserDefaults+Simplenote.swift in Sources */, BAF8D52826AE173D00CA9383 /* FileManager+Simplenote.swift in Sources */, + BA34B04D2BEAE9FE00580E15 /* SPConstants.m in Sources */, BA289B5F2BE43728000E6794 /* NoteWidgetIntentHandler.swift in Sources */, BAF8D50026AE171400CA9383 /* CoreDataManager.swift in Sources */, BAE63CB026E05313002BF81A /* NSString+Simplenote.swift in Sources */, @@ -3763,10 +3769,12 @@ 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 */, BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */, BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, + BA0AF10D2BE996600050EEBD /* KeychainManager.swift in Sources */, BA86625D26B3B14900466746 /* SortMode.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAF4A9AA26DB138600C51C1D /* NoteContentHelper.swift in Sources */, @@ -3788,7 +3796,9 @@ 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 /* ExtensionCoreDataWrapper.swift in Sources */, BAF8D53C26AE175C00CA9383 /* Bundle+Simplenote.swift in Sources */, diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 09e82e788..e32e88e74 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -40,20 +40,15 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { func handle(intent: AppendNoteIntent) async -> AppendNoteIntentResponse { guard let identifier = intent.note?.identifier, - let content = intent.content else { + let content = intent.content, + let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier), + let token = KeychainManager.extensionToken else { return AppendNoteIntentResponse(code: .failure, userActivity: nil) } - guard let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { - return AppendNoteIntentResponse(code: .failure, userActivity: nil) - } - - do { - note.content? += " \n\n\(content)" - try coreDataWrapper.context().save() - } catch { - return AppendNoteIntentResponse(code: .failure, userActivity: nil) - } + note.content? += "\n\n\(content)" + let uploader = Uploader(simperiumToken: token) + uploader.send(note) return AppendNoteIntentResponse(code: .success, userActivity: nil) } 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/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..d65b0e5ff 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.Development + diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements index bd8e7994c..d2102e458 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.Development + diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements index b11d6f107..065b3dbe3 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements @@ -6,6 +6,10 @@ group.com.codality.NotationalFlow + keychain-access-groups + + $(AppIdentifierPrefix)com.codality.NotationalFlow.Development + previous-application-identifiers 4ESDVWK654.com.codality.NotationalFlow diff --git a/SimplenoteWidgets/Models/Note+Widget.swift b/SimplenoteWidgets/Models/Note+Widget.swift index 093fe923f..a889e71a6 100644 --- a/SimplenoteWidgets/Models/Note+Widget.swift +++ b/SimplenoteWidgets/Models/Note+Widget.swift @@ -60,11 +60,32 @@ extension Note { } var url: URL { - guard let simperiumKey = simperiumKey else { - return URL(string: .simplenotePath())! - } return URL(string: .simplenotePath(withHost: SimplenoteConstants.simplenoteInterlinkHost) + simperiumKey)! } + + func toDictionary() -> [String: Any] { + var systemTags = [String]() + + return [ + "tags": [], + "deleted": 0, + "shareURL": String(), + "publishURL": String(), + "content": content ?? "", + "systemTags": systemTags, + "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 } From 5ae21cc51c24a6fc987535c736ed31b4d90e379c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 7 May 2024 17:23:59 -0600 Subject: [PATCH 140/547] Updated intent keychain entitlements to match share extension --- Simplenote.xcodeproj/project.pbxproj | 4 +++- .../SimplenoteIntentsRelease.entitlements | 19 +++++++++++++++++++ ...noteIntents-DistributionAlpha.entitlements | 2 +- ...eIntents-DistributionInternal.entitlements | 2 +- .../SimplenoteIntents-Release.entitlements | 3 ++- 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 SimplenoteIntents/SimplenoteIntentsRelease.entitlements diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index eb034ce58..14a0c1700 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -1150,6 +1150,7 @@ 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 = ""; }; 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 = ""; }; @@ -2409,6 +2410,7 @@ BAB5762526703C8200B0C56F /* SimplenoteIntents */ = { isa = PBXGroup; children = ( + BA34B04F2BEAEF4800580E15 /* SimplenoteIntentsRelease.entitlements */, 092FD78026D7BB72006BE8E2 /* Supporting Files */, BAB5762626703C8200B0C56F /* IntentHandler.swift */, BA6D7B8A2BE588F0006AE368 /* IntentsError.swift */, @@ -5352,7 +5354,7 @@ 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; 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/SimplenoteIntents-DistributionAlpha.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionAlpha.entitlements index d65b0e5ff..2483a1da9 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionAlpha.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionAlpha.entitlements @@ -8,7 +8,7 @@ keychain-access-groups - $(AppIdentifierPrefix)com.codality.NotationalFlow.Development + $(AppIdentifierPrefix)com.codality.NotationalFlow.Alpha diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements index d2102e458..f69540697 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-DistributionInternal.entitlements @@ -8,7 +8,7 @@ keychain-access-groups - $(AppIdentifierPrefix)com.codality.NotationalFlow.Development + $(AppIdentifierPrefix)com.codality.NotationalFlow.Internal diff --git a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements index 065b3dbe3..084553593 100644 --- a/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements +++ b/SimplenoteIntents/Supporting Files/SimplenoteIntents-Release.entitlements @@ -8,7 +8,8 @@ keychain-access-groups - $(AppIdentifierPrefix)com.codality.NotationalFlow.Development + $(AppIdentifierPrefix)com.codality.NotationalFlow + $(AppIdentifierPrefix)4ESDVWK654.com.codality.NotationalFlow previous-application-identifiers From 8099d7809e997d840a9f26e2667a698730c77ce9 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 7 May 2024 18:12:36 -0600 Subject: [PATCH 141/547] Fixed issue where running append note intent would overwrite tags --- SimplenoteWidgets/Models/Note+Widget.swift | 31 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/SimplenoteWidgets/Models/Note+Widget.swift b/SimplenoteWidgets/Models/Note+Widget.swift index a889e71a6..83eebc3b8 100644 --- a/SimplenoteWidgets/Models/Note+Widget.swift +++ b/SimplenoteWidgets/Models/Note+Widget.swift @@ -63,16 +63,41 @@ extension Note { 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] { - var systemTags = [String]() return [ - "tags": [], + "tags": tagsArray, "deleted": 0, "shareURL": String(), "publishURL": String(), "content": content ?? "", - "systemTags": systemTags, + "systemTags": systemTagsArray, "creationDate": (creationDate ?? .now).timeIntervalSince1970, "modificationDate": (modificationDate ?? .now).timeIntervalSince1970 ] From 7aa5424c5af769b0a9c0d6676a15df7ce7562a03 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 8 May 2024 11:29:21 -0600 Subject: [PATCH 142/547] Fixed issue where append note might overwrite publish or share urls --- SimplenoteWidgets/Models/Note+Widget.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SimplenoteWidgets/Models/Note+Widget.swift b/SimplenoteWidgets/Models/Note+Widget.swift index 83eebc3b8..5530b0ddb 100644 --- a/SimplenoteWidgets/Models/Note+Widget.swift +++ b/SimplenoteWidgets/Models/Note+Widget.swift @@ -94,8 +94,8 @@ extension Note { return [ "tags": tagsArray, "deleted": 0, - "shareURL": String(), - "publishURL": String(), + "shareURL": shareURL ?? String(), + "publishURL": publishURL ?? String(), "content": content ?? "", "systemTags": systemTagsArray, "creationDate": (creationDate ?? .now).timeIntervalSince1970, From 4e13ca1b906adb26ca0294a22b2bb2d58b974684 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 8 May 2024 15:20:32 -0600 Subject: [PATCH 143/547] Removed some code duplication in append and open note intents --- Simplenote.xcodeproj/project.pbxproj | 12 +++++++ .../AppendNoteIntentHandler.swift | 15 ++------ .../OpenNoteIntentHandler.swift | 15 ++------ .../IntentNote+Helpers.swift | 34 +++++++++++++++++++ .../Tools/WidgetResultsController.swift | 4 +++ 5 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 14a0c1700..e176d1187 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -456,6 +456,7 @@ 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 */; }; @@ -1151,6 +1152,7 @@ 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 = ""; }; @@ -2346,6 +2348,14 @@ path = "Intent Handlers"; sourceTree = ""; }; + BA34B0552BEC214800580E15 /* ResolutionResults */ = { + isa = PBXGroup; + children = ( + BA34B0562BEC216B00580E15 /* IntentNote+Helpers.swift */, + ); + path = ResolutionResults; + sourceTree = ""; + }; BA3FB8CD25FEA09F00EA9A1B /* Notice */ = { isa = PBXGroup; children = ( @@ -2416,6 +2426,7 @@ BA6D7B8A2BE588F0006AE368 /* IntentsError.swift */, BA289B702BE45A39000E6794 /* IntentsConstants.swift */, BA289B622BE43949000E6794 /* Intent Handlers */, + BA34B0552BEC214800580E15 /* ResolutionResults */, BAF8D4AD26AE136D00CA9383 /* Simplenote-Intents-Bridging-Header.h */, ); path = SimplenoteIntents; @@ -3773,6 +3784,7 @@ 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 */, BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index e32e88e74..54a53e653 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -19,22 +19,11 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { } func resolveNote(for intent: AppendNoteIntent) async -> IntentNoteResolutionResult { - guard let identifier = intent.note?.identifier, - let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { - return IntentNoteResolutionResult.confirmationRequired(with: nil) - } - - return IntentNoteResolutionResult.success(with: IntentNote(identifier: note.simperiumKey, display: note.title)) + IntentNoteResolutionResult.resolve(intent.note, in: coreDataWrapper) } func provideNoteOptionsCollection(for intent: AppendNoteIntent) async throws -> INObjectCollection { - guard let notes = coreDataWrapper.resultsController()?.notes() else { - throw IntentsError.couldNotFetchNotes - } - - let intentNotes = notes.map({ - IntentNote(identifier: $0.simperiumKey, display: $0.title) - }) + let intentNotes = try IntentNote.allNotes(in: coreDataWrapper) return INObjectCollection(items: intentNotes) } diff --git a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift index 5d678ee4a..1fec7c593 100644 --- a/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/OpenNoteIntentHandler.swift @@ -12,22 +12,11 @@ class OpenNoteIntentHandler: NSObject, OpenNoteIntentHandling { let coreDataWrapper = ExtensionCoreDataWrapper() func resolveNote(for intent: OpenNoteIntent) async -> IntentNoteResolutionResult { - guard let identifier = intent.note?.identifier, - let note = coreDataWrapper.resultsController()?.note(forSimperiumKey: identifier) else { - return IntentNoteResolutionResult.confirmationRequired(with: nil) - } - - return IntentNoteResolutionResult.success(with: IntentNote(identifier: note.simperiumKey, display: note.title)) + IntentNoteResolutionResult.resolve(intent.note, in: coreDataWrapper) } func provideNoteOptionsCollection(for intent: OpenNoteIntent) async throws -> INObjectCollection { - guard let notes = coreDataWrapper.resultsController()?.notes() else { - throw IntentsError.couldNotFetchNotes - } - - let intentNotes = notes.map({ - IntentNote(identifier: $0.simperiumKey, display: $0.title) - }) + let intentNotes = try IntentNote.allNotes(in: coreDataWrapper) return INObjectCollection(items: intentNotes) } diff --git a/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift new file mode 100644 index 000000000..6d2824ebe --- /dev/null +++ b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift @@ -0,0 +1,34 @@ +// +// 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) + } +} + +extension IntentNote { + static func allNotes(in coreDataWrapper: ExtensionCoreDataWrapper) throws -> [IntentNote] { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + throw IntentsError.couldNotFetchNotes + } + + return notes.map({ IntentNote(identifier: $0.simperiumKey, display: $0.title) }) + } +} diff --git a/SimplenoteWidgets/Tools/WidgetResultsController.swift b/SimplenoteWidgets/Tools/WidgetResultsController.swift index 61b775a0b..0440652a2 100644 --- a/SimplenoteWidgets/Tools/WidgetResultsController.swift +++ b/SimplenoteWidgets/Tools/WidgetResultsController.swift @@ -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 { From 007b947591d8027a28ceaff424ff06bcb9392952 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 8 May 2024 15:22:12 -0600 Subject: [PATCH 144/547] Renamed widgetResultsController to ExtensionResultsController --- Simplenote.xcodeproj/project.pbxproj | 12 ++++++------ .../Tools/ExtensionCoreDataWrapper.swift | 6 +++--- ...roller.swift => ExtensionResultsController.swift} | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) rename SimplenoteWidgets/Tools/{WidgetResultsController.swift => ExtensionResultsController.swift} (99%) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index e176d1187..f3b376944 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -496,8 +496,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 */; }; @@ -1189,7 +1189,7 @@ 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 = ""; }; @@ -2445,7 +2445,7 @@ BAF8D4CB26AE142C00CA9383 /* Tools */ = { isa = PBXGroup; children = ( - BA86622226B3AE4A00466746 /* WidgetResultsController.swift */, + BA86622226B3AE4A00466746 /* ExtensionResultsController.swift */, BA88765026B79324001C9C9E /* DemoContent.swift */, BAADC8A326C634DB004CAAA9 /* WidgetConstants.swift */, BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */, @@ -3796,7 +3796,7 @@ 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 */, BAF8D46E26AE118800CA9383 /* SPCredentials.swift in Sources */, @@ -3845,7 +3845,7 @@ 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 */, diff --git a/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift b/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift index 777740db1..a688a7788 100644 --- a/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift +++ b/SimplenoteWidgets/Tools/ExtensionCoreDataWrapper.swift @@ -10,11 +10,11 @@ class ExtensionCoreDataWrapper { } }() - private lazy var extensionResultsController: WidgetResultsController = { - WidgetResultsController(context: coreDataManager.managedObjectContext) + private lazy var extensionResultsController: ExtensionResultsController = { + ExtensionResultsController(context: coreDataManager.managedObjectContext) }() - func resultsController() -> WidgetResultsController? { + func resultsController() -> ExtensionResultsController? { guard FileManager.default.fileExists(atPath: StorageSettings().sharedStorageURL.path) else { return nil } diff --git a/SimplenoteWidgets/Tools/WidgetResultsController.swift b/SimplenoteWidgets/Tools/ExtensionResultsController.swift similarity index 99% rename from SimplenoteWidgets/Tools/WidgetResultsController.swift rename to SimplenoteWidgets/Tools/ExtensionResultsController.swift index 0440652a2..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 /// From 1ada14c5a631b03209015f488fa054d10814e8ab Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 8 May 2024 15:23:48 -0600 Subject: [PATCH 145/547] Updated append note intent to return correct response if missing content --- SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 54a53e653..07fe62c7e 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -13,7 +13,7 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { func resolveContent(for intent: AppendNoteIntent) async -> INStringResolutionResult { guard let content = intent.content else { - return INStringResolutionResult.confirmationRequired(with: nil) + return INStringResolutionResult.needsValue() } return INStringResolutionResult.success(with: content) } From a98c7262b2abc39ec4f81fc542cf94bc1d0aa9ae Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 8 May 2024 16:54:20 -0600 Subject: [PATCH 146/547] Added new note with content intent --- .../ShortcutIntents.intentdefinition | 119 +++++++++++++++--- .../Supporting Files/Simplenote-Info.plist | 1 + SimplenoteIntents/Supporting Files/Info.plist | 1 + 3 files changed, 101 insertions(+), 20 deletions(-) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 63acac2fc..7d7af764b 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -234,26 +234,6 @@ INIntentParameterPromptDialogType Primary - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${note}’. - INIntentParameterPromptDialogFormatStringID - 5Cplu9 - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${note}’? - INIntentParameterPromptDialogFormatStringID - P8Wb3q - INIntentParameterPromptDialogType - Confirmation - INIntentParameterSupportsDynamicEnumeration @@ -334,6 +314,105 @@ INIntentVerb Do + + INIntentCategory + create + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescription + Creates a note with supplied content and saves it in Simplenote + INIntentDescriptionID + sXkMgW + INIntentIneligibleForSuggestions + + INIntentInput + content + INIntentLastParameterTag + 1 + INIntentManagedParameterCombinations + + content + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Create new note with ${content} + INIntentParameterCombinationTitleID + CNjhWs + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + CreateNewNoteWithContent + 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 + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Create New Note With Content + INIntentTitleID + Ag8ugh + INIntentType + Custom + INIntentVerb + Create + INTypes diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index c70500170..3d798e4d9 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -79,6 +79,7 @@ NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent + SPCreateNewNoteWithContentIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index 43b7164eb..37df635d6 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -35,6 +35,7 @@ NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent + SPCreateNewNoteWithContentIntent NSExtensionPointIdentifier From aa2ca7ee49236b46b61fad002c1c23b697c53a30 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 8 May 2024 17:27:22 -0600 Subject: [PATCH 147/547] Updated created basic handler for create new note intent --- Simplenote.xcodeproj/project.pbxproj | 4 ++++ .../ShortcutIntents.intentdefinition | 10 ++++++++-- .../Supporting Files/Simplenote-Info.plist | 2 +- .../CreateNewNoteWithContentIntentHandler.swift | 16 ++++++++++++++++ SimplenoteIntents/Supporting Files/Info.plist | 2 +- 5 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 SimplenoteIntents/Intent Handlers/CreateNewNoteWithContentIntentHandler.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index f3b376944..d0d6bcdf5 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -535,6 +535,7 @@ 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 /* CreateNewNoteWithContentIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB898D22BEC404200E238B8 /* CreateNewNoteWithContentIntentHandler.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 */; }; @@ -1213,6 +1214,7 @@ 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 /* CreateNewNoteWithContentIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewNoteWithContentIntentHandler.swift; sourceTree = ""; }; BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDefaults.swift; sourceTree = ""; }; BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAddCollaboratorsViewController.swift; sourceTree = ""; }; BAE08625261282D1009D40CD /* Note+Publish.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Note+Publish.swift"; sourceTree = ""; }; @@ -2344,6 +2346,7 @@ BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */, BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */, BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */, + BAB898D22BEC404200E238B8 /* CreateNewNoteWithContentIntentHandler.swift */, ); path = "Intent Handlers"; sourceTree = ""; @@ -3790,6 +3793,7 @@ BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, BA0AF10D2BE996600050EEBD /* KeychainManager.swift in Sources */, BA86625D26B3B14900466746 /* SortMode.swift in Sources */, + BAB898D32BEC404200E238B8 /* CreateNewNoteWithContentIntentHandler.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAF4A9AA26DB138600C51C1D /* NoteContentHelper.swift in Sources */, BA6D7B8B2BE588F0006AE368 /* IntentsError.swift in Sources */, diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 7d7af764b..538bca056 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -25,6 +25,8 @@ SP INIntentConfigurable + INIntentDescription + Open Simplenote and create a new note INIntentDescriptionID fgSbr5 INIntentIneligibleForSuggestions @@ -181,6 +183,8 @@ SP INIntentConfigurable + INIntentDescription + Append content to an existing note in Simplenote INIntentDescriptionID EeqvcH INIntentIneligibleForSuggestions @@ -317,12 +321,14 @@ INIntentCategory create + INIntentClassName + CreateNewNoteIntent INIntentClassPrefix SP INIntentConfigurable INIntentDescription - Creates a note with supplied content and saves it in Simplenote + Creates a note with supplied content and save it in Simplenote INIntentDescriptionID sXkMgW INIntentIneligibleForSuggestions @@ -346,7 +352,7 @@ INIntentName - CreateNewNoteWithContent + CreateNewNote INIntentParameters diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index 3d798e4d9..db83424a6 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -75,11 +75,11 @@ NSUserActivityTypes AppendNoteIntent + CreateNewNoteIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent - SPCreateNewNoteWithContentIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteWithContentIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteWithContentIntentHandler.swift new file mode 100644 index 000000000..1cfea4d0f --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteWithContentIntentHandler.swift @@ -0,0 +1,16 @@ +// +// CreateNewNoteWithContentIntentHandler.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/8/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Intents + +class CreateNewNoteWithContentIntentHandler: NSObject, CreateNewNoteIntentHandling { + + func handle(intent: CreateNewNoteIntent) async -> CreateNewNoteIntentResponse { + CreateNewNoteIntentResponse(code: .failure, userActivity: nil) + } +} diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index 37df635d6..69a4ffa8d 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -31,11 +31,11 @@ IntentsSupported AppendNoteIntent + CreateNewNoteIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent - SPCreateNewNoteWithContentIntent NSExtensionPointIdentifier From c7734065c526dae82c9c8fe75a7935a7d3a96f72 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 8 May 2024 17:28:03 -0600 Subject: [PATCH 148/547] Renamed file for CreateNewNoteIntentHandler --- Simplenote.xcodeproj/project.pbxproj | 8 ++++---- ...tentHandler.swift => CreateNewNoteIntentHandler.swift} | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename SimplenoteIntents/Intent Handlers/{CreateNewNoteWithContentIntentHandler.swift => CreateNewNoteIntentHandler.swift} (69%) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index d0d6bcdf5..86bccc92d 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -535,7 +535,7 @@ 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 /* CreateNewNoteWithContentIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB898D22BEC404200E238B8 /* CreateNewNoteWithContentIntentHandler.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 */; }; @@ -1214,7 +1214,7 @@ 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 /* CreateNewNoteWithContentIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewNoteWithContentIntentHandler.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 = ""; }; BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAddCollaboratorsViewController.swift; sourceTree = ""; }; BAE08625261282D1009D40CD /* Note+Publish.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Note+Publish.swift"; sourceTree = ""; }; @@ -2346,7 +2346,7 @@ BA289B632BE43963000E6794 /* OpenNewNoteIntentHandler.swift */, BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */, BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */, - BAB898D22BEC404200E238B8 /* CreateNewNoteWithContentIntentHandler.swift */, + BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */, ); path = "Intent Handlers"; sourceTree = ""; @@ -3793,7 +3793,7 @@ BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, BA0AF10D2BE996600050EEBD /* KeychainManager.swift in Sources */, BA86625D26B3B14900466746 /* SortMode.swift in Sources */, - BAB898D32BEC404200E238B8 /* CreateNewNoteWithContentIntentHandler.swift in Sources */, + BAB898D32BEC404200E238B8 /* CreateNewNoteIntentHandler.swift in Sources */, BA12B07026B0D0150026F31D /* SPManagedObject+Widget.swift in Sources */, BAF4A9AA26DB138600C51C1D /* NoteContentHelper.swift in Sources */, BA6D7B8B2BE588F0006AE368 /* IntentsError.swift in Sources */, diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteWithContentIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift similarity index 69% rename from SimplenoteIntents/Intent Handlers/CreateNewNoteWithContentIntentHandler.swift rename to SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift index 1cfea4d0f..ba968a5d1 100644 --- a/SimplenoteIntents/Intent Handlers/CreateNewNoteWithContentIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift @@ -1,5 +1,5 @@ // -// CreateNewNoteWithContentIntentHandler.swift +// CreateNewNoteIntentHandler.swift // SimplenoteIntents // // Created by Charlie Scheer on 5/8/24. @@ -8,7 +8,7 @@ import Intents -class CreateNewNoteWithContentIntentHandler: NSObject, CreateNewNoteIntentHandling { +class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { func handle(intent: CreateNewNoteIntent) async -> CreateNewNoteIntentResponse { CreateNewNoteIntentResponse(code: .failure, userActivity: nil) From e1e16c3eef53868c456d43bbbe8ef439a78dfabe Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 09:12:32 -0600 Subject: [PATCH 149/547] Added handler method for create new note intent --- .../CreateNewNoteIntentHandler.swift | 18 +++++++++++++++++- SimplenoteIntents/IntentHandler.swift | 2 ++ SimplenoteWidgets/Models/Note+Widget.swift | 8 ++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift index ba968a5d1..e105c7436 100644 --- a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift @@ -9,8 +9,24 @@ import Intents class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { + let coreDataWrapper = ExtensionCoreDataWrapper() func handle(intent: CreateNewNoteIntent) async -> CreateNewNoteIntentResponse { - CreateNewNoteIntentResponse(code: .failure, userActivity: nil) + guard let content = intent.content, + let token = KeychainManager.extensionToken else { + return CreateNewNoteIntentResponse(code: .failure, userActivity: nil) + } + + Uploader(simperiumToken: token).send(note(with: content)) + return CreateNewNoteIntentResponse(code: .success, userActivity: nil) + } + + 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/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index 5cc6c985f..d6d073690 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -15,6 +15,8 @@ class IntentHandler: INExtension { return OpenNoteIntentHandler() case is AppendNoteIntent: return AppendNoteIntentHandler() + case is CreateNewNoteIntent: + return CreateNewNoteIntentHandler() default: return self } diff --git a/SimplenoteWidgets/Models/Note+Widget.swift b/SimplenoteWidgets/Models/Note+Widget.swift index 5530b0ddb..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 From c9952b59e8f36ed49fed34e3ec10d879cd577d50 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 12:12:58 -0600 Subject: [PATCH 150/547] Added file definition for find note with content intent --- .../ShortcutIntents.intentdefinition | 126 ++++++++++++++++++ .../Supporting Files/Simplenote-Info.plist | 1 + SimplenoteIntents/Supporting Files/Info.plist | 1 + 3 files changed, 128 insertions(+) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 538bca056..cb8b7bbbd 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -189,6 +189,8 @@ EeqvcH INIntentIneligibleForSuggestions + INIntentInput + note INIntentLastParameterTag 3 INIntentManagedParameterCombinations @@ -419,6 +421,130 @@ 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 + 4 + INIntentManagedParameterCombinations + + content + + 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 + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 4 + INIntentParameterType + String + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 4 + INIntentResponseOutput + note + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + Note + INIntentResponseParameterDisplayNameID + Mz9mrb + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + note + INIntentResponseParameterObjectType + IntentNote + INIntentResponseParameterObjectTypeNamespace + BxdWY5 + INIntentResponseParameterTag + 4 + INIntentResponseParameterType + Object + + + + INIntentTitle + Find Note With Content + INIntentTitleID + oawbyy + INIntentType + Custom + INIntentVerb + Search + INTypes diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index db83424a6..2fe3542f2 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -76,6 +76,7 @@ AppendNoteIntent CreateNewNoteIntent + FindNoteIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index 69a4ffa8d..946c2d7fc 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -32,6 +32,7 @@ AppendNoteIntent CreateNewNoteIntent + FindNoteIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent From ec0bd58bbae8f8043340917838387034fbb0e2b7 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 12:13:54 -0600 Subject: [PATCH 151/547] Added basic structure for find note intent handler --- Simplenote.xcodeproj/project.pbxproj | 4 ++ .../FindNoteIntentHandler.swift | 49 +++++++++++++++++++ SimplenoteIntents/IntentHandler.swift | 3 ++ 3 files changed, 56 insertions(+) create mode 100644 SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 86bccc92d..5b869b878 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -540,6 +540,7 @@ BABFFF2326CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; }; BABFFF2426CF9094003A4C25 /* WidgetDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */; }; BAC7264E2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.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 */; }; @@ -1217,6 +1218,7 @@ BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewNoteIntentHandler.swift; sourceTree = ""; }; BABFFF2126CF9094003A4C25 /* WidgetDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDefaults.swift; sourceTree = ""; }; BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAddCollaboratorsViewController.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 = ""; }; @@ -2347,6 +2349,7 @@ BA289B742BE45BBB000E6794 /* OpenNoteIntentHandler.swift */, BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */, BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */, + BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */, ); path = "Intent Handlers"; sourceTree = ""; @@ -3776,6 +3779,7 @@ 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 */, diff --git a/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift new file mode 100644 index 000000000..d762d1f6f --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift @@ -0,0 +1,49 @@ +// +// 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 handle(intent: FindNoteIntent) async -> FindNoteIntentResponse { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + return FindNoteIntentResponse(code: .failure, userActivity: nil) + } + + guard let content = intent.content else { + return FindNoteIntentResponse(code: .unspecified, userActivity: nil) + } + + let matchingNotes: [Note] = notes.compactMap({ + if $0.content?.contains(content) == true { + return $0 + } + return nil + }) + + guard matchingNotes.isEmpty == false else { + // TODO: Create custom respond code to alert user that no notes were found + return FindNoteIntentResponse(code: .failure, userActivity: nil) + } + + guard matchingNotes.count == 1 else { + // TODO: Disambiguate the notes + return FindNoteIntentResponse(code: .failure, userActivity: nil) + } + + guard let matchingNote = matchingNotes.first else { + return FindNoteIntentResponse(code: .failure, userActivity: nil) + } + let success = FindNoteIntentResponse(code: .success, userActivity: nil) + success.note = IntentNote(identifier: matchingNote.simperiumKey, display: matchingNote.title) + + return success + + } +} diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index d6d073690..ec49bb233 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -17,8 +17,11 @@ class IntentHandler: INExtension { return AppendNoteIntentHandler() case is CreateNewNoteIntent: return CreateNewNoteIntentHandler() + case is FindNoteIntent: + return FindNoteIntentHandler() default: return self } } } + From 8a7370d75d0436f850b72cf98e76e8f9d02c92ac Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 12:27:56 -0600 Subject: [PATCH 152/547] Added failure messages to find note intent handler --- .../ShortcutIntents.intentdefinition | 42 ++++++++++++++++++- .../FindNoteIntentHandler.swift | 19 +++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index cb8b7bbbd..49b987f90 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -437,7 +437,7 @@ INIntentIneligibleForSuggestions INIntentLastParameterTag - 4 + 7 INIntentManagedParameterCombinations content @@ -488,6 +488,26 @@ INIntentParameterPromptDialogType Primary + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${content}’. + INIntentParameterPromptDialogFormatStringID + NAzeXM + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${content}’? + INIntentParameterPromptDialogFormatStringID + wgf6Li + INIntentParameterPromptDialogType + Confirmation + INIntentParameterTag 4 @@ -506,12 +526,16 @@ + INIntentResponseCodeFormatString + ${failureReason} + INIntentResponseCodeFormatStringID + ThMQV4 INIntentResponseCodeName failure INIntentResponseLastParameterTag - 4 + 5 INIntentResponseOutput note INIntentResponseParameters @@ -534,6 +558,20 @@ INIntentResponseParameterType Object + + INIntentResponseParameterDisplayName + Failure Reason + INIntentResponseParameterDisplayNameID + Gde29V + INIntentResponseParameterDisplayPriority + 2 + INIntentResponseParameterName + failureReason + INIntentResponseParameterTag + 5 + INIntentResponseParameterType + String + INIntentTitle diff --git a/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift index d762d1f6f..e4e81160f 100644 --- a/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift @@ -8,12 +8,26 @@ import Intents +enum FindNoteError: String { + case couldNotFetchNotes + case noMatchingNotesFound + + var localizedDescription: String { + switch self { + case .couldNotFetchNotes: + NSLocalizedString("Could not fetch notes", comment: "Error warning user that their notes could not be fetched") + case .noMatchingNotesFound: + NSLocalizedString("Could not find notes matching the search terms", comment: "Error warning user that a matching note could not be found") + } + } +} + class FindNoteIntentHandler: NSObject, FindNoteIntentHandling { let coreDataWrapper = ExtensionCoreDataWrapper() func handle(intent: FindNoteIntent) async -> FindNoteIntentResponse { guard let notes = coreDataWrapper.resultsController()?.notes() else { - return FindNoteIntentResponse(code: .failure, userActivity: nil) + return FindNoteIntentResponse.failure(failureReason: FindNoteError.couldNotFetchNotes.localizedDescription) } guard let content = intent.content else { @@ -28,8 +42,7 @@ class FindNoteIntentHandler: NSObject, FindNoteIntentHandling { }) guard matchingNotes.isEmpty == false else { - // TODO: Create custom respond code to alert user that no notes were found - return FindNoteIntentResponse(code: .failure, userActivity: nil) + return FindNoteIntentResponse.failure(failureReason: FindNoteError.noMatchingNotesFound.localizedDescription) } guard matchingNotes.count == 1 else { From b096745fa7487f2695cefeca5e773556f94773ed Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 13:22:02 -0600 Subject: [PATCH 153/547] Refactored find note to disambiguate if multiple values are true --- .../ShortcutIntents.intentdefinition | 57 +++++++++++++++++-- .../FindNoteIntentHandler.swift | 48 +++++----------- SimplenoteIntents/IntentHandler.swift | 1 - .../IntentNote+Helpers.swift | 25 ++++++++ 4 files changed, 92 insertions(+), 39 deletions(-) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 49b987f90..7d3097a98 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -437,10 +437,10 @@ INIntentIneligibleForSuggestions INIntentLastParameterTag - 7 + 13 INIntentManagedParameterCombinations - content + content,note INIntentParameterCombinationSupportsBackgroundExecution @@ -485,6 +485,10 @@ INIntentParameterPromptDialogCustom + INIntentParameterPromptDialogFormatString + Which One? + INIntentParameterPromptDialogFormatStringID + yuIu0n INIntentParameterPromptDialogType Primary @@ -514,6 +518,51 @@ 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 @@ -535,7 +584,7 @@ INIntentResponseLastParameterTag - 5 + 10 INIntentResponseOutput note INIntentResponseParameters @@ -554,7 +603,7 @@ INIntentResponseParameterObjectTypeNamespace BxdWY5 INIntentResponseParameterTag - 4 + 10 INIntentResponseParameterType Object diff --git a/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift index e4e81160f..a73c24e76 100644 --- a/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/FindNoteIntentHandler.swift @@ -8,55 +8,35 @@ import Intents -enum FindNoteError: String { - case couldNotFetchNotes - case noMatchingNotesFound - - var localizedDescription: String { - switch self { - case .couldNotFetchNotes: - NSLocalizedString("Could not fetch notes", comment: "Error warning user that their notes could not be fetched") - case .noMatchingNotesFound: - NSLocalizedString("Could not find notes matching the search terms", comment: "Error warning user that a matching note could not be found") - } - } -} - class FindNoteIntentHandler: NSObject, FindNoteIntentHandling { let coreDataWrapper = ExtensionCoreDataWrapper() - func handle(intent: FindNoteIntent) async -> FindNoteIntentResponse { - guard let notes = coreDataWrapper.resultsController()?.notes() else { - return FindNoteIntentResponse.failure(failureReason: FindNoteError.couldNotFetchNotes.localizedDescription) + 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 FindNoteIntentResponse(code: .unspecified, userActivity: nil) + return IntentNoteResolutionResult.needsValue() } - let matchingNotes: [Note] = notes.compactMap({ - if $0.content?.contains(content) == true { - return $0 - } - return nil - }) + return IntentNoteResolutionResult.resolveIntentNote(for: content, in: coreDataWrapper) + } - guard matchingNotes.isEmpty == false else { - return FindNoteIntentResponse.failure(failureReason: FindNoteError.noMatchingNotesFound.localizedDescription) - } + func provideNoteOptionsCollection(for intent: FindNoteIntent) async throws -> INObjectCollection { + let intentNotes = try IntentNote.allNotes(in: coreDataWrapper) + return INObjectCollection(items: intentNotes) + } - guard matchingNotes.count == 1 else { - // TODO: Disambiguate the notes + func handle(intent: FindNoteIntent) async -> FindNoteIntentResponse { + guard let intentNote = intent.note else { return FindNoteIntentResponse(code: .failure, userActivity: nil) } - guard let matchingNote = matchingNotes.first else { - return FindNoteIntentResponse(code: .failure, userActivity: nil) - } let success = FindNoteIntentResponse(code: .success, userActivity: nil) - success.note = IntentNote(identifier: matchingNote.simperiumKey, display: matchingNote.title) + success.note = intentNote return success - } } diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index ec49bb233..637fee2a5 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -24,4 +24,3 @@ class IntentHandler: INExtension { } } } - diff --git a/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift index 6d2824ebe..3dc4ce1ae 100644 --- a/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift +++ b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift @@ -21,6 +21,31 @@ extension IntentNoteResolutionResult { return IntentNoteResolutionResult.success(with: intentNote) } + + static func resolveIntentNote(for content: String, in coreDataWrapper: ExtensionCoreDataWrapper) -> IntentNoteResolutionResult { + guard let notes = coreDataWrapper.resultsController()?.notes() else { + // TODO: Better error + return IntentNoteResolutionResult.unsupported() + } + let filteredNotes = notes.filter({ $0.content?.contains(content) == true }) + let intentNotes = filteredNotes.map({ IntentNote(identifier: $0.simperiumKey, display: $0.title) }) + + guard intentNotes.isEmpty == false else { + // TODO: Better error + return IntentNoteResolutionResult.unsupported() + } + + guard intentNotes.count == 1 else { + return IntentNoteResolutionResult.disambiguation(with: intentNotes) + } + + //This shouldn't happen but we to unwrap the existing note we need to return something + guard let matchingNote = intentNotes.first else { + return IntentNoteResolutionResult.unsupported() + } + + return IntentNoteResolutionResult.success(with: matchingNote) + } } extension IntentNote { From 39784dbed8f8c51f8e527b5e4f876b13a4047403 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 13:42:36 -0600 Subject: [PATCH 154/547] dropped error description for find note intent --- .../ShortcutIntents.intentdefinition | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 7d3097a98..02409700e 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -492,26 +492,6 @@ INIntentParameterPromptDialogType Primary - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${content}’. - INIntentParameterPromptDialogFormatStringID - NAzeXM - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${content}’? - INIntentParameterPromptDialogFormatStringID - wgf6Li - INIntentParameterPromptDialogType - Confirmation - INIntentParameterTag 4 @@ -607,20 +587,6 @@ INIntentResponseParameterType Object - - INIntentResponseParameterDisplayName - Failure Reason - INIntentResponseParameterDisplayNameID - Gde29V - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - failureReason - INIntentResponseParameterTag - 5 - INIntentResponseParameterType - String - INIntentTitle From 2d060126bb8f326e0809c6add0d224f36e0194a5 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 13:43:06 -0600 Subject: [PATCH 155/547] dropped failure reason response in find note intent --- Simplenote/Supporting Files/ShortcutIntents.intentdefinition | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 02409700e..cc8067b13 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -555,10 +555,6 @@ - INIntentResponseCodeFormatString - ${failureReason} - INIntentResponseCodeFormatStringID - ThMQV4 INIntentResponseCodeName failure From 836b73012a6ac1b6dbad3152915491118cfc201c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 13:52:24 -0600 Subject: [PATCH 156/547] Updated release notes PR1584 Extended support for iOS Shortcuts --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 2d445ee7c..991a8d7ed 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,6 @@ 4.52 ----- - +- Extended support for iOS Shortcuts 4.51 ----- From 8fea6d6c52052dac578010c91c96d2785cb369e6 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 13:57:21 -0600 Subject: [PATCH 157/547] Updated open note intent to accept a note input parameter --- Simplenote/Supporting Files/ShortcutIntents.intentdefinition | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index cc8067b13..694427c5a 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -87,6 +87,8 @@ sqM3pN INIntentIneligibleForSuggestions + INIntentInput + note INIntentLastParameterTag 2 INIntentManagedParameterCombinations From 647d41f1d761069bc9e593e24ce1d12a2c5b846f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 15:57:25 -0600 Subject: [PATCH 158/547] Added copy note content intent handler --- Simplenote.xcodeproj/project.pbxproj | 4 + .../ShortcutIntents.intentdefinition | 139 ++++++++++++++++++ .../Supporting Files/Simplenote-Info.plist | 1 + .../CopyNoteContentIntentHandler.swift | 9 ++ SimplenoteIntents/Supporting Files/Info.plist | 1 + 5 files changed, 154 insertions(+) create mode 100644 SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 5b869b878..5e0f5977f 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -511,6 +511,7 @@ 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 */; }; 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 */; }; @@ -1196,6 +1197,7 @@ 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 = ""; }; 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 = ""; }; @@ -2350,6 +2352,7 @@ BA35808F2BE95BE100CE1590 /* AppendNoteIntentHandler.swift */, BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */, BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */, + BA9C7EC82BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift */, ); path = "Intent Handlers"; sourceTree = ""; @@ -3793,6 +3796,7 @@ 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 */, BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, BA0AF10D2BE996600050EEBD /* KeychainManager.swift in Sources */, diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 694427c5a..64d84c459 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -596,6 +596,145 @@ 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 + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${note}’. + INIntentParameterPromptDialogFormatStringID + VBtgac + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${note}’? + INIntentParameterPromptDialogFormatStringID + MdiZid + INIntentParameterPromptDialogType + Confirmation + + + 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 + INTypes diff --git a/Simplenote/Supporting Files/Simplenote-Info.plist b/Simplenote/Supporting Files/Simplenote-Info.plist index 2fe3542f2..034199915 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -81,6 +81,7 @@ NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent + CopyNoteContentIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift new file mode 100644 index 000000000..269422fb5 --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift @@ -0,0 +1,9 @@ +// +// CopyNoteContentIntentHandler.swift +// SimplenoteIntents +// +// Created by Charlie Scheer on 5/9/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index 946c2d7fc..8f50fd5ef 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -37,6 +37,7 @@ NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent + CopyNoteContentIntent NSExtensionPointIdentifier From 58c13b46c10cfd2282a56b8b4716dbc922e0849f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 15:57:40 -0600 Subject: [PATCH 159/547] Created intent handler to copy content from a note --- .../CopyNoteContentIntentHandler.swift | 26 ++++++++++++++++++- SimplenoteIntents/IntentHandler.swift | 2 ++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift index 269422fb5..7b526493d 100644 --- a/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/CopyNoteContentIntentHandler.swift @@ -6,4 +6,28 @@ // Copyright © 2024 Automattic. All rights reserved. // -import Foundation +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/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index 637fee2a5..9541d55e9 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -19,6 +19,8 @@ class IntentHandler: INExtension { return CreateNewNoteIntentHandler() case is FindNoteIntent: return FindNoteIntentHandler() + case is CopyNoteContentIntent: + return CopyNoteContentIntentHandler() default: return self } From b0904b5ea919a2874755dbc505e672a74de6276c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 16:02:11 -0600 Subject: [PATCH 160/547] Added find note with tag intent --- .../ShortcutIntents.intentdefinition | 250 ++++++++++++++++++ .../Supporting Files/Simplenote-Info.plist | 3 +- SimplenoteIntents/Supporting Files/Info.plist | 3 +- 3 files changed, 254 insertions(+), 2 deletions(-) diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index 64d84c459..c2ac7df11 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -735,6 +735,191 @@ INIntentVerb Do + + INIntentCategory + search + INIntentClassName + FindNoteWithTagIntent + INIntentClassPrefix + SP + INIntentConfigurable + + INIntentDescriptionID + R4WdO6 + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 3 + 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 + Tag + INIntentParameterObjectTypeNamespace + BxdWY5 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${tag}’. + INIntentParameterPromptDialogFormatStringID + U4ce2H + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${tag}’? + INIntentParameterPromptDialogFormatStringID + qYKqOP + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 3 + INIntentParameterType + Object + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Note + INIntentParameterDisplayNameID + wLeRnn + INIntentParameterDisplayPriority + 2 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + Y8F8kT + + INIntentParameterName + note + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Which One? + INIntentParameterPromptDialogFormatStringID + uDDlMr + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 2 + INIntentParameterType + String + + + 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 @@ -805,6 +990,71 @@ + + INTypeClassPrefix + SP + INTypeDisplayName + Tag + INTypeDisplayNameID + IIfrvb + INTypeLastPropertyTag + 99 + INTypeName + Tag + 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 034199915..3f1354c73 100644 --- a/Simplenote/Supporting Files/Simplenote-Info.plist +++ b/Simplenote/Supporting Files/Simplenote-Info.plist @@ -75,13 +75,14 @@ NSUserActivityTypes AppendNoteIntent + CopyNoteContentIntent CreateNewNoteIntent FindNoteIntent + FindNoteWithTagIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent - CopyNoteContentIntent com.codality.NotationalFlow.launch com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote diff --git a/SimplenoteIntents/Supporting Files/Info.plist b/SimplenoteIntents/Supporting Files/Info.plist index 8f50fd5ef..4d033e2a1 100644 --- a/SimplenoteIntents/Supporting Files/Info.plist +++ b/SimplenoteIntents/Supporting Files/Info.plist @@ -31,13 +31,14 @@ IntentsSupported AppendNoteIntent + CopyNoteContentIntent CreateNewNoteIntent FindNoteIntent + FindNoteWithTagIntent ListWidgetIntent NoteWidgetIntent OpenNewNoteIntent OpenNoteIntent - CopyNoteContentIntent NSExtensionPointIdentifier From 0fdca0309264ee532a365d4b318e87a000faa400 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 16:14:33 -0600 Subject: [PATCH 161/547] Added structure to fetch tags for find note with tag intent --- Simplenote.xcodeproj/project.pbxproj | 8 ++++++ .../ShortcutIntents.intentdefinition | 25 +++++++++--------- .../FindNoteWithTagIntentHandler.swift | 26 +++++++++++++++++++ SimplenoteIntents/IntentHandler.swift | 2 ++ SimplenoteIntents/IntentsError.swift | 5 ++++ .../ResolutionResults/IntentTag+Helpers.swift | 19 ++++++++++++++ 6 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift create mode 100644 SimplenoteIntents/ResolutionResults/IntentTag+Helpers.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 5e0f5977f..f2727bbe1 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -512,6 +512,8 @@ 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 */; }; 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 */; }; @@ -1198,6 +1200,8 @@ 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 = ""; }; 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 = ""; }; @@ -2353,6 +2357,7 @@ BAB898D22BEC404200E238B8 /* CreateNewNoteIntentHandler.swift */, BAD0F1EC2BED49C200E73E45 /* FindNoteIntentHandler.swift */, BA9C7EC82BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift */, + BA9C7ECA2BED7F7B007A8460 /* FindNoteWithTagIntentHandler.swift */, ); path = "Intent Handlers"; sourceTree = ""; @@ -2361,6 +2366,7 @@ isa = PBXGroup; children = ( BA34B0562BEC216B00580E15 /* IntentNote+Helpers.swift */, + BA9C7ECC2BED813B007A8460 /* IntentTag+Helpers.swift */, ); path = ResolutionResults; sourceTree = ""; @@ -3804,6 +3810,7 @@ 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 */, @@ -3816,6 +3823,7 @@ 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 */, diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition index c2ac7df11..fcb648266 100644 --- a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/ShortcutIntents.intentdefinition @@ -749,7 +749,7 @@ INIntentIneligibleForSuggestions INIntentLastParameterTag - 3 + 4 INIntentManagedParameterCombinations tag,note @@ -780,7 +780,7 @@ INIntentParameterName tag INIntentParameterObjectType - Tag + IntentTag INIntentParameterObjectTypeNamespace BxdWY5 INIntentParameterPromptDialogs @@ -834,15 +834,12 @@ wLeRnn INIntentParameterDisplayPriority 2 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - Y8F8kT - INIntentParameterName note + INIntentParameterObjectType + IntentNote + INIntentParameterObjectTypeNamespace + BxdWY5 INIntentParameterPromptDialogs @@ -865,9 +862,9 @@ INIntentParameterSupportsResolution INIntentParameterTag - 2 + 4 INIntentParameterType - String + Object INIntentResponse @@ -991,16 +988,18 @@ + INTypeClassName + IntentTag INTypeClassPrefix SP INTypeDisplayName - Tag + Intent Tag INTypeDisplayNameID IIfrvb INTypeLastPropertyTag 99 INTypeName - Tag + IntentTag INTypeProperties diff --git a/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift b/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift new file mode 100644 index 000000000..05afd96ba --- /dev/null +++ b/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift @@ -0,0 +1,26 @@ +// +// 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 { + IntentNoteResolutionResult.success(with: IntentNote(identifier: "", display: "")) + } + + 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 { + FindNoteWithTagIntentResponse(code: .success, userActivity: nil) + } +} diff --git a/SimplenoteIntents/IntentHandler.swift b/SimplenoteIntents/IntentHandler.swift index 9541d55e9..253026efc 100644 --- a/SimplenoteIntents/IntentHandler.swift +++ b/SimplenoteIntents/IntentHandler.swift @@ -21,6 +21,8 @@ class IntentHandler: INExtension { return FindNoteIntentHandler() case is CopyNoteContentIntent: return CopyNoteContentIntentHandler() + case is FindNoteWithTagIntent: + return FindNoteWithTagIntentHandler() default: return self } diff --git a/SimplenoteIntents/IntentsError.swift b/SimplenoteIntents/IntentsError.swift index b3c149d2b..821b7edbc 100644 --- a/SimplenoteIntents/IntentsError.swift +++ b/SimplenoteIntents/IntentsError.swift @@ -10,11 +10,14 @@ 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") } } @@ -22,6 +25,8 @@ enum IntentsError: Error { 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/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()) }) + } +} From 0d1589bbc65777a498170becc1d0e168310ca81b Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 9 May 2024 16:42:24 -0600 Subject: [PATCH 162/547] Added resolve methods for fetching notes from tag --- .../FindNoteWithTagIntentHandler.swift | 18 ++++++++-- .../IntentNote+Helpers.swift | 34 +++++++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift b/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift index 05afd96ba..78eaf8aca 100644 --- a/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/FindNoteWithTagIntentHandler.swift @@ -12,7 +12,15 @@ class FindNoteWithTagIntentHandler: NSObject, FindNoteWithTagIntentHandling { let coreDataWrapper = ExtensionCoreDataWrapper() func resolveNote(for intent: FindNoteWithTagIntent) async -> IntentNoteResolutionResult { - IntentNoteResolutionResult.success(with: IntentNote(identifier: "", display: "")) + 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 { @@ -21,6 +29,12 @@ class FindNoteWithTagIntentHandler: NSObject, FindNoteWithTagIntentHandling { } func handle(intent: FindNoteWithTagIntent) async -> FindNoteWithTagIntentResponse { - FindNoteWithTagIntentResponse(code: .success, userActivity: nil) + 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/ResolutionResults/IntentNote+Helpers.swift b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift index 3dc4ce1ae..ddbca72df 100644 --- a/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift +++ b/SimplenoteIntents/ResolutionResults/IntentNote+Helpers.swift @@ -24,27 +24,35 @@ extension IntentNoteResolutionResult { static func resolveIntentNote(for content: String, in coreDataWrapper: ExtensionCoreDataWrapper) -> IntentNoteResolutionResult { guard let notes = coreDataWrapper.resultsController()?.notes() else { - // TODO: Better error return IntentNoteResolutionResult.unsupported() } let filteredNotes = notes.filter({ $0.content?.contains(content) == true }) - let intentNotes = filteredNotes.map({ IntentNote(identifier: $0.simperiumKey, display: $0.title) }) + let intentNotes = IntentNote.makeIntentNotes(from: filteredNotes) - guard intentNotes.isEmpty == false else { - // TODO: Better error + 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() } - guard intentNotes.count == 1 else { - return IntentNoteResolutionResult.disambiguation(with: intentNotes) - } + let intentNotes = IntentNote.makeIntentNotes(from: notesForTag) + + return resolve(intentNotes) + } - //This shouldn't happen but we to unwrap the existing note we need to return something - guard let matchingNote = intentNotes.first else { + private static func resolve(_ intentNotes: [IntentNote]) -> IntentNoteResolutionResult { + guard intentNotes.isEmpty == false else { return IntentNoteResolutionResult.unsupported() } - return IntentNoteResolutionResult.success(with: matchingNote) + if intentNotes.count == 1, + let intentNote = intentNotes.first { + return IntentNoteResolutionResult.success(with: intentNote) + } + + return IntentNoteResolutionResult.disambiguation(with: intentNotes) } } @@ -54,6 +62,10 @@ extension IntentNote { throw IntentsError.couldNotFetchNotes } - return notes.map({ IntentNote(identifier: $0.simperiumKey, display: $0.title) }) + return makeIntentNotes(from: notes) + } + + static func makeIntentNotes(from notes: [Note]) -> [IntentNote] { + notes.map({ IntentNote(identifier: $0.simperiumKey, display: $0.title) }) } } From 51877490af4df4eeeee1822f254345d820efa8ad Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 10 May 2024 12:18:42 -0600 Subject: [PATCH 163/547] Added localize support for siri kit intents --- Simplenote.xcodeproj/project.pbxproj | 60 +++++++++++-- .../ShortcutIntents.intentdefinition | 0 .../ar.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../cy.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../de.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../el.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../en.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../es.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../fa.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../fr.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../he.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../id.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../it.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../ja.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../ko.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../nl.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../pt-BR.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../ru.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../sv.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../tr.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../zh-Hans-CN.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ .../zh-Hant-TW.lproj/ShortcutIntents.strings | 86 +++++++++++++++++++ 22 files changed, 1774 insertions(+), 6 deletions(-) rename Simplenote/Supporting Files/{ => Base.lproj}/ShortcutIntents.intentdefinition (100%) create mode 100644 Simplenote/Supporting Files/ar.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/cy.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/de.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/el.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/en.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/es.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/fa.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/fr.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/he.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/id.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/it.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/ja.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/ko.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/nl.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/pt-BR.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/ru.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/sv.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/tr.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/zh-Hans-CN.lproj/ShortcutIntents.strings create mode 100644 Simplenote/Supporting Files/zh-Hant-TW.lproj/ShortcutIntents.strings diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index f2727bbe1..4bba92619 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -438,8 +438,6 @@ 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 */; }; - BA289B582BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */; }; - BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */; }; 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 */; }; @@ -514,6 +512,8 @@ 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 */; }; 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 */; }; @@ -1148,7 +1148,6 @@ BA12B06C26B0D0150026F31D /* SPManagedObject+Widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SPManagedObject+Widget.swift"; sourceTree = ""; }; BA16C6A82BC4968400C9079F /* Simplenote 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Simplenote 7.xcdatamodel"; sourceTree = ""; }; BA18532726488DBC00D9A347 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.swift; sourceTree = ""; }; - BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ShortcutIntents.intentdefinition; 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 = ""; }; @@ -1202,6 +1201,27 @@ 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 = ""; }; 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 = ""; }; @@ -2684,7 +2704,7 @@ B5250A6B22B922F900AE7797 /* Simplenote-Internal.entitlements */, E29ADD4717848E8500E55842 /* main.m */, E29ADD4917848E8500E55842 /* Simplenote-Prefix.pch */, - BA289B572BE436EB000E6794 /* ShortcutIntents.intentdefinition */, + BA9C7ECF2BEE9BA7007A8460 /* ShortcutIntents.intentdefinition */, ); path = "Supporting Files"; sourceTree = ""; @@ -3439,7 +3459,7 @@ B52BB74E22CFD1660042C162 /* SimplenoteActivityItemSource.swift in Sources */, B537730F252E14C600BC78C5 /* OptionsViewController.swift in Sources */, B56A696722F9D55F00B90398 /* UIView+Animations.swift in Sources */, - BA289B582BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, + BA9C7ED02BEE9BA7007A8460 /* ShortcutIntents.intentdefinition in Sources */, BA3856CD2681715700F388CC /* CoreDataManager.swift in Sources */, A60DF30825A44F0F00FDADF3 /* PinLockRemoveController.swift in Sources */, A694ABAB25D1549D00CC3A2D /* FileStorage.swift in Sources */, @@ -3804,7 +3824,7 @@ BAB5762726703C8200B0C56F /* IntentHandler.swift in Sources */, BA9C7EC92BED7AB1007A8460 /* CopyNoteContentIntentHandler.swift in Sources */, BA289B652BE43963000E6794 /* OpenNewNoteIntentHandler.swift in Sources */, - BA289B592BE436EB000E6794 /* ShortcutIntents.intentdefinition in Sources */, + BA9C7ED12BEE9BA7007A8460 /* ShortcutIntents.intentdefinition in Sources */, BA0AF10D2BE996600050EEBD /* KeychainManager.swift in Sources */, BA86625D26B3B14900466746 /* SortMode.swift in Sources */, BAB898D32BEC404200E238B8 /* CreateNewNoteIntentHandler.swift in Sources */, @@ -4001,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 = ( diff --git a/Simplenote/Supporting Files/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition similarity index 100% rename from Simplenote/Supporting Files/ShortcutIntents.intentdefinition rename to Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition 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?"; + From 886e226a20abf305973eb58de81aa0c632a2ed68 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 10 May 2024 12:42:28 -0600 Subject: [PATCH 164/547] Updated note list view controller top content inset --- Simplenote/Classes/SPNoteListViewController+Extensions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPNoteListViewController+Extensions.swift b/Simplenote/Classes/SPNoteListViewController+Extensions.swift index 8c0677a3c..e956246e5 100644 --- a/Simplenote/Classes/SPNoteListViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteListViewController+Extensions.swift @@ -157,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 / 2 + tableView.verticalScrollIndicatorInsets.top = searchBarStackView.frame.height / 2 } /// Scrolls to the First Row whenever the flag `mustScrollToFirstRow` was set to true From d57d253c0bc16bca9127d45570636876e5e292fc Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 10 May 2024 15:58:38 -0600 Subject: [PATCH 165/547] Updated SPNoteListVC to allow for viewing notes in the trash --- Simplenote/Classes/SPNoteListViewController.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Simplenote/Classes/SPNoteListViewController.m b/Simplenote/Classes/SPNoteListViewController.m index 512f0d3f4..9493b4881 100644 --- a/Simplenote/Classes/SPNoteListViewController.m +++ b/Simplenote/Classes/SPNoteListViewController.m @@ -523,7 +523,6 @@ - (void)update BOOL isTrashOnScreen = self.isDeletedFilterActive; [self refreshEmptyTrashState]; - self.tableView.allowsSelection = !isTrashOnScreen; [self displayPlaceholdersIfNeeded]; [self refershNavigationButtons]; From 4bd2876d9e58250e1c3116499ad189395863d9f3 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 13 May 2024 12:31:13 -0600 Subject: [PATCH 166/547] Modified the amount of gap between header and notes list --- Simplenote/Classes/SPNoteListViewController+Extensions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPNoteListViewController+Extensions.swift b/Simplenote/Classes/SPNoteListViewController+Extensions.swift index e956246e5..6714ec1a0 100644 --- a/Simplenote/Classes/SPNoteListViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteListViewController+Extensions.swift @@ -157,8 +157,8 @@ extension SPNoteListViewController { /// @objc func refreshTableViewTopInsets() { - tableView.contentInset.top = searchBarStackView.frame.height / 2 - tableView.verticalScrollIndicatorInsets.top = searchBarStackView.frame.height / 2 + 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 From 2b73f0def4c3ef8540691bab92564795d68bef3e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 13 May 2024 13:25:58 -0600 Subject: [PATCH 167/547] Refactored AppendNote intent to fetch the current note content to append --- .../AppendNoteIntentHandler.swift | 6 ++- SimplenoteShare/Simperium/Uploader.swift | 40 ++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 07fe62c7e..1de7c86bd 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -35,7 +35,11 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { return AppendNoteIntentResponse(code: .failure, userActivity: nil) } - note.content? += "\n\n\(content)" + guard let existingContent = try? await Downloader(simperiumToken: token).getNoteContent(for: identifier) else { + return AppendNoteIntentResponse(code: .failure, userActivity: nil) + } + + note.content = existingContent + "\n\n\(content)" let uploader = Uploader(simperiumToken: token) uploader.send(note) diff --git a/SimplenoteShare/Simperium/Uploader.swift b/SimplenoteShare/Simperium/Uploader.swift index 743e918e6..06b33b697 100644 --- a/SimplenoteShare/Simperium/Uploader.swift +++ b/SimplenoteShare/Simperium/Uploader.swift @@ -23,7 +23,7 @@ class Uploader: NSObject { // Request var request = URLRequest(url: targetURL) - request.httpMethod = Settings.httpMethod + request.httpMethod = Settings.httpMethodPost request.httpBody = note.toJsonData() request.setValue(token, forHTTPHeaderField: Settings.authHeader) @@ -61,10 +61,46 @@ extension Uploader: URLSessionTaskDelegate { } } +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] + return jsonObject?["content"] as? String + } +} + // MARK: - Settings // private struct Settings { static let authHeader = "X-Simperium-Token" static let bucketName = "note" - static let httpMethod = "POST" + static let httpMethodPost = "POST" + static let httpMethodGet = "GET" } From b5dfb3c7ba29105b4cf81da97c30fe0eee25f40d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 13 May 2024 16:33:29 -0600 Subject: [PATCH 168/547] Moved Downloader to it's own file --- Simplenote.xcodeproj/project.pbxproj | 6 +++ SimplenoteShare/Simperium/Downloader.swift | 52 ++++++++++++++++++++++ SimplenoteShare/Simperium/Uploader.swift | 36 --------------- 3 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 SimplenoteShare/Simperium/Downloader.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index f3b376944..daffbb9da 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -511,6 +511,8 @@ 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 */; }; + 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 */; }; @@ -1194,6 +1196,7 @@ 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 = ""; }; + 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 = ""; }; @@ -2242,6 +2245,7 @@ children = ( B5CFFFE822AA9DD700B968CD /* Note.swift */, B5CFFFE622AA9DB100B968CD /* Uploader.swift */, + BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */, ); path = Simperium; sourceTree = ""; @@ -3760,6 +3764,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 */, @@ -3777,6 +3782,7 @@ 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 */, diff --git a/SimplenoteShare/Simperium/Downloader.swift b/SimplenoteShare/Simperium/Downloader.swift new file mode 100644 index 000000000..b8a4f645e --- /dev/null +++ b/SimplenoteShare/Simperium/Downloader.swift @@ -0,0 +1,52 @@ +// +// Downloader.swift +// SimplenoteShare +// +// Created by Charlie Scheer on 5/13/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation + +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] + return jsonObject?["content"] as? String + } +} + +// MARK: - Settings +// +private struct Settings { + static let authHeader = "X-Simperium-Token" + static let bucketName = "note" + static let httpMethodGet = "GET" +} diff --git a/SimplenoteShare/Simperium/Uploader.swift b/SimplenoteShare/Simperium/Uploader.swift index 06b33b697..da6b6e9fe 100644 --- a/SimplenoteShare/Simperium/Uploader.swift +++ b/SimplenoteShare/Simperium/Uploader.swift @@ -61,46 +61,10 @@ extension Uploader: URLSessionTaskDelegate { } } -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] - return jsonObject?["content"] as? String - } -} - // MARK: - Settings // private struct Settings { static let authHeader = "X-Simperium-Token" static let bucketName = "note" static let httpMethodPost = "POST" - static let httpMethodGet = "GET" } From ebc36f0b9fc5913238c4664c78cc6cf4f5c175c0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 13 May 2024 16:33:59 -0600 Subject: [PATCH 169/547] Removed double space when appending to a note --- SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 1de7c86bd..2ab3df7b9 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -39,7 +39,7 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { return AppendNoteIntentResponse(code: .failure, userActivity: nil) } - note.content = existingContent + "\n\n\(content)" + note.content = existingContent + "\n\(content)" let uploader = Uploader(simperiumToken: token) uploader.send(note) From a58fca923284e02ef8d98576bbc0105817a4eba0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 14 May 2024 10:02:45 -0600 Subject: [PATCH 170/547] Revert "Merge pull request #1573 from Automattic drop-collaboration-mk1 This reverts commit 4c699914fd22e9af7d9ac2f8f9559d8ea90d2d28, reversing changes made to ed04027d0530048330f973235b519f8990433329. --- Simplenote.xcodeproj/project.pbxproj | 4 -- Simplenote/BannerView.swift | 62 +++++++++--------- Simplenote/BannerView.xib | 8 ++- .../SPAddCollaboratorsViewController.h | 2 - .../SPAddCollaboratorsViewController.m | 18 +++--- .../Classes/SPEntryListViewController.h | 6 +- .../Classes/SPEntryListViewController.m | 63 ++++++++++--------- .../SPAddCollaboratorsViewController.swift | 54 ---------------- 8 files changed, 78 insertions(+), 139 deletions(-) delete mode 100644 Simplenote/SPAddCollaboratorsViewController.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index bfc64dd47..c4d3df429 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -544,7 +544,6 @@ 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 */; }; - BAC7264E2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.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 */; }; @@ -1246,7 +1245,6 @@ 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 = ""; }; - BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAddCollaboratorsViewController.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 = ""; }; @@ -2741,7 +2739,6 @@ E2F1497F179D8E1A00DC9690 /* Settings */, E25C39CE17A41F3400B2591A /* SPAddCollaboratorsViewController.h */, E25C39CF17A41F3400B2591A /* SPAddCollaboratorsViewController.m */, - BAC7264D2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift */, E2EAE38117C2C2D400268682 /* SPEntryListViewController.h */, E2EAE38217C2C2D400268682 /* SPEntryListViewController.m */, E215C790180B115C00AD36B5 /* SPNavigationController.h */, @@ -3501,7 +3498,6 @@ B531090823D0B685002B0998 /* SPSimpleTextPrintFormatter.swift in Sources */, B5CDE61F2150834C00C3FED4 /* Simperium+Simplenote.m in Sources */, A60A1A1E25655D840041701E /* ApplicationShortcutItemType.swift in Sources */, - BAC7264E2BD96E7C0002FA68 /* SPAddCollaboratorsViewController.swift in Sources */, B51F6DD12460C3EE0074DDD9 /* AuthenticationValidator.swift in Sources */, B575736A232C567000443C2E /* UIImage+Dynamic.swift in Sources */, A60DF31025A4524100FDADF3 /* PinLockBaseController.swift in Sources */, diff --git a/Simplenote/BannerView.swift b/Simplenote/BannerView.swift index d5e602cff..ec98db8cc 100644 --- a/Simplenote/BannerView.swift +++ b/Simplenote/BannerView.swift @@ -44,22 +44,21 @@ class BannerView: UIView { override func awakeFromNib() { super.awakeFromNib() refreshInterface() - setupTapRecognizer() } // MARK: - Actions - @objc - func bannerWasPressed() { + @IBAction + func bannerWasPresssed() { onPress?() } +} - private func setupTapRecognizer() { - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(bannerWasPressed)) - backgroundView.addGestureRecognizer(tapRecognizer) - } +// MARK: - Private API(s) +// +private extension BannerView { - func refreshInterface(with style: BannerView.Style? = nil) { + func refreshInterface(with style: Style? = nil) { guard let style else { return } @@ -71,35 +70,32 @@ class BannerView: UIView { backgroundView.backgroundColor = style.backgroundColor backgroundView.layer.cornerRadius = Metrics.defaultCornerRadius } +} - // MARK: - Style - // - struct Style { - let title: String - let details: String - let textColor: UIColor - let backgroundColor: UIColor - - // 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"), - textColor: .white, - backgroundColor: .simplenoteSustainerViewBackgroundColor) - } - - static var notSubscriber: Style { - Style(title: NSLocalizedString("Become a Simplenote Sustainer", comment: "Become a Sustainer Title"), - details: NSLocalizedString("Support your favorite notes app to help unlock future features", comment: "Become a Sustainer Details"), - textColor: .white, - backgroundColor: .simplenoteBlue50Color) - } +// MARK: - Style +// +private struct Style { + let title: String + let details: String + let textColor: UIColor + let backgroundColor: UIColor +} - static var collaborationRetirement: Style { - Style(title: NSLocalizedString("Collaboration Retirement", comment: "Title announcing collaboration retirement"), details: NSLocalizedString("Collaboration is retiring on July 1st, 2024. For more details, tap here.", comment: "Description for retiring collaboration feature"), textColor: .white, backgroundColor: .simplenoteBlue50Color) - } +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"), + textColor: .white, + backgroundColor: .simplenoteSustainerViewBackgroundColor) } + static var notSubscriber: Style { + Style(title: NSLocalizedString("Become a Simplenote Sustainer", comment: "Become a Sustainer Title"), + details: NSLocalizedString("Support your favorite notes app to help unlock future features", comment: "Become a Sustainer Details"), + textColor: .white, + backgroundColor: .simplenoteBlue50Color) + } } // MARK: - Metrics diff --git a/Simplenote/BannerView.xib b/Simplenote/BannerView.xib index f667c7744..dc1d2af0e 100644 --- a/Simplenote/BannerView.xib +++ b/Simplenote/BannerView.xib @@ -10,6 +10,12 @@ + + + + + + @@ -84,10 +90,10 @@ + - diff --git a/Simplenote/Classes/SPAddCollaboratorsViewController.h b/Simplenote/Classes/SPAddCollaboratorsViewController.h index fe6ad6dce..83c11be63 100644 --- a/Simplenote/Classes/SPAddCollaboratorsViewController.h +++ b/Simplenote/Classes/SPAddCollaboratorsViewController.h @@ -5,7 +5,6 @@ NS_ASSUME_NONNULL_BEGIN @class SPAddCollaboratorsViewController; -@class BannerView; @protocol SPCollaboratorDelegate @required @@ -21,7 +20,6 @@ NS_ASSUME_NONNULL_BEGIN @interface SPAddCollaboratorsViewController : SPEntryListViewController -@property (nonatomic, strong) BannerView *bannerView; @property (nonatomic, nullable, weak) id collaboratorDelegate; - (void)setupWithCollaborators:(NSArray *)collaborators; diff --git a/Simplenote/Classes/SPAddCollaboratorsViewController.m b/Simplenote/Classes/SPAddCollaboratorsViewController.m index 18bc871f5..97ec18462 100644 --- a/Simplenote/Classes/SPAddCollaboratorsViewController.m +++ b/Simplenote/Classes/SPAddCollaboratorsViewController.m @@ -47,10 +47,8 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self.primaryTableView reloadData]; + [primaryTableView reloadData]; [self.contactsManager requestAuthorizationIfNeededWithCompletion:nil]; - [self setupBannerView]; - [self setupViewContraints]; } - (void)viewDidAppear:(BOOL)animated @@ -115,7 +113,7 @@ - (void)setupWithCollaborators:(NSArray *)collaborators self.dataSource = [[[merged allObjects] sortedArrayUsingSelector:@selector(compareName:)] mutableCopy]; - [self.primaryTableView reloadData]; + [primaryTableView reloadData]; } @@ -128,7 +126,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if ([tableView isEqual:self.primaryTableView]) { + if ([tableView isEqual:primaryTableView]) { return self.dataSource.count; } @@ -137,7 +135,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if ([tableView isEqual:self.primaryTableView]) { + if ([tableView isEqual:primaryTableView]) { return self.dataSource.count > 0 ? NSLocalizedString(@"Current Collaborators", nil) : nil; } @@ -146,7 +144,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { - if ([tableView isEqual:self.primaryTableView]) { + if ([tableView isEqual:primaryTableView]) { if (self.dataSource.count > 0) { return nil; } else { @@ -162,7 +160,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N SPEntryListCell *cell = (SPEntryListCell *)[super tableView:tableView cellForRowAtIndexPath:indexPath]; PersonTag *personTag; - if ([tableView isEqual:self.primaryTableView]) { + if ([tableView isEqual:primaryTableView]) { personTag = self.dataSource[indexPath.row]; } else { personTag = self.autoCompleteDataSource[indexPath.row]; @@ -193,7 +191,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.selected = NO; - if ([tableView isEqual:self.primaryTableView]) { + if ([tableView isEqual:primaryTableView]) { PersonTag *person = self.dataSource[indexPath.row]; person.active = !person.active; @@ -229,7 +227,7 @@ - (void)addPersonTag:(PersonTag *)person [self.dataSource addObject:person]; entryTextField.text = @""; - [self.primaryTableView reloadSections:[NSIndexSet indexSetWithIndex:0] + [primaryTableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [self updateAutoCompleteMatchesForString:nil]; diff --git a/Simplenote/Classes/SPEntryListViewController.h b/Simplenote/Classes/SPEntryListViewController.h index f4cc336a8..f3dcd95ec 100644 --- a/Simplenote/Classes/SPEntryListViewController.h +++ b/Simplenote/Classes/SPEntryListViewController.h @@ -9,18 +9,16 @@ #import #import "SPTextField.h" -static CGFloat const EntryListCellHeight = 44; - @interface SPEntryListViewController : UIViewController { + UITableView *primaryTableView; + UIView *entryFieldBackground; SPTextField *entryTextField; UIButton *entryFieldPlusButton; UITableView *autoCompleteTableView; } -@property (nonatomic, strong) UITableView *primaryTableView; -@property (nonatomic, strong) UIView *entryFieldBackground; @property (nonatomic, strong) NSMutableArray *dataSource; @property (nonatomic, strong) NSArray *autoCompleteDataSource; @property (nonatomic) BOOL showEntryFieldPlusButton; diff --git a/Simplenote/Classes/SPEntryListViewController.m b/Simplenote/Classes/SPEntryListViewController.m index 7ec1a1c86..6f9f2adf7 100644 --- a/Simplenote/Classes/SPEntryListViewController.m +++ b/Simplenote/Classes/SPEntryListViewController.m @@ -14,6 +14,7 @@ static NSString *cellIdentifier = @"primaryCell"; static NSString *autoCompleteCellIdentifier = @"autoCompleteCell"; static CGFloat const EntryListTextFieldSidePadding = 15; +static CGFloat const EntryListCellHeight = 44; @implementation SPEntryListViewController @@ -31,7 +32,7 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self.primaryTableView reloadData]; + [primaryTableView reloadData]; } - (void)setupViews { @@ -39,25 +40,25 @@ - (void)setupViews { // setup views CGFloat yOrigin = self.view.safeAreaInsets.top; - self.entryFieldBackground = [[UIView alloc] initWithFrame:CGRectMake(0, + entryFieldBackground = [[UIView alloc] initWithFrame:CGRectMake(0, yOrigin, self.view.frame.size.width, EntryListCellHeight)]; - self.entryFieldBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; - [self.view addSubview:self.entryFieldBackground]; - + entryFieldBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; + [self.view addSubview:entryFieldBackground]; + entryTextField = [[SPTextField alloc] initWithFrame:CGRectMake(EntryListTextFieldSidePadding, 0, - self.entryFieldBackground.frame.size.width - 2 * EntryListTextFieldSidePadding, - self.entryFieldBackground.frame.size.height)]; + entryFieldBackground.frame.size.width - 2 * EntryListTextFieldSidePadding, + entryFieldBackground.frame.size.height)]; entryTextField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; entryTextField.keyboardType = UIKeyboardTypeEmailAddress; entryTextField.keyboardAppearance = SPUserInterface.isDark ? UIKeyboardAppearanceDark : UIKeyboardAppearanceDefault; entryTextField.autocapitalizationType = UITextAutocapitalizationTypeNone; entryTextField.delegate = self; - [self.entryFieldBackground addSubview:entryTextField]; - + [entryFieldBackground addSubview:entryTextField]; + entryFieldPlusButton = [UIButton buttonWithType:UIButtonTypeCustom]; UIImage *pickerImage = [UIImage imageWithName:UIImageNameAdd]; [entryFieldPlusButton setImage:pickerImage forState:UIControlStateNormal]; @@ -69,17 +70,17 @@ - (void)setupViews { entryTextField.rightViewMode = UITextFieldViewModeAlways; - self.primaryTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, yOrigin + entryTextField.frame.size.height, + primaryTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, yOrigin + entryTextField.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - (yOrigin + entryTextField.frame.size.height)) style:UITableViewStyleGrouped]; - self.primaryTableView.rowHeight = EntryListCellHeight; - self.primaryTableView.delegate = self; - self.primaryTableView.dataSource = self; - self.primaryTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - [self.view addSubview:self.primaryTableView]; - + primaryTableView.rowHeight = EntryListCellHeight; + primaryTableView.delegate = self; + primaryTableView.dataSource = self; + primaryTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + [self.view addSubview:primaryTableView]; - [self.primaryTableView registerClass:[SPEntryListCell class] + + [primaryTableView registerClass:[SPEntryListCell class] forCellReuseIdentifier:cellIdentifier]; [autoCompleteTableView registerClass:[SPEntryListAutoCompleteCell class] @@ -123,7 +124,7 @@ - (void)applyDefaultStyle { self.view.backgroundColor = tableBackgroundColor; // entry field - self.entryFieldBackground.backgroundColor = tableBackgroundColor; + entryFieldBackground.backgroundColor = tableBackgroundColor; entryTextField.backgroundColor = [UIColor clearColor]; entryTextField.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; entryTextField.textColor = [UIColor simplenoteTextColor]; @@ -131,15 +132,15 @@ - (void)applyDefaultStyle { CALayer *entryFieldBorder = [[CALayer alloc] init]; entryFieldBorder.frame = CGRectMake(0, - self.entryFieldBackground.bounds.size.height - 1.0 / [[UIScreen mainScreen] scale], + entryFieldBackground.bounds.size.height - 1.0 / [[UIScreen mainScreen] scale], MAX(self.view.frame.size.width, self.view.frame.size.height), 1.0 / [[UIScreen mainScreen] scale]); entryFieldBorder.backgroundColor = tableSeparatorColor.CGColor; - [self.entryFieldBackground.layer addSublayer:entryFieldBorder]; - + [entryFieldBackground.layer addSublayer:entryFieldBorder]; + // tableview - self.primaryTableView.backgroundColor = [UIColor clearColor]; - self.primaryTableView.separatorColor = tableSeparatorColor; + primaryTableView.backgroundColor = [UIColor clearColor]; + primaryTableView.separatorColor = tableSeparatorColor; autoCompleteTableView.backgroundColor = backgroundColor; autoCompleteTableView.separatorColor = tableSeparatorColor; } @@ -168,7 +169,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if ([tableView isEqual:self.primaryTableView]) + if ([tableView isEqual:primaryTableView]) return 0; // this is implemented by subclassing else if ([tableView isEqual:autoCompleteTableView]) return _autoCompleteDataSource.count; @@ -191,8 +192,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N UITableViewCell *finalCell; - if ([tableView isEqual:self.primaryTableView]) { - + if ([tableView isEqual:primaryTableView]) { + SPEntryListCell *cell = (SPEntryListCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (!cell) { @@ -219,7 +220,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - return [tableView isEqual:self.primaryTableView] ? YES : NO; + return [tableView isEqual:primaryTableView] ? YES : NO; } - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { @@ -237,10 +238,10 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd if (editingStyle == UITableViewCellEditingStyleDelete) { [self removeItemFromDataSourceAtIndexPath:indexPath]; - [self.primaryTableView beginUpdates]; - [self.primaryTableView deleteRowsAtIndexPaths:@[indexPath] + [primaryTableView beginUpdates]; + [primaryTableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; - [self.primaryTableView endUpdates]; + [primaryTableView endUpdates]; } } - (void)removeItemFromDataSourceAtIndexPath:(NSIndexPath *)indexPath { @@ -294,7 +295,7 @@ - (void)updatedAutoCompleteMatches { - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { - if ([scrollView isEqual:self.primaryTableView]) + if ([scrollView isEqual:primaryTableView]) [entryTextField resignFirstResponder]; } diff --git a/Simplenote/SPAddCollaboratorsViewController.swift b/Simplenote/SPAddCollaboratorsViewController.swift deleted file mode 100644 index 8b370606f..000000000 --- a/Simplenote/SPAddCollaboratorsViewController.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// SPAddCollaboratorsViewController.swift -// Simplenote -// -// Created by Charlie Scheer on 4/24/24. -// Copyright © 2024 Automattic. All rights reserved. -// - -import Foundation -import UIKit - -extension SPAddCollaboratorsViewController { - @objc - func setupBannerView() { - bannerView = BannerView.instantiateFromNib() - bannerView.refreshInterface(with: .collaborationRetirement) - bannerView.onPress = { - guard let url = URL(string: "https://simplenote.com/2024/05/01/collaboration-feature-retirement") else { - return - } - - UIApplication.shared.open(url) - } - - view.addSubview(bannerView) - } - - @objc - func setupViewContraints() { - bannerView.translatesAutoresizingMaskIntoConstraints = false - primaryTableView.translatesAutoresizingMaskIntoConstraints = false - entryFieldBackground.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - bannerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - bannerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - bannerView.topAnchor.constraint(equalTo: view.topAnchor), - bannerView.bottomAnchor.constraint(equalTo: entryFieldBackground.topAnchor) - ]) - - NSLayoutConstraint.activate([ - entryFieldBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor), - entryFieldBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor), - entryFieldBackground.heightAnchor.constraint(equalToConstant: EntryListCellHeight) - ]) - - NSLayoutConstraint.activate([ - primaryTableView.topAnchor.constraint(equalTo: entryFieldBackground.bottomAnchor), - primaryTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - primaryTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - primaryTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - } -} From 654c005103dcb10c37a179c78c75eff508ab3df0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 14 May 2024 17:16:04 -0600 Subject: [PATCH 171/547] Fixed issue where the open new note and open note shortcuts failed --- Simplenote/Classes/ActivityType.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/ActivityType.swift b/Simplenote/Classes/ActivityType.swift index e3c066138..b303b2969 100644 --- a/Simplenote/Classes/ActivityType.swift +++ b/Simplenote/Classes/ActivityType.swift @@ -11,12 +11,12 @@ enum ActivityType: String { /// New Note Activity /// case newNote = "com.codality.NotationalFlow.newNote" - case newNoteShortcut = "SPOpenNewNoteIntent" + case newNoteShortcut = "OpenNewNoteIntent" /// Open a Note! /// case openNote = "com.codality.NotationalFlow.openNote" - case openNoteShortcut = "SPOpenNoteIntent" + case openNoteShortcut = "OpenNoteIntent" /// Open an Item that was indexed by Spotlight /// From 406430041869e004d37f3ba04dba8238f0a6bf19 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 14 May 2024 17:16:28 -0600 Subject: [PATCH 172/547] Stopped shortcuts from applying a selected tag when making note --- Simplenote/Classes/ShortcutsHandler.swift | 4 ++-- Simplenote/SPAppDelegate+Extensions.swift | 24 ++++++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Simplenote/Classes/ShortcutsHandler.swift b/Simplenote/Classes/ShortcutsHandler.swift index 27b309d21..b347cabe1 100644 --- a/Simplenote/Classes/ShortcutsHandler.swift +++ b/Simplenote/Classes/ShortcutsHandler.swift @@ -49,7 +49,7 @@ class ShortcutsHandler: NSObject { case .launch: break case .newNote, .newNoteShortcut: - SPAppDelegate.shared().presentNewNoteEditor() + SPAppDelegate.shared().presentNewNoteEditor(useSelectedTag: false) case .openNote, .openSpotlightItem: presentNote(for: userActivity) case .openNoteShortcut: @@ -105,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) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index f9bee8541..ad1a84a44 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -151,13 +151,17 @@ extension SPAppDelegate { /// Opens editor with a new note /// @objc - func presentNewNoteEditor(animated: Bool = false) { - if isPresentingPasscodeLock && SPPinLockManager.shared.isEnabled { - verifyController?.addOnSuccesBlock { + 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) } - } else { - presentNote(nil, animated: animated) } } @@ -204,6 +208,16 @@ extension SPAppDelegate { func setupNoticeController() { NoticeController.shared.setupNoticeController() } + + func performActionAfterUnlock(action: @escaping () -> Void) { + if isPresentingPasscodeLock && SPPinLockManager.shared.isEnabled { + verifyController?.addOnSuccesBlock { + action() + } + } else { + action() + } + } } // MARK: - UIViewControllerRestoration From 0c3916377e1f6b0da3988806867c264caa6570c4 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 14 May 2024 17:20:19 -0600 Subject: [PATCH 173/547] Fixed the new note widget still applying the selected tag --- Simplenote/SPAppDelegate+Extensions.swift | 5 +++++ Simplenote/SPAppDelegate.m | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index ad1a84a44..a2babfdfa 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -165,6 +165,11 @@ extension SPAppDelegate { } } + @objc + func presentNewNoteEditor(animated: Bool = false) { + presentNewNoteEditor(useSelectedTag: true, animated: animated) + } + var verifyController: PinLockVerifyController? { (pinLockWindow?.rootViewController as? PinLockViewController)?.controller as? PinLockVerifyController } diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index d0b1359b6..847d1c47b 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -508,7 +508,7 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDiction } if ([[components host] isEqualToString:@"widgetNew"]) { - [self presentNewNoteEditorWithAnimated: NO]; + [self presentNewNoteEditorWithUseSelectedTag:NO animated:NO]; } return YES; From b1c16dc7c21fd5e0557bd2de78ea96661b278332 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 15 May 2024 10:12:58 -0600 Subject: [PATCH 174/547] Preserve tableView selection when app entering background --- .../Classes/SPNoteListViewController+Extensions.swift | 9 +++++++++ Simplenote/SPAppDelegate.m | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/Simplenote/Classes/SPNoteListViewController+Extensions.swift b/Simplenote/Classes/SPNoteListViewController+Extensions.swift index 6714ec1a0..ac146c240 100644 --- a/Simplenote/Classes/SPNoteListViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteListViewController+Extensions.swift @@ -697,6 +697,15 @@ extension SPNoteListViewController { return count > 0 ? Localization.selectedTitle(with: count) : notesListController.filter.title }() } + + @objc + func selectRows(with indexPaths: [IndexPath]) { + guard isEditing else { + return + } + + indexPaths.forEach({ tableView.selectRow(at: $0, animated: false, scrollPosition: .none) }) + } } // MARK: - Row Actions diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index d0b1359b6..6619704be 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -41,6 +41,7 @@ @interface SPAppDelegate () @property (weak, nonatomic) SPModalActivityIndicator *signOutActivityIndicator; +@property (nonatomic) NSArray *selectedNotesEnteringBackground; @end @@ -189,12 +190,19 @@ - (void)applicationDidEnterBackground:(UIApplication *)application [self cleanupScrollPositionCache]; [self syncWidgetDefaults]; [self resetWidgetTimelines]; + + self.selectedNotesEnteringBackground = self.noteListViewController.tableView.indexPathsForSelectedRows; } - (void)applicationWillEnterForeground:(UIApplication *)application { [self dismissPasscodeLockIfPossible]; [self authenticateSimperiumIfAccountDeletionRequested]; + + if (self.selectedNotesEnteringBackground != nil) { + [self.noteListViewController selectRowsWith:self.selectedNotesEnteringBackground]; + self.selectedNotesEnteringBackground = nil; + } } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler From 4d0bee54b70bee135b6ad1aa2feaf41a70ea5992 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 15 May 2024 10:17:32 -0600 Subject: [PATCH 175/547] Updated release notes PR 1594 Fixed notes selection lost on background --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 991a8d7ed..243c0b600 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ 4.52 ----- - Extended support for iOS Shortcuts +- Fixed issue where notes selection was lost when app backgrounded 4.51 ----- From 27ab84b7bb82beb01dd5e8d1586f5838f012dc5f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 15 May 2024 16:09:12 -0600 Subject: [PATCH 176/547] Refactored restoring selected notes so note list handles restoring --- .../Classes/SPNoteListViewController+Extensions.swift | 7 +++++++ Simplenote/Classes/SPNoteListViewController.h | 1 + Simplenote/Classes/SPNoteListViewController.m | 9 +++++++++ Simplenote/SPAppDelegate.m | 9 +-------- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Simplenote/Classes/SPNoteListViewController+Extensions.swift b/Simplenote/Classes/SPNoteListViewController+Extensions.swift index ac146c240..c7478e309 100644 --- a/Simplenote/Classes/SPNoteListViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteListViewController+Extensions.swift @@ -699,6 +699,13 @@ extension SPNoteListViewController { } @objc + func restoreSelectedRowsAfterBackgrounding() { + if selectedNotesEnteringBackground.isEmpty == false { + selectRows(with: selectedNotesEnteringBackground) + selectedNotesEnteringBackground.removeAll() + } + } + func selectRows(with indexPaths: [IndexPath]) { guard isEditing else { return 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 9493b4881..5a04ad7ff 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,9 @@ - (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]; } - (void)condensedPreferenceWasUpdated:(id)sender @@ -213,6 +218,10 @@ - (void)themeDidChange { [self refreshStyle]; } +- (void)appWillEnterBackground { + self.selectedNotesEnteringBackground = self.tableView.indexPathsForSelectedRows; +} + - (void)refreshStyle { // Refresh the containerView's backgroundColor self.view.backgroundColor = [UIColor simplenoteBackgroundColor]; diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index 6619704be..2e1d5450a 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -41,7 +41,6 @@ @interface SPAppDelegate () @property (weak, nonatomic) SPModalActivityIndicator *signOutActivityIndicator; -@property (nonatomic) NSArray *selectedNotesEnteringBackground; @end @@ -190,19 +189,13 @@ - (void)applicationDidEnterBackground:(UIApplication *)application [self cleanupScrollPositionCache]; [self syncWidgetDefaults]; [self resetWidgetTimelines]; - - self.selectedNotesEnteringBackground = self.noteListViewController.tableView.indexPathsForSelectedRows; } - (void)applicationWillEnterForeground:(UIApplication *)application { [self dismissPasscodeLockIfPossible]; [self authenticateSimperiumIfAccountDeletionRequested]; - - if (self.selectedNotesEnteringBackground != nil) { - [self.noteListViewController selectRowsWith:self.selectedNotesEnteringBackground]; - self.selectedNotesEnteringBackground = nil; - } + [self.noteListViewController restoreSelectedRowsAfterBackgrounding]; } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler From de09d0d019a10cceb1648fa1268cafdf93a8936f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 15 May 2024 16:16:54 -0600 Subject: [PATCH 177/547] Fixed a couple of warnings --- Simplenote/Classes/SPNoteListViewController.m | 2 -- Simplenote/URL+Simplenote.swift | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Simplenote/Classes/SPNoteListViewController.m b/Simplenote/Classes/SPNoteListViewController.m index 5a04ad7ff..9c12f79d7 100644 --- a/Simplenote/Classes/SPNoteListViewController.m +++ b/Simplenote/Classes/SPNoteListViewController.m @@ -529,8 +529,6 @@ - (void)update [self refreshTitle]; [self refreshSearchBar]; - BOOL isTrashOnScreen = self.isDeletedFilterActive; - [self refreshEmptyTrashState]; [self displayPlaceholdersIfNeeded]; diff --git a/Simplenote/URL+Simplenote.swift b/Simplenote/URL+Simplenote.swift index 42895a6fd..b2a976f60 100644 --- a/Simplenote/URL+Simplenote.swift +++ b/Simplenote/URL+Simplenote.swift @@ -29,7 +29,7 @@ extension URL { } static func newNoteWidgetURL() -> URL { - guard var components = URLComponents.simplenoteURLComponents(with: Constants.newNotePath) else { + guard let components = URLComponents.simplenoteURLComponents(with: Constants.newNotePath) else { return URL(string: .simplenotePath())! } From 14904c8ca6041b0ec591d8d252078f2ceecd7c95 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 15 May 2024 16:51:22 -0600 Subject: [PATCH 178/547] fixed an issue can activate the faceid switch without a passcode --- Simplenote/Classes/SPSettingsViewController+Extensions.swift | 1 + Simplenote/Classes/SPSettingsViewController.h | 3 ++- Simplenote/Classes/SPSettingsViewController.m | 5 ++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index abd05fa21..e6cdb916c 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -87,6 +87,7 @@ extension SPSettingsViewController: PinLockSetupControllerDelegate { func pinLockSetupControllerDidComplete(_ controller: PinLockSetupController) { SPTracker.trackSettingsPinlockEnabled(true) dismissPresentedViewController() + SPPinLockManager.shared.shouldUseBiometry = biometrySwitch.isOn } func pinLockSetupControllerDidCancel(_ controller: PinLockSetupController) { diff --git a/Simplenote/Classes/SPSettingsViewController.h b/Simplenote/Classes/SPSettingsViewController.h index fcad4f121..5c38a03f0 100644 --- a/Simplenote/Classes/SPSettingsViewController.h +++ b/Simplenote/Classes/SPSettingsViewController.h @@ -2,12 +2,13 @@ #import "SPTableViewController.h" @interface SPSettingsViewController : SPTableViewController { - //Preferences NSNumber *sortOrderPref; NSNumber *numPreviewLinesPref; } +@property (nonatomic, strong) UISwitch *biometrySwitch; + @end extern NSString *const SPAlphabeticalTagSortPref; diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index df788f9dd..bb6d9706a 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -14,7 +14,6 @@ @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; @@ -747,11 +746,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; } } From e14ea53a52d9b9a7aab2dd27a5b35053c7b2f88d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 15 May 2024 16:54:54 -0600 Subject: [PATCH 179/547] Updated release notes: can activate the faceid switch without a passcode --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 991a8d7ed..e21298be1 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ 4.52 ----- - Extended support for iOS Shortcuts +- Fixed an issue can activate the faceid switch without a passcode 4.51 ----- From 2aef8a50f7e3d799d898eda3661a5441b6c1bfa9 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 16 May 2024 11:28:11 -0600 Subject: [PATCH 180/547] Refactored pinlockdelegate always enable shouldUseBiometry on complete --- Simplenote/Classes/SPSettingsViewController+Extensions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index e6cdb916c..a07f8d4e9 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -87,7 +87,7 @@ extension SPSettingsViewController: PinLockSetupControllerDelegate { func pinLockSetupControllerDidComplete(_ controller: PinLockSetupController) { SPTracker.trackSettingsPinlockEnabled(true) dismissPresentedViewController() - SPPinLockManager.shared.shouldUseBiometry = biometrySwitch.isOn + SPPinLockManager.shared.shouldUseBiometry = true } func pinLockSetupControllerDidCancel(_ controller: PinLockSetupController) { From 928062db393b88f228a0ad09573a39b5fe01d3b9 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 16 May 2024 11:29:53 -0600 Subject: [PATCH 181/547] removed exposure of biometry switch. Wasn't needed --- Simplenote/Classes/SPSettingsViewController.h | 2 -- Simplenote/Classes/SPSettingsViewController.m | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController.h b/Simplenote/Classes/SPSettingsViewController.h index 5c38a03f0..301f929a0 100644 --- a/Simplenote/Classes/SPSettingsViewController.h +++ b/Simplenote/Classes/SPSettingsViewController.h @@ -7,8 +7,6 @@ NSNumber *numPreviewLinesPref; } -@property (nonatomic, strong) UISwitch *biometrySwitch; - @end extern NSString *const SPAlphabeticalTagSortPref; diff --git a/Simplenote/Classes/SPSettingsViewController.m b/Simplenote/Classes/SPSettingsViewController.m index bb6d9706a..ee90df625 100644 --- a/Simplenote/Classes/SPSettingsViewController.m +++ b/Simplenote/Classes/SPSettingsViewController.m @@ -14,6 +14,7 @@ @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; From ab009a0a2f66c341a23990c2f7f5e86f72fd73d1 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 16 May 2024 11:33:51 -0600 Subject: [PATCH 182/547] Refactored SPNotelist to restore selected notes without the app delegate --- Simplenote/Classes/SPNoteListViewController.m | 5 +++++ Simplenote/SPAppDelegate.m | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPNoteListViewController.m b/Simplenote/Classes/SPNoteListViewController.m index 9c12f79d7..f2148c518 100644 --- a/Simplenote/Classes/SPNoteListViewController.m +++ b/Simplenote/Classes/SPNoteListViewController.m @@ -197,6 +197,7 @@ - (void)startListeningToNotifications { // 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 @@ -222,6 +223,10 @@ - (void)appWillEnterBackground { self.selectedNotesEnteringBackground = self.tableView.indexPathsForSelectedRows; } +- (void)appWillEnterForeground { + [self restoreSelectedRowsAfterBackgrounding]; +} + - (void)refreshStyle { // Refresh the containerView's backgroundColor self.view.backgroundColor = [UIColor simplenoteBackgroundColor]; diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index 2e1d5450a..d0b1359b6 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -195,7 +195,6 @@ - (void)applicationWillEnterForeground:(UIApplication *)application { [self dismissPasscodeLockIfPossible]; [self authenticateSimperiumIfAccountDeletionRequested]; - [self.noteListViewController restoreSelectedRowsAfterBackgrounding]; } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler From 91d42bf7a5f5a7c2fd217be7ed468565cb5e16ad Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 6 Jun 2024 16:57:05 -0300 Subject: [PATCH 183/547] SPNoteListViewController+Extensions: Prevents force unwrap a nil property --- .../Classes/SPNoteListViewController+Extensions.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Simplenote/Classes/SPNoteListViewController+Extensions.swift b/Simplenote/Classes/SPNoteListViewController+Extensions.swift index c7478e309..dbd1d8eff 100644 --- a/Simplenote/Classes/SPNoteListViewController+Extensions.swift +++ b/Simplenote/Classes/SPNoteListViewController+Extensions.swift @@ -700,10 +700,12 @@ extension SPNoteListViewController { @objc func restoreSelectedRowsAfterBackgrounding() { - if selectedNotesEnteringBackground.isEmpty == false { - selectRows(with: selectedNotesEnteringBackground) - selectedNotesEnteringBackground.removeAll() + guard let selectedNotesEnteringBackground, selectedNotesEnteringBackground.isEmpty == false else { + return } + + selectRows(with: selectedNotesEnteringBackground) + self.selectedNotesEnteringBackground = [] } func selectRows(with indexPaths: [IndexPath]) { From 20141f9293b68e0b403d77b88d653027a847ac3c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 6 Jun 2024 19:47:57 -0300 Subject: [PATCH 184/547] New Tracks Events --- Simplenote/Classes/SPTracker.h | 1 + Simplenote/Classes/SPTracker.m | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Simplenote/Classes/SPTracker.h b/Simplenote/Classes/SPTracker.h index 23e4f8ef8..a0804bfd5 100644 --- a/Simplenote/Classes/SPTracker.h +++ b/Simplenote/Classes/SPTracker.h @@ -81,6 +81,7 @@ #pragma mark - User + (void)trackUserAccountCreated; + (void)trackUserSignedIn; ++ (void)trackUserRequestedLoginLink; + (void)trackUserSignedOut; #pragma mark - WP.com Sign In diff --git a/Simplenote/Classes/SPTracker.m b/Simplenote/Classes/SPTracker.m index 83e656a21..a4872fb83 100644 --- a/Simplenote/Classes/SPTracker.m +++ b/Simplenote/Classes/SPTracker.m @@ -317,6 +317,11 @@ + (void)trackUserSignedIn [self trackAutomatticEventWithName:@"user_signed_in" properties:nil]; } ++ (void)trackUserRequestedLoginLink +{ + [self trackAutomatticEventWithName:@"user_requested_login_link" properties:nil]; +} + + (void)trackUserSignedOut { [self trackAutomatticEventWithName:@"user_signed_out" properties:nil]; From 21d9006f8a88ed4a58b0d42887d86efbd8cbb109 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 6 Jun 2024 19:50:56 -0300 Subject: [PATCH 185/547] Implements LoginRemote --- Simplenote.xcodeproj/project.pbxproj | 10 +++++--- Simplenote/Classes/SPAuthHandler.swift | 14 +++++++++++ Simplenote/LoginRemote.swift | 34 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 Simplenote/LoginRemote.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index c4d3df429..2046d04ef 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -308,6 +308,7 @@ B55E428C22A1A4550018C0CE /* SPSortOrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55E428B22A1A4550018C0CE /* SPSortOrderViewController.swift */; }; B56315BE236BD9970066C151 /* UIKitConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AB169B22FB2DF300B4EBA5 /* UIKitConstants.swift */; }; B5686178195B9E93005F1245 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E2F149471799F5A500DC9690 /* libsqlite3.dylib */; }; + B5699B092C1246C40096D6B7 /* LoginRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5699B082C1246C40096D6B7 /* LoginRemote.swift */; }; B56A695822F9CD1500B90398 /* SPOnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B56A695622F9CD1500B90398 /* SPOnboardingViewController.xib */; }; B56A695922F9CD1500B90398 /* SPOnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56A695722F9CD1500B90398 /* SPOnboardingViewController.swift */; }; B56A695B22F9CD4E00B90398 /* UINavigationBar+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56A695A22F9CD4E00B90398 /* UINavigationBar+Simplenote.swift */; }; @@ -509,13 +510,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 */; }; - BA9C7EFB2BF2CC3E007A8460 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */; }; - BA9C7EFC2BF2CCAE007A8460 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7EFA2BF2CC3E007A8460 /* Downloader.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 */; }; @@ -1013,6 +1014,7 @@ 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 = ""; }; B566A1DD189AA74600DC3F2D /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; + B5699B082C1246C40096D6B7 /* LoginRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRemote.swift; sourceTree = ""; }; B56A695622F9CD1500B90398 /* SPOnboardingViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = SPOnboardingViewController.xib; path = Classes/SPOnboardingViewController.xib; sourceTree = ""; }; B56A695722F9CD1500B90398 /* SPOnboardingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SPOnboardingViewController.swift; path = Classes/SPOnboardingViewController.swift; sourceTree = ""; }; B56A695A22F9CD4E00B90398 /* UINavigationBar+Simplenote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UINavigationBar+Simplenote.swift"; path = "Classes/UINavigationBar+Simplenote.swift"; sourceTree = ""; }; @@ -1199,7 +1201,6 @@ 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 = ""; }; - BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.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 = ""; }; @@ -1224,6 +1225,7 @@ 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 = ""; }; @@ -2413,6 +2415,7 @@ BA57692A269D2103008B510E /* Remotes */ = { isa = PBXGroup; children = ( + B5699B082C1246C40096D6B7 /* LoginRemote.swift */, A6C7647F25E9131C00A39067 /* SignupRemote.swift */, A6CC0B0625B8287F00F12A85 /* AccountRemote.swift */, BA5768E2269A803F008B510E /* Remote.swift */, @@ -3595,6 +3598,7 @@ B52BB75022CFD18F0042C162 /* UIActivity+Simplenote.swift in Sources */, F9E197D42283D05C0092B3E1 /* CrashLogging.swift in Sources */, BA9B59022685549F00DAD1ED /* StorageSettings.swift in Sources */, + B5699B092C1246C40096D6B7 /* LoginRemote.swift in Sources */, DE7E545B214E34C8008D9928 /* NSString+Count.swift in Sources */, A6E6CE0025A4B0A9005A92DB /* PinLockVerifyController.swift in Sources */, BA55124E2600210B00D8F882 /* TimerFactory.swift in Sources */, diff --git a/Simplenote/Classes/SPAuthHandler.swift b/Simplenote/Classes/SPAuthHandler.swift index bf0a85a4a..a9a875bc5 100644 --- a/Simplenote/Classes/SPAuthHandler.swift +++ b/Simplenote/Classes/SPAuthHandler.swift @@ -53,6 +53,20 @@ class SPAuthHandler { }) } + /// Requests an Authentication Magic Link + /// + func requestLoginEmail(username: String, onCompletion: @escaping (SPAuthError?) -> Void) { + let remote = LoginRemote() + remote.requestLoginEmail(with: username) { (result) in + switch result { + case .success: + onCompletion(nil) + case .failure(let error): + onCompletion(self.authenticationError(for: error)) + } + } + } + /// Registers a new user in the Simperium Backend. /// /// - Note: Errors are mapped into SPAuthError Instances diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift new file mode 100644 index 000000000..6a3831584 --- /dev/null +++ b/Simplenote/LoginRemote.swift @@ -0,0 +1,34 @@ +// +// LoginRemote.swift +// Simplenote +// +// Created by Jorge Leandro Perez on 6/6/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation + +// MARK: - SignupRemote +// +class LoginRemote: Remote { + + func requestLoginEmail(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.loginURL)! + + 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 + } +} From 8166562d78b1df287dc79e445cf679172c5d7495 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 6 Jun 2024 19:51:10 -0300 Subject: [PATCH 186/547] SimplenoteConstants: New constant --- Simplenote/Classes/SimplenoteConstants.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Simplenote/Classes/SimplenoteConstants.swift b/Simplenote/Classes/SimplenoteConstants.swift index f273e382e..d49f0a8d5 100644 --- a/Simplenote/Classes/SimplenoteConstants.swift +++ b/Simplenote/Classes/SimplenoteConstants.swift @@ -41,6 +41,8 @@ class SimplenoteConstants: NSObject { /// static let resetPasswordURL = currentEngineBaseURL.appendingPathComponent("/reset/?redirect=simplenote://launch&email=") static let settingsURL = currentEngineBaseURL.appendingPathComponent("/settings") +/// TODO: FIXME + static let loginURL = "https://magic-links-dot-simple-note-hrd.appspot.com/account/request-login" ////currentEngineBaseURL.appendingPathComponent("/account/request-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/") From e1d5c04c216282b73affdd888895d93d160a96ed Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 6 Jun 2024 19:52:49 -0300 Subject: [PATCH 187/547] Updates release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 680d9a9fb..f544d30d6 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ - 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 4.51 ----- From 0d0699a150a047ff1b7e4cd487e2cfa3ae60e4a0 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 11:09:46 -0300 Subject: [PATCH 188/547] SwiftLint: Disabling whitespace rules --- .swiftlint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index cf03a5a91..998f8251c 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -42,9 +42,9 @@ only_rules: - trailing_semicolon # Lines should not have trailing whitespace. - - trailing_whitespace + # - trailing_whitespace - - vertical_whitespace + # - vertical_whitespace - custom_rules From 246da886ebe523cb62d2ab97945b5c08141ca8db Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 11:20:48 -0300 Subject: [PATCH 189/547] SimplenoteConstants: New URL(s) --- Simplenote/Classes/SimplenoteConstants.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SimplenoteConstants.swift b/Simplenote/Classes/SimplenoteConstants.swift index d49f0a8d5..0aca91f4b 100644 --- a/Simplenote/Classes/SimplenoteConstants.swift +++ b/Simplenote/Classes/SimplenoteConstants.swift @@ -42,7 +42,8 @@ class SimplenoteConstants: NSObject { static let resetPasswordURL = currentEngineBaseURL.appendingPathComponent("/reset/?redirect=simplenote://launch&email=") static let settingsURL = currentEngineBaseURL.appendingPathComponent("/settings") /// TODO: FIXME - static let loginURL = "https://magic-links-dot-simple-note-hrd.appspot.com/account/request-login" ////currentEngineBaseURL.appendingPathComponent("/account/request-login") + static let loginRequestURL = "https://magic-links-dot-simple-note-hrd.appspot.com/account/request-login" ////currentEngineBaseURL.appendingPathComponent("/account/request-login") + static let loginCompletionURL = "https://magic-links-dot-simple-note-hrd.appspot.com/account/complete-login" ////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/") From b22ca5f2eaf63d61493bb3ada286918ad47f9ca9 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 11:28:39 -0300 Subject: [PATCH 190/547] MagicLinkAuthenticator: Spliting legacy MagicLinkAuthenticator handler --- .../Classes/MagicLinkAuthenticator.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 38c97012a..c4655b473 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -14,16 +14,30 @@ struct MagicLinkAuthenticator { return } + if attemptLoginWithToken(queryItems: queryItems) { + return + } + + +// 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 + !email.isEmpty, !token.isEmpty + else { + return false } authenticator.authenticate(withUsername: email, token: token) + return true } } + // MARK: - [URLQueryItem] Helper // private extension Array where Element == URLQueryItem { From 974febbeca49a2fd2aaa0cd81254c9f53ec87261 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 11:49:42 -0300 Subject: [PATCH 191/547] Remote: New Request API(s) --- Simplenote/Remote.swift | 45 ++++++++++++++++++++++++++++++++++++ Simplenote/RemoteError.swift | 2 ++ 2 files changed, 47 insertions(+) diff --git a/Simplenote/Remote.swift b/Simplenote/Remote.swift index 6b9424613..e9c119e5c 100644 --- a/Simplenote/Remote.swift +++ b/Simplenote/Remote.swift @@ -30,4 +30,49 @@ class Remote { dataTask.resume() } + + /// Performs a URLSession Data Task + /// + func performDataTask(with request: URLRequest) async throws -> Data { + let (data, response) = try await urlSession.data(for: request) + let statusCode = (response as? HTTPURLResponse)?.statusCode ?? .zero + + // Check for 2xx status code + guard statusCode / 100 == 2 else { + throw RemoteError.serverError(statusCode: statusCode) + } + + return data + } + + /// Performs a URLSession Data Task, and decodes a given Type + /// + func performDataTask(with request: URLRequest, type: T.Type) async throws -> T { + let data = try await performDataTask(with: request) + + do { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + return try decoder.decode(type, from: data) + + } catch { + throw RemoteError.responseUnableToDecode + } + } + + /// Builds a URLRequest for the specified URL / Method / params + /// + func requestForURL(_ url: URL, method: String, httpBody: [String: String]?) -> URLRequest { + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: RemoteConstants.timeout) + + request.httpMethod = method + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + if let httpBody { + request.httpBody = try? JSONEncoder().encode(httpBody) + } + + return request + } } diff --git a/Simplenote/RemoteError.swift b/Simplenote/RemoteError.swift index 74a96c9eb..0bc594341 100644 --- a/Simplenote/RemoteError.swift +++ b/Simplenote/RemoteError.swift @@ -2,6 +2,8 @@ import Foundation enum RemoteError: Error { case network + case responseUnableToDecode + case serverError(statusCode: Int) case requestError(Int, Error?) } From 531f5ba3a6ca4f0d291f51dced67e32de3b07dfa Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 11:49:54 -0300 Subject: [PATCH 192/547] LoginRemote: Implements login completion API(s) --- Simplenote/LoginRemote.swift | 49 ++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift index 6a3831584..ae9e5b9ac 100644 --- a/Simplenote/LoginRemote.swift +++ b/Simplenote/LoginRemote.swift @@ -8,27 +8,50 @@ import Foundation -// MARK: - SignupRemote +// MARK: - LoginRemote // class LoginRemote: Remote { - func requestLoginEmail(with email: String, completion: @escaping (_ result: Result) -> Void) { - let urlRequest = request(with: email) + func requestLoginEmail(email: String, completion: @escaping (_ result: Result) -> Void) { + let request = requestForLoginRequest(with: email) - performDataTask(with: urlRequest, completion: completion) + performDataTask(with: request, completion: completion) } - private func request(with email: String) -> URLRequest { - let url = URL(string: SimplenoteConstants.loginURL)! + func requestSyncToken(email: String, authCode: String) async throws -> String { + let request = requestForLoginCompletion(email: email, authCode: authCode) + let response = try await performDataTask(with: request, type: LoginConfirmationResponse.self) + + return response.syncToken + } +} + + +// MARK: - LoginConfirmationResponse +// +struct LoginConfirmationResponse: Decodable { + let syncToken: String +} - 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()]) +// MARK: - Private API(s) +// +private extension LoginRemote { + + func requestForLoginRequest(with email: String) -> URLRequest { + let url = URL(string: SimplenoteConstants.loginRequestURL)! + + return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ + "username": email.lowercased() + ]) + } + + func requestForLoginCompletion(email: String, authCode: String) -> URLRequest { + let url = URL(string: SimplenoteConstants.loginCompletionURL)! - return request + return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ + "username": email.lowercased(), + "auth_code": authCode + ]) } } From 80ae1cdbfff0ece11954fa6e6fef7be18180e3fd Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 11:50:12 -0300 Subject: [PATCH 193/547] SPAuthHandler: Adjusts remote invocation --- Simplenote/Classes/SPAuthHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPAuthHandler.swift b/Simplenote/Classes/SPAuthHandler.swift index a9a875bc5..1196d0c13 100644 --- a/Simplenote/Classes/SPAuthHandler.swift +++ b/Simplenote/Classes/SPAuthHandler.swift @@ -57,7 +57,7 @@ class SPAuthHandler { /// func requestLoginEmail(username: String, onCompletion: @escaping (SPAuthError?) -> Void) { let remote = LoginRemote() - remote.requestLoginEmail(with: username) { (result) in + remote.requestLoginEmail(email: username) { (result) in switch result { case .success: onCompletion(nil) From 2c9b092003f3088304d4b896a4e730e8d375b105 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 11:55:21 -0300 Subject: [PATCH 194/547] New SPAuthError case --- Simplenote/Classes/SPAuthError.swift | 3 ++- Simplenote/Classes/SPAuthHandler.swift | 2 ++ Simplenote/Classes/SPSettingsViewController+Extensions.swift | 2 +- Simplenote/Remote.swift | 2 +- Simplenote/RemoteError.swift | 1 - 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Simplenote/Classes/SPAuthError.swift b/Simplenote/Classes/SPAuthError.swift index 21d3e61a3..8f84d634c 100644 --- a/Simplenote/Classes/SPAuthError.swift +++ b/Simplenote/Classes/SPAuthError.swift @@ -10,6 +10,7 @@ enum SPAuthError: Error { case compromisedPassword case unverifiedEmail case tooManyAttempts + case unableToDecode case unknown(statusCode: Int, response: String?, error: Error?) } @@ -89,7 +90,7 @@ 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: + default: return NSLocalizedString("We're having problems. Please try again soon.", comment: "Generic error") } } diff --git a/Simplenote/Classes/SPAuthHandler.swift b/Simplenote/Classes/SPAuthHandler.swift index 1196d0c13..0c658f82a 100644 --- a/Simplenote/Classes/SPAuthHandler.swift +++ b/Simplenote/Classes/SPAuthHandler.swift @@ -90,6 +90,8 @@ class SPAuthHandler { switch remoteError { case .network: return SPAuthError.network + case .responseUnableToDecode: + return SPAuthError.unableToDecode case .requestError(let statusCode, let error): return SPAuthError(signupErrorCode: statusCode, response: error?.localizedDescription, error: error) } diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index a07f8d4e9..5b4392105 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -191,7 +191,7 @@ extension SPSettingsViewController { switch error { case .network: NoticeController.shared.present(NoticeFactory.networkError()) - case .requestError: + case .responseUnableToDecode, .requestError: presentRequestErrorAlert() } } diff --git a/Simplenote/Remote.swift b/Simplenote/Remote.swift index e9c119e5c..8bcf20cb5 100644 --- a/Simplenote/Remote.swift +++ b/Simplenote/Remote.swift @@ -39,7 +39,7 @@ class Remote { // Check for 2xx status code guard statusCode / 100 == 2 else { - throw RemoteError.serverError(statusCode: statusCode) + throw RemoteError.requestError(statusCode, nil) } return data diff --git a/Simplenote/RemoteError.swift b/Simplenote/RemoteError.swift index 0bc594341..cf87d6e30 100644 --- a/Simplenote/RemoteError.swift +++ b/Simplenote/RemoteError.swift @@ -3,7 +3,6 @@ import Foundation enum RemoteError: Error { case network case responseUnableToDecode - case serverError(statusCode: Int) case requestError(Int, Error?) } From 1c3a1f630792a02699ed68dc94a1299b8c10d3eb Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 12:08:32 -0300 Subject: [PATCH 195/547] SPAuthViewController: Login with Magic Link Support --- Simplenote/Classes/SPAuthViewController.swift | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index f6e27223e..f7ad1da98 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -295,7 +295,19 @@ private extension SPAuthViewController { performSimperiumAuthentication() } + + @IBAction func performLogInWithMagicLink() { + guard ensureWarningsAreOnScreenWhenNeeded() else { + return + } + + requestLoginLink() + } + + @IBAction func switchToLoginWithPassword() { + } + @IBAction func performSignUp() { guard ensureWarningsAreOnScreenWhenNeeded() else { return @@ -369,6 +381,20 @@ private extension SPAuthViewController { self.unlockInterface() } } + + func requestLoginLink() { + lockdownInterface() + + controller.requestLoginEmail(username: email) { error in + if let error { + self.handleError(error: error) + } else { + SPTracker.trackUserRequestedLoginLink() + } + + self.unlockInterface() + } + } } // MARK: - Password Reset Flow @@ -646,6 +672,19 @@ extension AuthenticationMode { isPasswordHidden: false) } + /// Login Operation Mode: Authentication is handled via Magic Links! + /// + static var loginWithMagicLink: AuthenticationMode { + return .init(title: AuthenticationStrings.loginTitle, + validationStyle: .legacy, + primaryActionSelector: #selector(SPAuthViewController.performLogInWithMagicLink), + primaryActionText: AuthenticationStrings.loginWithLinkPrimaryAction, + secondaryActionSelector: #selector(SPAuthViewController.switchToLoginWithPassword), + secondaryActionText: AuthenticationStrings.loginWithLinkSecondaryAction, + secondaryActionAttributedText: nil, + isPasswordHidden: true) + } + /// Signup Operation Mode: Contains all of the strings + delegate wirings, so that the AuthUI handles user account creation scenarios. /// static var signup: AuthenticationMode { @@ -666,6 +705,8 @@ 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 loginWithLinkPrimaryAction = NSLocalizedString("Instantly Log In with Email", comment: "LogIn with Magic Link Action") + static let loginWithLinkSecondaryAction = NSLocalizedString("Continue with Password", comment: "Password fallback 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") From 6bfd6bad4d4269d4eda2b348648608dd46e42d4f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 12:35:06 -0300 Subject: [PATCH 196/547] New UIButton Extension --- Simplenote.xcodeproj/project.pbxproj | 4 ++++ Simplenote/UIButton+Simplenote.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 Simplenote/UIButton+Simplenote.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 2046d04ef..ee7f9e892 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -295,6 +295,7 @@ 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 */; }; B54B04D82407169500401FBB /* SPAppDelegate+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54B04D72407169500401FBB /* SPAppDelegate+Extensions.swift */; }; B54D9C572909BA2600D0E0EC /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C562909BA2600D0E0EC /* StoreManager.swift */; }; B54D9C592909CC4400D0E0EC /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C582909CC4400D0E0EC /* StoreProduct.swift */; }; @@ -989,6 +990,7 @@ 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 = ""; }; B54B04D72407169500401FBB /* SPAppDelegate+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPAppDelegate+Extensions.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 = ""; }; @@ -2018,6 +2020,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 */, @@ -3463,6 +3466,7 @@ 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 */, diff --git a/Simplenote/UIButton+Simplenote.swift b/Simplenote/UIButton+Simplenote.swift new file mode 100644 index 000000000..da092c987 --- /dev/null +++ b/Simplenote/UIButton+Simplenote.swift @@ -0,0 +1,27 @@ +// +// UIButton+Simplenote.swift +// Simplenote +// +// Created by Jorge Leandro Perez on 6/7/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +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() + } + } +} From 04fed63476fe8a834d6fe5d647689af19fba4a18 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 12:35:22 -0300 Subject: [PATCH 197/547] SPAuthViewController: Mode Switching Support --- Simplenote/Classes/SPAuthViewController.swift | 67 +++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index f7ad1da98..cbe1b94d1 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -159,7 +159,11 @@ class SPAuthViewController: UIViewController { /// # Authentication Mode: Signup or Login /// - let mode: AuthenticationMode + private(set) var mode: AuthenticationMode { + didSet { + refreshInterface(mode: mode) + } + } /// Indicates if the Extended Debug Mode is enabled /// @@ -173,7 +177,7 @@ class SPAuthViewController: UIViewController { /// Designated Initializer /// - init(controller: SPAuthHandler, mode: AuthenticationMode = .login) { + init(controller: SPAuthHandler, mode: AuthenticationMode = .loginWithMagicLink) { self.controller = controller self.mode = mode super.init(nibName: nil, bundle: nil) @@ -305,7 +309,7 @@ private extension SPAuthViewController { } @IBAction func switchToLoginWithPassword() { - + mode = .loginWithPassword } @IBAction func performSignUp() { @@ -504,7 +508,7 @@ private extension SPAuthViewController { } // Prefill the LoginViewController - let loginViewController = SPAuthViewController(controller: controller, mode: .login) + let loginViewController = SPAuthViewController(controller: controller, mode: .loginWithPassword) loginViewController.loadViewIfNeeded() loginViewController.email = email @@ -531,6 +535,11 @@ private extension SPAuthViewController { refreshPasswordInput(inErrorState: true) } + func dismissAllValidationWarnings() { + refreshEmailInput(inErrorState: false) + refreshPasswordInput(inErrorState: false) + } + func dismissEmailValidationWarning() { refreshEmailInput(inErrorState: false) } @@ -642,6 +651,54 @@ extension SPAuthViewController: SPTextInputViewDelegate { } } +// MARK: - Mode Switching +// +extension SPAuthViewController { + + func refreshInterface(mode: AuthenticationMode, animated: Bool = true) { + title = mode.title + + dismissAllValidationWarnings() + + refreshPasswordInput(isHidden: mode.isPasswordHidden, animated: animated) + refreshPrimaryAction(mode: mode) + refreshSecondaryAction(mode: mode) + + } + + func refreshPasswordInput(isHidden: Bool, animated: Bool) { + let work = { + self.passwordInputView.isHidden = isHidden + } + + guard animated else { + work() + return + } + + UIView.animate(withDuration: UIKitConstants.animationDelayShort, animations: work) + } + + func refreshPrimaryAction(mode: AuthenticationMode) { + primaryActionButton.setTitleWithoutAnimation(mode.primaryActionText, for: .normal) + primaryActionButton.addTarget(self, action: mode.primaryActionSelector, for: .touchUpInside) + } + + func refreshSecondaryAction(mode: AuthenticationMode) { + if let title = mode.secondaryActionText { + secondaryActionButton.setTitleWithoutAnimation(title, for: .normal) + } + + if let attributedTitle = mode.secondaryActionAttributedText { + secondaryActionButton.setAttributedTitleWithoutAnimation(attributedTitle, for: .normal) + } + + secondaryActionButton.setTitleColor(.simplenoteBlue60Color, for: .normal) + secondaryActionButton.addTarget(self, action: mode.secondaryActionSelector, for: .touchUpInside) + } +} + + // MARK: - AuthenticationMode: Signup / Login // struct AuthenticationMode { @@ -661,7 +718,7 @@ extension AuthenticationMode { /// Login Operation Mode: Contains all of the strings + delegate wirings, so that the AuthUI handles authentication scenarios. /// - static var login: AuthenticationMode { + static var loginWithPassword: AuthenticationMode { return .init(title: AuthenticationStrings.loginTitle, validationStyle: .legacy, primaryActionSelector: #selector(SPAuthViewController.performLogIn), From 6666d9ec22efde11384f8afa53ae07059f51f45b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 12:35:34 -0300 Subject: [PATCH 198/547] SPOnboardingViewController: Magic Link Auth by default --- Simplenote/Classes/SPOnboardingViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPOnboardingViewController.swift b/Simplenote/Classes/SPOnboardingViewController.swift index c80e9a0f5..008476bc2 100644 --- a/Simplenote/Classes/SPOnboardingViewController.swift +++ b/Simplenote/Classes/SPOnboardingViewController.swift @@ -154,7 +154,7 @@ private extension SPOnboardingViewController { sheetController.setTitleForButton1(title: OnboardingStrings.loginWithWpcomText) sheetController.onClickButton0 = { [weak self] in - self?.presentAuthenticationInterface(mode: .login) + self?.presentAuthenticationInterface(mode: .loginWithMagicLink) } sheetController.onClickButton1 = { [weak self] in From b14ef779083da6a9b02d387b7b72c6b768072294 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 12:36:13 -0300 Subject: [PATCH 199/547] MagicLinkAuthenticator: AuthCode link support --- .../Classes/MagicLinkAuthenticator.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index c4655b473..0f44767a6 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -18,6 +18,9 @@ struct MagicLinkAuthenticator { return } + attemptLoginWithAuthCode(queryItems: queryItems) + } +} // MARK: - Private API(s) // @@ -35,6 +38,35 @@ private extension MagicLinkAuthenticator { 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)") + + Task { + do { + let remote = LoginRemote() + let syncToken = try await remote.requestSyncToken(email: email, authCode: authCode) + + Task { @MainActor in + NSLog("[MagicLinkAuthenticator] Should auth with token \(syncToken)") + authenticator.authenticate(withUsername: email, token: syncToken) + } + + } catch { + NSLog("[MagicLinkAuthenticator] Magic Link TokenExchange Error: \(error)") + } + } + + return true + } } @@ -61,4 +93,5 @@ private struct Constants { static let host = "login" static let emailField = "email" static let tokenField = "token" + static let authCodeField = "auth_code" } From 12417c11b5b3fc2502342f92c265ae4f54deb2cc Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 13:06:01 -0300 Subject: [PATCH 200/547] Implements MagicLinkConfirmationView --- Simplenote.xcodeproj/project.pbxproj | 4 + Simplenote/Classes/SPAuthViewController.swift | 45 ++++++----- Simplenote/MagicLinkConfirmationView.swift | 76 +++++++++++++++++++ 3 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 Simplenote/MagicLinkConfirmationView.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index ee7f9e892..49cee357c 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -296,6 +296,7 @@ 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 /* MagicLinkConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A11C32C136225002AC8AA /* MagicLinkConfirmationView.swift */; }; B54B04D82407169500401FBB /* SPAppDelegate+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54B04D72407169500401FBB /* SPAppDelegate+Extensions.swift */; }; B54D9C572909BA2600D0E0EC /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C562909BA2600D0E0EC /* StoreManager.swift */; }; B54D9C592909CC4400D0E0EC /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C582909CC4400D0E0EC /* StoreProduct.swift */; }; @@ -991,6 +992,7 @@ 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 /* MagicLinkConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkConfirmationView.swift; sourceTree = ""; }; B54B04D72407169500401FBB /* SPAppDelegate+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPAppDelegate+Extensions.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 = ""; }; @@ -2077,6 +2079,7 @@ A6CDD9C725F0163D00E0BC4D /* MagicLinkAuthenticator.swift */, A628BEB425ECD97900121B64 /* SignupVerificationViewController.swift */, A628BEB525ECD97900121B64 /* SignupVerificationViewController.xib */, + B54A11C32C136225002AC8AA /* MagicLinkConfirmationView.swift */, ); name = Onboarding; sourceTree = ""; @@ -3700,6 +3703,7 @@ B5CBEF4222D3B419009DBE67 /* Bundle+Simplenote.swift in Sources */, B575736C232D454300443C2E /* UIColor+Helpers.swift in Sources */, 37E55A6721BF2B1800F14241 /* SPTextAttachment.swift in Sources */, + B54A11C42C136225002AC8AA /* MagicLinkConfirmationView.swift in Sources */, BA8FC2A5267AC7470082962E /* SharedStorageMigrator.swift in Sources */, A69F850F253EC2B2005140F2 /* SPCardConfigurable.swift in Sources */, B5BE05541AB75C3B002417BF /* Settings.m in Sources */, diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index cbe1b94d1..ee282cdc2 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import SafariServices +import SwiftUI // MARK: - SPAuthViewController // @@ -305,11 +306,19 @@ private extension SPAuthViewController { return } - requestLoginLink() - } + lockdownInterface() - @IBAction func switchToLoginWithPassword() { - mode = .loginWithPassword + let email = self.email + controller.requestLoginEmail(username: email) { error in + if let error { + self.handleError(error: error) + } else { + self.presentMagicLinkConfirmationView(email: email) + SPTracker.trackUserRequestedLoginLink() + } + + self.unlockInterface() + } } @IBAction func performSignUp() { @@ -333,6 +342,10 @@ private extension SPAuthViewController { self.unlockInterface() } } + + @IBAction func switchToLoginWithPassword() { + mode = .loginWithPassword + } @IBAction func presentPasswordReset() { controller.presentPasswordReset(from: self, username: email) @@ -353,8 +366,17 @@ private extension SPAuthViewController { viewController.title = title navigationController?.pushViewController(viewController, animated: true) } + + private func presentMagicLinkConfirmationView(email: String) { + let rootView = MagicLinkConfirmationView(email: email) + let hostingController = UIHostingController(rootView: rootView) + hostingController.sheetPresentationController?.detents = [.medium()] + + present(hostingController, animated: true) + } } + // MARK: - Simperium Services // private extension SPAuthViewController { @@ -385,20 +407,6 @@ private extension SPAuthViewController { self.unlockInterface() } } - - func requestLoginLink() { - lockdownInterface() - - controller.requestLoginEmail(username: email) { error in - if let error { - self.handleError(error: error) - } else { - SPTracker.trackUserRequestedLoginLink() - } - - self.unlockInterface() - } - } } // MARK: - Password Reset Flow @@ -421,6 +429,7 @@ private extension SPAuthViewController { } } + // MARK: - Error Handling // private extension SPAuthViewController { diff --git a/Simplenote/MagicLinkConfirmationView.swift b/Simplenote/MagicLinkConfirmationView.swift new file mode 100644 index 000000000..3007df207 --- /dev/null +++ b/Simplenote/MagicLinkConfirmationView.swift @@ -0,0 +1,76 @@ +// +// MagicLinkConfirmationView.swift +// Simplenote +// +// Created by Jorge Leandro Perez on 6/7/24. +// Copyright © 2024 Automattic. All rights reserved. +// + +import Foundation +import SwiftUI +import Gridicons + + +// MARK: - MagicLinkConfirmationView +// +struct MagicLinkConfirmationView: View { + let email: String + @Environment(\.presentationMode) var presentationMode + + var body: some View { + NavigationView { + VStack(alignment: .center, spacing: 10) { + Image(uiImage: MagicLinkImages.mail) + .renderingMode(.template) + .foregroundColor(Color(.simplenoteBlue60Color)) + + 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 to **\(email)** containing a link that'll log you in.") + .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)) + } + } + } + } + } +} + + +// 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 MagicLinkConfirmationView_Previews: PreviewProvider { + static var previews: some View { + MagicLinkConfirmationView(email: "lord@yosemite.com") + } +} From 1220c6636065d0b3fd05f8943a272531d9e2f2cb Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 13:11:54 -0300 Subject: [PATCH 201/547] Drops copyright --- Simplenote/LoginRemote.swift | 8 -------- Simplenote/MagicLinkConfirmationView.swift | 8 -------- Simplenote/UIButton+Simplenote.swift | 8 -------- 3 files changed, 24 deletions(-) diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift index ae9e5b9ac..9cae5241e 100644 --- a/Simplenote/LoginRemote.swift +++ b/Simplenote/LoginRemote.swift @@ -1,11 +1,3 @@ -// -// LoginRemote.swift -// Simplenote -// -// Created by Jorge Leandro Perez on 6/6/24. -// Copyright © 2024 Automattic. All rights reserved. -// - import Foundation // MARK: - LoginRemote diff --git a/Simplenote/MagicLinkConfirmationView.swift b/Simplenote/MagicLinkConfirmationView.swift index 3007df207..d03e20771 100644 --- a/Simplenote/MagicLinkConfirmationView.swift +++ b/Simplenote/MagicLinkConfirmationView.swift @@ -1,11 +1,3 @@ -// -// MagicLinkConfirmationView.swift -// Simplenote -// -// Created by Jorge Leandro Perez on 6/7/24. -// Copyright © 2024 Automattic. All rights reserved. -// - import Foundation import SwiftUI import Gridicons diff --git a/Simplenote/UIButton+Simplenote.swift b/Simplenote/UIButton+Simplenote.swift index da092c987..1d2dad366 100644 --- a/Simplenote/UIButton+Simplenote.swift +++ b/Simplenote/UIButton+Simplenote.swift @@ -1,11 +1,3 @@ -// -// UIButton+Simplenote.swift -// Simplenote -// -// Created by Jorge Leandro Perez on 6/7/24. -// Copyright © 2024 Automattic. All rights reserved. -// - import Foundation From 431df3f6841207d48c2213b72a9652d347760691 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 13:44:07 -0300 Subject: [PATCH 202/547] MagicLinkAuthenticator: Drops unneeded changes --- Simplenote/Classes/MagicLinkAuthenticator.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 0f44767a6..ef2e24651 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -43,8 +43,7 @@ private extension MagicLinkAuthenticator { 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 { + !email.isEmpty, !authCode.isEmpty else { return false } @@ -69,7 +68,6 @@ private extension MagicLinkAuthenticator { } } - // MARK: - [URLQueryItem] Helper // private extension Array where Element == URLQueryItem { From 207c586bdf41ca1f7c1ebea01b9bdf0de2884472 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 13:50:38 -0300 Subject: [PATCH 203/547] Drops unneeded newlines --- Simplenote/Classes/MagicLinkAuthenticator.swift | 3 ++- Simplenote/Classes/SPAuthViewController.swift | 1 - Simplenote/Remote.swift | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index ef2e24651..7aa562b45 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -43,7 +43,8 @@ private extension MagicLinkAuthenticator { 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 { + !email.isEmpty, !authCode.isEmpty + else { return false } diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index ee282cdc2..ab2203597 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -672,7 +672,6 @@ extension SPAuthViewController { refreshPasswordInput(isHidden: mode.isPasswordHidden, animated: animated) refreshPrimaryAction(mode: mode) refreshSecondaryAction(mode: mode) - } func refreshPasswordInput(isHidden: Bool, animated: Bool) { diff --git a/Simplenote/Remote.swift b/Simplenote/Remote.swift index 8bcf20cb5..c334efd16 100644 --- a/Simplenote/Remote.swift +++ b/Simplenote/Remote.swift @@ -53,7 +53,6 @@ class Remote { do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - return try decoder.decode(type, from: data) } catch { From f9b8526232a62291276474e78cfccf99bd2b7477 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 13:59:51 -0300 Subject: [PATCH 204/547] SPAuthViewController: Spinner is now white --- Simplenote/Classes/SPAuthViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index ab2203597..82fdc6848 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -81,6 +81,7 @@ class SPAuthViewController: UIViewController { @IBOutlet private var primaryActionSpinner: UIActivityIndicatorView! { didSet { primaryActionSpinner.style = .medium + primaryActionSpinner.color = .white } } From 0a6a08be648aea24ed5202c87ef6d854f581ae24 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 14:24:40 -0300 Subject: [PATCH 205/547] MagicLinkConfirmationView: Adjusts iPad UX --- Simplenote/Classes/SPAuthViewController.swift | 1 + Simplenote/MagicLinkConfirmationView.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 82fdc6848..05751f8b1 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -371,6 +371,7 @@ private extension SPAuthViewController { private func presentMagicLinkConfirmationView(email: String) { let rootView = MagicLinkConfirmationView(email: email) let hostingController = UIHostingController(rootView: rootView) + hostingController.modalPresentationStyle = .formSheet hostingController.sheetPresentationController?.detents = [.medium()] present(hostingController, animated: true) diff --git a/Simplenote/MagicLinkConfirmationView.swift b/Simplenote/MagicLinkConfirmationView.swift index d03e20771..08a025c38 100644 --- a/Simplenote/MagicLinkConfirmationView.swift +++ b/Simplenote/MagicLinkConfirmationView.swift @@ -41,6 +41,8 @@ struct MagicLinkConfirmationView: View { } } } + .navigationViewStyle(.stack) + .preferredColorScheme(.light) } } From 51a1f0f8600ca283318f25f40028a7e7ca61986e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 16:25:53 -0300 Subject: [PATCH 206/547] MagicLinkConfirmationView: Forcing Light Mode --- Simplenote/MagicLinkConfirmationView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Simplenote/MagicLinkConfirmationView.swift b/Simplenote/MagicLinkConfirmationView.swift index 08a025c38..3d5511a4a 100644 --- a/Simplenote/MagicLinkConfirmationView.swift +++ b/Simplenote/MagicLinkConfirmationView.swift @@ -42,7 +42,9 @@ struct MagicLinkConfirmationView: View { } } .navigationViewStyle(.stack) - .preferredColorScheme(.light) + + /// Force Light Mode (since the Authentication UI is all light!) + .environment(\.colorScheme, .light) } } From 3c5d4b693835e4dda70178ea56abd9de09ba09c4 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 16:37:48 -0300 Subject: [PATCH 207/547] MagicLinkConfirmationView: Animation --- Simplenote/MagicLinkConfirmationView.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Simplenote/MagicLinkConfirmationView.swift b/Simplenote/MagicLinkConfirmationView.swift index 3d5511a4a..5a4e951cf 100644 --- a/Simplenote/MagicLinkConfirmationView.swift +++ b/Simplenote/MagicLinkConfirmationView.swift @@ -6,8 +6,10 @@ import Gridicons // MARK: - MagicLinkConfirmationView // struct MagicLinkConfirmationView: View { - let email: String @Environment(\.presentationMode) var presentationMode + @State private var displaysFullImage: Bool = false + let email: String + var body: some View { NavigationView { @@ -15,6 +17,12 @@ struct MagicLinkConfirmationView: View { 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() From 32b35e71d225051e97b845c45b7cd402b3f545a9 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 17:01:07 -0300 Subject: [PATCH 208/547] SPAuthViewController: Fixes autofill glitch --- Simplenote/Classes/SPAuthViewController.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 05751f8b1..6fe8d4b43 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -674,6 +674,11 @@ extension SPAuthViewController { refreshPasswordInput(isHidden: mode.isPasswordHidden, animated: animated) refreshPrimaryAction(mode: mode) refreshSecondaryAction(mode: mode) + + /// Workaround: + /// When revealing the Password Field, iOS's Autofill mechanism may not properly fill the password field! + passwordInputView.becomeFirstResponder() + emailInputView.becomeFirstResponder() } func refreshPasswordInput(isHidden: Bool, animated: Bool) { From f93f970b21ee9855dda27e9938b66ad70114fec7 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 7 Jun 2024 17:01:15 -0300 Subject: [PATCH 209/547] SPAuthViewController: Fixes multiple Targets glitch --- Simplenote/Classes/SPAuthViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 6fe8d4b43..df535f45c 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -696,6 +696,7 @@ extension SPAuthViewController { func refreshPrimaryAction(mode: AuthenticationMode) { primaryActionButton.setTitleWithoutAnimation(mode.primaryActionText, for: .normal) + primaryActionButton.removeTarget(self, action: nil, for: .touchUpInside) primaryActionButton.addTarget(self, action: mode.primaryActionSelector, for: .touchUpInside) } @@ -709,6 +710,7 @@ extension SPAuthViewController { } secondaryActionButton.setTitleColor(.simplenoteBlue60Color, for: .normal) + secondaryActionButton.removeTarget(self, action: nil, for: .touchUpInside) secondaryActionButton.addTarget(self, action: mode.secondaryActionSelector, for: .touchUpInside) } } From fbae38380ab40257bba29898c0189361c3499871 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 13 Jun 2024 15:37:15 -0600 Subject: [PATCH 210/547] Updated append intents to be able to return a failure reason --- .../ShortcutIntents.intentdefinition | 90 ++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition b/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition index fcb648266..04ca75224 100644 --- a/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition +++ b/Simplenote/Supporting Files/Base.lproj/ShortcutIntents.intentdefinition @@ -11,9 +11,9 @@ INIntentDefinitionSystemVersion 23E214 INIntentDefinitionToolsBuildVersion - 15E204a + 15F31d INIntentDefinitionToolsVersion - 15.3 + 15.4 INIntents @@ -308,10 +308,33 @@ + 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 @@ -409,10 +432,33 @@ + 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 @@ -660,26 +706,6 @@ INIntentParameterPromptDialogType Primary - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${note}’. - INIntentParameterPromptDialogFormatStringID - VBtgac - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${note}’? - INIntentParameterPromptDialogFormatStringID - MdiZid - INIntentParameterPromptDialogType - Confirmation - INIntentParameterSupportsDynamicEnumeration @@ -797,26 +823,6 @@ INIntentParameterPromptDialogType Primary - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${tag}’. - INIntentParameterPromptDialogFormatStringID - U4ce2H - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${tag}’? - INIntentParameterPromptDialogFormatStringID - qYKqOP - INIntentParameterPromptDialogType - Confirmation - INIntentParameterSupportsDynamicEnumeration From b833946e7e03648da413eec1f0cd1ae676d48a48 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 13 Jun 2024 15:44:45 -0600 Subject: [PATCH 211/547] Refactored downloader and uploader to throw if they fail --- SimplenoteShare/Simperium/Downloader.swift | 24 ++++++++++++++++++++- SimplenoteShare/Simperium/Uploader.swift | 25 ++++++++++++++++------ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/SimplenoteShare/Simperium/Downloader.swift b/SimplenoteShare/Simperium/Downloader.swift index b8a4f645e..d86fd86c5 100644 --- a/SimplenoteShare/Simperium/Downloader.swift +++ b/SimplenoteShare/Simperium/Downloader.swift @@ -8,6 +8,24 @@ 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 @@ -39,7 +57,11 @@ class Downloader: NSObject { func extractNoteContent(from data: Data) throws -> String? { let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] - return jsonObject?["content"] as? String + guard let content = jsonObject?["content"] as? String else { + throw DownloaderError.couldNotFetchNoteContent + } + + return content } } diff --git a/SimplenoteShare/Simperium/Uploader.swift b/SimplenoteShare/Simperium/Uploader.swift index da6b6e9fe..dc57082a6 100644 --- a/SimplenoteShare/Simperium/Uploader.swift +++ b/SimplenoteShare/Simperium/Uploader.swift @@ -17,6 +17,24 @@ class Uploader: NSObject { // 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())! @@ -27,12 +45,7 @@ class Uploader: NSObject { 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 } } From 162eedf20d06112bfc852db6a4f7e0857c1a89ab Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 13 Jun 2024 15:49:59 -0600 Subject: [PATCH 212/547] Refactored append note handlers to return a reason for failure --- .../AppendNoteIntentHandler.swift | 27 +++++++++++-------- .../CreateNewNoteIntentHandler.swift | 16 +++++------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 2ab3df7b9..88baa9593 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -1,11 +1,3 @@ -// -// AppendNoteIntentHandler.swift -// SimplenoteIntents -// -// Created by Charlie Scheer on 5/6/24. -// Copyright © 2024 Automattic. All rights reserved. -// - import Intents class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { @@ -38,11 +30,24 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { guard let existingContent = try? await Downloader(simperiumToken: token).getNoteContent(for: identifier) else { return AppendNoteIntentResponse(code: .failure, userActivity: nil) } + do { + let existingContent = try await Downloader(simperiumToken: token).getNoteContent(for: identifier) ?? String() + note.content = existingContent + "\n\(content)" + } catch { + return handleFailure(with: error, content: content) + } - note.content = existingContent + "\n\(content)" let uploader = Uploader(simperiumToken: token) - uploader.send(note) - return AppendNoteIntentResponse(code: .success, userActivity: nil) + do { + _ = try await uploader.send(note) + return AppendNoteIntentResponse(code: .success, userActivity: nil) + } catch { + return handleFailure(with: error, content: content) + } + } + + private func handleFailure(with error: Error, content: String) -> AppendNoteIntentResponse { + return AppendNoteIntentResponse.failure(failureReason: error.localizedDescription) } } diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift index e105c7436..3ccc24781 100644 --- a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift @@ -1,11 +1,3 @@ -// -// CreateNewNoteIntentHandler.swift -// SimplenoteIntents -// -// Created by Charlie Scheer on 5/8/24. -// Copyright © 2024 Automattic. All rights reserved. -// - import Intents class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { @@ -17,8 +9,12 @@ class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { return CreateNewNoteIntentResponse(code: .failure, userActivity: nil) } - Uploader(simperiumToken: token).send(note(with: content)) - return CreateNewNoteIntentResponse(code: .success, userActivity: nil) + do { + _ = try await Uploader(simperiumToken: token).send(note(with: content)) + return CreateNewNoteIntentResponse(code: .success, userActivity: nil) + } catch { + return CreateNewNoteIntentResponse.failure(failureReason: error.localizedDescription) + } } private func note(with content: String) -> Note { From 0bd1eef2892818e504dd2865b56b683b4c582ce0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 13 Jun 2024 16:07:26 -0600 Subject: [PATCH 213/547] Added shortcut content recovery if shortcut fails --- Simplenote.xcodeproj/project.pbxproj | 12 ++- .../Classes/FileManager+Simplenote.swift | 14 ++++ Simplenote/ContentRecoveryManager.swift | 75 +++++++++++++++++++ Simplenote/SPAppDelegate+Extensions.swift | 26 +++++++ Simplenote/SPAppDelegate.m | 1 + .../AppendNoteIntentHandler.swift | 1 + .../CreateNewNoteIntentHandler.swift | 1 + 7 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 Simplenote/ContentRecoveryManager.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index c4d3df429..d1c297534 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -485,6 +485,8 @@ 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 /* ContentRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7240212C1BA1210088EC11 /* ContentRecoveryManager.swift */; }; + BA7240232C1BA1930088EC11 /* ContentRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7240212C1BA1210088EC11 /* ContentRecoveryManager.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 */; }; @@ -509,13 +511,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 */; }; - BA9C7EFB2BF2CC3E007A8460 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */; }; - BA9C7EFC2BF2CCAE007A8460 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9C7EFA2BF2CC3E007A8460 /* Downloader.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 */; }; @@ -1185,6 +1187,7 @@ 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 /* ContentRecoveryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentRecoveryManager.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 = ""; }; @@ -1199,7 +1202,6 @@ 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 = ""; }; - BA9C7EFA2BF2CC3E007A8460 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.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 = ""; }; @@ -1224,6 +1226,7 @@ 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 = ""; }; @@ -2233,6 +2236,7 @@ BA3856CC2681715700F388CC /* CoreDataManager.swift */, BA9B59012685549F00DAD1ED /* StorageSettings.swift */, BA32A90E26B7469F00727247 /* WidgetError.swift */, + BA7240212C1BA1210088EC11 /* ContentRecoveryManager.swift */, ); name = Tools; sourceTree = ""; @@ -3454,6 +3458,7 @@ B543C7E323CF76EA00003A80 /* NotesListFilter.swift in Sources */, 46A3C96717DFA81A002865AE /* NSManagedObjectContext+CoreDataExtensions.m in Sources */, A6C0DFA725C0992D00B9BE39 /* NoteScrollPositionCache.swift in Sources */, + BA7240222C1BA1210088EC11 /* ContentRecoveryManager.swift in Sources */, B59314D81A486B3800B651ED /* SPConstants.m in Sources */, 375D24B421E01131007AB25A /* escape.c in Sources */, B50F47A61D1D791B00822748 /* NSURL+Extensions.swift in Sources */, @@ -3858,6 +3863,7 @@ BAF8D45526AE10FC00CA9383 /* Tag+Widget.swift in Sources */, BAFFCEA726DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift in Sources */, BAF8D53C26AE175C00CA9383 /* Bundle+Simplenote.swift in Sources */, + BA7240232C1BA1930088EC11 /* ContentRecoveryManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Simplenote/Classes/FileManager+Simplenote.swift b/Simplenote/Classes/FileManager+Simplenote.swift index 95905de1e..c99e7e6b0 100644 --- a/Simplenote/Classes/FileManager+Simplenote.swift +++ b/Simplenote/Classes/FileManager+Simplenote.swift @@ -19,4 +19,18 @@ extension FileManager { var sharedContainerURL: URL { containerURL(forSecurityApplicationGroupIdentifier: SimplenoteConstants.sharedGroupDomain)! } + + var recoveryDirectoryURL: URL { + sharedContainerURL.appendingPathComponent(Constants.recoveryDir) + } + + 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/ContentRecoveryManager.swift b/Simplenote/ContentRecoveryManager.swift new file mode 100644 index 000000000..b168a05a3 --- /dev/null +++ b/Simplenote/ContentRecoveryManager.swift @@ -0,0 +1,75 @@ +import Foundation +import UniformTypeIdentifiers +import CoreData + +public class ContentRecoveryManager { + private let fileManager: FileManager + + public init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + private func createRecoveryDirIfNeeded() { + guard !fileManager.directoryExistsAtURL(fileManager.recoveryDirectoryURL) else { + return + } + + try? fileManager.createDirectory(at: fileManager.recoveryDirectoryURL, withIntermediateDirectories: true) + } + + // MARK: Archive + // + public func archiveContent(_ content: String) { + createRecoveryDirIfNeeded() + + guard let data = content.data(using: .utf8) else { + return + } + try? data.write(to: url(for: UUID().uuidString)) + } + + private func url(for identifier: String) -> URL { + let formattedID = identifier.replacingOccurrences(of: "/", with: "-") + let fileName = "\(Constants.recoveredContent)-\(formattedID)" + return fileManager.recoveryDirectoryURL.appendingPathComponent(fileName, conformingTo: UTType.json) + } + + // MARK: Restore + // + public func prepareRecoveredNoteContentIfNeeded(in context: NSManagedObjectContext) async -> [String] { + createRecoveryDirIfNeeded() + + guard let recoveryFiles = try? fileManager.contentsOfDirectory(at: fileManager.recoveryDirectoryURL, includingPropertiesForKeys: nil), + !recoveryFiles.isEmpty else { + return [] + } + + return recoveryFiles.compactMap { url in + guard let content = prepareNoteContent(at: url, in: context) else { + return nil + } + + try? fileManager.removeItem(at: url) + return content + } + } + + private func prepareNoteContent(at url: URL, in context: NSManagedObjectContext) -> String? { + guard let data = fileManager.contents(atPath: url.path), + let content = String(data: data, encoding: .utf8) else { + return nil + } + return content + } + + private func identifier(from url: URL) -> String? { + let fileName = url.deletingPathExtension().lastPathComponent + let components = fileName.components(separatedBy: "-") + return components.last + } + } + +private struct Constants { + static let recoveredContent = "recoveredContent" + static let richTextKey = "richText" +} diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index a2babfdfa..cfac0e0a0 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -557,3 +557,29 @@ extension SPAppDelegate { UserDefaults.standard.set(true, forKey: .hasMigratedSustainerPreferences) } } + +// MARK: - Content Recovery +// +extension SPAppDelegate { + @objc + func attemptContentRecoveryIfNeeded() { + let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + context.persistentStoreCoordinator = coreDataManager.persistentStoreCoordinator + + Task { + let restoredContent = await ContentRecoveryManager().prepareRecoveredNoteContentIfNeeded(in: context) + restoredContent.forEach({ insertNote(with: $0) }) + } + } + + private func insertNote(with content: String) { + guard let note = simperium.notesBucket.insertNewObject() as? Note else { + return + } + note.modificationDate = Date() + note.creationDate = Date() + note.content = content + note.markdown = UserDefaults.standard.bool(forKey: "kMarkdownPreferencesKey") + simperium.save() + } +} diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index 847d1c47b..9537e48de 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -167,6 +167,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [self setupNoticeController]; + [self attemptContentRecoveryIfNeeded]; return YES; } diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 88baa9593..7a03f1e1f 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -48,6 +48,7 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { } private func handleFailure(with error: Error, content: String) -> AppendNoteIntentResponse { + ContentRecoveryManager().archiveContent(content) return AppendNoteIntentResponse.failure(failureReason: error.localizedDescription) } } diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift index 3ccc24781..d95f2ee88 100644 --- a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift @@ -13,6 +13,7 @@ class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { _ = try await Uploader(simperiumToken: token).send(note(with: content)) return CreateNewNoteIntentResponse(code: .success, userActivity: nil) } catch { + ContentRecoveryManager().archiveContent(content) return CreateNewNoteIntentResponse.failure(failureReason: error.localizedDescription) } } From 0fadd457deb7c1f8fe1f5316d8af6dad595b7c72 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 13 Jun 2024 16:11:34 -0600 Subject: [PATCH 214/547] Added header to note recovery files --- Simplenote/ContentRecoveryManager.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Simplenote/ContentRecoveryManager.swift b/Simplenote/ContentRecoveryManager.swift index b168a05a3..861c600de 100644 --- a/Simplenote/ContentRecoveryManager.swift +++ b/Simplenote/ContentRecoveryManager.swift @@ -56,20 +56,19 @@ public class ContentRecoveryManager { private func prepareNoteContent(at url: URL, in context: NSManagedObjectContext) -> String? { guard let data = fileManager.contents(atPath: url.path), - let content = String(data: data, encoding: .utf8) else { + let recoveredContent = String(data: data, encoding: .utf8) else { return nil } - return content - } - private func identifier(from url: URL) -> String? { - let fileName = url.deletingPathExtension().lastPathComponent - let components = fileName.components(separatedBy: "-") - return components.last + var content = Constants.recoveredContentHeader + content += "\n\n" + content += recoveredContent + return content } } private struct Constants { static let recoveredContent = "recoveredContent" static let richTextKey = "richText" + static let recoveredContentHeader = NSLocalizedString("Recovered Note Cotent - ", comment: "Header to put on any files that need to be recovered") } From 4d4485931cb05a2f92087ad91915916e8543b5b0 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 10:54:09 -0300 Subject: [PATCH 215/547] MagicLinkAuthenticator: Wiring endpoint updates --- Simplenote/Classes/MagicLinkAuthenticator.swift | 13 +++++++------ Simplenote/Classes/SimplenoteConstants.swift | 4 ++++ Simplenote/LoginRemote.swift | 12 +++++++----- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 7aa562b45..d66c856ba 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -41,23 +41,23 @@ private extension MagicLinkAuthenticator { @discardableResult func attemptLoginWithAuthCode(queryItems: [URLQueryItem]) -> Bool { - guard let email = queryItems.base64DecodedValue(for: Constants.emailField), + guard let authKey = queryItems.value(for: Constants.authKeyField), let authCode = queryItems.value(for: Constants.authCodeField), - !email.isEmpty, !authCode.isEmpty + !authKey.isEmpty, !authCode.isEmpty else { return false } - NSLog("[MagicLinkAuthenticator] Requesting SyncToken for \(email) and \(authCode)") + NSLog("[MagicLinkAuthenticator] Requesting SyncToken for \(authKey) and \(authCode)") Task { do { let remote = LoginRemote() - let syncToken = try await remote.requestSyncToken(email: email, authCode: authCode) + let confirmation = try await remote.requestLoginConfirmation(authKey: authKey, authCode: authCode) Task { @MainActor in - NSLog("[MagicLinkAuthenticator] Should auth with token \(syncToken)") - authenticator.authenticate(withUsername: email, token: syncToken) + NSLog("[MagicLinkAuthenticator] Should auth with token \(confirmation.syncToken)") + authenticator.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) } } catch { @@ -92,5 +92,6 @@ private struct Constants { static let host = "login" static let emailField = "email" static let tokenField = "token" + static let authKeyField = "auth_key" static let authCodeField = "auth_code" } diff --git a/Simplenote/Classes/SimplenoteConstants.swift b/Simplenote/Classes/SimplenoteConstants.swift index 0aca91f4b..44ebabac8 100644 --- a/Simplenote/Classes/SimplenoteConstants.swift +++ b/Simplenote/Classes/SimplenoteConstants.swift @@ -28,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 /// diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift index 9cae5241e..57b8a7b33 100644 --- a/Simplenote/LoginRemote.swift +++ b/Simplenote/LoginRemote.swift @@ -10,11 +10,11 @@ class LoginRemote: Remote { performDataTask(with: request, completion: completion) } - func requestSyncToken(email: String, authCode: String) async throws -> String { - let request = requestForLoginCompletion(email: email, authCode: authCode) + func requestLoginConfirmation(authKey: String, authCode: String) async throws -> LoginConfirmationResponse { + let request = requestForLoginCompletion(authKey: authKey, authCode: authCode) let response = try await performDataTask(with: request, type: LoginConfirmationResponse.self) - return response.syncToken + return response } } @@ -22,6 +22,7 @@ class LoginRemote: Remote { // MARK: - LoginConfirmationResponse // struct LoginConfirmationResponse: Decodable { + let username: String let syncToken: String } @@ -34,15 +35,16 @@ private extension LoginRemote { let url = URL(string: SimplenoteConstants.loginRequestURL)! return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ + "request_source": SimplenoteConstants.simplenotePlatformName, "username": email.lowercased() ]) } - func requestForLoginCompletion(email: String, authCode: String) -> URLRequest { + func requestForLoginCompletion(authKey: String, authCode: String) -> URLRequest { let url = URL(string: SimplenoteConstants.loginCompletionURL)! return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ - "username": email.lowercased(), + "auth_key": authKey, "auth_code": authCode ]) } From 28a478c4bcb74a3d6305d6a9338de88ba05edce5 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 11:21:40 -0300 Subject: [PATCH 216/547] MagicLinkAuthenticator: Reverts unneeded change --- Simplenote/Classes/MagicLinkAuthenticator.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index d66c856ba..b2cecdb16 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -30,8 +30,7 @@ private extension MagicLinkAuthenticator { 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 { + !email.isEmpty, !token.isEmpty else { return false } From 9ad541342fbaa227a8755ac4e8ea3c94c7a20374 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 11:51:54 -0300 Subject: [PATCH 217/547] Remote: Simplifies RemoteError Parsing --- Simplenote/Remote.swift | 36 ++++++++++++++++-------------------- Simplenote/RemoteError.swift | 20 +++++++++++++++++++- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Simplenote/Remote.swift b/Simplenote/Remote.swift index c334efd16..e06b4ead5 100644 --- a/Simplenote/Remote.swift +++ b/Simplenote/Remote.swift @@ -13,13 +13,8 @@ class Remote { 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 + + if let response, let error = RemoteError(statusCode: response.responseStatusCode, error: dataTaskError) { completion(.failure(error)) return } @@ -35,11 +30,9 @@ class Remote { /// func performDataTask(with request: URLRequest) async throws -> Data { let (data, response) = try await urlSession.data(for: request) - let statusCode = (response as? HTTPURLResponse)?.statusCode ?? .zero - - // Check for 2xx status code - guard statusCode / 100 == 2 else { - throw RemoteError.requestError(statusCode, nil) + + if let error = RemoteError(statusCode: response.responseStatusCode) { + throw error } return data @@ -50,14 +43,9 @@ class Remote { func performDataTask(with request: URLRequest, type: T.Type) async throws -> T { let data = try await performDataTask(with: request) - do { - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - return try decoder.decode(type, from: data) - - } catch { - throw RemoteError.responseUnableToDecode - } + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return try decoder.decode(type, from: data) } /// Builds a URLRequest for the specified URL / Method / params @@ -75,3 +63,11 @@ class Remote { return request } } + + +extension URLResponse { + + var responseStatusCode: Int { + (self as? HTTPURLResponse)?.statusCode ?? .zero + } +} diff --git a/Simplenote/RemoteError.swift b/Simplenote/RemoteError.swift index cf87d6e30..4366d98e1 100644 --- a/Simplenote/RemoteError.swift +++ b/Simplenote/RemoteError.swift @@ -2,10 +2,28 @@ import Foundation enum RemoteError: Error { case network - case responseUnableToDecode + case tooManyAttempts case requestError(Int, Error?) } + +extension RemoteError { + + init?(statusCode: Int, error: Error? = nil) { + if statusCode / 100 == 2 { + return nil + } + + switch statusCode { + case 429: + self = .tooManyAttempts + default: + self = statusCode > 0 ? .requestError(statusCode, error) : .network + } + } +} + + extension RemoteError: Equatable { static func == (lhs: RemoteError, rhs: RemoteError) -> Bool { switch (lhs, rhs) { From bfa25395fcb82d14c3623225b653b1279b6e006b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 11:52:08 -0300 Subject: [PATCH 218/547] SPAuthHandler: Wires tooManyAttempts Error --- Simplenote/Classes/SPAuthError.swift | 1 - Simplenote/Classes/SPAuthHandler.swift | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Simplenote/Classes/SPAuthError.swift b/Simplenote/Classes/SPAuthError.swift index 8f84d634c..f8155d900 100644 --- a/Simplenote/Classes/SPAuthError.swift +++ b/Simplenote/Classes/SPAuthError.swift @@ -10,7 +10,6 @@ enum SPAuthError: Error { case compromisedPassword case unverifiedEmail case tooManyAttempts - case unableToDecode case unknown(statusCode: Int, response: String?, error: Error?) } diff --git a/Simplenote/Classes/SPAuthHandler.swift b/Simplenote/Classes/SPAuthHandler.swift index 0c658f82a..0562286c6 100644 --- a/Simplenote/Classes/SPAuthHandler.swift +++ b/Simplenote/Classes/SPAuthHandler.swift @@ -89,9 +89,9 @@ class SPAuthHandler { private func authenticationError(for remoteError: RemoteError) -> SPAuthError { switch remoteError { case .network: - return SPAuthError.network - case .responseUnableToDecode: - return SPAuthError.unableToDecode + return .network + case .tooManyAttempts: + return .tooManyAttempts case .requestError(let statusCode, let error): return SPAuthError(signupErrorCode: statusCode, response: error?.localizedDescription, error: error) } From d7f319d7421fec6f5bc06d631ce7e9b6cfed784f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 11:52:20 -0300 Subject: [PATCH 219/547] SPSettingsViewController: Adds missing handling --- Simplenote/Classes/SPSettingsViewController+Extensions.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 5b4392105..18d09e6f9 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -191,7 +191,10 @@ extension SPSettingsViewController { switch error { case .network: NoticeController.shared.present(NoticeFactory.networkError()) - case .responseUnableToDecode, .requestError: + case .tooManyAttempts: +// TODO + break + case .requestError: presentRequestErrorAlert() } } From 73c9da177dfeba82ad6ba944dcb15ad7f2cc5ca7 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 12:00:37 -0300 Subject: [PATCH 220/547] LoginRemote: Drops extra newlines --- Simplenote/Classes/SPAuthError.swift | 2 +- Simplenote/LoginRemote.swift | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Simplenote/Classes/SPAuthError.swift b/Simplenote/Classes/SPAuthError.swift index f8155d900..21d3e61a3 100644 --- a/Simplenote/Classes/SPAuthError.swift +++ b/Simplenote/Classes/SPAuthError.swift @@ -89,7 +89,7 @@ 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") - default: + case .unknown: return NSLocalizedString("We're having problems. Please try again soon.", comment: "Generic error") } } diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift index 57b8a7b33..9b7fe181e 100644 --- a/Simplenote/LoginRemote.swift +++ b/Simplenote/LoginRemote.swift @@ -6,15 +6,12 @@ class LoginRemote: Remote { func requestLoginEmail(email: String, completion: @escaping (_ result: Result) -> Void) { let request = requestForLoginRequest(with: email) - performDataTask(with: request, completion: completion) } func requestLoginConfirmation(authKey: String, authCode: String) async throws -> LoginConfirmationResponse { let request = requestForLoginCompletion(authKey: authKey, authCode: authCode) - let response = try await performDataTask(with: request, type: LoginConfirmationResponse.self) - - return response + return try await performDataTask(with: request, type: LoginConfirmationResponse.self) } } @@ -33,7 +30,6 @@ private extension LoginRemote { func requestForLoginRequest(with email: String) -> URLRequest { let url = URL(string: SimplenoteConstants.loginRequestURL)! - return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ "request_source": SimplenoteConstants.simplenotePlatformName, "username": email.lowercased() @@ -42,7 +38,6 @@ private extension LoginRemote { func requestForLoginCompletion(authKey: String, authCode: String) -> URLRequest { let url = URL(string: SimplenoteConstants.loginCompletionURL)! - return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ "auth_key": authKey, "auth_code": authCode From 6f70abc3a9570d716a535ad49a9f3e2b65460ba8 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 12:07:45 -0300 Subject: [PATCH 221/547] MagicLinkAuthenticator: Adds tracks event --- Simplenote/Classes/MagicLinkAuthenticator.swift | 1 + Simplenote/Classes/SPTracker.h | 1 + Simplenote/Classes/SPTracker.m | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index b2cecdb16..ce3743ab7 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -57,6 +57,7 @@ private extension MagicLinkAuthenticator { Task { @MainActor in NSLog("[MagicLinkAuthenticator] Should auth with token \(confirmation.syncToken)") authenticator.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) + SPTracker.trackUserConfirmedLoginLink() } } catch { diff --git a/Simplenote/Classes/SPTracker.h b/Simplenote/Classes/SPTracker.h index a0804bfd5..8ce28db92 100644 --- a/Simplenote/Classes/SPTracker.h +++ b/Simplenote/Classes/SPTracker.h @@ -82,6 +82,7 @@ + (void)trackUserAccountCreated; + (void)trackUserSignedIn; + (void)trackUserRequestedLoginLink; ++ (void)trackUserConfirmedLoginLink; + (void)trackUserSignedOut; #pragma mark - WP.com Sign In diff --git a/Simplenote/Classes/SPTracker.m b/Simplenote/Classes/SPTracker.m index a4872fb83..0d24392b1 100644 --- a/Simplenote/Classes/SPTracker.m +++ b/Simplenote/Classes/SPTracker.m @@ -322,6 +322,11 @@ + (void)trackUserRequestedLoginLink [self trackAutomatticEventWithName:@"user_requested_login_link" properties:nil]; } ++ (void)trackUserConfirmedLoginLink +{ + [self trackAutomatticEventWithName:@"user_confirmed_login_link" properties:nil]; +} + + (void)trackUserSignedOut { [self trackAutomatticEventWithName:@"user_signed_out" properties:nil]; From fe807724543a195a5b502d15dc0c1eb39a314601 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 14 Jun 2024 12:28:59 -0300 Subject: [PATCH 222/547] SPSettingsViewController: Adds missing case --- Simplenote/Classes/SPSettingsViewController+Extensions.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Simplenote/Classes/SPSettingsViewController+Extensions.swift b/Simplenote/Classes/SPSettingsViewController+Extensions.swift index 18d09e6f9..fb9bcead3 100644 --- a/Simplenote/Classes/SPSettingsViewController+Extensions.swift +++ b/Simplenote/Classes/SPSettingsViewController+Extensions.swift @@ -191,10 +191,7 @@ extension SPSettingsViewController { switch error { case .network: NoticeController.shared.present(NoticeFactory.networkError()) - case .tooManyAttempts: -// TODO - break - case .requestError: + default: presentRequestErrorAlert() } } From 4915f55521fc40d28d4c172527bf8ceed6203694 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 13:25:29 -0600 Subject: [PATCH 223/547] Split ContentRecoveryManager into two separate files --- Simplenote.xcodeproj/project.pbxproj | 22 ++++++++--- ...Manager.swift => RecoveryUnarchiver.swift} | 20 +--------- Simplenote/SPAppDelegate+Extensions.swift | 2 +- .../AppendNoteIntentHandler.swift | 2 +- .../CreateNewNoteIntentHandler.swift | 2 +- .../Tools/RecoveryArchiver.swift | 39 +++++++++++++++++++ 6 files changed, 59 insertions(+), 28 deletions(-) rename Simplenote/{ContentRecoveryManager.swift => RecoveryUnarchiver.swift} (72%) create mode 100644 SimplenoteIntents/Tools/RecoveryArchiver.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index d1c297534..95330ae55 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -464,6 +464,7 @@ 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 */; }; @@ -485,8 +486,7 @@ 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 /* ContentRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7240212C1BA1210088EC11 /* ContentRecoveryManager.swift */; }; - BA7240232C1BA1930088EC11 /* ContentRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7240212C1BA1210088EC11 /* ContentRecoveryManager.swift */; }; + 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 */; }; @@ -1168,6 +1168,7 @@ 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 = ""; }; @@ -1187,7 +1188,7 @@ 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 /* ContentRecoveryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentRecoveryManager.swift; 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 = ""; }; @@ -2236,7 +2237,7 @@ BA3856CC2681715700F388CC /* CoreDataManager.swift */, BA9B59012685549F00DAD1ED /* StorageSettings.swift */, BA32A90E26B7469F00727247 /* WidgetError.swift */, - BA7240212C1BA1210088EC11 /* ContentRecoveryManager.swift */, + BA7240212C1BA1210088EC11 /* RecoveryUnarchiver.swift */, ); name = Tools; sourceTree = ""; @@ -2414,6 +2415,14 @@ name = Notices; sourceTree = ""; }; + BA4A018E2C1CCEB300EEE567 /* Tools */ = { + isa = PBXGroup; + children = ( + BA4A018F2C1CCEC100EEE567 /* RecoveryArchiver.swift */, + ); + path = Tools; + sourceTree = ""; + }; BA57692A269D2103008B510E /* Remotes */ = { isa = PBXGroup; children = ( @@ -2461,6 +2470,7 @@ BAB5762526703C8200B0C56F /* SimplenoteIntents */ = { isa = PBXGroup; children = ( + BA4A018E2C1CCEB300EEE567 /* Tools */, BA34B04F2BEAEF4800580E15 /* SimplenoteIntentsRelease.entitlements */, 092FD78026D7BB72006BE8E2 /* Supporting Files */, BAB5762626703C8200B0C56F /* IntentHandler.swift */, @@ -3458,7 +3468,7 @@ B543C7E323CF76EA00003A80 /* NotesListFilter.swift in Sources */, 46A3C96717DFA81A002865AE /* NSManagedObjectContext+CoreDataExtensions.m in Sources */, A6C0DFA725C0992D00B9BE39 /* NoteScrollPositionCache.swift in Sources */, - BA7240222C1BA1210088EC11 /* ContentRecoveryManager.swift in Sources */, + BA7240222C1BA1210088EC11 /* RecoveryUnarchiver.swift in Sources */, B59314D81A486B3800B651ED /* SPConstants.m in Sources */, 375D24B421E01131007AB25A /* escape.c in Sources */, B50F47A61D1D791B00822748 /* NSURL+Extensions.swift in Sources */, @@ -3845,6 +3855,7 @@ 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 */, @@ -3863,7 +3874,6 @@ BAF8D45526AE10FC00CA9383 /* Tag+Widget.swift in Sources */, BAFFCEA726DDA9F6007F5EE3 /* ExtensionCoreDataWrapper.swift in Sources */, BAF8D53C26AE175C00CA9383 /* Bundle+Simplenote.swift in Sources */, - BA7240232C1BA1930088EC11 /* ContentRecoveryManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Simplenote/ContentRecoveryManager.swift b/Simplenote/RecoveryUnarchiver.swift similarity index 72% rename from Simplenote/ContentRecoveryManager.swift rename to Simplenote/RecoveryUnarchiver.swift index 861c600de..cc19495d8 100644 --- a/Simplenote/ContentRecoveryManager.swift +++ b/Simplenote/RecoveryUnarchiver.swift @@ -2,7 +2,7 @@ import Foundation import UniformTypeIdentifiers import CoreData -public class ContentRecoveryManager { +public class RecoveryUnarchiver { private let fileManager: FileManager public init(fileManager: FileManager = .default) { @@ -17,23 +17,6 @@ public class ContentRecoveryManager { try? fileManager.createDirectory(at: fileManager.recoveryDirectoryURL, withIntermediateDirectories: true) } - // MARK: Archive - // - public func archiveContent(_ content: String) { - createRecoveryDirIfNeeded() - - guard let data = content.data(using: .utf8) else { - return - } - try? data.write(to: url(for: UUID().uuidString)) - } - - private func url(for identifier: String) -> URL { - let formattedID = identifier.replacingOccurrences(of: "/", with: "-") - let fileName = "\(Constants.recoveredContent)-\(formattedID)" - return fileManager.recoveryDirectoryURL.appendingPathComponent(fileName, conformingTo: UTType.json) - } - // MARK: Restore // public func prepareRecoveredNoteContentIfNeeded(in context: NSManagedObjectContext) async -> [String] { @@ -68,7 +51,6 @@ public class ContentRecoveryManager { } private struct Constants { - static let recoveredContent = "recoveredContent" static let richTextKey = "richText" static let recoveredContentHeader = NSLocalizedString("Recovered Note Cotent - ", comment: "Header to put on any files that need to be recovered") } diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index cfac0e0a0..44c959874 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -567,7 +567,7 @@ extension SPAppDelegate { context.persistentStoreCoordinator = coreDataManager.persistentStoreCoordinator Task { - let restoredContent = await ContentRecoveryManager().prepareRecoveredNoteContentIfNeeded(in: context) + let restoredContent = await RecoveryUnarchiver().prepareRecoveredNoteContentIfNeeded(in: context) restoredContent.forEach({ insertNote(with: $0) }) } } diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 7a03f1e1f..fe045a504 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -48,7 +48,7 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { } private func handleFailure(with error: Error, content: String) -> AppendNoteIntentResponse { - ContentRecoveryManager().archiveContent(content) + RecoveryArchiver().archiveContent(content) return AppendNoteIntentResponse.failure(failureReason: error.localizedDescription) } } diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift index d95f2ee88..ce5ec9078 100644 --- a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift @@ -13,7 +13,7 @@ class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { _ = try await Uploader(simperiumToken: token).send(note(with: content)) return CreateNewNoteIntentResponse(code: .success, userActivity: nil) } catch { - ContentRecoveryManager().archiveContent(content) + RecoveryArchiver().archiveContent(content) return CreateNewNoteIntentResponse.failure(failureReason: error.localizedDescription) } } diff --git a/SimplenoteIntents/Tools/RecoveryArchiver.swift b/SimplenoteIntents/Tools/RecoveryArchiver.swift new file mode 100644 index 000000000..78e71a458 --- /dev/null +++ b/SimplenoteIntents/Tools/RecoveryArchiver.swift @@ -0,0 +1,39 @@ +import Foundation +import UniformTypeIdentifiers + +public class RecoveryArchiver { + private let fileManager: FileManager + + public init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + private func createRecoveryDirIfNeeded() { + guard !fileManager.directoryExistsAtURL(fileManager.recoveryDirectoryURL) else { + return + } + + try? fileManager.createDirectory(at: fileManager.recoveryDirectoryURL, withIntermediateDirectories: true) + } + + // MARK: Archive + // + public func archiveContent(_ content: String) { + createRecoveryDirIfNeeded() + + guard let data = content.data(using: .utf8) else { + return + } + try? data.write(to: url(for: UUID().uuidString)) + } + + private func url(for identifier: String) -> URL { + let formattedID = identifier.replacingOccurrences(of: "/", with: "-") + let fileName = "\(Constants.recoveredContent)-\(formattedID)" + return fileManager.recoveryDirectoryURL.appendingPathComponent(fileName, conformingTo: UTType.json) + } +} + +private struct Constants { + static let recoveredContent = "recoveredContent" +} From c46fe7a4ac849078cba9f538e53908e7fdb02d14 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 13:33:27 -0600 Subject: [PATCH 224/547] Refactored recovery unarchiver to handle creating new objects --- Simplenote/RecoveryUnarchiver.swift | 33 +++++++++++++---------- Simplenote/SPAppDelegate+Extensions.swift | 17 +----------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/Simplenote/RecoveryUnarchiver.swift b/Simplenote/RecoveryUnarchiver.swift index cc19495d8..beaa20dbe 100644 --- a/Simplenote/RecoveryUnarchiver.swift +++ b/Simplenote/RecoveryUnarchiver.swift @@ -9,6 +9,10 @@ public class RecoveryUnarchiver { self.fileManager = fileManager } + var simperium: Simperium { + SPAppDelegate.shared().simperium + } + private func createRecoveryDirIfNeeded() { guard !fileManager.directoryExistsAtURL(fileManager.recoveryDirectoryURL) else { return @@ -19,38 +23,39 @@ public class RecoveryUnarchiver { // MARK: Restore // - public func prepareRecoveredNoteContentIfNeeded(in context: NSManagedObjectContext) async -> [String] { + public func prepareRecoveredNoteContentIfNeeded() { createRecoveryDirIfNeeded() - guard let recoveryFiles = try? fileManager.contentsOfDirectory(at: fileManager.recoveryDirectoryURL, includingPropertiesForKeys: nil), !recoveryFiles.isEmpty else { - return [] + return } - return recoveryFiles.compactMap { url in - guard let content = prepareNoteContent(at: url, in: context) else { - return nil - } - + recoveryFiles.forEach { url in + insertNote(from: url) try? fileManager.removeItem(at: url) - return content } } - private func prepareNoteContent(at url: URL, in context: NSManagedObjectContext) -> String? { + private func insertNote(from url: URL) { guard let data = fileManager.contents(atPath: url.path), - let recoveredContent = String(data: data, encoding: .utf8) else { - return nil + 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 - return content + note.content = content + + note.modificationDate = Date() + note.creationDate = Date() + note.markdown = UserDefaults.standard.bool(forKey: "kMarkdownPreferencesKey") + + simperium.save() } } private struct Constants { - static let richTextKey = "richText" static let recoveredContentHeader = NSLocalizedString("Recovered Note Cotent - ", comment: "Header to put on any files that need to be recovered") } diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 44c959874..b7a268076 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -565,21 +565,6 @@ extension SPAppDelegate { func attemptContentRecoveryIfNeeded() { let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) context.persistentStoreCoordinator = coreDataManager.persistentStoreCoordinator - - Task { - let restoredContent = await RecoveryUnarchiver().prepareRecoveredNoteContentIfNeeded(in: context) - restoredContent.forEach({ insertNote(with: $0) }) - } - } - - private func insertNote(with content: String) { - guard let note = simperium.notesBucket.insertNewObject() as? Note else { - return - } - note.modificationDate = Date() - note.creationDate = Date() - note.content = content - note.markdown = UserDefaults.standard.bool(forKey: "kMarkdownPreferencesKey") - simperium.save() + RecoveryUnarchiver().prepareRecoveredNoteContentIfNeeded() } } From a0f1276a61737ec4333f73953431a34dfa3aa95b Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 13:35:29 -0600 Subject: [PATCH 225/547] Fixed issue defining markdown on recovered notes --- Simplenote/RecoveryUnarchiver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/RecoveryUnarchiver.swift b/Simplenote/RecoveryUnarchiver.swift index beaa20dbe..e1fb2433b 100644 --- a/Simplenote/RecoveryUnarchiver.swift +++ b/Simplenote/RecoveryUnarchiver.swift @@ -50,7 +50,7 @@ public class RecoveryUnarchiver { note.modificationDate = Date() note.creationDate = Date() - note.markdown = UserDefaults.standard.bool(forKey: "kMarkdownPreferencesKey") + note.markdown = UserDefaults.standard.bool(forKey: .markdown) simperium.save() } From 677710cd81cf83fdc679199b5b76439003053997 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 13:36:11 -0600 Subject: [PATCH 226/547] Dropped unused context --- Simplenote/SPAppDelegate+Extensions.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index b7a268076..da9ba5da6 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -563,8 +563,6 @@ extension SPAppDelegate { extension SPAppDelegate { @objc func attemptContentRecoveryIfNeeded() { - let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - context.persistentStoreCoordinator = coreDataManager.persistentStoreCoordinator RecoveryUnarchiver().prepareRecoveredNoteContentIfNeeded() } } From 7824122ab7f47ae22a8aadd806d482f3d2829409 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 13:37:13 -0600 Subject: [PATCH 227/547] Dropped repeat fetching of existing app content --- .../Intent Handlers/AppendNoteIntentHandler.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index fe045a504..73b3a150b 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -27,9 +27,6 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { return AppendNoteIntentResponse(code: .failure, userActivity: nil) } - guard let existingContent = try? await Downloader(simperiumToken: token).getNoteContent(for: identifier) else { - return AppendNoteIntentResponse(code: .failure, userActivity: nil) - } do { let existingContent = try await Downloader(simperiumToken: token).getNoteContent(for: identifier) ?? String() note.content = existingContent + "\n\(content)" From de636357cd399243965b95329e9e56c2412870fe Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 13:46:00 -0600 Subject: [PATCH 228/547] Refactored creating recovery dir to filemanager extension --- .../Classes/FileManager+Simplenote.swift | 21 +++++++++++++++++-- Simplenote/RecoveryUnarchiver.swift | 14 +++---------- .../Tools/RecoveryArchiver.swift | 21 ++++++++----------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Simplenote/Classes/FileManager+Simplenote.swift b/Simplenote/Classes/FileManager+Simplenote.swift index c99e7e6b0..782f685a7 100644 --- a/Simplenote/Classes/FileManager+Simplenote.swift +++ b/Simplenote/Classes/FileManager+Simplenote.swift @@ -20,8 +20,25 @@ extension FileManager { containerURL(forSecurityApplicationGroupIdentifier: SimplenoteConstants.sharedGroupDomain)! } - var recoveryDirectoryURL: URL { - sharedContainerURL.appendingPathComponent(Constants.recoveryDir) + 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 { diff --git a/Simplenote/RecoveryUnarchiver.swift b/Simplenote/RecoveryUnarchiver.swift index e1fb2433b..d7c4d84e0 100644 --- a/Simplenote/RecoveryUnarchiver.swift +++ b/Simplenote/RecoveryUnarchiver.swift @@ -13,20 +13,12 @@ public class RecoveryUnarchiver { SPAppDelegate.shared().simperium } - private func createRecoveryDirIfNeeded() { - guard !fileManager.directoryExistsAtURL(fileManager.recoveryDirectoryURL) else { - return - } - - try? fileManager.createDirectory(at: fileManager.recoveryDirectoryURL, withIntermediateDirectories: true) - } - // MARK: Restore // public func prepareRecoveredNoteContentIfNeeded() { - createRecoveryDirIfNeeded() - guard let recoveryFiles = try? fileManager.contentsOfDirectory(at: fileManager.recoveryDirectoryURL, includingPropertiesForKeys: nil), - !recoveryFiles.isEmpty else { + guard let recoveryURL = fileManager.recoveryDirectoryURL(), + let recoveryFiles = try? fileManager.contentsOfDirectory(at: recoveryURL, includingPropertiesForKeys: nil), + !recoveryFiles.isEmpty else { return } diff --git a/SimplenoteIntents/Tools/RecoveryArchiver.swift b/SimplenoteIntents/Tools/RecoveryArchiver.swift index 78e71a458..17f516c9c 100644 --- a/SimplenoteIntents/Tools/RecoveryArchiver.swift +++ b/SimplenoteIntents/Tools/RecoveryArchiver.swift @@ -8,29 +8,26 @@ public class RecoveryArchiver { self.fileManager = fileManager } - private func createRecoveryDirIfNeeded() { - guard !fileManager.directoryExistsAtURL(fileManager.recoveryDirectoryURL) else { - return - } - - try? fileManager.createDirectory(at: fileManager.recoveryDirectoryURL, withIntermediateDirectories: true) - } - // MARK: Archive // public func archiveContent(_ content: String) { - createRecoveryDirIfNeeded() + guard let fileURL = url(for: UUID().uuidString) else { + return + } guard let data = content.data(using: .utf8) else { return } - try? data.write(to: url(for: UUID().uuidString)) + try? data.write(to: fileURL) } - private func url(for identifier: String) -> URL { + 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 fileManager.recoveryDirectoryURL.appendingPathComponent(fileName, conformingTo: UTType.json) + return recoveryDirURL.appendingPathComponent(fileName, conformingTo: UTType.json) } } From 2d42e295a35ae82ee73dd97c56e443c891fe02ba Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 14:07:36 -0600 Subject: [PATCH 229/547] Recover files when app becomes active instead of on launch --- Simplenote/RecoveryUnarchiver.swift | 2 +- Simplenote/SPAppDelegate+Extensions.swift | 2 +- Simplenote/SPAppDelegate.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Simplenote/RecoveryUnarchiver.swift b/Simplenote/RecoveryUnarchiver.swift index d7c4d84e0..c6b40fb51 100644 --- a/Simplenote/RecoveryUnarchiver.swift +++ b/Simplenote/RecoveryUnarchiver.swift @@ -15,7 +15,7 @@ public class RecoveryUnarchiver { // MARK: Restore // - public func prepareRecoveredNoteContentIfNeeded() { + public func insertNotesFromRecoveryFilesIfNeeded() { guard let recoveryURL = fileManager.recoveryDirectoryURL(), let recoveryFiles = try? fileManager.contentsOfDirectory(at: recoveryURL, includingPropertiesForKeys: nil), !recoveryFiles.isEmpty else { diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index da9ba5da6..66ed67990 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -563,6 +563,6 @@ extension SPAppDelegate { extension SPAppDelegate { @objc func attemptContentRecoveryIfNeeded() { - RecoveryUnarchiver().prepareRecoveredNoteContentIfNeeded() + RecoveryUnarchiver().insertNotesFromRecoveryFilesIfNeeded() } } diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index 9537e48de..b83831128 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -167,7 +167,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [self setupNoticeController]; - [self attemptContentRecoveryIfNeeded]; return YES; } @@ -175,6 +174,7 @@ - (void)applicationDidBecomeActive:(UIApplication *)application { [SPTracker trackApplicationOpened]; [self syncWidgetDefaults]; + [self attemptContentRecoveryIfNeeded]; } - (void)applicationDidEnterBackground:(UIApplication *)application From b9da5bf632077007e0841e51057a8b4d34b808e1 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 14:29:23 -0600 Subject: [PATCH 230/547] Added information into shortcut errors showing recovery details --- SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift | 2 +- .../Intent Handlers/CreateNewNoteIntentHandler.swift | 2 +- SimplenoteIntents/IntentsConstants.swift | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 73b3a150b..02d8908f4 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -46,6 +46,6 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { private func handleFailure(with error: Error, content: String) -> AppendNoteIntentResponse { RecoveryArchiver().archiveContent(content) - return AppendNoteIntentResponse.failure(failureReason: error.localizedDescription) + return AppendNoteIntentResponse.failure(failureReason: "\(error.localizedDescription) - \(IntentsConstants.recoveryMessage)") } } diff --git a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift index ce5ec9078..c36b20a72 100644 --- a/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/CreateNewNoteIntentHandler.swift @@ -14,7 +14,7 @@ class CreateNewNoteIntentHandler: NSObject, CreateNewNoteIntentHandling { return CreateNewNoteIntentResponse(code: .success, userActivity: nil) } catch { RecoveryArchiver().archiveContent(content) - return CreateNewNoteIntentResponse.failure(failureReason: error.localizedDescription) + return CreateNewNoteIntentResponse.failure(failureReason: "\(error.localizedDescription) - \(IntentsConstants.recoveryMessage)") } } diff --git a/SimplenoteIntents/IntentsConstants.swift b/SimplenoteIntents/IntentsConstants.swift index 18e8bfdb8..f91def3a8 100644 --- a/SimplenoteIntents/IntentsConstants.swift +++ b/SimplenoteIntents/IntentsConstants.swift @@ -10,4 +10,6 @@ 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") } From a34a3195fed6110df2d1e2992fc09949e9a90340 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 17:02:24 -0600 Subject: [PATCH 231/547] Refactored append note intent handler to avoid multiple do catches --- .../AppendNoteIntentHandler.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 02d8908f4..97a7b15ac 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -28,20 +28,23 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { } do { - let existingContent = try await Downloader(simperiumToken: token).getNoteContent(for: identifier) ?? String() + let existingContent = try await downloadNoteContent(for: identifier, token: token) ?? String() note.content = existingContent + "\n\(content)" + + try await uploadNoteContent(note, token: token) } catch { return handleFailure(with: error, content: content) } + } - let uploader = Uploader(simperiumToken: token) + private func downloadNoteContent(for identifier: String, token: String) async throws -> String? { + let downloader = Downloader(simperiumToken: token) + return try await downloader.getNoteContent(for: identifier) + } - do { - _ = try await uploader.send(note) - return AppendNoteIntentResponse(code: .success, userActivity: nil) - } catch { - return handleFailure(with: error, content: content) - } + 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 { From 5fb295472898f6e7c4ed477fe0c4624f3f06cc1e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 17:03:13 -0600 Subject: [PATCH 232/547] Added missing return for append note intent handler --- SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift index 97a7b15ac..4a9350882 100644 --- a/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift +++ b/SimplenoteIntents/Intent Handlers/AppendNoteIntentHandler.swift @@ -32,6 +32,7 @@ class AppendNoteIntentHandler: NSObject, AppendNoteIntentHandling { note.content = existingContent + "\n\(content)" try await uploadNoteContent(note, token: token) + return AppendNoteIntentResponse(code: .success, userActivity: nil) } catch { return handleFailure(with: error, content: content) } From 23bd4e9a21c1813a1a12dbdea4341385575ae514 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 14 Jun 2024 17:04:35 -0600 Subject: [PATCH 233/547] Moved simperium for unarchiver to be stored as a property --- Simplenote/RecoveryUnarchiver.swift | 8 +++----- Simplenote/SPAppDelegate+Extensions.swift | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Simplenote/RecoveryUnarchiver.swift b/Simplenote/RecoveryUnarchiver.swift index c6b40fb51..0e67e8c46 100644 --- a/Simplenote/RecoveryUnarchiver.swift +++ b/Simplenote/RecoveryUnarchiver.swift @@ -4,13 +4,11 @@ import CoreData public class RecoveryUnarchiver { private let fileManager: FileManager + private let simperium: Simperium - public init(fileManager: FileManager = .default) { + public init(fileManager: FileManager = .default, simperium: Simperium) { self.fileManager = fileManager - } - - var simperium: Simperium { - SPAppDelegate.shared().simperium + self.simperium = simperium } // MARK: Restore diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 66ed67990..fd4430759 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -563,6 +563,6 @@ extension SPAppDelegate { extension SPAppDelegate { @objc func attemptContentRecoveryIfNeeded() { - RecoveryUnarchiver().insertNotesFromRecoveryFilesIfNeeded() + RecoveryUnarchiver(simperium: simperium).insertNotesFromRecoveryFilesIfNeeded() } } From 88676927ec3602632173cf202a9858192e60e332 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 18 Jun 2024 15:13:33 -0300 Subject: [PATCH 234/547] Fixes breaking Unit Tests --- Simplenote/Remote.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/Remote.swift b/Simplenote/Remote.swift index e06b4ead5..5c32883ea 100644 --- a/Simplenote/Remote.swift +++ b/Simplenote/Remote.swift @@ -14,7 +14,7 @@ class Remote { let dataTask = urlSession.dataTask(with: request) { (data, response, dataTaskError) in DispatchQueue.main.async { - if let response, let error = RemoteError(statusCode: response.responseStatusCode, error: dataTaskError) { + if let error = RemoteError(statusCode: response?.responseStatusCode ?? .zero, error: dataTaskError) { completion(.failure(error)) return } From 3709c897f096d1f019692fbbcc904099635230bc Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 26 Jun 2024 14:32:42 -0300 Subject: [PATCH 235/547] SimplenoteConstants: Updates Magic Link Endpoints --- Simplenote/Classes/SimplenoteConstants.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Simplenote/Classes/SimplenoteConstants.swift b/Simplenote/Classes/SimplenoteConstants.swift index 44ebabac8..7d1c734fd 100644 --- a/Simplenote/Classes/SimplenoteConstants.swift +++ b/Simplenote/Classes/SimplenoteConstants.swift @@ -45,9 +45,8 @@ class SimplenoteConstants: NSObject { /// static let resetPasswordURL = currentEngineBaseURL.appendingPathComponent("/reset/?redirect=simplenote://launch&email=") static let settingsURL = currentEngineBaseURL.appendingPathComponent("/settings") -/// TODO: FIXME - static let loginRequestURL = "https://magic-links-dot-simple-note-hrd.appspot.com/account/request-login" ////currentEngineBaseURL.appendingPathComponent("/account/request-login") - static let loginCompletionURL = "https://magic-links-dot-simple-note-hrd.appspot.com/account/complete-login" ////currentEngineBaseURL.appendingPathComponent("/account/complete-login") + 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/") From 59da15534e87271264540db8148dfd31cbed762c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 27 Jun 2024 14:44:44 -0300 Subject: [PATCH 236/547] MagicLinkConfirmationView: Updates copy --- Simplenote/MagicLinkConfirmationView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/MagicLinkConfirmationView.swift b/Simplenote/MagicLinkConfirmationView.swift index 5a4e951cf..b14dbb173 100644 --- a/Simplenote/MagicLinkConfirmationView.swift +++ b/Simplenote/MagicLinkConfirmationView.swift @@ -31,7 +31,7 @@ struct MagicLinkConfirmationView: View { Spacer() .frame(height: Metrics.titlePaddingBottom) - Text("If an account exists, we've sent an email to **\(email)** containing a link that'll log you in.") + 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) } From 40575b588dab22e24383763794a46b6932202b30 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 27 Jun 2024 15:24:15 -0300 Subject: [PATCH 237/547] Renames MagicLinkConfirmationView > MagicLinkRequestedView --- Simplenote.xcodeproj/project.pbxproj | 8 ++++---- Simplenote/Classes/SPAuthViewController.swift | 6 +++--- ...onfirmationView.swift => MagicLinkRequestedView.swift} | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) rename Simplenote/{MagicLinkConfirmationView.swift => MagicLinkRequestedView.swift} (92%) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 49cee357c..129029306 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -296,7 +296,7 @@ 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 /* MagicLinkConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A11C32C136225002AC8AA /* MagicLinkConfirmationView.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 */; }; B54D9C572909BA2600D0E0EC /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C562909BA2600D0E0EC /* StoreManager.swift */; }; B54D9C592909CC4400D0E0EC /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D9C582909CC4400D0E0EC /* StoreProduct.swift */; }; @@ -992,7 +992,7 @@ 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 /* MagicLinkConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkConfirmationView.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 = ""; }; 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 = ""; }; @@ -2077,9 +2077,9 @@ B5AB169722FA124F00B4EBA5 /* SPSheetController.swift */, B5AB169922FA128000B4EBA5 /* SPSheetController.xib */, A6CDD9C725F0163D00E0BC4D /* MagicLinkAuthenticator.swift */, + B54A11C32C136225002AC8AA /* MagicLinkRequestedView.swift */, A628BEB425ECD97900121B64 /* SignupVerificationViewController.swift */, A628BEB525ECD97900121B64 /* SignupVerificationViewController.xib */, - B54A11C32C136225002AC8AA /* MagicLinkConfirmationView.swift */, ); name = Onboarding; sourceTree = ""; @@ -3703,7 +3703,7 @@ B5CBEF4222D3B419009DBE67 /* Bundle+Simplenote.swift in Sources */, B575736C232D454300443C2E /* UIColor+Helpers.swift in Sources */, 37E55A6721BF2B1800F14241 /* SPTextAttachment.swift in Sources */, - B54A11C42C136225002AC8AA /* MagicLinkConfirmationView.swift in Sources */, + B54A11C42C136225002AC8AA /* MagicLinkRequestedView.swift in Sources */, BA8FC2A5267AC7470082962E /* SharedStorageMigrator.swift in Sources */, A69F850F253EC2B2005140F2 /* SPCardConfigurable.swift in Sources */, B5BE05541AB75C3B002417BF /* Settings.m in Sources */, diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index df535f45c..39e19309e 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -314,7 +314,7 @@ private extension SPAuthViewController { if let error { self.handleError(error: error) } else { - self.presentMagicLinkConfirmationView(email: email) + self.presentMagicLinkRequestedView(email: email) SPTracker.trackUserRequestedLoginLink() } @@ -368,8 +368,8 @@ private extension SPAuthViewController { navigationController?.pushViewController(viewController, animated: true) } - private func presentMagicLinkConfirmationView(email: String) { - let rootView = MagicLinkConfirmationView(email: email) + private func presentMagicLinkRequestedView(email: String) { + let rootView = MagicLinkRequestedView(email: email) let hostingController = UIHostingController(rootView: rootView) hostingController.modalPresentationStyle = .formSheet hostingController.sheetPresentationController?.detents = [.medium()] diff --git a/Simplenote/MagicLinkConfirmationView.swift b/Simplenote/MagicLinkRequestedView.swift similarity index 92% rename from Simplenote/MagicLinkConfirmationView.swift rename to Simplenote/MagicLinkRequestedView.swift index b14dbb173..d1afdbca6 100644 --- a/Simplenote/MagicLinkConfirmationView.swift +++ b/Simplenote/MagicLinkRequestedView.swift @@ -3,9 +3,9 @@ import SwiftUI import Gridicons -// MARK: - MagicLinkConfirmationView +// MARK: - MagicLinkRequestedView // -struct MagicLinkConfirmationView: View { +struct MagicLinkRequestedView: View { @Environment(\.presentationMode) var presentationMode @State private var displaysFullImage: Bool = false let email: String @@ -73,8 +73,8 @@ private enum MagicLinkImages { } -struct MagicLinkConfirmationView_Previews: PreviewProvider { +struct MagicLinkRequestedView_Previews: PreviewProvider { static var previews: some View { - MagicLinkConfirmationView(email: "lord@yosemite.com") + MagicLinkRequestedView(email: "lord@yosemite.com") } } From d0e26e2452717a1cf31a5794bf25ba4497170be5 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 27 Jun 2024 15:44:52 -0300 Subject: [PATCH 238/547] Implements MagicLinkInvalidView --- Simplenote.xcodeproj/project.pbxproj | 4 ++ Simplenote/MagicLinkInvalidView.swift | 61 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 Simplenote/MagicLinkInvalidView.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 129029306..ac6b13a17 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -327,6 +327,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 */; }; @@ -1036,6 +1037,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 = ""; }; @@ -2078,6 +2080,7 @@ B5AB169922FA128000B4EBA5 /* SPSheetController.xib */, A6CDD9C725F0163D00E0BC4D /* MagicLinkAuthenticator.swift */, B54A11C32C136225002AC8AA /* MagicLinkRequestedView.swift */, + B56BAF602C2DE317005065C9 /* MagicLinkInvalidView.swift */, A628BEB425ECD97900121B64 /* SignupVerificationViewController.swift */, A628BEB525ECD97900121B64 /* SignupVerificationViewController.xib */, ); @@ -3441,6 +3444,7 @@ 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 */, diff --git a/Simplenote/MagicLinkInvalidView.swift b/Simplenote/MagicLinkInvalidView.swift new file mode 100644 index 000000000..a686a4f9f --- /dev/null +++ b/Simplenote/MagicLinkInvalidView.swift @@ -0,0 +1,61 @@ +import Foundation +import SwiftUI +import Gridicons + + +// MARK: - MagicLinkConfirmationView +// +struct MagicLinkInvalidView: View { + @Environment(\.presentationMode) var presentationMode + + + var body: some View { + NavigationView { + VStack(alignment: .center, spacing: 10) { + Image(uiImage: MagicLinkImages.cross) + .renderingMode(.template) + .foregroundColor(Color(.simplenoteLightBlueColor)) + + Text("Invalid Link") + .bold() + .font(.system(size: Metrics.titleFontSize)) + .padding() + + Text("Your authentication link is no longer valid") + .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 detailsFontSize: CGFloat = 17 + static let crossIconSize = CGSize(width: 100, height: 100) + static let dismissSize = CGSize(width: 30, height: 30) +} + +private enum MagicLinkImages { + static let cross = Gridicon.iconOfType(.crossCircle, withSize: Metrics.crossIconSize) + static let dismiss = Gridicon.iconOfType(.crossCircle, withSize: Metrics.dismissSize) +} From 7443d84d27f9ad21386fe6a0092ea95c4bf75bf3 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 27 Jun 2024 15:45:01 -0300 Subject: [PATCH 239/547] MagicLinkAuthenticator: Posting Notification Events --- Simplenote/Classes/MagicLinkAuthenticator.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index ce3743ab7..6ce6b355c 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -1,5 +1,15 @@ import Foundation + +// 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 { @@ -48,7 +58,8 @@ private extension MagicLinkAuthenticator { } NSLog("[MagicLinkAuthenticator] Requesting SyncToken for \(authKey) and \(authCode)") - + NotificationCenter.default.post(name: .magicLinkAuthWillStart, object: nil) + Task { do { let remote = LoginRemote() @@ -57,11 +68,14 @@ private extension MagicLinkAuthenticator { 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.trackUserConfirmedLoginLink() } } catch { NSLog("[MagicLinkAuthenticator] Magic Link TokenExchange Error: \(error)") + NotificationCenter.default.post(name: .magicLinkAuthDidFail, object: error) } } From 487466f58f9663487742368e74dd0339a711141f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 27 Jun 2024 15:50:21 -0300 Subject: [PATCH 240/547] SPOnboardingViewController: Presenting Invalid Link UI --- .../Classes/SPOnboardingViewController.swift | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPOnboardingViewController.swift b/Simplenote/Classes/SPOnboardingViewController.swift index 008476bc2..797ff5780 100644 --- a/Simplenote/Classes/SPOnboardingViewController.swift +++ b/Simplenote/Classes/SPOnboardingViewController.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import SafariServices +import SwiftUI // MARK: - SPOnboardingViewController // @@ -196,8 +197,10 @@ 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) { @@ -210,9 +213,31 @@ 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 { + + private func presentMagicLinkInvalidView() { + let rootView = MagicLinkInvalidView() + let hostingController = UIHostingController(rootView: rootView) + hostingController.modalPresentationStyle = .formSheet + hostingController.sheetPresentationController?.detents = [.medium()] + + let presenter = presentedViewController ?? self + presenter.present(hostingController, animated: true) + } } + // MARK: - Private Types // private struct OnboardingStrings { From 1a2c0871e28636d59480b016aeb2aeb477f18be6 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 28 Jun 2024 13:37:09 -0300 Subject: [PATCH 241/547] MagicLinkInvalidView: Implements Request new Link Action --- Simplenote/Classes/SPAuthViewController.swift | 2 +- .../Classes/SPOnboardingViewController.swift | 20 ++++++++++- Simplenote/MagicLinkInvalidView.swift | 35 +++++++++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 39e19309e..e8af5e7d3 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -718,7 +718,7 @@ extension SPAuthViewController { // MARK: - AuthenticationMode: Signup / Login // -struct AuthenticationMode { +struct AuthenticationMode: Equatable { let title: String let validationStyle: AuthenticationValidator.Style let primaryActionSelector: Selector diff --git a/Simplenote/Classes/SPOnboardingViewController.swift b/Simplenote/Classes/SPOnboardingViewController.swift index 797ff5780..d0904949d 100644 --- a/Simplenote/Classes/SPOnboardingViewController.swift +++ b/Simplenote/Classes/SPOnboardingViewController.swift @@ -226,8 +226,14 @@ private extension SPOnboardingViewController { // private extension SPOnboardingViewController { + /// Presents the Invalid Magic Link UI + /// private func presentMagicLinkInvalidView() { - let rootView = MagicLinkInvalidView() + var rootView = MagicLinkInvalidView() + rootView.onPressRequestNewLink = { [weak self] in + self?.presentAuthenticationInterfaceIfNeeded(mode: .loginWithMagicLink) + } + let hostingController = UIHostingController(rootView: rootView) hostingController.modalPresentationStyle = .formSheet hostingController.sheetPresentationController?.detents = [.medium()] @@ -235,6 +241,18 @@ private extension SPOnboardingViewController { let presenter = presentedViewController ?? 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) { + if let authController = navigationController?.topViewController as? SPAuthViewController, authController.mode == mode { + return + } + + navigationController?.popToRootViewController(animated: true) + presentAuthenticationInterface(mode: mode) + } } diff --git a/Simplenote/MagicLinkInvalidView.swift b/Simplenote/MagicLinkInvalidView.swift index a686a4f9f..500ff462f 100644 --- a/Simplenote/MagicLinkInvalidView.swift +++ b/Simplenote/MagicLinkInvalidView.swift @@ -8,6 +8,8 @@ import Gridicons struct MagicLinkInvalidView: View { @Environment(\.presentationMode) var presentationMode + var onPressRequestNewLink: (() -> Void)? + var body: some View { NavigationView { @@ -24,6 +26,18 @@ struct MagicLinkInvalidView: View { Text("Your authentication link is no longer valid") .font(.system(size: Metrics.detailsFontSize)) .multilineTextAlignment(.center) + .padding(.bottom, Metrics.detailsPaddingBottom) + + Button(action: pressedRequestNewLink) { + Text("Request a new Link") + .fontWeight(.bold) + .foregroundStyle(.white) + } + .padding() + .background(Color(.simplenoteBlue50Color)) + .cornerRadius(Metrics.actionCornerRadius) + .buttonStyle(PlainButtonStyle()) + } .padding() .navigationBarTitleDisplayMode(.inline) @@ -44,18 +58,35 @@ struct MagicLinkInvalidView: View { /// 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 titleFontSize: CGFloat = 22 - static let detailsFontSize: CGFloat = 17 static let crossIconSize = CGSize(width: 100, height: 100) static let dismissSize = CGSize(width: 30, height: 30) + static let titleFontSize: CGFloat = 22 + static let detailsFontSize: CGFloat = 17 + static let detailsPaddingBottom: 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() + } +} From e7c55fc78de058a214b6a5013cabb962a4017032 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 28 Jun 2024 18:43:45 -0300 Subject: [PATCH 242/547] Entitlements: App Links --- Simplenote/Supporting Files/Simplenote-Internal.entitlements | 1 + Simplenote/Supporting Files/Simplenote.entitlements | 1 + 2 files changed, 2 insertions(+) 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 From 9aadac1178080c4815cc9dcc0f2f47488ee81371 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 1 Jul 2024 10:27:49 -0300 Subject: [PATCH 243/547] SPAppDelegate: Supporting Authentication User Activity --- Simplenote/Classes/MagicLinkAuthenticator.swift | 9 +++++---- Simplenote/SPAppDelegate+Extensions.swift | 14 ++++++++++++-- Simplenote/SPAppDelegate.m | 4 ++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 38c97012a..4f511f0f5 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -5,22 +5,23 @@ import Foundation struct MagicLinkAuthenticator { let authenticator: SPAuthenticator - func handle(url: URL) { + func handle(url: URL) -> Bool { guard url.host == Constants.host else { - return + return false } guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else { - return + return false } 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 } } diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index fd4430759..4e19255b3 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -447,10 +447,20 @@ 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 diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index b83831128..6d2979ba4 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -200,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]; } From 09aeb50a53c76d16eb749d2b9836ec575569e71e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 1 Jul 2024 12:13:50 -0300 Subject: [PATCH 244/547] MagicLinkAuthenticator: Allowing AppEngine URLs --- Simplenote/Classes/MagicLinkAuthenticator.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 4f511f0f5..b168ecf3b 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -6,7 +6,7 @@ struct MagicLinkAuthenticator { let authenticator: SPAuthenticator func handle(url: URL) -> Bool { - guard url.host == Constants.host else { + guard AllowedHosts.all.contains(url.host) else { return false } @@ -44,8 +44,13 @@ 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" } From 8cfaa215006112a14a93a07b33cc010883dfc26b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 1 Jul 2024 16:52:37 -0300 Subject: [PATCH 245/547] SPAppDelegate+Extensions: Drops whitespaces --- Simplenote/SPAppDelegate+Extensions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 4e19255b3..151fc1c60 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -452,13 +452,13 @@ extension SPAppDelegate { 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) } } From 812d62298603f0e2539d71ccac15f04b50d98320 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 1 Jul 2024 16:55:37 -0300 Subject: [PATCH 246/547] Updates Linter Rules --- .swiftlint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index cf03a5a91..0826295f6 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -42,9 +42,9 @@ only_rules: - trailing_semicolon # Lines should not have trailing whitespace. - - trailing_whitespace +# - trailing_whitespace - - vertical_whitespace +# - vertical_whitespace - custom_rules From 59f95421a065292f37ae41a4a975fbdb6d3b1668 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 1 Jul 2024 17:06:35 -0300 Subject: [PATCH 247/547] Fixes UI Tests --- SimplenoteUITests/EmailLogin.swift | 1 + SimplenoteUITests/UIDs.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/SimplenoteUITests/EmailLogin.swift b/SimplenoteUITests/EmailLogin.swift index 6fae2609a..22041a7f6 100644 --- a/SimplenoteUITests/EmailLogin.swift +++ b/SimplenoteUITests/EmailLogin.swift @@ -34,6 +34,7 @@ class EmailLogin { class func logIn(email: String, password: String) { enterEmail(enteredValue: email) + app.buttons[UID.Button.continueWithPassword].tap() enterPassword(enteredValue: password) app.buttons[UID.Button.logIn].tap() handleSavePasswordPrompt() diff --git a/SimplenoteUITests/UIDs.swift b/SimplenoteUITests/UIDs.swift index 65ae52e39..e47813a04 100644 --- a/SimplenoteUITests/UIDs.swift +++ b/SimplenoteUITests/UIDs.swift @@ -26,6 +26,7 @@ enum UID { static let menu = "menu" static let signUp = "Sign Up" static let logIn = "Log In" + static let continueWithPassword = "Continue with Password" static let logInWithEmail = "Log in with email" static let allNotes = "All Notes" static let trash = "Trash" From d5d9526a7e60a6f3be32216cf368cea84aeefa63 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 1 Jul 2024 18:13:22 -0300 Subject: [PATCH 248/547] Fixes UI Tests. Take II --- SimplenoteUITests/EmailLogin.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SimplenoteUITests/EmailLogin.swift b/SimplenoteUITests/EmailLogin.swift index 22041a7f6..9c3e3431b 100644 --- a/SimplenoteUITests/EmailLogin.swift +++ b/SimplenoteUITests/EmailLogin.swift @@ -35,6 +35,7 @@ class EmailLogin { class func logIn(email: String, password: String) { enterEmail(enteredValue: email) app.buttons[UID.Button.continueWithPassword].tap() + _ = app.buttons[UID.Button.logIn].waitForExistence(timeout: minLoadTimeout) enterPassword(enteredValue: password) app.buttons[UID.Button.logIn].tap() handleSavePasswordPrompt() From 2ab79c02731d5fe3aa31f9828ee6508017e5f085 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 11 Jul 2024 11:43:20 -0300 Subject: [PATCH 249/547] MagicLinkAuthenticator: Wires code based endpoints --- Simplenote/Classes/MagicLinkAuthenticator.swift | 9 ++++----- Simplenote/LoginRemote.swift | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 48962c7c8..6170983d7 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -50,20 +50,20 @@ private extension MagicLinkAuthenticator { @discardableResult func attemptLoginWithAuthCode(queryItems: [URLQueryItem]) -> Bool { - guard let authKey = queryItems.value(for: Constants.authKeyField), + guard let email = queryItems.value(for: Constants.emailField), let authCode = queryItems.value(for: Constants.authCodeField), - !authKey.isEmpty, !authCode.isEmpty + !email.isEmpty, !authCode.isEmpty else { return false } - NSLog("[MagicLinkAuthenticator] Requesting SyncToken for \(authKey) and \(authCode)") + NSLog("[MagicLinkAuthenticator] Requesting SyncToken for \(email) and \(authCode)") NotificationCenter.default.post(name: .magicLinkAuthWillStart, object: nil) Task { do { let remote = LoginRemote() - let confirmation = try await remote.requestLoginConfirmation(authKey: authKey, authCode: authCode) + let confirmation = try await remote.requestLoginConfirmation(email: email, authCode: authCode) Task { @MainActor in NSLog("[MagicLinkAuthenticator] Should auth with token \(confirmation.syncToken)") @@ -111,6 +111,5 @@ private struct AllowedHosts { private struct Constants { static let emailField = "email" static let tokenField = "token" - static let authKeyField = "auth_key" static let authCodeField = "auth_code" } diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift index 9b7fe181e..6d059f506 100644 --- a/Simplenote/LoginRemote.swift +++ b/Simplenote/LoginRemote.swift @@ -9,8 +9,8 @@ class LoginRemote: Remote { performDataTask(with: request, completion: completion) } - func requestLoginConfirmation(authKey: String, authCode: String) async throws -> LoginConfirmationResponse { - let request = requestForLoginCompletion(authKey: authKey, authCode: authCode) + func requestLoginConfirmation(email: String, authCode: String) async throws -> LoginConfirmationResponse { + let request = requestForLoginCompletion(email: email, authCode: authCode) return try await performDataTask(with: request, type: LoginConfirmationResponse.self) } } @@ -36,11 +36,11 @@ private extension LoginRemote { ]) } - func requestForLoginCompletion(authKey: String, authCode: String) -> URLRequest { + func requestForLoginCompletion(username: String, authCode: String) -> URLRequest { let url = URL(string: SimplenoteConstants.loginCompletionURL)! return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ "auth_key": authKey, - "auth_code": authCode + "username": username ]) } } From 2513a19869becf2e99ae02632a117fc38d6f6ee4 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 11 Jul 2024 11:43:54 -0300 Subject: [PATCH 250/547] SimplenoteConstants: Switching to Magic Links Staging --- Simplenote/Classes/SimplenoteConstants.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/SimplenoteConstants.swift b/Simplenote/Classes/SimplenoteConstants.swift index 7d1c734fd..63cca619e 100644 --- a/Simplenote/Classes/SimplenoteConstants.swift +++ b/Simplenote/Classes/SimplenoteConstants.swift @@ -45,8 +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 loginRequestURL = "https://magic-code-dot-simple-note-hrd.appspot.com/account/request-login" //googleAppEngineBaseURL.appendingPathComponent("/account/request-login") + static let loginCompletionURL = "https://magic-code-dot-simple-note-hrd.appspot.com/account/complete-login" //googleAppEngineBaseURL.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/") From 8d76ccd32fdb48e7600fc074f119dbf3b79aab91 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 11 Jul 2024 11:46:36 -0300 Subject: [PATCH 251/547] LoginRemote: Fixes incorrect constants --- Simplenote/LoginRemote.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift index 6d059f506..b69fce3f6 100644 --- a/Simplenote/LoginRemote.swift +++ b/Simplenote/LoginRemote.swift @@ -5,7 +5,7 @@ import Foundation class LoginRemote: Remote { func requestLoginEmail(email: String, completion: @escaping (_ result: Result) -> Void) { - let request = requestForLoginRequest(with: email) + let request = requestForLoginRequest(email: email) performDataTask(with: request, completion: completion) } @@ -28,7 +28,7 @@ struct LoginConfirmationResponse: Decodable { // private extension LoginRemote { - func requestForLoginRequest(with email: String) -> URLRequest { + func requestForLoginRequest(email: String) -> URLRequest { let url = URL(string: SimplenoteConstants.loginRequestURL)! return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ "request_source": SimplenoteConstants.simplenotePlatformName, @@ -36,11 +36,11 @@ private extension LoginRemote { ]) } - func requestForLoginCompletion(username: String, authCode: String) -> URLRequest { + func requestForLoginCompletion(email: String, authCode: String) -> URLRequest { let url = URL(string: SimplenoteConstants.loginCompletionURL)! return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ - "auth_key": authKey, - "username": username + "auth_code": authCode, + "username": email ]) } } From a9e74b69eadab8c78906a54d37589d7e6cd5289f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 11 Jul 2024 11:51:30 -0300 Subject: [PATCH 252/547] MagicLinkAuthenticator: Adds missing decode --- Simplenote/Classes/MagicLinkAuthenticator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 6170983d7..9edc05435 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -50,7 +50,7 @@ private extension MagicLinkAuthenticator { @discardableResult func attemptLoginWithAuthCode(queryItems: [URLQueryItem]) -> Bool { - guard let email = queryItems.value(for: Constants.emailField), + guard let email = queryItems.base64DecodedValue(for: Constants.emailField), let authCode = queryItems.value(for: Constants.authCodeField), !email.isEmpty, !authCode.isEmpty else { From 067a05404fade344d6ad438917de61f7a5130e8e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 11 Jul 2024 15:37:42 -0300 Subject: [PATCH 253/547] MagicLinkAuthenticator: Updates Events --- .../Classes/MagicLinkAuthenticator.swift | 3 ++- Simplenote/Classes/SPAuthViewController.swift | 2 +- Simplenote/Classes/SPTracker.h | 7 +++++-- Simplenote/Classes/SPTracker.m | 21 +++++++++++++------ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Simplenote/Classes/MagicLinkAuthenticator.swift b/Simplenote/Classes/MagicLinkAuthenticator.swift index 9edc05435..cc8cbcee4 100644 --- a/Simplenote/Classes/MagicLinkAuthenticator.swift +++ b/Simplenote/Classes/MagicLinkAuthenticator.swift @@ -70,12 +70,13 @@ private extension MagicLinkAuthenticator { authenticator.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) NotificationCenter.default.post(name: .magicLinkAuthDidSucceed, object: nil) - SPTracker.trackUserConfirmedLoginLink() + SPTracker.trackLoginLinkConfirmationSuccess() } } catch { NSLog("[MagicLinkAuthenticator] Magic Link TokenExchange Error: \(error)") NotificationCenter.default.post(name: .magicLinkAuthDidFail, object: error) + SPTracker.trackLoginLinkConfirmationFailure() } } diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index e8af5e7d3..db8c3c4e1 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -315,7 +315,7 @@ private extension SPAuthViewController { self.handleError(error: error) } else { self.presentMagicLinkRequestedView(email: email) - SPTracker.trackUserRequestedLoginLink() + SPTracker.trackLoginLinkRequested() } self.unlockInterface() diff --git a/Simplenote/Classes/SPTracker.h b/Simplenote/Classes/SPTracker.h index 8ce28db92..9bd867c26 100644 --- a/Simplenote/Classes/SPTracker.h +++ b/Simplenote/Classes/SPTracker.h @@ -81,10 +81,13 @@ #pragma mark - User + (void)trackUserAccountCreated; + (void)trackUserSignedIn; -+ (void)trackUserRequestedLoginLink; -+ (void)trackUserConfirmedLoginLink; + (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 0d24392b1..548e2b786 100644 --- a/Simplenote/Classes/SPTracker.m +++ b/Simplenote/Classes/SPTracker.m @@ -317,21 +317,30 @@ + (void)trackUserSignedIn [self trackAutomatticEventWithName:@"user_signed_in" properties:nil]; } -+ (void)trackUserRequestedLoginLink ++ (void)trackUserSignedOut { - [self trackAutomatticEventWithName:@"user_requested_login_link" properties:nil]; + [self trackAutomatticEventWithName:@"user_signed_out" properties:nil]; } -+ (void)trackUserConfirmedLoginLink + +#pragma mark - Login Links + ++ (void)trackLoginLinkRequested { - [self trackAutomatticEventWithName:@"user_confirmed_login_link" properties:nil]; + [self trackAutomatticEventWithName:@"login_link_requested" properties:nil]; } -+ (void)trackUserSignedOut ++ (void)trackLoginLinkConfirmationSuccess { - [self trackAutomatticEventWithName:@"user_signed_out" properties:nil]; + [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 From 6d779c0f22c2b80b4f71bc7996d0b5a0e26ed437 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 11 Jul 2024 15:37:51 -0300 Subject: [PATCH 254/547] MagicLinkInvalidView: Adjusting Copy --- Simplenote/MagicLinkInvalidView.swift | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Simplenote/MagicLinkInvalidView.swift b/Simplenote/MagicLinkInvalidView.swift index 500ff462f..dcb6faf81 100644 --- a/Simplenote/MagicLinkInvalidView.swift +++ b/Simplenote/MagicLinkInvalidView.swift @@ -18,15 +18,11 @@ struct MagicLinkInvalidView: View { .renderingMode(.template) .foregroundColor(Color(.simplenoteLightBlueColor)) - Text("Invalid Link") + Text("Link no longer valid") .bold() .font(.system(size: Metrics.titleFontSize)) - .padding() - - Text("Your authentication link is no longer valid") - .font(.system(size: Metrics.detailsFontSize)) .multilineTextAlignment(.center) - .padding(.bottom, Metrics.detailsPaddingBottom) + .padding(.bottom, Metrics.titlePaddingBottom) Button(action: pressedRequestNewLink) { Text("Request a new Link") @@ -71,9 +67,8 @@ struct MagicLinkInvalidView: View { private enum Metrics { static let crossIconSize = CGSize(width: 100, height: 100) static let dismissSize = CGSize(width: 30, height: 30) - static let titleFontSize: CGFloat = 22 - static let detailsFontSize: CGFloat = 17 - static let detailsPaddingBottom: CGFloat = 30 + static let titleFontSize: CGFloat = 20 + static let titlePaddingBottom: CGFloat = 30 static let actionCornerRadius: CGFloat = 10 } From b0ba41e2e3291ac89ad712b6cf85ee16855b7959 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:43:34 -0300 Subject: [PATCH 255/547] SPOnboardingViewController: Removes Sheet UI on Login --- .../Classes/SPOnboardingViewController.swift | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/Simplenote/Classes/SPOnboardingViewController.swift b/Simplenote/Classes/SPOnboardingViewController.swift index d0904949d..dc5ec7f87 100644 --- a/Simplenote/Classes/SPOnboardingViewController.swift +++ b/Simplenote/Classes/SPOnboardingViewController.swift @@ -149,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: .loginWithMagicLink) - } - - sheetController.onClickButton1 = { [weak self] in - self?.presentWordpressSSO() - } - - sheetController.present(from: self) + presentAuthenticationInterface(mode: .loginWithMagicLink) } @IBAction @@ -185,10 +172,6 @@ private extension SPOnboardingViewController { viewController.debugEnabled = debugEnabled navigationController?.pushViewController(viewController, animated: true) } - - func presentWordpressSSO() { - WPAuthHandler.presentWordPressSSO(from: self) - } } // MARK: - Actions @@ -246,10 +229,6 @@ private extension SPOnboardingViewController { /// - Note: Whenever the required AuthUI is already onscreen, we'll do nothing /// func presentAuthenticationInterfaceIfNeeded(mode: AuthenticationMode) { - if let authController = navigationController?.topViewController as? SPAuthViewController, authController.mode == mode { - return - } - navigationController?.popToRootViewController(animated: true) presentAuthenticationInterface(mode: mode) } @@ -265,8 +244,6 @@ 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 { From 982dd12d3fa2ee9693c33fb9cae1b142c25479a1 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:44:10 -0300 Subject: [PATCH 256/547] SPAuthViewController: Mode is static again --- Simplenote/Classes/SPAuthViewController.swift | 59 +------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index e8af5e7d3..629e68502 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -161,11 +161,7 @@ class SPAuthViewController: UIViewController { /// # Authentication Mode: Signup or Login /// - private(set) var mode: AuthenticationMode { - didSet { - refreshInterface(mode: mode) - } - } + private let mode: AuthenticationMode /// Indicates if the Extended Debug Mode is enabled /// @@ -662,59 +658,6 @@ extension SPAuthViewController: SPTextInputViewDelegate { } } -// MARK: - Mode Switching -// -extension SPAuthViewController { - - func refreshInterface(mode: AuthenticationMode, animated: Bool = true) { - title = mode.title - - dismissAllValidationWarnings() - - refreshPasswordInput(isHidden: mode.isPasswordHidden, animated: animated) - refreshPrimaryAction(mode: mode) - refreshSecondaryAction(mode: mode) - - /// Workaround: - /// When revealing the Password Field, iOS's Autofill mechanism may not properly fill the password field! - passwordInputView.becomeFirstResponder() - emailInputView.becomeFirstResponder() - } - - func refreshPasswordInput(isHidden: Bool, animated: Bool) { - let work = { - self.passwordInputView.isHidden = isHidden - } - - guard animated else { - work() - return - } - - UIView.animate(withDuration: UIKitConstants.animationDelayShort, animations: work) - } - - func refreshPrimaryAction(mode: AuthenticationMode) { - primaryActionButton.setTitleWithoutAnimation(mode.primaryActionText, for: .normal) - primaryActionButton.removeTarget(self, action: nil, for: .touchUpInside) - primaryActionButton.addTarget(self, action: mode.primaryActionSelector, for: .touchUpInside) - } - - func refreshSecondaryAction(mode: AuthenticationMode) { - if let title = mode.secondaryActionText { - secondaryActionButton.setTitleWithoutAnimation(title, for: .normal) - } - - if let attributedTitle = mode.secondaryActionAttributedText { - secondaryActionButton.setAttributedTitleWithoutAnimation(attributedTitle, for: .normal) - } - - secondaryActionButton.setTitleColor(.simplenoteBlue60Color, for: .normal) - secondaryActionButton.removeTarget(self, action: nil, for: .touchUpInside) - secondaryActionButton.addTarget(self, action: mode.secondaryActionSelector, for: .touchUpInside) - } -} - // MARK: - AuthenticationMode: Signup / Login // From f364ad321e9898133bcbadc7f65a8bcc96cc83b4 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:46:19 -0300 Subject: [PATCH 257/547] SPAuthViewController: Updates mode descriptors --- Simplenote/Classes/SPAuthViewController.swift | 93 +++++++++++++------ 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 629e68502..0553e541e 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -664,12 +664,18 @@ extension SPAuthViewController: SPTextInputViewDelegate { struct AuthenticationMode: Equatable { let title: String let validationStyle: AuthenticationValidator.Style + let isUsernameHidden: Bool + let isPasswordHidden: Bool + let primaryActionSelector: Selector let primaryActionText: String - let secondaryActionSelector: Selector + + let secondaryActionSelector: Selector? let secondaryActionText: String? let secondaryActionAttributedText: NSAttributedString? - let isPasswordHidden: Bool + + let tertiaryActionSelector: Selector? + let tertiaryActionText: String? } // MARK: - Default Operation Modes @@ -679,55 +685,56 @@ extension AuthenticationMode { /// Login Operation Mode: Contains all of the strings + delegate wirings, so that the AuthUI handles authentication scenarios. /// static var loginWithPassword: AuthenticationMode { - return .init(title: AuthenticationStrings.loginTitle, + return .init(title: PasswordStrings.title, validationStyle: .legacy, - primaryActionSelector: #selector(SPAuthViewController.performLogIn), - primaryActionText: AuthenticationStrings.loginPrimaryAction, + isUsernameHidden: true, + isPasswordHidden: false, + primaryActionSelector: #selector(SPAuthViewController.performLogInWithPassword), + primaryActionText: PasswordStrings.login, secondaryActionSelector: #selector(SPAuthViewController.presentPasswordReset), - secondaryActionText: AuthenticationStrings.loginSecondaryAction, + secondaryActionText: PasswordStrings.forgotPassword, secondaryActionAttributedText: nil, - isPasswordHidden: false) + tertiaryActionSelector: nil, + tertiaryActionText: nil) } /// Login Operation Mode: Authentication is handled via Magic Links! /// static var loginWithMagicLink: AuthenticationMode { - return .init(title: AuthenticationStrings.loginTitle, + return .init(title: MagicLinkStrings.title, validationStyle: .legacy, + isUsernameHidden: false, + isPasswordHidden: true, primaryActionSelector: #selector(SPAuthViewController.performLogInWithMagicLink), - primaryActionText: AuthenticationStrings.loginWithLinkPrimaryAction, - secondaryActionSelector: #selector(SPAuthViewController.switchToLoginWithPassword), - secondaryActionText: AuthenticationStrings.loginWithLinkSecondaryAction, + primaryActionText: MagicLinkStrings.loginWithEmail, + secondaryActionSelector: nil, + secondaryActionText: nil, secondaryActionAttributedText: nil, - isPasswordHidden: true) + tertiaryActionSelector: #selector(SPAuthViewController.performLogInWithWPCOM), + tertiaryActionText: MagicLinkStrings.loginWithWPCOM) } /// 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, + return .init(title: SignupStrings.title, validationStyle: .strong, + isUsernameHidden: false, + isPasswordHidden: true, primaryActionSelector: #selector(SPAuthViewController.performSignUp), - primaryActionText: AuthenticationStrings.signupPrimaryAction, + primaryActionText: SignupStrings.signup, secondaryActionSelector: #selector(SPAuthViewController.presentTermsOfService), secondaryActionText: nil, - secondaryActionAttributedText: AuthenticationStrings.signupSecondaryAttributedAction, - isPasswordHidden: true) + secondaryActionAttributedText: SignupStrings.termsOfService, + tertiaryActionSelector: nil, + tertiaryActionText: nil) } } + // 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 loginWithLinkPrimaryAction = NSLocalizedString("Instantly Log In with Email", comment: "LogIn with Magic Link Action") - static let loginWithLinkSecondaryAction = NSLocalizedString("Continue with Password", comment: "Password fallback 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 emailPlaceholder = NSLocalizedString("Email", comment: "Email TextField Placeholder") static let passwordPlaceholder = NSLocalizedString("Password", comment: "Password TextField Placeholder") static let acceptActionText = NSLocalizedString("Accept", comment: "Accept Action") @@ -758,20 +765,46 @@ private enum PasswordInsecureString { ].joined(separator: .newline) } -// MARK: - Strings >> Authenticated Strings Convenience Properties + +// MARK: - Mode: .loginWithPassword +// +private enum PasswordStrings { + static let title = NSLocalizedString("Log In", comment: "LogIn Interface Title") + static let login = NSLocalizedString("Log In", comment: "LogIn Action") + static let forgotPassword = NSLocalizedString("Forgot your password?", comment: "Password Reset Action") +} + + +// MARK: - Mode: .loginWithMagicLink // -private extension AuthenticationStrings { +private enum MagicLinkStrings { + static let title = NSLocalizedString("Log In", comment: "LogIn Interface Title") + static let loginWithEmail = NSLocalizedString("Log in with email", comment: "Sends the User an email with an Authentication Code") + static let loginWithWPCOM = NSLocalizedString("Log in with WordPress.com", comment: "Password fallback Action") +} + + +// MARK: - Mode: .signup +// +private enum SignupStrings { + static let title = NSLocalizedString("Sign Up", comment: "SignUp Interface Title") + static let signup = NSLocalizedString("Sign Up", comment: "SignUp Action") + static let termsOfServicePrefix = NSLocalizedString("By creating an account you agree to our", comment: "Terms of Service Legend *PREFIX*: printed in dark color") + static let termsOfServiceSuffix = NSLocalizedString("Terms and Conditions", comment: "Terms of Service Legend *SUFFIX*: Concatenated with a space, after the PREFIX, and printed in blue") +} + +private extension SignupStrings { /// Returns a properly formatted Secondary Action String for Signup /// - static var signupSecondaryAttributedAction: NSAttributedString { + static var termsOfService: NSAttributedString { let output = NSMutableAttributedString(string: String(), attributes: [ .font: UIFont.preferredFont(forTextStyle: .subheadline) ]) - output.append(string: signupSecondaryActionPrefix, foregroundColor: .simplenoteGray60Color) + output.append(string: termsOfServicePrefix, foregroundColor: .simplenoteGray60Color) output.append(string: " ") - output.append(string: signupSecondaryActionSuffix, foregroundColor: .simplenoteBlue60Color) + output.append(string: termsOfServiceSuffix, foregroundColor: .simplenoteBlue60Color) return output } From a80d7921378287b98dcbdbf7d8ecf18b04368387 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:46:35 -0300 Subject: [PATCH 258/547] SPAuthViewController: New Navigation Helper --- Simplenote/Classes/SPAuthViewController.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 0553e541e..0902321cb 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -357,20 +357,22 @@ private extension SPAuthViewController { safariViewController.modalPresentationStyle = .overFullScreen present(safariViewController, animated: true, completion: nil) } +} + - private func presentSignupVerification() { +// MARK: - Navigation Helpers +// +private extension SPAuthViewController { + + func presentSignupVerification() { let viewController = SignupVerificationViewController(email: email) viewController.title = title navigationController?.pushViewController(viewController, animated: true) } - private func presentMagicLinkRequestedView(email: String) { - let rootView = MagicLinkRequestedView(email: email) - let hostingController = UIHostingController(rootView: rootView) - hostingController.modalPresentationStyle = .formSheet - hostingController.sheetPresentationController?.detents = [.medium()] - - present(hostingController, animated: true) + func presentPasswordInterface() { + let viewController = SPAuthViewController(controller: controller, mode: .loginWithPassword) + navigationController?.pushViewController(viewController, animated: true) } } From c9b82e6877af90c7edaca96ead7824574fc4f175 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:46:42 -0300 Subject: [PATCH 259/547] SPAuthViewController: Drops dead API --- Simplenote/Classes/SPAuthViewController.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 0902321cb..8b47a7d2e 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -340,10 +340,6 @@ private extension SPAuthViewController { } } - @IBAction func switchToLoginWithPassword() { - mode = .loginWithPassword - } - @IBAction func presentPasswordReset() { controller.presentPasswordReset(from: self, username: email) } From 66f37140f71423060054e63b282eb4292db03027 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:46:55 -0300 Subject: [PATCH 260/547] SPAuthViewController: Adds WPCOM SSO --- Simplenote/Classes/SPAuthViewController.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 8b47a7d2e..1e313243e 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -318,6 +318,10 @@ private extension SPAuthViewController { } } + @IBAction func performLogInWithWPCOM() { + WPAuthHandler.presentWordPressSSO(from: self) + } + @IBAction func performSignUp() { guard ensureWarningsAreOnScreenWhenNeeded() else { return From 77ca14704e575ed997016ade7ce7b98aebb478ad Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:47:09 -0300 Subject: [PATCH 261/547] SPAuthViewController: Updates tertiaryAction Management --- Simplenote/Classes/SPAuthViewController.swift | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 1e313243e..f9ef1704a 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -100,7 +100,10 @@ class SPAuthViewController: UIViewController { secondaryActionButton.titleLabel?.textAlignment = .center secondaryActionButton.titleLabel?.numberOfLines = 0 - secondaryActionButton.addTarget(self, action: mode.secondaryActionSelector, for: .touchUpInside) + + if let action = mode.secondaryActionSelector { + secondaryActionButton.addTarget(self, action: action, for: .touchUpInside) + } } } @@ -119,6 +122,29 @@ class SPAuthViewController: UIViewController { return button }() + /// # Tertiary Action: Container View + /// + @IBOutlet private var tertiaryActionContainerView: UIView! { + didSet { + tertiaryActionContainerView.isHidden = mode.tertiaryActionSelector == nil + } + } + + /// # Tertiary Action: WPCOM SSO + /// + @IBOutlet private var tertiaryActionButton: SPSquaredButton! { + didSet { + guard let title = mode.tertiaryActionText, let action = mode.tertiaryActionSelector else { + return + } + + tertiaryActionButton.setTitle(title, for: .normal) + tertiaryActionButton.setTitleColor(.white, for: .normal) + tertiaryActionButton.backgroundColor = .simplenoteWPBlue50Color + tertiaryActionButton.addTarget(self, action: action, for: .touchUpInside) + } + } + /// # Simperium's Authenticator Instance /// private let controller: SPAuthHandler From ec93dc6a5376102895427617b130cf76b7f9fe00 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:47:27 -0300 Subject: [PATCH 262/547] SPAuthViewController: Enhancing default first responder management --- Simplenote/Classes/SPAuthViewController.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index f9ef1704a..43f121758 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -214,6 +214,7 @@ class SPAuthViewController: UIViewController { setupNavigationController() startListeningToNotifications() + emailInputView.isHidden = mode.isUsernameHidden passwordInputView.isHidden = mode.isPasswordHidden // hiding text from back button @@ -232,7 +233,9 @@ 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() + // + let initialFirstResponder = mode.isPasswordHidden ? emailInputView : passwordInputView + initialFirstResponder?.becomeFirstResponder() } } From fce0c2f416d96046b7015353c79ef3407da7092d Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:47:35 -0300 Subject: [PATCH 263/547] SPAuthViewController: Renames API --- Simplenote/Classes/SPAuthViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 43f121758..1dad9591f 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -313,8 +313,8 @@ private extension SPAuthViewController { perform(mode.primaryActionSelector) } - - @IBAction func performLogIn() { + + @IBAction func performLogInWithPassword() { guard ensureWarningsAreOnScreenWhenNeeded() else { return } From ac25839fbe98312942108c128c2ee5586ddb6ff8 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 15:47:42 -0300 Subject: [PATCH 264/547] SPAuthViewController: Updates nib --- Simplenote/Classes/SPAuthViewController.xib | 66 ++++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.xib b/Simplenote/Classes/SPAuthViewController.xib index ac1bcbb84..4fddaadda 100644 --- a/Simplenote/Classes/SPAuthViewController.xib +++ b/Simplenote/Classes/SPAuthViewController.xib @@ -1,10 +1,11 @@ - + - + + @@ -19,6 +20,8 @@ + + @@ -28,7 +31,7 @@ - + @@ -43,7 +46,7 @@ @@ -59,13 +62,13 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -138,7 +181,7 @@ - + @@ -160,10 +203,10 @@ - + - + From 1812b5babc531a376bd5a1351c1ffd5f5cba7ad0 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 18:59:42 -0300 Subject: [PATCH 270/547] Fixes UI Tests --- SimplenoteUITests/EmailLogin.swift | 11 +++++++---- SimplenoteUITests/SimplenoteUITestsLogin.swift | 15 ++------------- SimplenoteUITests/UIDs.swift | 2 +- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/SimplenoteUITests/EmailLogin.swift b/SimplenoteUITests/EmailLogin.swift index 9c3e3431b..c3ea46142 100644 --- a/SimplenoteUITests/EmailLogin.swift +++ b/SimplenoteUITests/EmailLogin.swift @@ -5,8 +5,6 @@ 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() { @@ -34,13 +32,18 @@ class EmailLogin { class func logIn(email: String, password: String) { enterEmail(enteredValue: email) - app.buttons[UID.Button.continueWithPassword].tap() + app.buttons[UID.Button.logInWithEmail].tap() _ = 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/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 e47813a04..3e6bece97 100644 --- a/SimplenoteUITests/UIDs.swift +++ b/SimplenoteUITests/UIDs.swift @@ -26,7 +26,7 @@ enum UID { static let menu = "menu" static let signUp = "Sign Up" static let logIn = "Log In" - static let continueWithPassword = "Continue with Password" + static let mainAction = "Main Action" static let logInWithEmail = "Log in with email" static let allNotes = "All Notes" static let trash = "Trash" From 2d1dccc9c063818c511ecdf43e9c9c1814fe6a57 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 19:01:17 -0300 Subject: [PATCH 271/547] SPAuthViewController: Wires Mail Auth by default --- Simplenote/Classes/SPAuthViewController.swift | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 4dcb9d539..f64bf84b8 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -73,6 +73,7 @@ class SPAuthViewController: UIViewController { primaryActionButton.setTitle(mode.primaryActionText, for: .normal) primaryActionButton.setTitleColor(.white, for: .normal) primaryActionButton.addTarget(self, action: mode.primaryActionSelector, for: .touchUpInside) + primaryActionButton.accessibilityIdentifier = "Main Action" } } @@ -361,19 +362,24 @@ private extension SPAuthViewController { return } - lockdownInterface() - - let email = self.email - controller.requestLoginEmail(username: email) { error in - if let error { - self.handleError(error: error) - } else { - self.presentMagicLinkRequestedView(email: email) - SPTracker.trackUserRequestedLoginLink() - } - - self.unlockInterface() - } + presentPasswordInterface() + +// TODO: Restore Mail + Code Auth Flow +// lockdownInterface() +// +// let email = self.email +// controller.requestLoginEmail(username: email) { error in +// // TODO: 429 (Rate Limited)? push PW auth instead +// self.presentPasswordInterface() +// +// if let error { +// self.handleError(error: error) +// } else { +// SPTracker.trackUserRequestedLoginLink() +// } +// +// self.unlockInterface() +// } } @IBAction func performLogInWithWPCOM() { From 5e88edb219941f95714a441fa60ccc1bf17de7cd Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 12 Jul 2024 19:55:59 -0300 Subject: [PATCH 272/547] Fixes UI Tests. Take II --- SimplenoteUITests/EmailLogin.swift | 15 ++++++++++++--- SimplenoteUITests/UIDs.swift | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/SimplenoteUITests/EmailLogin.swift b/SimplenoteUITests/EmailLogin.swift index c3ea46142..0a5615393 100644 --- a/SimplenoteUITests/EmailLogin.swift +++ b/SimplenoteUITests/EmailLogin.swift @@ -8,10 +8,19 @@ class EmailLogin { } class func close() { - let backButton = app.navigationBars[UID.NavBar.logIn].buttons[UID.Button.back] - guard backButton.isHittable else { return } + /// Back from Password > Email UI + let backFromPasswordUI = app.navigationBars[UID.NavBar.logInWithPassword].buttons.element(boundBy: 0) + if backFromPasswordUI.exists { + backFromPasswordUI.tap() + _ = app.navigationBars[UID.NavBar.logIn].waitForExistence(timeout: minLoadTimeout) + } + + /// Back from Email UI > Onboarding + let backButton = app.navigationBars[UID.NavBar.logIn].buttons.element(boundBy: 0) + if backButton.isHittable { + backButton.tap() + } - backButton.tap() handleSavePasswordPrompt() } diff --git a/SimplenoteUITests/UIDs.swift b/SimplenoteUITests/UIDs.swift index 3e6bece97..4075fc887 100644 --- a/SimplenoteUITests/UIDs.swift +++ b/SimplenoteUITests/UIDs.swift @@ -11,6 +11,7 @@ enum UID { enum NavBar { static let allNotes = "All Notes" static let logIn = "Log In" + static let logInWithPassword = "Log In with Password" static let noteEditorPreview = "Preview" static let noteEditorOptions = "Options" static let trash = "Trash" From 05869f28ff9e819459f2e25ae4fb79f83ebd8e26 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 09:48:50 -0300 Subject: [PATCH 273/547] Updates Release Notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f544d30d6..ed8a89755 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -4,6 +4,7 @@ - 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 ----- From c77d3d8cae55d1927da0f361cf73c49bb1ca4c15 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:07:28 -0300 Subject: [PATCH 274/547] SPAuthHandler: New loginWithCode API --- Simplenote/Classes/SPAuthHandler.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPAuthHandler.swift b/Simplenote/Classes/SPAuthHandler.swift index 0562286c6..899b30b54 100644 --- a/Simplenote/Classes/SPAuthHandler.swift +++ b/Simplenote/Classes/SPAuthHandler.swift @@ -53,7 +53,7 @@ class SPAuthHandler { }) } - /// Requests an Authentication Magic Link + /// Requests an Authentication Email /// func requestLoginEmail(username: String, onCompletion: @escaping (SPAuthError?) -> Void) { let remote = LoginRemote() @@ -67,6 +67,20 @@ class SPAuthHandler { } } + /// 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 error as RemoteError { + throw authenticationError(for: error) + } + } + /// Registers a new user in the Simperium Backend. /// /// - Note: Errors are mapped into SPAuthError Instances From bb77cf543ed68e9f71b65cf07da4142dd4edff6b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:07:59 -0300 Subject: [PATCH 275/547] SPAuthViewController: CodeInputView --- Simplenote/Classes/SPAuthViewController.swift | 13 +++ Simplenote/Classes/SPAuthViewController.xib | 81 ++++++++++++------- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index f64bf84b8..0ab20b421 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -65,6 +65,19 @@ class SPAuthViewController: UIViewController { passwordWarningLabel.isHidden = true } } + + /// # Password: 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 + } + } /// # Primary Action: LogIn / SignUp /// diff --git a/Simplenote/Classes/SPAuthViewController.xib b/Simplenote/Classes/SPAuthViewController.xib index 92d384f0a..8b0aa0d55 100644 --- a/Simplenote/Classes/SPAuthViewController.xib +++ b/Simplenote/Classes/SPAuthViewController.xib @@ -11,19 +11,20 @@ + + + + - - - @@ -33,9 +34,9 @@ - + - + @@ -51,7 +52,7 @@ - + @@ -67,8 +68,18 @@ - + + + + + + + + + + + - - + + @@ -137,38 +148,42 @@ - - - - - - + + + + + + - + + - @@ -185,6 +200,9 @@ + + + @@ -202,11 +220,14 @@ + + + - + - + From c6b06aff1efb62b7610d661cda35d357ab58cf8a Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:08:43 -0300 Subject: [PATCH 276/547] Updates AuthenticationMode Descriptors --- Simplenote/Classes/SPAuthViewController.swift | 141 ++++++++++++------ 1 file changed, 95 insertions(+), 46 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 0ab20b421..7b53c61c9 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -752,26 +752,41 @@ extension SPAuthViewController: SPTextInputViewDelegate { struct AuthenticationState { var username = String() var password = String() + var code = String() } +struct AuthenticationElements: OptionSet { + let rawValue: UInt + + static let username = AuthenticationElements(rawValue: 1 << 0) + static let password = AuthenticationElements(rawValue: 1 << 1) + static let code = AuthenticationElements(rawValue: 1 << 2) + static let actionSeparator = AuthenticationElements(rawValue: 1 << 7) +} + +enum AuthenticationActionName { + case primary + case secondary + case tertiary + case quaternary +} + +struct AuthenticationAction { + let name: AuthenticationActionName + let selector: Selector + let text: String? + let attributedText: NSAttributedString? +} + + // MARK: - AuthenticationMode: Signup / Login // -struct AuthenticationMode: Equatable { +struct AuthenticationMode { let title: String let validationStyle: AuthenticationValidator.Style - let isUsernameHidden: Bool - let isPasswordHidden: Bool - - let primaryActionSelector: Selector - let primaryActionText: String - - let secondaryActionSelector: Selector? - let secondaryActionText: String? - let secondaryActionAttributedText: NSAttributedString? - - let tertiaryActionSelector: Selector? - let tertiaryActionText: String? + let visibleElements: AuthenticationElements + let actions: [AuthenticationAction] } // MARK: - Default Operation Modes @@ -783,31 +798,53 @@ extension AuthenticationMode { static var loginWithPassword: AuthenticationMode { return .init(title: PasswordStrings.title, validationStyle: .legacy, - isUsernameHidden: true, - isPasswordHidden: false, - primaryActionSelector: #selector(SPAuthViewController.performLogInWithPassword), - primaryActionText: PasswordStrings.login, - secondaryActionSelector: #selector(SPAuthViewController.presentPasswordReset), - secondaryActionText: PasswordStrings.forgotPassword, - secondaryActionAttributedText: nil, - tertiaryActionSelector: nil, - tertiaryActionText: nil) + visibleElements: [.password], + actions: [ + AuthenticationAction(name: .primary, + selector: #selector(SPAuthViewController.performLogInWithPassword), + text: PasswordStrings.login, + attributedText: nil), + AuthenticationAction(name: .secondary, + selector: #selector(SPAuthViewController.presentPasswordReset), + text: PasswordStrings.forgotPassword, + attributedText: nil) + ]) + } + + /// Login Operation Mode: Request Login Code + /// + static var requestLoginCode: AuthenticationMode { + return .init(title: RequestCodeStrings.title, + validationStyle: .legacy, + visibleElements: [.username, .actionSeparator], + actions: [ + AuthenticationAction(name: .primary, + selector: #selector(SPAuthViewController.requestLogInCode), + text: RequestCodeStrings.loginWithEmail, + attributedText: nil), + AuthenticationAction(name: .tertiary, + selector: #selector(SPAuthViewController.performLogInWithWPCOM), + text: RequestCodeStrings.loginWithWPCOM, + attributedText: nil), + ]) } - - /// Login Operation Mode: Authentication is handled via Magic Links! + + /// Login Operation Mode: Submit Code + Authenticate the user /// - static var loginWithMagicLink: AuthenticationMode { - return .init(title: MagicLinkStrings.title, + static var loginWithCode: AuthenticationMode { + return .init(title: LoginWithCodeStrings.title, validationStyle: .legacy, - isUsernameHidden: false, - isPasswordHidden: true, - primaryActionSelector: #selector(SPAuthViewController.performLogInWithMagicLink), - primaryActionText: MagicLinkStrings.loginWithEmail, - secondaryActionSelector: nil, - secondaryActionText: nil, - secondaryActionAttributedText: nil, - tertiaryActionSelector: #selector(SPAuthViewController.performLogInWithWPCOM), - tertiaryActionText: MagicLinkStrings.loginWithWPCOM) + visibleElements: [.code, .actionSeparator], + actions: [ + AuthenticationAction(name: .primary, + selector: #selector(SPAuthViewController.performLogInWithCode), + text: LoginWithCodeStrings.login, + attributedText: nil), + AuthenticationAction(name: .quaternary, + selector: #selector(SPAuthViewController.presentPasswordInterface), + text: LoginWithCodeStrings.enterPassword, + attributedText: nil), + ]) } /// Signup Operation Mode: Contains all of the strings + delegate wirings, so that the AuthUI handles user account creation scenarios. @@ -815,15 +852,17 @@ extension AuthenticationMode { static var signup: AuthenticationMode { return .init(title: SignupStrings.title, validationStyle: .strong, - isUsernameHidden: false, - isPasswordHidden: true, - primaryActionSelector: #selector(SPAuthViewController.performSignUp), - primaryActionText: SignupStrings.signup, - secondaryActionSelector: #selector(SPAuthViewController.presentTermsOfService), - secondaryActionText: nil, - secondaryActionAttributedText: SignupStrings.termsOfService, - tertiaryActionSelector: nil, - tertiaryActionText: nil) + visibleElements: [.username], + actions: [ + AuthenticationAction(name: .primary, + selector: #selector(SPAuthViewController.performSignUp), + text: SignupStrings.signup, + attributedText: nil), + AuthenticationAction(name: .secondary, + selector: #selector(SPAuthViewController.presentTermsOfService), + text: nil, + attributedText: SignupStrings.termsOfService) + ]) } } @@ -834,6 +873,7 @@ private enum AuthenticationStrings { 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") @@ -872,15 +912,24 @@ private enum PasswordStrings { } -// MARK: - Mode: .loginWithMagicLink +// MARK: - Mode: .requestLoginCode // -private enum MagicLinkStrings { +private enum RequestCodeStrings { static let title = NSLocalizedString("Log In", comment: "LogIn Interface Title") static let loginWithEmail = NSLocalizedString("Log in with email", comment: "Sends the User an email with an Authentication Code") static let loginWithWPCOM = NSLocalizedString("Log in with WordPress.com", comment: "Password fallback Action") } +// MARK: - Mode: .code +// +private enum LoginWithCodeStrings { + static let title = NSLocalizedString("Enter Code", comment: "LogIn Interface Title") + static let login = NSLocalizedString("Log In", comment: "LogIn Interface Title") + static let enterPassword = NSLocalizedString("Enter password", comment: "Enter Password fallback Action") +} + + // MARK: - Mode: .signup // private enum SignupStrings { From c14736c9834c9e77ff82cbc2d1c609211bc6f537 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:09:13 -0300 Subject: [PATCH 277/547] SPAuthViewController: Updates UI Refresh Logic --- Simplenote/Classes/SPAuthViewController.swift | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 7b53c61c9..a0bb76169 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -244,8 +244,9 @@ class SPAuthViewController: UIViewController { setupNavigationController() startListeningToNotifications() - refreshActions() - refreshInputViews() + refreshVisibleElements() + refreshActionViews() + reloadInputViewsFromState() // hiding text from back button navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) @@ -300,17 +301,50 @@ private extension SPAuthViewController { navigationController?.setNavigationBarHidden(false, animated: true) } - func refreshActions() { - secondaryActionButton.isHidden = mode.secondaryActionSelector == nil - tertiaryActionButton.isHidden = mode.tertiaryActionSelector == nil + func refreshVisibleElements() { + let visibleElements = mode.visibleElements + + emailInputView.isHidden = !visibleElements.contains(.username) + passwordInputView.isHidden = !visibleElements.contains(.password) + codeInputView.isHidden = !visibleElements.contains(.code) + actionsSeparator.isHidden = !visibleElements.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) + } - func refreshInputViews() { + actionView.addTarget(self, action: descriptor.selector, for: .touchUpInside) + actionView.isHidden = false + } + } + + func reloadInputViewsFromState() { emailInputView.text = state.username passwordInputView.text = state.password - - emailInputView.isHidden = mode.isUsernameHidden - passwordInputView.isHidden = mode.isPasswordHidden + codeInputView.text = state.code } func ensureStylesMatchValidationState() { From d6965ab8f6c7452602f2ba8f28f5baa6c740821c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:09:26 -0300 Subject: [PATCH 278/547] SPAuthViewController: Fixes Main Action Handler --- Simplenote/Classes/SPAuthViewController.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index a0bb76169..4a9513c15 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -387,8 +387,13 @@ private extension SPAuthViewController { guard isInputValid else { return } + + guard let primaryActionDescriptor = mode.actions.first(where: { $0.name == .primary}) else { + assertionFailure() + return + } - perform(mode.primaryActionSelector) + perform(primaryActionDescriptor.selector) } @IBAction func performLogInWithPassword() { From 436af4afa1484e50bab0468ece6b9efb6e00ce43 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:09:41 -0300 Subject: [PATCH 279/547] SPAuthViewController: Wires requestLoginCode Mode --- Simplenote/Classes/SPAuthViewController.swift | 2 +- Simplenote/Classes/SPOnboardingViewController.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 4a9513c15..14e50a11f 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -230,7 +230,7 @@ class SPAuthViewController: UIViewController { /// Designated Initializer /// - init(controller: SPAuthHandler, mode: AuthenticationMode = .loginWithMagicLink, state: AuthenticationState = .init()) { + init(controller: SPAuthHandler, mode: AuthenticationMode = .requestLoginCode, state: AuthenticationState = .init()) { self.controller = controller self.mode = mode self.state = state diff --git a/Simplenote/Classes/SPOnboardingViewController.swift b/Simplenote/Classes/SPOnboardingViewController.swift index dc5ec7f87..f8d9094e4 100644 --- a/Simplenote/Classes/SPOnboardingViewController.swift +++ b/Simplenote/Classes/SPOnboardingViewController.swift @@ -149,7 +149,7 @@ private extension SPOnboardingViewController { @IBAction func loginWasPressed() { - presentAuthenticationInterface(mode: .loginWithMagicLink) + presentAuthenticationInterface(mode: .requestLoginCode) } @IBAction @@ -214,7 +214,7 @@ private extension SPOnboardingViewController { private func presentMagicLinkInvalidView() { var rootView = MagicLinkInvalidView() rootView.onPressRequestNewLink = { [weak self] in - self?.presentAuthenticationInterfaceIfNeeded(mode: .loginWithMagicLink) + self?.presentAuthenticationInterfaceIfNeeded(mode: .requestLoginCode) } let hostingController = UIHostingController(rootView: rootView) From b085df8ee51b7b2576f6306108f64bedec4dacbb Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:10:00 -0300 Subject: [PATCH 280/547] SPAuthViewController: Drops code duplication --- Simplenote/Classes/SPAuthViewController.swift | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 14e50a11f..efebec3d4 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -83,9 +83,7 @@ class SPAuthViewController: UIViewController { /// @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" } } @@ -103,21 +101,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 - - if let action = mode.secondaryActionSelector { - secondaryActionButton.addTarget(self, action: action, for: .touchUpInside) - } } } From 27e17c5aaf81d5a0d3d9150f49eaa32b4b33a6dc Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:10:24 -0300 Subject: [PATCH 281/547] SPAuthViewController: Updates validation --- Simplenote/Classes/SPAuthViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index efebec3d4..0aea3ee19 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -672,7 +672,7 @@ private extension SPAuthViewController { private extension SPAuthViewController { func performUsernameValidation() -> AuthenticationValidator.Result { - if mode.isUsernameHidden { + guard mode.visibleElements.contains(.username) else { return .success } @@ -683,7 +683,7 @@ private extension SPAuthViewController { /// That's where the `validationStyle` comes in. /// func performPasswordValidation() -> AuthenticationValidator.Result { - if mode.isPasswordHidden { + guard mode.visibleElements.contains(.password) else { return .success } From fdd12ab31fe3c8177fff6b1717d18cfd28a0b4aa Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:11:07 -0300 Subject: [PATCH 282/547] SPAuthViewController: New calculated properties --- Simplenote/Classes/SPAuthViewController.swift | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 0aea3ee19..f501096fc 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -122,44 +122,51 @@ class SPAuthViewController: UIViewController { return button }() - /// # Tertiary Separator: COntainer + /// # Actions Separator: Container /// - @IBOutlet private var tertiaryTopSeparator: UIView! { + @IBOutlet private var actionsSeparator: UIView! + + /// # Tertiary Separator: Label (Or) + /// + @IBOutlet private var actionsSeparatorLabel: UILabel! { didSet { - tertiaryTopSeparator.isHidden = mode.tertiaryActionSelector == nil + actionsSeparatorLabel.text = AuthenticationStrings.separatorText } } - /// # Tertiary Separator: Label (Or) + /// # Tertiary Action: WPCOM SSO /// - @IBOutlet private var tertiaryTopSeparatorLabel: UILabel! { + @IBOutlet private var tertiaryActionButton: SPSquaredButton! { didSet { - tertiaryTopSeparatorLabel.text = AuthenticationStrings.separatorText + tertiaryActionButton.setTitleColor(.white, for: .normal) + tertiaryActionButton.backgroundColor = .simplenoteWPBlue50Color } } - /// # Tertiary Action: Container View + /// # Tertiary Action: /// - @IBOutlet private var tertiaryActionContainerView: UIView! { + @IBOutlet private var quaternaryActionButton: SPSquaredButton! { didSet { - tertiaryActionContainerView.isHidden = mode.tertiaryActionSelector == nil + quaternaryActionButton.setTitleColor(.black, for: .normal) + quaternaryActionButton.backgroundColor = .clear + quaternaryActionButton.layer.borderWidth = 1 + quaternaryActionButton.layer.borderColor = UIColor.black.cgColor } } /// # Tertiary Action: WPCOM SSO /// - @IBOutlet private var tertiaryActionButton: SPSquaredButton! { - didSet { - guard let title = mode.tertiaryActionText, let action = mode.tertiaryActionSelector else { - return - } - - tertiaryActionButton.setTitle(title, for: .normal) - tertiaryActionButton.setTitleColor(.white, for: .normal) - tertiaryActionButton.backgroundColor = .simplenoteWPBlue50Color - tertiaryActionButton.addTarget(self, action: action, for: .touchUpInside) + 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 /// From b5b9aa4651c3ee56d67e2b7b39bc15680a457a86 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:11:25 -0300 Subject: [PATCH 283/547] SPAuthViewController: Relocates presentPasswordInterface API --- Simplenote/Classes/SPAuthViewController.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index f501096fc..1401fb67d 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -466,6 +466,11 @@ private extension SPAuthViewController { safariViewController.modalPresentationStyle = .overFullScreen present(safariViewController, animated: true, completion: nil) } + + @IBAction func presentPasswordInterface() { + let viewController = SPAuthViewController(controller: controller, mode: .loginWithPassword, state: state) + navigationController?.pushViewController(viewController, animated: true) + } } @@ -478,9 +483,9 @@ private extension SPAuthViewController { viewController.title = title navigationController?.pushViewController(viewController, animated: true) } - - func presentPasswordInterface() { - let viewController = SPAuthViewController(controller: controller, mode: .loginWithPassword, state: state) + + func presentCodeInterface() { + let viewController = SPAuthViewController(controller: controller, mode: .loginWithCode, state: state) navigationController?.pushViewController(viewController, animated: true) } } @@ -741,6 +746,8 @@ extension SPAuthViewController: SPTextInputViewDelegate { state.username = textInput.text ?? "" case passwordInputView: state.password = textInput.text ?? "" + case codeInputView: + state.code = textInput.text ?? "" default: break } @@ -751,10 +758,10 @@ extension SPAuthViewController: SPTextInputViewDelegate { case emailInputView: switch performUsernameValidation() { case .success: - if mode.isPasswordHidden { - performPrimaryActionIfPossible() - } else { + if mode.visibleElements.contains(.password) { passwordInputView.becomeFirstResponder() + } else { + performPrimaryActionIfPossible() } case let error: From 6972b66f53b3a85a38f6cc2437039b040959df05 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:11:51 -0300 Subject: [PATCH 284/547] SPAuthViewController: Implements loginWithCode --- Simplenote/Classes/SPAuthViewController.swift | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 1401fb67d..c6dc650f5 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -402,29 +402,47 @@ private extension SPAuthViewController { performSimperiumAuthentication() } - @IBAction func performLogInWithMagicLink() { + @IBAction func requestLogInCode() { guard ensureWarningsAreOnScreenWhenNeeded() else { return } + + lockdownInterface() + + controller.requestLoginEmail(username: email) { error in +// TODO: 429 (Rate Limited)? push PW auth instead - presentPasswordInterface() + if let error { + self.handleError(error: error) + } else { + self.presentCodeInterface() + SPTracker.trackUserRequestedLoginLink() + } + + self.unlockInterface() + } + } + + @IBAction func performLogInWithCode() { + Task { @MainActor in + await performLogInWithCodeInTask() + } + } + + @MainActor + private func performLogInWithCodeInTask() async { + lockdownInterface() -// TODO: Restore Mail + Code Auth Flow -// lockdownInterface() -// -// let email = self.email -// controller.requestLoginEmail(username: email) { error in -// // TODO: 429 (Rate Limited)? push PW auth instead -// self.presentPasswordInterface() -// -// if let error { -// self.handleError(error: error) -// } else { -// SPTracker.trackUserRequestedLoginLink() -// } -// -// self.unlockInterface() -// } + do { + try await controller.loginWithCode(username: state.username, code: state.code) + SPTracker.trackUserConfirmedLoginLink() + } catch let error as SPAuthError { + self.handleError(error: error) + } catch { +// TODO: Fixme + } + + unlockInterface() } @IBAction func performLogInWithWPCOM() { From 0b4ef0c8e2f31abbf86475ed03158ee0cbb54685 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:12:01 -0300 Subject: [PATCH 285/547] SPAuthViewController: Fixes firstResponder Mechanism --- Simplenote/Classes/SPAuthViewController.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index c6dc650f5..1abc091b2 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -258,8 +258,7 @@ class SPAuthViewController: UIViewController { // repositioning in the Text Field. Seriously. // Ref. https://github.com/Automattic/simplenote-ios/issues/453 // - let initialFirstResponder = mode.isPasswordHidden ? emailInputView : passwordInputView - initialFirstResponder?.becomeFirstResponder() + visibleInputViews.first?.becomeFirstResponder() } } From d2c187e57b7611da7d2911ed9a3f4bd5202d2895 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:12:10 -0300 Subject: [PATCH 286/547] SPAuthViewController: Updates Comment --- Simplenote/Classes/SPAuthViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index 1abc091b2..a11490ff3 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -154,7 +154,7 @@ class SPAuthViewController: UIViewController { } } - /// # Tertiary Action: WPCOM SSO + /// # All of the Visible InputView(s) /// private var visibleInputViews: [SPTextInputView] { [emailInputView, passwordInputView, codeInputView].filter { inputView in From ca0c764b937b20412c5b00267ce28d1d16056fed Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 15 Jul 2024 14:54:31 -0300 Subject: [PATCH 287/547] SPAuthViewController: Implements Code Validation --- Simplenote/AuthenticationValidator.swift | 15 +++++ Simplenote/Classes/SPAuthViewController.swift | 56 ++++++++++++++++++- Simplenote/Classes/SPAuthViewController.xib | 22 ++++++-- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/Simplenote/AuthenticationValidator.swift b/Simplenote/AuthenticationValidator.swift index b922fd899..c9466bc93 100644 --- a/Simplenote/AuthenticationValidator.swift +++ b/Simplenote/AuthenticationValidator.swift @@ -11,6 +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 /// @@ -48,6 +52,14 @@ 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 @@ -65,6 +77,7 @@ extension AuthenticationValidator { case passwordMatchesUsername case passwordTooShort(length: UInt) case passwordContainsInvalidCharacter + case codeTooShort } } @@ -91,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/Classes/SPAuthViewController.swift b/Simplenote/Classes/SPAuthViewController.swift index a11490ff3..e7f78fd91 100644 --- a/Simplenote/Classes/SPAuthViewController.swift +++ b/Simplenote/Classes/SPAuthViewController.swift @@ -66,7 +66,7 @@ class SPAuthViewController: UIViewController { } } - /// # Password: Input Field + /// # Code: Input Field /// @IBOutlet private var codeInputView: SPTextInputView! { didSet { @@ -78,6 +78,16 @@ class SPAuthViewController: UIViewController { 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 /// @@ -179,7 +189,7 @@ 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 + return performUsernameValidation() == .success && performPasswordValidation() == .success && performCodeValidation() == .success } /// # Returns the EmailInputView's Text: When empty this getter returns an empty string, instead of nil @@ -671,6 +681,11 @@ private extension SPAuthViewController { passwordWarningLabel.text = string refreshPasswordInput(inErrorState: true) } + + func displayCodeValidationWarning(_ string: String) { + codeWarningLabel.text = string + refreshCodeInput(inErrorState: true) + } func dismissAllValidationWarnings() { refreshEmailInput(inErrorState: false) @@ -684,6 +699,10 @@ private extension SPAuthViewController { func dismissPasswordValidationWarning() { refreshPasswordInput(inErrorState: false) } + + func dismissCodeValidationWarning() { + refreshCodeInput(inErrorState: false) + } func refreshEmailInput(inErrorState: Bool) { emailWarningLabel.animateVisibility(isHidden: !inErrorState) @@ -694,6 +713,11 @@ private extension SPAuthViewController { passwordWarningLabel.animateVisibility(isHidden: !inErrorState) passwordInputView.inErrorState = inErrorState } + + func refreshCodeInput(inErrorState: Bool) { + codeWarningLabel.animateVisibility(isHidden: !inErrorState) + codeInputView.inErrorState = inErrorState + } } // MARK: - Validation @@ -718,6 +742,14 @@ private extension SPAuthViewController { return validator.performPasswordValidation(username: email, password: password, style: mode.validationStyle) } + + func performCodeValidation() -> AuthenticationValidator.Result { + guard mode.visibleElements.contains(.code) else { + return .success + } + + return validator.performCodeValidation(code: state.code) + } /// Whenever we're in `.login` mode, and the password is valid in `.legacy` terms (but invalid in `.strong` mode), we must request the /// user to reset the password associated to his/her account. @@ -729,6 +761,7 @@ private extension SPAuthViewController { func ensureWarningsAreOnScreenWhenNeeded() -> Bool { let usernameValidationResult = performUsernameValidation() let passwordValidationResult = performPasswordValidation() + let codeValidationResult = performCodeValidation() if usernameValidationResult != .success { displayEmailValidationWarning(usernameValidationResult.description) @@ -737,8 +770,12 @@ private extension SPAuthViewController { if passwordValidationResult != .success { displayPasswordValidationWarning(passwordValidationResult.description) } + + if codeValidationResult != .success { + displayCodeValidationWarning(codeValidationResult.description) + } - return usernameValidationResult == .success && passwordValidationResult == .success + return usernameValidationResult == .success && passwordValidationResult == .success && codeValidationResult == .success } func ensureWarningsAreDismissedWhenNeeded() { @@ -749,6 +786,10 @@ private extension SPAuthViewController { if performPasswordValidation() == .success { dismissPasswordValidationWarning() } + + if performCodeValidation() == .success { + dismissCodeValidationWarning() + } } } @@ -794,6 +835,15 @@ extension SPAuthViewController: SPTextInputViewDelegate { displayPasswordValidationWarning(error.description) } + case codeInputView: + switch performCodeValidation() { + case .success: + performPrimaryActionIfPossible() + + case let error: + displayCodeValidationWarning(error.description) + } + default: break } diff --git a/Simplenote/Classes/SPAuthViewController.xib b/Simplenote/Classes/SPAuthViewController.xib index 8b0aa0d55..4febb6038 100644 --- a/Simplenote/Classes/SPAuthViewController.xib +++ b/Simplenote/Classes/SPAuthViewController.xib @@ -14,6 +14,7 @@ + @@ -34,7 +35,7 @@ - + @@ -78,8 +79,14 @@ + - + - + @@ -149,7 +156,7 @@ - + @@ -156,7 +178,7 @@