diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..5a9268b93 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,124 @@ +defaults: &defaults + macos: + xcode: "9.0" + shell: /bin/bash --login -eo pipefail +aliases: + - &prepare + | + git submodule update --init --recursive + gem install bundler + bundle install + - &filter-only-master + branches: + only: + - master + +version: 2 +jobs: + ios: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: bundle exec rake test:ios + - run: bash <(curl -s https://codecov.io/bash) + - store_test_results: + path: build/reports + macos: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: bundle exec rake test:macos + - run: bash <(curl -s https://codecov.io/bash) + - store_test_results: + path: build/reports + facebook_utils: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: bundle exec rake test:facebook_utils:ios + - run: bash <(curl -s https://codecov.io/bash) + - store_test_results: + path: build/reports + twitter_utils: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: bundle exec rake test:twitter_utils:ios + - run: bash <(curl -s https://codecov.io/bash) + - store_test_results: + path: build/reports + parseui: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: bundle exec rake test:parseui:all + deployment: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: | + xcrun simctl create "Apple TV 1080p" com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p com.apple.CoreSimulator.SimRuntime.tvOS-11-0 + bundle exec rake test:deployment + jazzy: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: ./Scripts/jazzy.sh + carthage: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: bundle exec rake test:carthage + cocoapods: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: bundle exec rake test:cocoapods + publish-cocoapods: + <<: *defaults + steps: + - checkout + - run: *prepare + - run: ./Scripts/publish.sh + +workflows: + version: 2 + pr: + jobs: + - ios + - macos + - facebook_utils + - twitter_utils + - parseui + - jazzy + nightly: + jobs: + - deployment + - cocoapods: + requires: + - deployment + - carthage: + requires: + - deployment + triggers: + - schedule: + cron: "0 1 * * *" + filters: *filter-only-master + publish: + jobs: + - hold: + type: approval + filters: *filter-only-master + - publish-cocoapods: + requires: + - hold + filters: *filter-only-master diff --git a/.codecov.yml b/.codecov.yml index c61725b35..1b84209f7 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,8 +1,10 @@ coverage: ignore: - - Tests/.* + - Parse/Tests/.* + - ParseTwitterUtils/Tests/.* + - ParseFacebookUtils/Tests/.* status: - patch: false + patch: true changes: false project: default: diff --git a/.gitignore b/.gitignore index e6b0f1991..e5b047c51 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ docs/ ## AppCode .idea/ +ParseFacebookUtils/Vendor diff --git a/.ruby-version b/.ruby-version index 2bf1c1ccf..81af5fe54 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.1 +ruby-2.3 diff --git a/.travis.yml b/.travis.yml index 5272391e4..a8d2e58c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,30 +11,12 @@ git: before_install: - git submodule update --init --recursive -stage: test -env: - global: - - LC_CTYPE=en_US.UTF-8 - - LANG=en_US.UTF-8 - matrix: - - TEST_TYPE=iOS - - TEST_TYPE=macOS -install: bundle install -script: -- | - RAKE_TASK_NAME=$(echo "$TEST_TYPE" | tr '[:upper:]' '[:lower:]') - bundle exec rake test:$RAKE_TASK_NAME -after_success: bash <(curl -s https://codecov.io/bash) - jobs: include: - stage: release env: Deployment install: bundle install script: - # Create a simulator that match current SDK + name - - xcrun simctl create "Apple TV 1080p" com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p com.apple.CoreSimulator.SimRuntime.tvOS-11-2 - - bundle exec rake test:deployment - ./Scripts/jazzy.sh deploy: - provider: releases @@ -55,7 +37,7 @@ jobs: - stage: release env: Cocoapods install: bundle install - script: bundle exec rake test:cocoapods + script: skip deploy: provider: script skip_cleanup: true @@ -63,10 +45,3 @@ jobs: on: tags: true all_branches: true - - stage: release - env: Carthage - install: - - bundle install - - brew update - - brew install carthage || brew upgrade carthage - script: bundle exec rake test:carthage diff --git a/Configurations/Shared b/Configurations/Shared deleted file mode 120000 index 818aee708..000000000 --- a/Configurations/Shared +++ /dev/null @@ -1 +0,0 @@ -../Vendor/xctoolchain/Configurations/ \ No newline at end of file diff --git a/Parse.podspec b/Parse.podspec index f2da8af71..00eb76fcf 100644 --- a/Parse.podspec +++ b/Parse.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Parse' - s.version = '1.15.4' + s.version = '1.16.0' s.license = { :type => 'BSD', :file => 'LICENSE' } s.homepage = 'http://parseplatform.org/' s.summary = 'A library that gives you access to the powerful Parse cloud platform from your iOS/OS X/watchOS/tvOS app.' @@ -15,76 +15,157 @@ Pod::Spec.new do |s| s.tvos.deployment_target = '9.0' s.watchos.deployment_target = '2.0' - s.requires_arc = true + s.default_subspec = 'Core' + + s.subspec 'Core' do |s| + s.requires_arc = true - s.source_files = 'Parse/*.{h,m}', - 'Parse/Internal/**/*.{h,m}' - s.public_header_files = 'Parse/*.h' + s.source_files = 'Parse/Parse/*.{h,m}', + 'Parse/Parse/Internal/**/*.{h,m}' + s.public_header_files = 'Parse/Parse/*.h' + + s.ios.exclude_files = 'Parse/Parse/Internal/PFMemoryEventuallyQueue.{h,m}' + s.osx.exclude_files = 'Parse/Parse/PFNetworkActivityIndicatorManager.{h,m}', + 'Parse/Parse/PFProduct.{h,m}', + 'Parse/Parse/PFPurchase.{h,m}', + 'Parse/Parse/Internal/PFAlertView.{h,m}', + 'Parse/Parse/Internal/Product/**/*.{h,m}', + 'Parse/Parse/Internal/Purchase/**/*.{h,m}', + 'Parse/Parse/Internal/PFMemoryEventuallyQueue.{h,m}' + s.tvos.exclude_files = 'Parse/Parse/PFNetworkActivityIndicatorManager.{h,m}', + 'Parse/Parse/PFPush.{h,m}', + 'Parse/Parse/PFPush+Synchronous.{h,m}', + 'Parse/Parse/PFPush+Deprecated.{h,m}', + 'Parse/Parse/PFInstallation.{h,m}', + 'Parse/Parse/Internal/PFAlertView.{h,m}', + 'Parse/Parse/Internal/Push/**/*.{h,m}', + 'Parse/Parse/Internal/Installation/Controller/*.{h,m}', + 'Parse/Parse/Internal/Installation/Constants/*.{h,m}', + 'Parse/Parse/Internal/Installation/CurrentInstallationController/*.{h,m}', + 'Parse/Parse/Internal/Installation/PFInstallationPrivate.h', + 'Parse/Parse/Internal/Commands/PFRESTPushCommand.{h,m}' + s.watchos.exclude_files = 'Parse/Parse/PFNetworkActivityIndicatorManager.{h,m}', + 'Parse/Parse/PFProduct.{h,m}', + 'Parse/Parse/PFPurchase.{h,m}', + 'Parse/Parse/PFPush.{h,m}', + 'Parse/Parse/PFPush+Synchronous.{h,m}', + 'Parse/Parse/PFPush+Deprecated.{h,m}', + 'Parse/Parse/PFInstallation.{h,m}', + 'Parse/Parse/Internal/PFAlertView.{h,m}', + 'Parse/Parse/Internal/PFReachability.{h,m}', + 'Parse/Parse/Internal/Product/**/*.{h,m}', + 'Parse/Parse/Internal/Purchase/**/*.{h,m}', + 'Parse/Parse/Internal/Push/**/*.{h,m}', + 'Parse/Parse/Internal/Installation/Controller/*.{h,m}', + 'Parse/Parse/Internal/Installation/Constants/*.{h,m}', + 'Parse/Parse/Internal/Installation/CurrentInstallationController/*.{h,m}', + 'Parse/Parse/Internal/Installation/PFInstallationPrivate.h', + 'Parse/Parse/Internal/Commands/PFRESTPushCommand.{h,m}', + 'Parse/Parse/Internal/PFMemoryEventuallyQueue.{h,m}' + + s.resource_bundle = { 'Parse' => 'Parse/Parse/Resources/en.lproj' } + + s.ios.frameworks = 'AudioToolbox', + 'CFNetwork', + 'CoreGraphics', + 'CoreLocation', + 'QuartzCore', + 'Security', + 'StoreKit', + 'SystemConfiguration' + s.ios.weak_frameworks = 'Accounts', + 'Social' + s.osx.frameworks = 'ApplicationServices', + 'CFNetwork', + 'CoreGraphics', + 'CoreLocation', + 'QuartzCore', + 'Security', + 'SystemConfiguration' + s.tvos.frameworks = 'CoreLocation', + 'StoreKit', + 'SystemConfiguration', + 'Security' + + s.libraries = 'z', 'sqlite3' + + s.dependency 'Bolts/Tasks', '~> 1.9' + end - s.ios.exclude_files = 'Parse/Internal/PFMemoryEventuallyQueue.{h,m}' - s.osx.exclude_files = 'Parse/PFNetworkActivityIndicatorManager.{h,m}', - 'Parse/PFProduct.{h,m}', - 'Parse/PFPurchase.{h,m}', - 'Parse/Internal/PFAlertView.{h,m}', - 'Parse/Internal/Product/**/*.{h,m}', - 'Parse/Internal/Purchase/**/*.{h,m}', - 'Parse/Internal/PFMemoryEventuallyQueue.{h,m}' - s.tvos.exclude_files = 'Parse/PFNetworkActivityIndicatorManager.{h,m}', - 'Parse/PFPush.{h,m}', - 'Parse/PFPush+Synchronous.{h,m}', - 'Parse/PFPush+Deprecated.{h,m}', - 'Parse/PFInstallation.{h,m}', - 'Parse/Internal/PFAlertView.{h,m}', - 'Parse/Internal/Push/**/*.{h,m}', - 'Parse/Internal/Installation/Controller/*.{h,m}', - 'Parse/Internal/Installation/Constants/*.{h,m}', - 'Parse/Internal/Installation/CurrentInstallationController/*.{h,m}', - 'Parse/Internal/Installation/PFInstallationPrivate.h', - 'Parse/Internal/Commands/PFRESTPushCommand.{h,m}' - s.watchos.exclude_files = 'Parse/PFNetworkActivityIndicatorManager.{h,m}', - 'Parse/PFProduct.{h,m}', - 'Parse/PFPurchase.{h,m}', - 'Parse/PFPush.{h,m}', - 'Parse/PFPush+Synchronous.{h,m}', - 'Parse/PFPush+Deprecated.{h,m}', - 'Parse/PFInstallation.{h,m}', - 'Parse/Internal/PFAlertView.{h,m}', - 'Parse/Internal/PFReachability.{h,m}', - 'Parse/Internal/Product/**/*.{h,m}', - 'Parse/Internal/Purchase/**/*.{h,m}', - 'Parse/Internal/Push/**/*.{h,m}', - 'Parse/Internal/Installation/Controller/*.{h,m}', - 'Parse/Internal/Installation/Constants/*.{h,m}', - 'Parse/Internal/Installation/CurrentInstallationController/*.{h,m}', - 'Parse/Internal/Installation/PFInstallationPrivate.h', - 'Parse/Internal/Commands/PFRESTPushCommand.{h,m}', - 'Parse/Internal/PFMemoryEventuallyQueue.{h,m}' + s.subspec 'FacebookUtils' do |s| + s.platform = :ios, :tvos + s.ios.deployment_target = '9.0' + s.tvos.deployment_target = '9.0' + s.public_header_files = 'ParseFacebookUtils/ParseFacebookUtils/*.h' + s.source_files = 'ParseFacebookUtils/ParseFacebookUtils/**/*.{h,m}' + s.exclude_files = 'ParseFacebookUtils/ParseFacebookUtils/ParseFacebookUtilsV4.h' + s.ios.exclude_files = 'ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/tvOS/**/*.{h,m}' + s.tvos.exclude_files = 'ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/**/*.{h,m}' + + s.frameworks = 'AudioToolbox', + 'CFNetwork', + 'CoreGraphics', + 'CoreLocation', + 'QuartzCore', + 'Security', + 'SystemConfiguration' + s.ios.weak_frameworks = 'Accounts', + 'Social' + s.libraries = 'z', 'sqlite3' - s.resources = 'Parse/Resources/en.lproj' + s.dependency 'Parse/Core' + s.dependency 'FBSDKCoreKit', '~> 4.28.0' + s.ios.dependency 'FBSDKLoginKit', '~> 4.28.0' + s.tvos.dependency 'FBSDKTVOSKit', '~> 4.28.0' + s.tvos.dependency 'FBSDKShareKit', '~> 4.28.0' + end - s.ios.frameworks = 'AudioToolbox', - 'CFNetwork', - 'CoreGraphics', - 'CoreLocation', - 'QuartzCore', - 'Security', - 'StoreKit', - 'SystemConfiguration' - s.ios.weak_frameworks = 'Accounts', - 'Social' - s.osx.frameworks = 'ApplicationServices', - 'CFNetwork', - 'CoreGraphics', - 'CoreLocation', - 'QuartzCore', - 'Security', - 'SystemConfiguration' - s.tvos.frameworks = 'CoreLocation', - 'StoreKit', - 'SystemConfiguration', - 'Security' + s.subspec 'TwitterUtils' do |s| + s.platform = :ios + s.public_header_files = 'ParseTwitterUtils/ParseTwitterUtils/*.h' + s.source_files = 'ParseTwitterUtils/ParseTwitterUtils/**/*.{h,m}' + s.exclude_files = 'ParseTwitterUtils/ParseTwitterUtils/ParseTwitterUtils.h' + s.resource_bundle = { 'TwitterUtils' => 'ParseTwitterUtils/Resources/en.lproj' } + s.frameworks = 'AudioToolbox', + 'CFNetwork', + 'CoreGraphics', + 'CoreLocation', + 'QuartzCore', + 'Security', + 'StoreKit', + 'SystemConfiguration' + s.weak_frameworks = 'Accounts', + 'Social' + s.libraries = 'z', 'sqlite3' + s.dependency 'Parse/Core' + end - s.libraries = 'z', 'sqlite3' + s.subspec 'UI' do |s| + s.platform = :ios + s.requires_arc = true + s.ios.deployment_target = '9.0' + s.source_files = "ParseUI/**/*.{h,m}" + s.exclude_files = 'ParseUI/ParseUIDemo/**/*', 'ParseUI/Other/ParseUI.h' + s.public_header_files = 'ParseUI/Classes/LogInViewController/*.h', + 'ParseUI/Classes/SignUpViewController/*.h', + 'ParseUI/Classes/QueryTableViewController/*.h', + 'ParseUI/Classes/QueryCollectionViewController/*.h', + 'ParseUI/Classes/ProductTableViewController/*.h', + 'ParseUI/Classes/Views/*.h', + 'ParseUI/Classes/Cells/*.h', + 'ParseUI/Other/*.h' + s.resource_bundles = { 'ParseUI' => ['ParseUI/Resources/Localization/*.lproj'] } + s.frameworks = 'Foundation', + 'UIKit', + 'CoreGraphics', + 'QuartzCore' + s.dependency 'Parse/Core' + end - s.dependency 'Bolts/Tasks', '~> 1.9' + # prepare command for parseUI + s.prepare_command = <<-CMD + ruby ParseUI/Scripts/convert_images.rb \ + ParseUI/Resources/Images/ \ + ParseUI/Generated/PFResources + CMD end diff --git a/Parse.xcworkspace/contents.xcworkspacedata b/Parse.xcworkspace/contents.xcworkspacedata index 696df7871..b8678d197 100644 --- a/Parse.xcworkspace/contents.xcworkspacedata +++ b/Parse.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,16 @@ + location = "group:Parse/Parse.xcodeproj"> + + + + + + CFBundlePackageType FMWK CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 diff --git a/Parse/Resources/Parse-iOS.Info.plist b/Parse/Parse/Resources/Parse-iOS.Info.plist similarity index 94% rename from Parse/Resources/Parse-iOS.Info.plist rename to Parse/Parse/Resources/Parse-iOS.Info.plist index 741b5486a..f8a0bda0a 100644 --- a/Parse/Resources/Parse-iOS.Info.plist +++ b/Parse/Parse/Resources/Parse-iOS.Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -22,7 +22,7 @@ iPhoneOS CFBundleVersion - 1.15.4 + 1.16.0 MinimumOSVersion 6.0 diff --git a/Parse/Resources/Parse-tvOS.Info.plist b/Parse/Parse/Resources/Parse-tvOS.Info.plist similarity index 93% rename from Parse/Resources/Parse-tvOS.Info.plist rename to Parse/Parse/Resources/Parse-tvOS.Info.plist index 80666f44c..1dee6e1de 100644 --- a/Parse/Resources/Parse-tvOS.Info.plist +++ b/Parse/Parse/Resources/Parse-tvOS.Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 NSPrincipalClass diff --git a/Parse/Resources/Parse-watchOS.Info.plist b/Parse/Parse/Resources/Parse-watchOS.Info.plist similarity index 93% rename from Parse/Resources/Parse-watchOS.Info.plist rename to Parse/Parse/Resources/Parse-watchOS.Info.plist index 80666f44c..1dee6e1de 100644 --- a/Parse/Resources/Parse-watchOS.Info.plist +++ b/Parse/Parse/Resources/Parse-watchOS.Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 NSPrincipalClass diff --git a/Parse/Resources/en.lproj/Parse.strings b/Parse/Parse/Resources/en.lproj/Parse.strings similarity index 100% rename from Parse/Resources/en.lproj/Parse.strings rename to Parse/Parse/Resources/en.lproj/Parse.strings diff --git a/ParseUnitTests-iOS-host/AppDelegate.h b/Parse/ParseUnitTests-iOS-host/AppDelegate.h similarity index 100% rename from ParseUnitTests-iOS-host/AppDelegate.h rename to Parse/ParseUnitTests-iOS-host/AppDelegate.h diff --git a/ParseUnitTests-iOS-host/AppDelegate.m b/Parse/ParseUnitTests-iOS-host/AppDelegate.m similarity index 100% rename from ParseUnitTests-iOS-host/AppDelegate.m rename to Parse/ParseUnitTests-iOS-host/AppDelegate.m diff --git a/ParseUnitTests-iOS-host/Assets.xcassets/AppIcon.appiconset/Contents.json b/Parse/ParseUnitTests-iOS-host/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from ParseUnitTests-iOS-host/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Parse/ParseUnitTests-iOS-host/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/ParseUnitTests-iOS-host/Info.plist b/Parse/ParseUnitTests-iOS-host/Info.plist similarity index 100% rename from ParseUnitTests-iOS-host/Info.plist rename to Parse/ParseUnitTests-iOS-host/Info.plist diff --git a/ParseUnitTests-iOS-host/Main.storyboard b/Parse/ParseUnitTests-iOS-host/Main.storyboard similarity index 100% rename from ParseUnitTests-iOS-host/Main.storyboard rename to Parse/ParseUnitTests-iOS-host/Main.storyboard diff --git a/ParseUnitTests-iOS-host/ViewController.h b/Parse/ParseUnitTests-iOS-host/ViewController.h similarity index 100% rename from ParseUnitTests-iOS-host/ViewController.h rename to Parse/ParseUnitTests-iOS-host/ViewController.h diff --git a/ParseUnitTests-iOS-host/ViewController.m b/Parse/ParseUnitTests-iOS-host/ViewController.m similarity index 100% rename from ParseUnitTests-iOS-host/ViewController.m rename to Parse/ParseUnitTests-iOS-host/ViewController.m diff --git a/ParseUnitTests-iOS-host/main.m b/Parse/ParseUnitTests-iOS-host/main.m similarity index 100% rename from ParseUnitTests-iOS-host/main.m rename to Parse/ParseUnitTests-iOS-host/main.m diff --git a/Tests/Other/Cache/TestCache.h b/Parse/Tests/Other/Cache/TestCache.h similarity index 100% rename from Tests/Other/Cache/TestCache.h rename to Parse/Tests/Other/Cache/TestCache.h diff --git a/Tests/Other/Cache/TestCache.m b/Parse/Tests/Other/Cache/TestCache.m similarity index 100% rename from Tests/Other/Cache/TestCache.m rename to Parse/Tests/Other/Cache/TestCache.m diff --git a/Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.h b/Parse/Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.h similarity index 100% rename from Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.h rename to Parse/Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.h diff --git a/Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.m b/Parse/Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.m similarity index 100% rename from Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.m rename to Parse/Tests/Other/ExtensionDataSharing/PFExtensionDataSharingTestHelper.m diff --git a/Tests/Other/FileManager/TestFileManager.h b/Parse/Tests/Other/FileManager/TestFileManager.h similarity index 100% rename from Tests/Other/FileManager/TestFileManager.h rename to Parse/Tests/Other/FileManager/TestFileManager.h diff --git a/Tests/Other/FileManager/TestFileManager.m b/Parse/Tests/Other/FileManager/TestFileManager.m similarity index 100% rename from Tests/Other/FileManager/TestFileManager.m rename to Parse/Tests/Other/FileManager/TestFileManager.m diff --git a/Tests/Other/LocationManager/CLLocationManager+TestAdditions.h b/Parse/Tests/Other/LocationManager/CLLocationManager+TestAdditions.h similarity index 100% rename from Tests/Other/LocationManager/CLLocationManager+TestAdditions.h rename to Parse/Tests/Other/LocationManager/CLLocationManager+TestAdditions.h diff --git a/Tests/Other/LocationManager/CLLocationManager+TestAdditions.m b/Parse/Tests/Other/LocationManager/CLLocationManager+TestAdditions.m similarity index 100% rename from Tests/Other/LocationManager/CLLocationManager+TestAdditions.m rename to Parse/Tests/Other/LocationManager/CLLocationManager+TestAdditions.m diff --git a/Tests/Other/NetworkMocking/PFMockURLProtocol.h b/Parse/Tests/Other/NetworkMocking/PFMockURLProtocol.h similarity index 100% rename from Tests/Other/NetworkMocking/PFMockURLProtocol.h rename to Parse/Tests/Other/NetworkMocking/PFMockURLProtocol.h diff --git a/Tests/Other/NetworkMocking/PFMockURLProtocol.m b/Parse/Tests/Other/NetworkMocking/PFMockURLProtocol.m similarity index 100% rename from Tests/Other/NetworkMocking/PFMockURLProtocol.m rename to Parse/Tests/Other/NetworkMocking/PFMockURLProtocol.m diff --git a/Tests/Other/NetworkMocking/PFMockURLResponse.h b/Parse/Tests/Other/NetworkMocking/PFMockURLResponse.h similarity index 100% rename from Tests/Other/NetworkMocking/PFMockURLResponse.h rename to Parse/Tests/Other/NetworkMocking/PFMockURLResponse.h diff --git a/Tests/Other/NetworkMocking/PFMockURLResponse.m b/Parse/Tests/Other/NetworkMocking/PFMockURLResponse.m similarity index 100% rename from Tests/Other/NetworkMocking/PFMockURLResponse.m rename to Parse/Tests/Other/NetworkMocking/PFMockURLResponse.m diff --git a/Tests/Other/OCMock/OCMock+Parse.h b/Parse/Tests/Other/OCMock/OCMock+Parse.h similarity index 100% rename from Tests/Other/OCMock/OCMock+Parse.h rename to Parse/Tests/Other/OCMock/OCMock+Parse.h diff --git a/Tests/Other/OCMock/OCMock+Parse.m b/Parse/Tests/Other/OCMock/OCMock+Parse.m similarity index 100% rename from Tests/Other/OCMock/OCMock+Parse.m rename to Parse/Tests/Other/OCMock/OCMock+Parse.m diff --git a/Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.h b/Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.h similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.h rename to Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.h diff --git a/Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.m b/Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.m similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.m rename to Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentQueue.m diff --git a/Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.h b/Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.h similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.h rename to Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.h diff --git a/Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.m b/Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.m similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.m rename to Parse/Tests/Other/StoreKitMocking/PFTestSKPaymentTransaction.m diff --git a/Tests/Other/StoreKitMocking/PFTestSKProduct.h b/Parse/Tests/Other/StoreKitMocking/PFTestSKProduct.h similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKProduct.h rename to Parse/Tests/Other/StoreKitMocking/PFTestSKProduct.h diff --git a/Tests/Other/StoreKitMocking/PFTestSKProduct.m b/Parse/Tests/Other/StoreKitMocking/PFTestSKProduct.m similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKProduct.m rename to Parse/Tests/Other/StoreKitMocking/PFTestSKProduct.m diff --git a/Tests/Other/StoreKitMocking/PFTestSKProductsRequest.h b/Parse/Tests/Other/StoreKitMocking/PFTestSKProductsRequest.h similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKProductsRequest.h rename to Parse/Tests/Other/StoreKitMocking/PFTestSKProductsRequest.h diff --git a/Tests/Other/StoreKitMocking/PFTestSKProductsRequest.m b/Parse/Tests/Other/StoreKitMocking/PFTestSKProductsRequest.m similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKProductsRequest.m rename to Parse/Tests/Other/StoreKitMocking/PFTestSKProductsRequest.m diff --git a/Tests/Other/StoreKitMocking/PFTestSKProductsResponse.h b/Parse/Tests/Other/StoreKitMocking/PFTestSKProductsResponse.h similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKProductsResponse.h rename to Parse/Tests/Other/StoreKitMocking/PFTestSKProductsResponse.h diff --git a/Tests/Other/StoreKitMocking/PFTestSKProductsResponse.m b/Parse/Tests/Other/StoreKitMocking/PFTestSKProductsResponse.m similarity index 100% rename from Tests/Other/StoreKitMocking/PFTestSKProductsResponse.m rename to Parse/Tests/Other/StoreKitMocking/PFTestSKProductsResponse.m diff --git a/Tests/Other/Swift/SwiftSubclass.swift b/Parse/Tests/Other/Swift/SwiftSubclass.swift similarity index 100% rename from Tests/Other/Swift/SwiftSubclass.swift rename to Parse/Tests/Other/Swift/SwiftSubclass.swift diff --git a/Tests/Other/Swizzling/PFTestSwizzledMethod.h b/Parse/Tests/Other/Swizzling/PFTestSwizzledMethod.h similarity index 100% rename from Tests/Other/Swizzling/PFTestSwizzledMethod.h rename to Parse/Tests/Other/Swizzling/PFTestSwizzledMethod.h diff --git a/Tests/Other/Swizzling/PFTestSwizzledMethod.m b/Parse/Tests/Other/Swizzling/PFTestSwizzledMethod.m similarity index 100% rename from Tests/Other/Swizzling/PFTestSwizzledMethod.m rename to Parse/Tests/Other/Swizzling/PFTestSwizzledMethod.m diff --git a/Tests/Other/Swizzling/PFTestSwizzlingUtilities.h b/Parse/Tests/Other/Swizzling/PFTestSwizzlingUtilities.h similarity index 100% rename from Tests/Other/Swizzling/PFTestSwizzlingUtilities.h rename to Parse/Tests/Other/Swizzling/PFTestSwizzlingUtilities.h diff --git a/Tests/Other/Swizzling/PFTestSwizzlingUtilities.m b/Parse/Tests/Other/Swizzling/PFTestSwizzlingUtilities.m similarity index 100% rename from Tests/Other/Swizzling/PFTestSwizzlingUtilities.m rename to Parse/Tests/Other/Swizzling/PFTestSwizzlingUtilities.m diff --git a/Tests/Other/TestCases/TestCase/PFTestCase.h b/Parse/Tests/Other/TestCases/TestCase/PFTestCase.h similarity index 100% rename from Tests/Other/TestCases/TestCase/PFTestCase.h rename to Parse/Tests/Other/TestCases/TestCase/PFTestCase.h diff --git a/Tests/Other/TestCases/TestCase/PFTestCase.m b/Parse/Tests/Other/TestCases/TestCase/PFTestCase.m similarity index 100% rename from Tests/Other/TestCases/TestCase/PFTestCase.m rename to Parse/Tests/Other/TestCases/TestCase/PFTestCase.m diff --git a/Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.h b/Parse/Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.h similarity index 100% rename from Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.h rename to Parse/Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.h diff --git a/Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.m b/Parse/Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.m similarity index 100% rename from Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.m rename to Parse/Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.m diff --git a/Tests/Resources/ParseUnitTests-OSX-Info.plist b/Parse/Tests/Resources/ParseUnitTests-OSX-Info.plist similarity index 100% rename from Tests/Resources/ParseUnitTests-OSX-Info.plist rename to Parse/Tests/Resources/ParseUnitTests-OSX-Info.plist diff --git a/Tests/Resources/ParseUnitTests-iOS-Info.plist b/Parse/Tests/Resources/ParseUnitTests-iOS-Info.plist similarity index 100% rename from Tests/Resources/ParseUnitTests-iOS-Info.plist rename to Parse/Tests/Resources/ParseUnitTests-iOS-Info.plist diff --git a/Tests/Unit/ACLDefaultTests.m b/Parse/Tests/Unit/ACLDefaultTests.m similarity index 100% rename from Tests/Unit/ACLDefaultTests.m rename to Parse/Tests/Unit/ACLDefaultTests.m diff --git a/Tests/Unit/ACLStateTests.m b/Parse/Tests/Unit/ACLStateTests.m similarity index 100% rename from Tests/Unit/ACLStateTests.m rename to Parse/Tests/Unit/ACLStateTests.m diff --git a/Tests/Unit/ACLTests.m b/Parse/Tests/Unit/ACLTests.m similarity index 100% rename from Tests/Unit/ACLTests.m rename to Parse/Tests/Unit/ACLTests.m diff --git a/Tests/Unit/AlertViewTests.m b/Parse/Tests/Unit/AlertViewTests.m similarity index 100% rename from Tests/Unit/AlertViewTests.m rename to Parse/Tests/Unit/AlertViewTests.m diff --git a/Tests/Unit/AnalyticsCommandTests.m b/Parse/Tests/Unit/AnalyticsCommandTests.m similarity index 100% rename from Tests/Unit/AnalyticsCommandTests.m rename to Parse/Tests/Unit/AnalyticsCommandTests.m diff --git a/Tests/Unit/AnalyticsControllerTests.m b/Parse/Tests/Unit/AnalyticsControllerTests.m similarity index 100% rename from Tests/Unit/AnalyticsControllerTests.m rename to Parse/Tests/Unit/AnalyticsControllerTests.m diff --git a/Tests/Unit/AnalyticsUnitTests.m b/Parse/Tests/Unit/AnalyticsUnitTests.m similarity index 100% rename from Tests/Unit/AnalyticsUnitTests.m rename to Parse/Tests/Unit/AnalyticsUnitTests.m diff --git a/Tests/Unit/AnalyticsUtilitiesTests.m b/Parse/Tests/Unit/AnalyticsUtilitiesTests.m similarity index 100% rename from Tests/Unit/AnalyticsUtilitiesTests.m rename to Parse/Tests/Unit/AnalyticsUtilitiesTests.m diff --git a/Tests/Unit/AnonymousAuthenticationProviderTests.m b/Parse/Tests/Unit/AnonymousAuthenticationProviderTests.m similarity index 100% rename from Tests/Unit/AnonymousAuthenticationProviderTests.m rename to Parse/Tests/Unit/AnonymousAuthenticationProviderTests.m diff --git a/Tests/Unit/AnonymousUtilsTests.m b/Parse/Tests/Unit/AnonymousUtilsTests.m similarity index 100% rename from Tests/Unit/AnonymousUtilsTests.m rename to Parse/Tests/Unit/AnonymousUtilsTests.m diff --git a/Tests/Unit/BaseStateTests.m b/Parse/Tests/Unit/BaseStateTests.m similarity index 100% rename from Tests/Unit/BaseStateTests.m rename to Parse/Tests/Unit/BaseStateTests.m diff --git a/Tests/Unit/CloudCodeControllerTests.m b/Parse/Tests/Unit/CloudCodeControllerTests.m similarity index 100% rename from Tests/Unit/CloudCodeControllerTests.m rename to Parse/Tests/Unit/CloudCodeControllerTests.m diff --git a/Tests/Unit/CloudCommandTests.m b/Parse/Tests/Unit/CloudCommandTests.m similarity index 100% rename from Tests/Unit/CloudCommandTests.m rename to Parse/Tests/Unit/CloudCommandTests.m diff --git a/Tests/Unit/CloudUnitTests.m b/Parse/Tests/Unit/CloudUnitTests.m similarity index 100% rename from Tests/Unit/CloudUnitTests.m rename to Parse/Tests/Unit/CloudUnitTests.m diff --git a/Tests/Unit/CommandResultTests.m b/Parse/Tests/Unit/CommandResultTests.m similarity index 100% rename from Tests/Unit/CommandResultTests.m rename to Parse/Tests/Unit/CommandResultTests.m diff --git a/Tests/Unit/CommandURLRequestConstructorTests.m b/Parse/Tests/Unit/CommandURLRequestConstructorTests.m similarity index 100% rename from Tests/Unit/CommandURLRequestConstructorTests.m rename to Parse/Tests/Unit/CommandURLRequestConstructorTests.m diff --git a/Tests/Unit/CommandUnitTests.m b/Parse/Tests/Unit/CommandUnitTests.m similarity index 100% rename from Tests/Unit/CommandUnitTests.m rename to Parse/Tests/Unit/CommandUnitTests.m diff --git a/Tests/Unit/ConfigCommandTests.m b/Parse/Tests/Unit/ConfigCommandTests.m similarity index 100% rename from Tests/Unit/ConfigCommandTests.m rename to Parse/Tests/Unit/ConfigCommandTests.m diff --git a/Tests/Unit/ConfigControllerTests.m b/Parse/Tests/Unit/ConfigControllerTests.m similarity index 100% rename from Tests/Unit/ConfigControllerTests.m rename to Parse/Tests/Unit/ConfigControllerTests.m diff --git a/Tests/Unit/ConfigUnitTests.m b/Parse/Tests/Unit/ConfigUnitTests.m similarity index 100% rename from Tests/Unit/ConfigUnitTests.m rename to Parse/Tests/Unit/ConfigUnitTests.m diff --git a/Tests/Unit/CurrentConfigControllerTests.m b/Parse/Tests/Unit/CurrentConfigControllerTests.m similarity index 100% rename from Tests/Unit/CurrentConfigControllerTests.m rename to Parse/Tests/Unit/CurrentConfigControllerTests.m diff --git a/Tests/Unit/DateFormatterTests.m b/Parse/Tests/Unit/DateFormatterTests.m similarity index 100% rename from Tests/Unit/DateFormatterTests.m rename to Parse/Tests/Unit/DateFormatterTests.m diff --git a/Tests/Unit/DecoderTests.m b/Parse/Tests/Unit/DecoderTests.m similarity index 100% rename from Tests/Unit/DecoderTests.m rename to Parse/Tests/Unit/DecoderTests.m diff --git a/Tests/Unit/DefaultACLControllerTests.m b/Parse/Tests/Unit/DefaultACLControllerTests.m similarity index 100% rename from Tests/Unit/DefaultACLControllerTests.m rename to Parse/Tests/Unit/DefaultACLControllerTests.m diff --git a/Tests/Unit/DeviceTests.m b/Parse/Tests/Unit/DeviceTests.m similarity index 100% rename from Tests/Unit/DeviceTests.m rename to Parse/Tests/Unit/DeviceTests.m diff --git a/Tests/Unit/ExtensionDataSharingMobileTests.m b/Parse/Tests/Unit/ExtensionDataSharingMobileTests.m similarity index 100% rename from Tests/Unit/ExtensionDataSharingMobileTests.m rename to Parse/Tests/Unit/ExtensionDataSharingMobileTests.m diff --git a/Tests/Unit/ExtensionDataSharingTests.m b/Parse/Tests/Unit/ExtensionDataSharingTests.m similarity index 100% rename from Tests/Unit/ExtensionDataSharingTests.m rename to Parse/Tests/Unit/ExtensionDataSharingTests.m diff --git a/Tests/Unit/FieldOperationDecoderTests.m b/Parse/Tests/Unit/FieldOperationDecoderTests.m similarity index 100% rename from Tests/Unit/FieldOperationDecoderTests.m rename to Parse/Tests/Unit/FieldOperationDecoderTests.m diff --git a/Tests/Unit/FieldOperationTests.m b/Parse/Tests/Unit/FieldOperationTests.m similarity index 100% rename from Tests/Unit/FieldOperationTests.m rename to Parse/Tests/Unit/FieldOperationTests.m diff --git a/Tests/Unit/FileCommandTests.m b/Parse/Tests/Unit/FileCommandTests.m similarity index 100% rename from Tests/Unit/FileCommandTests.m rename to Parse/Tests/Unit/FileCommandTests.m diff --git a/Tests/Unit/FileControllerTests.m b/Parse/Tests/Unit/FileControllerTests.m similarity index 100% rename from Tests/Unit/FileControllerTests.m rename to Parse/Tests/Unit/FileControllerTests.m diff --git a/Tests/Unit/FileDataStreamTests.m b/Parse/Tests/Unit/FileDataStreamTests.m similarity index 100% rename from Tests/Unit/FileDataStreamTests.m rename to Parse/Tests/Unit/FileDataStreamTests.m diff --git a/Tests/Unit/FileStateTests.m b/Parse/Tests/Unit/FileStateTests.m similarity index 100% rename from Tests/Unit/FileStateTests.m rename to Parse/Tests/Unit/FileStateTests.m diff --git a/Tests/Unit/FileUnitTests.m b/Parse/Tests/Unit/FileUnitTests.m similarity index 100% rename from Tests/Unit/FileUnitTests.m rename to Parse/Tests/Unit/FileUnitTests.m diff --git a/Tests/Unit/GeoPointLocationTests.m b/Parse/Tests/Unit/GeoPointLocationTests.m similarity index 100% rename from Tests/Unit/GeoPointLocationTests.m rename to Parse/Tests/Unit/GeoPointLocationTests.m diff --git a/Tests/Unit/GeoPointUnitTests.m b/Parse/Tests/Unit/GeoPointUnitTests.m similarity index 100% rename from Tests/Unit/GeoPointUnitTests.m rename to Parse/Tests/Unit/GeoPointUnitTests.m diff --git a/Tests/Unit/HashTests.m b/Parse/Tests/Unit/HashTests.m similarity index 100% rename from Tests/Unit/HashTests.m rename to Parse/Tests/Unit/HashTests.m diff --git a/Tests/Unit/IncrementUnitTests.m b/Parse/Tests/Unit/IncrementUnitTests.m similarity index 100% rename from Tests/Unit/IncrementUnitTests.m rename to Parse/Tests/Unit/IncrementUnitTests.m diff --git a/Tests/Unit/InstallationIdentifierUnitTests.m b/Parse/Tests/Unit/InstallationIdentifierUnitTests.m similarity index 100% rename from Tests/Unit/InstallationIdentifierUnitTests.m rename to Parse/Tests/Unit/InstallationIdentifierUnitTests.m diff --git a/Tests/Unit/InstallationUnitTests.m b/Parse/Tests/Unit/InstallationUnitTests.m similarity index 100% rename from Tests/Unit/InstallationUnitTests.m rename to Parse/Tests/Unit/InstallationUnitTests.m diff --git a/Tests/Unit/KeyValueCacheTests.m b/Parse/Tests/Unit/KeyValueCacheTests.m similarity index 100% rename from Tests/Unit/KeyValueCacheTests.m rename to Parse/Tests/Unit/KeyValueCacheTests.m diff --git a/Tests/Unit/KeychainStoreTests.m b/Parse/Tests/Unit/KeychainStoreTests.m similarity index 100% rename from Tests/Unit/KeychainStoreTests.m rename to Parse/Tests/Unit/KeychainStoreTests.m diff --git a/Tests/Unit/LocationManagerMobileTests.m b/Parse/Tests/Unit/LocationManagerMobileTests.m similarity index 100% rename from Tests/Unit/LocationManagerMobileTests.m rename to Parse/Tests/Unit/LocationManagerMobileTests.m diff --git a/Tests/Unit/LocationManagerTests.m b/Parse/Tests/Unit/LocationManagerTests.m similarity index 100% rename from Tests/Unit/LocationManagerTests.m rename to Parse/Tests/Unit/LocationManagerTests.m diff --git a/Tests/Unit/ObjectBatchCommandTests.m b/Parse/Tests/Unit/ObjectBatchCommandTests.m similarity index 100% rename from Tests/Unit/ObjectBatchCommandTests.m rename to Parse/Tests/Unit/ObjectBatchCommandTests.m diff --git a/Tests/Unit/ObjectBatchControllerTests.m b/Parse/Tests/Unit/ObjectBatchControllerTests.m similarity index 100% rename from Tests/Unit/ObjectBatchControllerTests.m rename to Parse/Tests/Unit/ObjectBatchControllerTests.m diff --git a/Tests/Unit/ObjectCommandTests.m b/Parse/Tests/Unit/ObjectCommandTests.m similarity index 100% rename from Tests/Unit/ObjectCommandTests.m rename to Parse/Tests/Unit/ObjectCommandTests.m diff --git a/Tests/Unit/ObjectEstimatedDataTests.m b/Parse/Tests/Unit/ObjectEstimatedDataTests.m similarity index 100% rename from Tests/Unit/ObjectEstimatedDataTests.m rename to Parse/Tests/Unit/ObjectEstimatedDataTests.m diff --git a/Tests/Unit/ObjectFileCoderTests.m b/Parse/Tests/Unit/ObjectFileCoderTests.m similarity index 100% rename from Tests/Unit/ObjectFileCoderTests.m rename to Parse/Tests/Unit/ObjectFileCoderTests.m diff --git a/Tests/Unit/ObjectFileCodingLogicTests.m b/Parse/Tests/Unit/ObjectFileCodingLogicTests.m similarity index 100% rename from Tests/Unit/ObjectFileCodingLogicTests.m rename to Parse/Tests/Unit/ObjectFileCodingLogicTests.m diff --git a/Tests/Unit/ObjectFilePersistenceControllerTests.m b/Parse/Tests/Unit/ObjectFilePersistenceControllerTests.m similarity index 100% rename from Tests/Unit/ObjectFilePersistenceControllerTests.m rename to Parse/Tests/Unit/ObjectFilePersistenceControllerTests.m diff --git a/Tests/Unit/ObjectLocalIdStoreTests.m b/Parse/Tests/Unit/ObjectLocalIdStoreTests.m similarity index 100% rename from Tests/Unit/ObjectLocalIdStoreTests.m rename to Parse/Tests/Unit/ObjectLocalIdStoreTests.m diff --git a/Tests/Unit/ObjectOfflineTests.m b/Parse/Tests/Unit/ObjectOfflineTests.m similarity index 100% rename from Tests/Unit/ObjectOfflineTests.m rename to Parse/Tests/Unit/ObjectOfflineTests.m diff --git a/Tests/Unit/ObjectPinTests.m b/Parse/Tests/Unit/ObjectPinTests.m similarity index 100% rename from Tests/Unit/ObjectPinTests.m rename to Parse/Tests/Unit/ObjectPinTests.m diff --git a/Tests/Unit/ObjectStateTests.m b/Parse/Tests/Unit/ObjectStateTests.m similarity index 100% rename from Tests/Unit/ObjectStateTests.m rename to Parse/Tests/Unit/ObjectStateTests.m diff --git a/Tests/Unit/ObjectSubclassPropertiesTests.m b/Parse/Tests/Unit/ObjectSubclassPropertiesTests.m similarity index 100% rename from Tests/Unit/ObjectSubclassPropertiesTests.m rename to Parse/Tests/Unit/ObjectSubclassPropertiesTests.m diff --git a/Tests/Unit/ObjectSubclassTests.m b/Parse/Tests/Unit/ObjectSubclassTests.m similarity index 100% rename from Tests/Unit/ObjectSubclassTests.m rename to Parse/Tests/Unit/ObjectSubclassTests.m diff --git a/Tests/Unit/ObjectSubclassingControllerTests.m b/Parse/Tests/Unit/ObjectSubclassingControllerTests.m similarity index 100% rename from Tests/Unit/ObjectSubclassingControllerTests.m rename to Parse/Tests/Unit/ObjectSubclassingControllerTests.m diff --git a/Tests/Unit/ObjectUnitTests.m b/Parse/Tests/Unit/ObjectUnitTests.m similarity index 100% rename from Tests/Unit/ObjectUnitTests.m rename to Parse/Tests/Unit/ObjectUnitTests.m diff --git a/Tests/Unit/ObjectUtilitiesTests.m b/Parse/Tests/Unit/ObjectUtilitiesTests.m similarity index 100% rename from Tests/Unit/ObjectUtilitiesTests.m rename to Parse/Tests/Unit/ObjectUtilitiesTests.m diff --git a/Tests/Unit/OfflineQueryControllerTests.m b/Parse/Tests/Unit/OfflineQueryControllerTests.m similarity index 100% rename from Tests/Unit/OfflineQueryControllerTests.m rename to Parse/Tests/Unit/OfflineQueryControllerTests.m diff --git a/Tests/Unit/OfflineQueryLogicUnitTests.m b/Parse/Tests/Unit/OfflineQueryLogicUnitTests.m similarity index 100% rename from Tests/Unit/OfflineQueryLogicUnitTests.m rename to Parse/Tests/Unit/OfflineQueryLogicUnitTests.m diff --git a/Tests/Unit/OperationSetUnitTests.m b/Parse/Tests/Unit/OperationSetUnitTests.m similarity index 100% rename from Tests/Unit/OperationSetUnitTests.m rename to Parse/Tests/Unit/OperationSetUnitTests.m diff --git a/Tests/Unit/ParseClientConfigurationTests.m b/Parse/Tests/Unit/ParseClientConfigurationTests.m similarity index 100% rename from Tests/Unit/ParseClientConfigurationTests.m rename to Parse/Tests/Unit/ParseClientConfigurationTests.m diff --git a/Tests/Unit/ParseModuleUnitTests.m b/Parse/Tests/Unit/ParseModuleUnitTests.m similarity index 100% rename from Tests/Unit/ParseModuleUnitTests.m rename to Parse/Tests/Unit/ParseModuleUnitTests.m diff --git a/Tests/Unit/ParseSetupUnitTests.m b/Parse/Tests/Unit/ParseSetupUnitTests.m similarity index 100% rename from Tests/Unit/ParseSetupUnitTests.m rename to Parse/Tests/Unit/ParseSetupUnitTests.m diff --git a/Tests/Unit/PinUnitTests.m b/Parse/Tests/Unit/PinUnitTests.m similarity index 100% rename from Tests/Unit/PinUnitTests.m rename to Parse/Tests/Unit/PinUnitTests.m diff --git a/Tests/Unit/PinningObjectStoreTests.m b/Parse/Tests/Unit/PinningObjectStoreTests.m similarity index 100% rename from Tests/Unit/PinningObjectStoreTests.m rename to Parse/Tests/Unit/PinningObjectStoreTests.m diff --git a/Tests/Unit/PolygonUnitTests.m b/Parse/Tests/Unit/PolygonUnitTests.m similarity index 100% rename from Tests/Unit/PolygonUnitTests.m rename to Parse/Tests/Unit/PolygonUnitTests.m diff --git a/Tests/Unit/ProductTests.m b/Parse/Tests/Unit/ProductTests.m similarity index 100% rename from Tests/Unit/ProductTests.m rename to Parse/Tests/Unit/ProductTests.m diff --git a/Tests/Unit/PropertyInfoTests.m b/Parse/Tests/Unit/PropertyInfoTests.m similarity index 100% rename from Tests/Unit/PropertyInfoTests.m rename to Parse/Tests/Unit/PropertyInfoTests.m diff --git a/Tests/Unit/PurchaseControllerTests.m b/Parse/Tests/Unit/PurchaseControllerTests.m similarity index 100% rename from Tests/Unit/PurchaseControllerTests.m rename to Parse/Tests/Unit/PurchaseControllerTests.m diff --git a/Tests/Unit/PurchaseUnitTests.m b/Parse/Tests/Unit/PurchaseUnitTests.m similarity index 100% rename from Tests/Unit/PurchaseUnitTests.m rename to Parse/Tests/Unit/PurchaseUnitTests.m diff --git a/Tests/Unit/PushChannelsControllerTests.m b/Parse/Tests/Unit/PushChannelsControllerTests.m similarity index 100% rename from Tests/Unit/PushChannelsControllerTests.m rename to Parse/Tests/Unit/PushChannelsControllerTests.m diff --git a/Tests/Unit/PushCommandTests.m b/Parse/Tests/Unit/PushCommandTests.m similarity index 100% rename from Tests/Unit/PushCommandTests.m rename to Parse/Tests/Unit/PushCommandTests.m diff --git a/Tests/Unit/PushControllerTests.m b/Parse/Tests/Unit/PushControllerTests.m similarity index 100% rename from Tests/Unit/PushControllerTests.m rename to Parse/Tests/Unit/PushControllerTests.m diff --git a/Tests/Unit/PushManagerTests.m b/Parse/Tests/Unit/PushManagerTests.m similarity index 100% rename from Tests/Unit/PushManagerTests.m rename to Parse/Tests/Unit/PushManagerTests.m diff --git a/Tests/Unit/PushMobileTests.m b/Parse/Tests/Unit/PushMobileTests.m similarity index 100% rename from Tests/Unit/PushMobileTests.m rename to Parse/Tests/Unit/PushMobileTests.m diff --git a/Tests/Unit/PushStateTests.m b/Parse/Tests/Unit/PushStateTests.m similarity index 100% rename from Tests/Unit/PushStateTests.m rename to Parse/Tests/Unit/PushStateTests.m diff --git a/Tests/Unit/PushUnitTests.m b/Parse/Tests/Unit/PushUnitTests.m similarity index 100% rename from Tests/Unit/PushUnitTests.m rename to Parse/Tests/Unit/PushUnitTests.m diff --git a/Tests/Unit/QueryCachedControllerTests.m b/Parse/Tests/Unit/QueryCachedControllerTests.m similarity index 100% rename from Tests/Unit/QueryCachedControllerTests.m rename to Parse/Tests/Unit/QueryCachedControllerTests.m diff --git a/Tests/Unit/QueryControllerUnitTests.m b/Parse/Tests/Unit/QueryControllerUnitTests.m similarity index 100% rename from Tests/Unit/QueryControllerUnitTests.m rename to Parse/Tests/Unit/QueryControllerUnitTests.m diff --git a/Tests/Unit/QueryPredicateUnitTests.m b/Parse/Tests/Unit/QueryPredicateUnitTests.m similarity index 100% rename from Tests/Unit/QueryPredicateUnitTests.m rename to Parse/Tests/Unit/QueryPredicateUnitTests.m diff --git a/Tests/Unit/QueryStateUnitTests.m b/Parse/Tests/Unit/QueryStateUnitTests.m similarity index 100% rename from Tests/Unit/QueryStateUnitTests.m rename to Parse/Tests/Unit/QueryStateUnitTests.m diff --git a/Tests/Unit/QueryUnitTests.m b/Parse/Tests/Unit/QueryUnitTests.m similarity index 100% rename from Tests/Unit/QueryUnitTests.m rename to Parse/Tests/Unit/QueryUnitTests.m diff --git a/Tests/Unit/QueryUtilitiesTests.m b/Parse/Tests/Unit/QueryUtilitiesTests.m similarity index 100% rename from Tests/Unit/QueryUtilitiesTests.m rename to Parse/Tests/Unit/QueryUtilitiesTests.m diff --git a/Tests/Unit/RelationStateTests.m b/Parse/Tests/Unit/RelationStateTests.m similarity index 100% rename from Tests/Unit/RelationStateTests.m rename to Parse/Tests/Unit/RelationStateTests.m diff --git a/Tests/Unit/RelationUnitTests.m b/Parse/Tests/Unit/RelationUnitTests.m similarity index 100% rename from Tests/Unit/RelationUnitTests.m rename to Parse/Tests/Unit/RelationUnitTests.m diff --git a/Tests/Unit/RoleUnitTests.m b/Parse/Tests/Unit/RoleUnitTests.m similarity index 100% rename from Tests/Unit/RoleUnitTests.m rename to Parse/Tests/Unit/RoleUnitTests.m diff --git a/Tests/Unit/SQLiteDatabaseTest.m b/Parse/Tests/Unit/SQLiteDatabaseTest.m similarity index 100% rename from Tests/Unit/SQLiteDatabaseTest.m rename to Parse/Tests/Unit/SQLiteDatabaseTest.m diff --git a/Tests/Unit/SessionControllerTests.m b/Parse/Tests/Unit/SessionControllerTests.m similarity index 100% rename from Tests/Unit/SessionControllerTests.m rename to Parse/Tests/Unit/SessionControllerTests.m diff --git a/Tests/Unit/SessionUnitTests.m b/Parse/Tests/Unit/SessionUnitTests.m similarity index 100% rename from Tests/Unit/SessionUnitTests.m rename to Parse/Tests/Unit/SessionUnitTests.m diff --git a/Tests/Unit/SessionUtilitiesTests.m b/Parse/Tests/Unit/SessionUtilitiesTests.m similarity index 100% rename from Tests/Unit/SessionUtilitiesTests.m rename to Parse/Tests/Unit/SessionUtilitiesTests.m diff --git a/Tests/Unit/URLConstructorTests.m b/Parse/Tests/Unit/URLConstructorTests.m similarity index 100% rename from Tests/Unit/URLConstructorTests.m rename to Parse/Tests/Unit/URLConstructorTests.m diff --git a/Tests/Unit/URLSessionCommandRunnerTests.m b/Parse/Tests/Unit/URLSessionCommandRunnerTests.m similarity index 100% rename from Tests/Unit/URLSessionCommandRunnerTests.m rename to Parse/Tests/Unit/URLSessionCommandRunnerTests.m diff --git a/Tests/Unit/URLSessionDataTaskDelegateTests.m b/Parse/Tests/Unit/URLSessionDataTaskDelegateTests.m similarity index 100% rename from Tests/Unit/URLSessionDataTaskDelegateTests.m rename to Parse/Tests/Unit/URLSessionDataTaskDelegateTests.m diff --git a/Tests/Unit/URLSessionTests.m b/Parse/Tests/Unit/URLSessionTests.m similarity index 100% rename from Tests/Unit/URLSessionTests.m rename to Parse/Tests/Unit/URLSessionTests.m diff --git a/Tests/Unit/URLSessionUploadTaskDelegateTests.m b/Parse/Tests/Unit/URLSessionUploadTaskDelegateTests.m similarity index 100% rename from Tests/Unit/URLSessionUploadTaskDelegateTests.m rename to Parse/Tests/Unit/URLSessionUploadTaskDelegateTests.m diff --git a/Tests/Unit/UserCommandTests.m b/Parse/Tests/Unit/UserCommandTests.m similarity index 100% rename from Tests/Unit/UserCommandTests.m rename to Parse/Tests/Unit/UserCommandTests.m diff --git a/Tests/Unit/UserControllerTests.m b/Parse/Tests/Unit/UserControllerTests.m similarity index 100% rename from Tests/Unit/UserControllerTests.m rename to Parse/Tests/Unit/UserControllerTests.m diff --git a/Tests/Unit/UserFileCodingLogicTests.m b/Parse/Tests/Unit/UserFileCodingLogicTests.m similarity index 100% rename from Tests/Unit/UserFileCodingLogicTests.m rename to Parse/Tests/Unit/UserFileCodingLogicTests.m diff --git a/Tests/Unit/UserUnitTests.m b/Parse/Tests/Unit/UserUnitTests.m similarity index 100% rename from Tests/Unit/UserUnitTests.m rename to Parse/Tests/Unit/UserUnitTests.m diff --git a/Tests/testServer.config b/Parse/Tests/testServer.config similarity index 100% rename from Tests/testServer.config rename to Parse/Tests/testServer.config diff --git a/third_party_licenses.txt b/Parse/third_party_licenses.txt similarity index 100% rename from third_party_licenses.txt rename to Parse/third_party_licenses.txt diff --git a/ParseFacebookUtils/CHANGELOG.md b/ParseFacebookUtils/CHANGELOG.md new file mode 100644 index 000000000..c932cc491 --- /dev/null +++ b/ParseFacebookUtils/CHANGELOG.md @@ -0,0 +1,51 @@ +# Change Log +All notable changes to this project will be documented in this file. + +--- + +## [ParseFacebookUtils-V4 1.11.1](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/releases/tag/v4-1.11.1) (02/20/2016) + +#### Updated + +- `ParseFacebookUtilsV4` CocoaPod now uses the latest version of Facebook SDK. + [#50](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/pull/50), + [#48](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/pull/48) + by [@nlutsenko](https://github.com/nlutsenko) + +#### Fixed + +- Fixed static analyzer warnings that appeared in Xcode 7.3 beta. + [#51](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/pull/51) + by [@nlutsenko](https://github.com/nlutsenko) + +## [ParseFacebookUtils-V4 1.11.0](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/releases/tag/v4-1.11.0) (01/07/2016) + +#### New + +- ParseFacebookUtils now requires Xcode 7.0+. + [#38](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/pull/38) + by [@nlutsenko](https://github.com/nlutsenko) + +## [ParseFacebookUtils-V4 1.10.0](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/releases/tag/v4-1.10.0) (12/14/2015) + +#### New +- ParseFacebookUtils now supports tvOS. #23 + +#### Improved +- Updated, cleaned up and improved documentation. #22 + +## [ParseFacebookUtils-V4 1.9.1](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/releases/tag/v4-1.9.1) (11/17/2015) + +#### New +- ParseFacebookUtils now supports Xcode 7.1. #7 +- ParseFacebookUtils now depends on FBSDK 4.8+. + +#### Improved +- Removed unused imports. #10 + +## [ParseFacebookUtils-V4 1.9.0](https://github.com/ParsePlatform/ParseFacebookUtils-iOS/releases/tag/v4-1.9.0) (10/08/2015) + +Hello, open source! + +#### Fixed +- Fixed initialization of Facebook Utils blocking the main thread. diff --git a/ParseFacebookUtils/Configurations/ParseFacebookTestApplicationV4.xcconfig b/ParseFacebookUtils/Configurations/ParseFacebookTestApplicationV4.xcconfig new file mode 100644 index 000000000..f5af58177 --- /dev/null +++ b/ParseFacebookUtils/Configurations/ParseFacebookTestApplicationV4.xcconfig @@ -0,0 +1,16 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/Application.xcconfig" + +PRODUCT_NAME = ParseFacebookTestApplicationV4 +PRODUCT_BUNDLE_IDENTIFIER = com.parse.facebookutils-ios.unit.app + +INFOPLIST_FILE = $(SRCROOT)/Tests/TestApplication/Resources/Info.plist diff --git a/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-UnitTests.xcconfig b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-UnitTests.xcconfig new file mode 100644 index 000000000..ab5e22c93 --- /dev/null +++ b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-UnitTests.xcconfig @@ -0,0 +1,21 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/LogicTests.xcconfig" + +PRODUCT_NAME = ParseFacebookUtilsV4-UnitTests +PRODUCT_BUNDLE_IDENTIFIER = com.parse.facebookutils-ios.unit + +IPHONEOS_DEPLOYMENT_TARGET = 8.0 + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(BUILT_PRODUCTS_DIR) $(SRCROOT)/Vendor + +INFOPLIST_FILE = $(SRCROOT)/Tests/Resources/Info.plist +TEST_HOST = $(BUILT_PRODUCTS_DIR)/ParseFacebookTestApplicationV4.app/ParseFacebookTestApplicationV4 diff --git a/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-iOS-Dynamic.xcconfig b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-iOS-Dynamic.xcconfig new file mode 100644 index 000000000..05782f499 --- /dev/null +++ b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-iOS-Dynamic.xcconfig @@ -0,0 +1,18 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/DynamicFramework.xcconfig" + +PRODUCT_NAME = ParseFacebookUtilsV4 +PRODUCT_BUNDLE_IDENTIFIER = com.parse.facebookutils-ios + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info-iOS.plist + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(SRCROOT)/Vendor diff --git a/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-iOS.xcconfig b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-iOS.xcconfig new file mode 100644 index 000000000..d2aaa65f1 --- /dev/null +++ b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-iOS.xcconfig @@ -0,0 +1,18 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/StaticFramework.xcconfig" + +PRODUCT_NAME = ParseFacebookUtilsV4 +PRODUCT_BUNDLE_IDENTIFIER = com.parse.facebookutils-ios + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info-iOS.plist + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(SRCROOT)/Vendor diff --git a/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig new file mode 100644 index 000000000..9b25911c6 --- /dev/null +++ b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig @@ -0,0 +1,18 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/tvOS.xcconfig" +#include "Shared/Product/DynamicFramework.xcconfig" + +PRODUCT_NAME = ParseFacebookUtilsV4 +PRODUCT_BUNDLE_IDENTIFIER = com.parse.facebookutils-tvos + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info-tvOS.plist + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(SRCROOT)/Vendor/tvOS diff --git a/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-tvOS.xcconfig b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-tvOS.xcconfig new file mode 100644 index 000000000..f3d40fb39 --- /dev/null +++ b/ParseFacebookUtils/Configurations/ParseFacebookUtilsV4-tvOS.xcconfig @@ -0,0 +1,18 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/tvOS.xcconfig" +#include "Shared/Product/StaticFramework.xcconfig" + +PRODUCT_NAME = ParseFacebookUtilsV4 +PRODUCT_BUNDLE_IDENTIFIER = com.parse.facebookutils-tvos + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info-tvOS.plist + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(SRCROOT)/Vendor/tvOS diff --git a/ParseFacebookUtils/Configurations/Shared b/ParseFacebookUtils/Configurations/Shared new file mode 120000 index 000000000..657caabf0 --- /dev/null +++ b/ParseFacebookUtils/Configurations/Shared @@ -0,0 +1 @@ +../../Vendor/xctoolchain/Configurations \ No newline at end of file diff --git a/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/project.pbxproj b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/project.pbxproj new file mode 100644 index 000000000..a06c24b3c --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/project.pbxproj @@ -0,0 +1,1249 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4A9A9502200D1462005D8F4B /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A9503200D1462005D8F4B /* Parse.framework */; }; + 4AAEAA41200BE19A00AA7479 /* OCMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA42200BE19A00AA7479 /* OCMock.framework */; }; + 4AAEAA43200BE26C00AA7479 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA45200BE26C00AA7479 /* Bolts.framework */; }; + 4AAEAA4F200BED7A00AA7479 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA51200BED7A00AA7479 /* Bolts.framework */; }; + 4AAEAA50200BED7A00AA7479 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA52200BED7A00AA7479 /* Parse.framework */; }; + 4AAEAA5F200C020E00AA7479 /* PFFacebookUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 813DFC841AB2510300F25A08 /* PFFacebookUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4AAEAA60200C020E00AA7479 /* PFFacebookAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C91C178DED00B0F875 /* PFFacebookAuthenticationProvider.h */; }; + 4AAEAA61200C020E00AA7479 /* PFFacebookMobileAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C61C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.h */; }; + 4AAEAA62200C020E00AA7479 /* ParseFacebookUtilsV4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EDD4B41B58AC7D002F69C0 /* ParseFacebookUtilsV4.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4AAEAA63200C020E00AA7479 /* PFFacebookPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 81E35FD21BAA6F8400348526 /* PFFacebookPrivateUtilities.h */; }; + 4AAEAA64200C020E00AA7479 /* PFFacebookUtils_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 81643D761AB79ABD00DD3E65 /* PFFacebookUtils_Private.h */; }; + 4AAEAA65200C020E00AA7479 /* PFFacebookMobileAuthenticationProvider_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C81C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider_Private.h */; }; + 4AAEAA67200C020E00AA7479 /* PFFacebookAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09CA1C178DED00B0F875 /* PFFacebookAuthenticationProvider.m */; }; + 4AAEAA68200C020E00AA7479 /* PFFacebookUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 813DFC851AB2510300F25A08 /* PFFacebookUtils.m */; }; + 4AAEAA69200C020E00AA7479 /* PFFacebookMobileAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09C71C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.m */; }; + 4AAEAA6A200C020E00AA7479 /* PFFacebookPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E35FD31BAA6F8400348526 /* PFFacebookPrivateUtilities.m */; }; + 4AAEAA6E200C020E00AA7479 /* FBSDKCoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81DA692F1AB25A36008C3B7F /* FBSDKCoreKit.framework */; }; + 4AAEAA6F200C020E00AA7479 /* FBSDKLoginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81DA69301AB25A36008C3B7F /* FBSDKLoginKit.framework */; }; + 4AAEAA71200C020E00AA7479 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 81B3F22A1AC9CA5300A92677 /* Localizable.strings */; }; + 4AAEAA7B200C022300AA7479 /* PFFacebookUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 813DFC841AB2510300F25A08 /* PFFacebookUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4AAEAA7C200C022300AA7479 /* PFFacebookDeviceAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09CC1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.h */; }; + 4AAEAA7D200C022300AA7479 /* PFFacebookAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C91C178DED00B0F875 /* PFFacebookAuthenticationProvider.h */; }; + 4AAEAA7E200C022300AA7479 /* ParseFacebookUtilsV4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EDD4B41B58AC7D002F69C0 /* ParseFacebookUtilsV4.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4AAEAA7F200C022300AA7479 /* PFFacebookPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 81E35FD21BAA6F8400348526 /* PFFacebookPrivateUtilities.h */; }; + 4AAEAA80200C022300AA7479 /* PFFacebookUtils_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 81643D761AB79ABD00DD3E65 /* PFFacebookUtils_Private.h */; }; + 4AAEAA82200C022300AA7479 /* PFFacebookDeviceAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09CD1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.m */; }; + 4AAEAA83200C022300AA7479 /* PFFacebookAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09CA1C178DED00B0F875 /* PFFacebookAuthenticationProvider.m */; }; + 4AAEAA84200C022300AA7479 /* PFFacebookUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 813DFC851AB2510300F25A08 /* PFFacebookUtils.m */; }; + 4AAEAA85200C022300AA7479 /* PFFacebookPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E35FD31BAA6F8400348526 /* PFFacebookPrivateUtilities.m */; }; + 4AAEAA89200C022300AA7479 /* FBSDKCoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81EA09BD1C178D0600B0F875 /* FBSDKCoreKit.framework */; }; + 4AAEAA8A200C022300AA7479 /* FBSDKTVOSKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81EA09BE1C178D0600B0F875 /* FBSDKTVOSKit.framework */; }; + 4AAEAA8C200C022300AA7479 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 81B3F22A1AC9CA5300A92677 /* Localizable.strings */; }; + 4AAEAA94200C0E6F00AA7479 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA96200C0E6F00AA7479 /* Bolts.framework */; }; + 4AAEAA95200C0E6F00AA7479 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA97200C0E6F00AA7479 /* Parse.framework */; }; + 4AAEAA9C200C0E8900AA7479 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA9E200C0E8900AA7479 /* Bolts.framework */; }; + 4AAEAA9D200C0E8900AA7479 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAA9F200C0E8900AA7479 /* Parse.framework */; }; + 4AAEAAA1200C0FC500AA7479 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAA0200C0FC500AA7479 /* SystemConfiguration.framework */; }; + 4AAEAAA3200C0FCC00AA7479 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAA2200C0FCC00AA7479 /* libsqlite3.tbd */; }; + 4AAEAAA5200C0FD200AA7479 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAA4200C0FD200AA7479 /* StoreKit.framework */; }; + 4AAEAAA7200C0FDC00AA7479 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAA6200C0FDC00AA7479 /* Security.framework */; }; + 4AAEAAA9200C0FE100AA7479 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAA8200C0FE100AA7479 /* CoreLocation.framework */; }; + 4AAEAAAB200C0FEC00AA7479 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAAA200C0FEC00AA7479 /* AudioToolbox.framework */; }; + 4AAEAAAD200C0FF300AA7479 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAAC200C0FF300AA7479 /* UIKit.framework */; }; + 4AAEAAAF200C101100AA7479 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAAE200C101100AA7479 /* AudioToolbox.framework */; }; + 4AAEAAB1200C101700AA7479 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAB0200C101700AA7479 /* libsqlite3.tbd */; }; + 4AAEAAB3200C102100AA7479 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAB2200C102100AA7479 /* UIKit.framework */; }; + 4AAEAAB5200C102600AA7479 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAB4200C102600AA7479 /* StoreKit.framework */; }; + 4AAEAAB7200C103000AA7479 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAB6200C103000AA7479 /* CoreLocation.framework */; }; + 4AAEAAB9200C103500AA7479 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAAB8200C103500AA7479 /* Security.framework */; }; + 4AAEAABB200C103B00AA7479 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAABA200C103B00AA7479 /* SystemConfiguration.framework */; }; + 813DFC8A1AB2510300F25A08 /* PFFacebookUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 813DFC841AB2510300F25A08 /* PFFacebookUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 813DFC8B1AB2510300F25A08 /* PFFacebookUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 813DFC851AB2510300F25A08 /* PFFacebookUtils.m */; }; + 815FC59A1BBF74890006AF6E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 815FC5971BBF74890006AF6E /* main.m */; }; + 81643D771AB79ABD00DD3E65 /* PFFacebookUtils_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 81643D761AB79ABD00DD3E65 /* PFFacebookUtils_Private.h */; }; + 81930A3E1BBE1A0600A5E4BB /* PFFacebookTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 81930A3D1BBE1A0600A5E4BB /* PFFacebookTestCase.m */; }; + 81B3F22B1AC9CA5300A92677 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 81B3F22A1AC9CA5300A92677 /* Localizable.strings */; }; + 81CB98CC1AB7905D00136FA5 /* ParseFacebookUtilsV4.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AAC07E0554694100DB518D /* ParseFacebookUtilsV4.framework */; }; + 81CB98D81AB791FB00136FA5 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 813DFC921AB2515A00F25A08 /* Bolts.framework */; }; + 81CB98D91AB7920700136FA5 /* FBSDKCoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81DA692F1AB25A36008C3B7F /* FBSDKCoreKit.framework */; }; + 81CB98DA1AB7920700136FA5 /* FBSDKLoginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81DA69301AB25A36008C3B7F /* FBSDKLoginKit.framework */; }; + 81CB98DB1AB7920E00136FA5 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 813DFC941AB251F700F25A08 /* Parse.framework */; }; + 81CB98DD1AB7921C00136FA5 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 81CB98DC1AB7921C00136FA5 /* libsqlite3.dylib */; }; + 81CB98DF1AB7922600136FA5 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81CB98DE1AB7922600136FA5 /* AudioToolbox.framework */; }; + 81CB98E11AB7922C00136FA5 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81CB98E01AB7922C00136FA5 /* SystemConfiguration.framework */; }; + 81DA69311AB25A36008C3B7F /* FBSDKCoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81DA692F1AB25A36008C3B7F /* FBSDKCoreKit.framework */; }; + 81DA69321AB25A37008C3B7F /* FBSDKLoginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81DA69301AB25A36008C3B7F /* FBSDKLoginKit.framework */; }; + 81E35FD41BAA6F8400348526 /* PFFacebookPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 81E35FD21BAA6F8400348526 /* PFFacebookPrivateUtilities.h */; }; + 81E35FD51BAA6F8400348526 /* PFFacebookPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E35FD31BAA6F8400348526 /* PFFacebookPrivateUtilities.m */; }; + 81EA09C11C178D0600B0F875 /* FBSDKCoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81EA09BD1C178D0600B0F875 /* FBSDKCoreKit.framework */; }; + 81EA09C21C178D0600B0F875 /* FBSDKTVOSKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81EA09BE1C178D0600B0F875 /* FBSDKTVOSKit.framework */; }; + 81EA09CE1C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C61C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.h */; }; + 81EA09D01C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09C71C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.m */; }; + 81EA09D21C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C81C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider_Private.h */; }; + 81EA09D41C178DED00B0F875 /* PFFacebookAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C91C178DED00B0F875 /* PFFacebookAuthenticationProvider.h */; }; + 81EA09D51C178DED00B0F875 /* PFFacebookAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09C91C178DED00B0F875 /* PFFacebookAuthenticationProvider.h */; }; + 81EA09D61C178DED00B0F875 /* PFFacebookAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09CA1C178DED00B0F875 /* PFFacebookAuthenticationProvider.m */; }; + 81EA09D71C178DED00B0F875 /* PFFacebookAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09CA1C178DED00B0F875 /* PFFacebookAuthenticationProvider.m */; }; + 81EA09D91C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EA09CC1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.h */; }; + 81EA09DB1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EA09CD1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.m */; }; + 81EDD4B51B58AC7D002F69C0 /* ParseFacebookUtilsV4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EDD4B41B58AC7D002F69C0 /* ParseFacebookUtilsV4.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81FE7F771C17790400E6BD34 /* PFFacebookUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 813DFC841AB2510300F25A08 /* PFFacebookUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81FE7F781C17790400E6BD34 /* ParseFacebookUtilsV4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EDD4B41B58AC7D002F69C0 /* ParseFacebookUtilsV4.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81FE7F791C17790400E6BD34 /* PFFacebookPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 81E35FD21BAA6F8400348526 /* PFFacebookPrivateUtilities.h */; }; + 81FE7F7B1C17790400E6BD34 /* PFFacebookUtils_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 81643D761AB79ABD00DD3E65 /* PFFacebookUtils_Private.h */; }; + 81FE7F7E1C17790400E6BD34 /* PFFacebookUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 813DFC851AB2510300F25A08 /* PFFacebookUtils.m */; }; + 81FE7F801C17790400E6BD34 /* PFFacebookPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E35FD31BAA6F8400348526 /* PFFacebookPrivateUtilities.m */; }; + 81FE7F871C17790400E6BD34 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 81B3F22A1AC9CA5300A92677 /* Localizable.strings */; }; + F5E3229B1B549C2C00E319F9 /* FacebookAuthenticationProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F5E3229A1B549C2C00E319F9 /* FacebookAuthenticationProviderTests.m */; }; + F5E3229D1B5583A800E319F9 /* FacebookUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F5E3229C1B5583A800E319F9 /* FacebookUtilsTests.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 81CB98CD1AB7905D00136FA5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2AAC07D0554694100DB518D; + remoteInfo = "ParseFacebookUtils_v4-iOS"; + }; + F52CD6191B58311F0051AB86 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F535C73A1B54B4A800A7D81E; + remoteInfo = ParseFacebookTestApplicationV4; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 4A9A9501200D144F005D8F4B /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A9503200D1462005D8F4B /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA42200BE19A00AA7479 /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA45200BE26C00AA7479 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA46200BE26C00AA7479 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA49200BE27D00AA7479 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA4A200BE27D00AA7479 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA51200BED7A00AA7479 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA52200BED7A00AA7479 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA75200C020E00AA7479 /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA90200C022300AA7479 /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA92200C025100AA7479 /* ParseFacebookUtilsV4-iOS-Dynamic.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseFacebookUtilsV4-iOS-Dynamic.xcconfig"; sourceTree = ""; }; + 4AAEAA93200C026300AA7479 /* ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig"; sourceTree = ""; }; + 4AAEAA96200C0E6F00AA7479 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA97200C0E6F00AA7479 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA9A200C0E7D00AA7479 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA9B200C0E7D00AA7479 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA9E200C0E8900AA7479 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAA9F200C0E8900AA7479 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAAA0200C0FC500AA7479 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAA2200C0FCC00AA7479 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 4AAEAAA4200C0FD200AA7479 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAA6200C0FDC00AA7479 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAA8200C0FE100AA7479 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAAA200C0FEC00AA7479 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAAC200C0FF300AA7479 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAAE200C101100AA7479 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAB0200C101700AA7479 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 4AAEAAB2200C102100AA7479 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAB4200C102600AA7479 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAB6200C103000AA7479 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAAB8200C103500AA7479 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + 4AAEAABA200C103B00AA7479 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + 8121EA9F1D39862400AC0B02 /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; + 8121EAA11D39862400AC0B02 /* iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = iOS.xcconfig; sourceTree = ""; }; + 8121EAA21D39862400AC0B02 /* macOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = ""; }; + 8121EAA31D39862400AC0B02 /* tvOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = tvOS.xcconfig; sourceTree = ""; }; + 8121EAA41D39862400AC0B02 /* watchOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = watchOS.xcconfig; sourceTree = ""; }; + 8121EAA61D39862400AC0B02 /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = ""; }; + 8121EAA71D39862400AC0B02 /* DynamicFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DynamicFramework.xcconfig; sourceTree = ""; }; + 8121EAA81D39862400AC0B02 /* LogicTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = LogicTests.xcconfig; sourceTree = ""; }; + 8121EAA91D39862400AC0B02 /* StaticFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticFramework.xcconfig; sourceTree = ""; }; + 8121EAAB1D39862400AC0B02 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 8121EAAC1D39862400AC0B02 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8121EAAD1D39862400AC0B02 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 813DFC841AB2510300F25A08 /* PFFacebookUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFacebookUtils.h; sourceTree = ""; }; + 813DFC851AB2510300F25A08 /* PFFacebookUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFacebookUtils.m; sourceTree = ""; }; + 813DFC921AB2515A00F25A08 /* Bolts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Bolts.framework; path = Vendor/Bolts.framework; sourceTree = ""; }; + 813DFC941AB251F700F25A08 /* Parse.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 815FC5971BBF74890006AF6E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 815FC5991BBF74890006AF6E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 81643D761AB79ABD00DD3E65 /* PFFacebookUtils_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PFFacebookUtils_Private.h; path = Internal/PFFacebookUtils_Private.h; sourceTree = ""; }; + 81930A3C1BBE1A0600A5E4BB /* PFFacebookTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFacebookTestCase.h; sourceTree = ""; }; + 81930A3D1BBE1A0600A5E4BB /* PFFacebookTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFacebookTestCase.m; sourceTree = ""; }; + 81B3F22A1AC9CA5300A92677 /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/Localizable.strings; sourceTree = SOURCE_ROOT; }; + 81CB98C61AB7905D00136FA5 /* ParseFacebookUtilsV4-UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ParseFacebookUtilsV4-UnitTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 81CB98DC1AB7921C00136FA5 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.2.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; }; + 81CB98DE1AB7922600136FA5 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.2.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; + 81CB98E01AB7922C00136FA5 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.2.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + 81DA692F1AB25A36008C3B7F /* FBSDKCoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FBSDKCoreKit.framework; path = Vendor/FBSDKCoreKit.framework; sourceTree = ""; }; + 81DA69301AB25A36008C3B7F /* FBSDKLoginKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FBSDKLoginKit.framework; path = Vendor/FBSDKLoginKit.framework; sourceTree = ""; }; + 81E35FD21BAA6F8400348526 /* PFFacebookPrivateUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFacebookPrivateUtilities.h; sourceTree = ""; }; + 81E35FD31BAA6F8400348526 /* PFFacebookPrivateUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFacebookPrivateUtilities.m; sourceTree = ""; }; + 81E41FB51C178BA200F5BF3F /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; + 81E41FB61C178BA200F5BF3F /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; + 81EA09BC1C178D0600B0F875 /* Bolts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Bolts.framework; sourceTree = ""; }; + 81EA09BD1C178D0600B0F875 /* FBSDKCoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FBSDKCoreKit.framework; sourceTree = ""; }; + 81EA09BE1C178D0600B0F875 /* FBSDKTVOSKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FBSDKTVOSKit.framework; sourceTree = ""; }; + 81EA09BF1C178D0600B0F875 /* Parse.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Parse.framework; sourceTree = ""; }; + 81EA09C61C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFacebookMobileAuthenticationProvider.h; sourceTree = ""; }; + 81EA09C71C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFacebookMobileAuthenticationProvider.m; sourceTree = ""; }; + 81EA09C81C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFacebookMobileAuthenticationProvider_Private.h; sourceTree = ""; }; + 81EA09C91C178DED00B0F875 /* PFFacebookAuthenticationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFacebookAuthenticationProvider.h; sourceTree = ""; }; + 81EA09CA1C178DED00B0F875 /* PFFacebookAuthenticationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFacebookAuthenticationProvider.m; sourceTree = ""; }; + 81EA09CC1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFacebookDeviceAuthenticationProvider.h; sourceTree = ""; }; + 81EA09CD1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFacebookDeviceAuthenticationProvider.m; sourceTree = ""; }; + 81EDD4B41B58AC7D002F69C0 /* ParseFacebookUtilsV4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParseFacebookUtilsV4.h; sourceTree = ""; }; + 81FE7F721C1778FC00E6BD34 /* ParseFacebookUtilsV4-tvOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseFacebookUtilsV4-tvOS.xcconfig"; sourceTree = ""; }; + 81FE7F8B1C17790400E6BD34 /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D2AAC07E0554694100DB518D /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F52CD64A1B5838560051AB86 /* ParseFacebookUtilsV4-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseFacebookUtilsV4-iOS.xcconfig"; sourceTree = ""; }; + F52CD64B1B5838620051AB86 /* ParseFacebookUtilsV4-UnitTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseFacebookUtilsV4-UnitTests.xcconfig"; sourceTree = ""; }; + F52CD64C1B58386F0051AB86 /* ParseFacebookTestApplicationV4.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ParseFacebookTestApplicationV4.xcconfig; sourceTree = ""; }; + F535C73B1B54B4A800A7D81E /* ParseFacebookTestApplicationV4.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ParseFacebookTestApplicationV4.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F5E3229A1B549C2C00E319F9 /* FacebookAuthenticationProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FacebookAuthenticationProviderTests.m; sourceTree = ""; }; + F5E3229C1B5583A800E319F9 /* FacebookUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FacebookUtilsTests.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4AAEAA6B200C020E00AA7479 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAAAD200C0FF300AA7479 /* UIKit.framework in Frameworks */, + 4AAEAAAB200C0FEC00AA7479 /* AudioToolbox.framework in Frameworks */, + 4AAEAAA9200C0FE100AA7479 /* CoreLocation.framework in Frameworks */, + 4AAEAAA7200C0FDC00AA7479 /* Security.framework in Frameworks */, + 4AAEAAA5200C0FD200AA7479 /* StoreKit.framework in Frameworks */, + 4AAEAAA3200C0FCC00AA7479 /* libsqlite3.tbd in Frameworks */, + 4AAEAAA1200C0FC500AA7479 /* SystemConfiguration.framework in Frameworks */, + 4AAEAA94200C0E6F00AA7479 /* Bolts.framework in Frameworks */, + 4AAEAA95200C0E6F00AA7479 /* Parse.framework in Frameworks */, + 4AAEAA6E200C020E00AA7479 /* FBSDKCoreKit.framework in Frameworks */, + 4AAEAA6F200C020E00AA7479 /* FBSDKLoginKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4AAEAA86200C022300AA7479 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAABB200C103B00AA7479 /* SystemConfiguration.framework in Frameworks */, + 4AAEAAB9200C103500AA7479 /* Security.framework in Frameworks */, + 4AAEAAB7200C103000AA7479 /* CoreLocation.framework in Frameworks */, + 4AAEAAB5200C102600AA7479 /* StoreKit.framework in Frameworks */, + 4AAEAAB3200C102100AA7479 /* UIKit.framework in Frameworks */, + 4AAEAAB1200C101700AA7479 /* libsqlite3.tbd in Frameworks */, + 4AAEAAAF200C101100AA7479 /* AudioToolbox.framework in Frameworks */, + 4AAEAA9C200C0E8900AA7479 /* Bolts.framework in Frameworks */, + 4AAEAA9D200C0E8900AA7479 /* Parse.framework in Frameworks */, + 4AAEAA89200C022300AA7479 /* FBSDKCoreKit.framework in Frameworks */, + 4AAEAA8A200C022300AA7479 /* FBSDKTVOSKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81CB98C31AB7905D00136FA5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA41200BE19A00AA7479 /* OCMock.framework in Frameworks */, + 81CB98E11AB7922C00136FA5 /* SystemConfiguration.framework in Frameworks */, + 81CB98DF1AB7922600136FA5 /* AudioToolbox.framework in Frameworks */, + 81CB98DD1AB7921C00136FA5 /* libsqlite3.dylib in Frameworks */, + 81CB98CC1AB7905D00136FA5 /* ParseFacebookUtilsV4.framework in Frameworks */, + 81CB98D81AB791FB00136FA5 /* Bolts.framework in Frameworks */, + 81CB98D91AB7920700136FA5 /* FBSDKCoreKit.framework in Frameworks */, + 81CB98DA1AB7920700136FA5 /* FBSDKLoginKit.framework in Frameworks */, + 81CB98DB1AB7920E00136FA5 /* Parse.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81FE7F811C17790400E6BD34 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA4F200BED7A00AA7479 /* Bolts.framework in Frameworks */, + 4AAEAA50200BED7A00AA7479 /* Parse.framework in Frameworks */, + 81EA09C11C178D0600B0F875 /* FBSDKCoreKit.framework in Frameworks */, + 81EA09C21C178D0600B0F875 /* FBSDKTVOSKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A9A9502200D1462005D8F4B /* Parse.framework in Frameworks */, + 4AAEAA43200BE26C00AA7479 /* Bolts.framework in Frameworks */, + 81DA69311AB25A36008C3B7F /* FBSDKCoreKit.framework in Frameworks */, + 81DA69321AB25A37008C3B7F /* FBSDKLoginKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F535C7381B54B4A800A7D81E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC07E0554694100DB518D /* ParseFacebookUtilsV4.framework */, + 81CB98C61AB7905D00136FA5 /* ParseFacebookUtilsV4-UnitTests.xctest */, + F535C73B1B54B4A800A7D81E /* ParseFacebookTestApplicationV4.app */, + 81FE7F8B1C17790400E6BD34 /* ParseFacebookUtilsV4.framework */, + 4AAEAA75200C020E00AA7479 /* ParseFacebookUtilsV4.framework */, + 4AAEAA90200C022300AA7479 /* ParseFacebookUtilsV4.framework */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* Breakpad */ = { + isa = PBXGroup; + children = ( + F52CD63A1B58383C0051AB86 /* Configurations */, + 813DFC7E1AB2510300F25A08 /* ParseFacebookUtilsV4 */, + 813DFC961AB2524C00F25A08 /* Resources */, + 81CB98D21AB7906D00136FA5 /* Tests */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + indentWidth = 4; + name = Breakpad; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4A9A9503200D1462005D8F4B /* Parse.framework */, + 4A9A9501200D144F005D8F4B /* Parse.framework */, + 4AAEAABA200C103B00AA7479 /* SystemConfiguration.framework */, + 4AAEAAB8200C103500AA7479 /* Security.framework */, + 4AAEAAB6200C103000AA7479 /* CoreLocation.framework */, + 4AAEAAB4200C102600AA7479 /* StoreKit.framework */, + 4AAEAAB2200C102100AA7479 /* UIKit.framework */, + 4AAEAAB0200C101700AA7479 /* libsqlite3.tbd */, + 4AAEAAAE200C101100AA7479 /* AudioToolbox.framework */, + 4AAEAAAC200C0FF300AA7479 /* UIKit.framework */, + 4AAEAAAA200C0FEC00AA7479 /* AudioToolbox.framework */, + 4AAEAAA8200C0FE100AA7479 /* CoreLocation.framework */, + 4AAEAAA6200C0FDC00AA7479 /* Security.framework */, + 4AAEAAA4200C0FD200AA7479 /* StoreKit.framework */, + 4AAEAAA2200C0FCC00AA7479 /* libsqlite3.tbd */, + 4AAEAAA0200C0FC500AA7479 /* SystemConfiguration.framework */, + 4AAEAA9E200C0E8900AA7479 /* Bolts.framework */, + 4AAEAA9F200C0E8900AA7479 /* Parse.framework */, + 4AAEAA9A200C0E7D00AA7479 /* Bolts.framework */, + 4AAEAA9B200C0E7D00AA7479 /* Parse.framework */, + 4AAEAA96200C0E6F00AA7479 /* Bolts.framework */, + 4AAEAA97200C0E6F00AA7479 /* Parse.framework */, + 4AAEAA51200BED7A00AA7479 /* Bolts.framework */, + 4AAEAA52200BED7A00AA7479 /* Parse.framework */, + 4AAEAA49200BE27D00AA7479 /* Bolts.framework */, + 4AAEAA4A200BE27D00AA7479 /* Parse.framework */, + 4AAEAA45200BE26C00AA7479 /* Bolts.framework */, + 4AAEAA46200BE26C00AA7479 /* Parse.framework */, + 4AAEAA42200BE19A00AA7479 /* OCMock.framework */, + 813DFC8F1AB2513D00F25A08 /* User Frameworks */, + 813DFC8E1AB2513300F25A08 /* System Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8121EA9E1D39862400AC0B02 /* Shared */ = { + isa = PBXGroup; + children = ( + 8121EA9F1D39862400AC0B02 /* Common.xcconfig */, + 8121EAA01D39862400AC0B02 /* Platform */, + 8121EAA51D39862400AC0B02 /* Product */, + 8121EAAA1D39862400AC0B02 /* Project */, + 8121EAAD1D39862400AC0B02 /* Warnings.xcconfig */, + ); + path = Shared; + sourceTree = ""; + }; + 8121EAA01D39862400AC0B02 /* Platform */ = { + isa = PBXGroup; + children = ( + 8121EAA11D39862400AC0B02 /* iOS.xcconfig */, + 8121EAA21D39862400AC0B02 /* macOS.xcconfig */, + 8121EAA31D39862400AC0B02 /* tvOS.xcconfig */, + 8121EAA41D39862400AC0B02 /* watchOS.xcconfig */, + ); + path = Platform; + sourceTree = ""; + }; + 8121EAA51D39862400AC0B02 /* Product */ = { + isa = PBXGroup; + children = ( + 8121EAA61D39862400AC0B02 /* Application.xcconfig */, + 8121EAA71D39862400AC0B02 /* DynamicFramework.xcconfig */, + 8121EAA81D39862400AC0B02 /* LogicTests.xcconfig */, + 8121EAA91D39862400AC0B02 /* StaticFramework.xcconfig */, + ); + path = Product; + sourceTree = ""; + }; + 8121EAAA1D39862400AC0B02 /* Project */ = { + isa = PBXGroup; + children = ( + 8121EAAB1D39862400AC0B02 /* Debug.xcconfig */, + 8121EAAC1D39862400AC0B02 /* Release.xcconfig */, + ); + path = Project; + sourceTree = ""; + }; + 813DFC7E1AB2510300F25A08 /* ParseFacebookUtilsV4 */ = { + isa = PBXGroup; + children = ( + 813DFC7F1AB2510300F25A08 /* Internal */, + 81EDD4B41B58AC7D002F69C0 /* ParseFacebookUtilsV4.h */, + 813DFC841AB2510300F25A08 /* PFFacebookUtils.h */, + 81643D761AB79ABD00DD3E65 /* PFFacebookUtils_Private.h */, + 813DFC851AB2510300F25A08 /* PFFacebookUtils.m */, + ); + name = ParseFacebookUtilsV4; + path = ParseFacebookUtils; + sourceTree = ""; + }; + 813DFC7F1AB2510300F25A08 /* Internal */ = { + isa = PBXGroup; + children = ( + 81EA09C41C178DED00B0F875 /* AuthenticationProvider */, + 81E35FD21BAA6F8400348526 /* PFFacebookPrivateUtilities.h */, + 81E35FD31BAA6F8400348526 /* PFFacebookPrivateUtilities.m */, + ); + path = Internal; + sourceTree = ""; + }; + 813DFC8E1AB2513300F25A08 /* System Frameworks */ = { + isa = PBXGroup; + children = ( + 81CB98E01AB7922C00136FA5 /* SystemConfiguration.framework */, + 81CB98DE1AB7922600136FA5 /* AudioToolbox.framework */, + 81CB98DC1AB7921C00136FA5 /* libsqlite3.dylib */, + ); + name = "System Frameworks"; + sourceTree = ""; + }; + 813DFC8F1AB2513D00F25A08 /* User Frameworks */ = { + isa = PBXGroup; + children = ( + 81EA09B91C178CEE00B0F875 /* iOS */, + 81EA09BB1C178D0600B0F875 /* tvOS */, + ); + name = "User Frameworks"; + sourceTree = ""; + }; + 813DFC961AB2524C00F25A08 /* Resources */ = { + isa = PBXGroup; + children = ( + 81E41FB51C178BA200F5BF3F /* Info-iOS.plist */, + 81E41FB61C178BA200F5BF3F /* Info-tvOS.plist */, + 81B3F22A1AC9CA5300A92677 /* Localizable.strings */, + ); + path = Resources; + sourceTree = ""; + }; + 815FC5951BBF74890006AF6E /* TestApplication */ = { + isa = PBXGroup; + children = ( + 815FC5961BBF74890006AF6E /* Classes */, + 815FC5981BBF74890006AF6E /* Resources */, + ); + path = TestApplication; + sourceTree = ""; + }; + 815FC5961BBF74890006AF6E /* Classes */ = { + isa = PBXGroup; + children = ( + 815FC5971BBF74890006AF6E /* main.m */, + ); + path = Classes; + sourceTree = ""; + }; + 815FC5981BBF74890006AF6E /* Resources */ = { + isa = PBXGroup; + children = ( + 815FC5991BBF74890006AF6E /* Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 81930A3A1BBE1A0600A5E4BB /* Other */ = { + isa = PBXGroup; + children = ( + 81930A3B1BBE1A0600A5E4BB /* TestCase */, + ); + path = Other; + sourceTree = ""; + }; + 81930A3B1BBE1A0600A5E4BB /* TestCase */ = { + isa = PBXGroup; + children = ( + 81930A3C1BBE1A0600A5E4BB /* PFFacebookTestCase.h */, + 81930A3D1BBE1A0600A5E4BB /* PFFacebookTestCase.m */, + ); + path = TestCase; + sourceTree = ""; + }; + 81CB98D21AB7906D00136FA5 /* Tests */ = { + isa = PBXGroup; + children = ( + F535C7311B54B45400A7D81E /* Unit */, + 81930A3A1BBE1A0600A5E4BB /* Other */, + 815FC5951BBF74890006AF6E /* TestApplication */, + ); + path = Tests; + sourceTree = ""; + }; + 81EA09B91C178CEE00B0F875 /* iOS */ = { + isa = PBXGroup; + children = ( + 81DA692F1AB25A36008C3B7F /* FBSDKCoreKit.framework */, + 81DA69301AB25A36008C3B7F /* FBSDKLoginKit.framework */, + 813DFC941AB251F700F25A08 /* Parse.framework */, + 813DFC921AB2515A00F25A08 /* Bolts.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 81EA09BB1C178D0600B0F875 /* tvOS */ = { + isa = PBXGroup; + children = ( + 81EA09BC1C178D0600B0F875 /* Bolts.framework */, + 81EA09BF1C178D0600B0F875 /* Parse.framework */, + 81EA09BD1C178D0600B0F875 /* FBSDKCoreKit.framework */, + 81EA09BE1C178D0600B0F875 /* FBSDKTVOSKit.framework */, + ); + name = tvOS; + path = Vendor/tvOS; + sourceTree = ""; + }; + 81EA09C41C178DED00B0F875 /* AuthenticationProvider */ = { + isa = PBXGroup; + children = ( + 81EA09C91C178DED00B0F875 /* PFFacebookAuthenticationProvider.h */, + 81EA09CA1C178DED00B0F875 /* PFFacebookAuthenticationProvider.m */, + 81EA09C51C178DED00B0F875 /* iOS */, + 81EA09CB1C178DED00B0F875 /* tvOS */, + ); + path = AuthenticationProvider; + sourceTree = ""; + }; + 81EA09C51C178DED00B0F875 /* iOS */ = { + isa = PBXGroup; + children = ( + 81EA09C61C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.h */, + 81EA09C71C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.m */, + 81EA09C81C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider_Private.h */, + ); + path = iOS; + sourceTree = ""; + }; + 81EA09CB1C178DED00B0F875 /* tvOS */ = { + isa = PBXGroup; + children = ( + 81EA09CC1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.h */, + 81EA09CD1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.m */, + ); + path = tvOS; + sourceTree = ""; + }; + F52CD63A1B58383C0051AB86 /* Configurations */ = { + isa = PBXGroup; + children = ( + F52CD64A1B5838560051AB86 /* ParseFacebookUtilsV4-iOS.xcconfig */, + 4AAEAA92200C025100AA7479 /* ParseFacebookUtilsV4-iOS-Dynamic.xcconfig */, + 81FE7F721C1778FC00E6BD34 /* ParseFacebookUtilsV4-tvOS.xcconfig */, + 4AAEAA93200C026300AA7479 /* ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig */, + F52CD64B1B5838620051AB86 /* ParseFacebookUtilsV4-UnitTests.xcconfig */, + F52CD64C1B58386F0051AB86 /* ParseFacebookTestApplicationV4.xcconfig */, + 8121EA9E1D39862400AC0B02 /* Shared */, + ); + path = Configurations; + sourceTree = ""; + }; + F535C7311B54B45400A7D81E /* Unit */ = { + isa = PBXGroup; + children = ( + F5E3229A1B549C2C00E319F9 /* FacebookAuthenticationProviderTests.m */, + F5E3229C1B5583A800E319F9 /* FacebookUtilsTests.m */, + ); + path = Unit; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 4AAEAA5E200C020E00AA7479 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA5F200C020E00AA7479 /* PFFacebookUtils.h in Headers */, + 4AAEAA60200C020E00AA7479 /* PFFacebookAuthenticationProvider.h in Headers */, + 4AAEAA61200C020E00AA7479 /* PFFacebookMobileAuthenticationProvider.h in Headers */, + 4AAEAA62200C020E00AA7479 /* ParseFacebookUtilsV4.h in Headers */, + 4AAEAA63200C020E00AA7479 /* PFFacebookPrivateUtilities.h in Headers */, + 4AAEAA64200C020E00AA7479 /* PFFacebookUtils_Private.h in Headers */, + 4AAEAA65200C020E00AA7479 /* PFFacebookMobileAuthenticationProvider_Private.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4AAEAA7A200C022300AA7479 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA7B200C022300AA7479 /* PFFacebookUtils.h in Headers */, + 4AAEAA7C200C022300AA7479 /* PFFacebookDeviceAuthenticationProvider.h in Headers */, + 4AAEAA7D200C022300AA7479 /* PFFacebookAuthenticationProvider.h in Headers */, + 4AAEAA7E200C022300AA7479 /* ParseFacebookUtilsV4.h in Headers */, + 4AAEAA7F200C022300AA7479 /* PFFacebookPrivateUtilities.h in Headers */, + 4AAEAA80200C022300AA7479 /* PFFacebookUtils_Private.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81FE7F761C17790400E6BD34 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 81FE7F771C17790400E6BD34 /* PFFacebookUtils.h in Headers */, + 81EA09D91C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.h in Headers */, + 81EA09D51C178DED00B0F875 /* PFFacebookAuthenticationProvider.h in Headers */, + 81FE7F781C17790400E6BD34 /* ParseFacebookUtilsV4.h in Headers */, + 81FE7F791C17790400E6BD34 /* PFFacebookPrivateUtilities.h in Headers */, + 81FE7F7B1C17790400E6BD34 /* PFFacebookUtils_Private.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 813DFC8A1AB2510300F25A08 /* PFFacebookUtils.h in Headers */, + 81EA09D41C178DED00B0F875 /* PFFacebookAuthenticationProvider.h in Headers */, + 81EA09CE1C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.h in Headers */, + 81EDD4B51B58AC7D002F69C0 /* ParseFacebookUtilsV4.h in Headers */, + 81E35FD41BAA6F8400348526 /* PFFacebookPrivateUtilities.h in Headers */, + 81643D771AB79ABD00DD3E65 /* PFFacebookUtils_Private.h in Headers */, + 81EA09D21C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider_Private.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 4AAEAA5B200C020E00AA7479 /* ParseFacebookUtilsV4-iOS-Dynamic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4AAEAA72200C020E00AA7479 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-iOS-Dynamic" */; + buildPhases = ( + 4AAEAA5C200C020E00AA7479 /* Fetch latest Dependencies */, + 4AAEAA5D200C020E00AA7479 /* Generate Localizable Strings */, + 4AAEAA5E200C020E00AA7479 /* Headers */, + 4AAEAA66200C020E00AA7479 /* Sources */, + 4AAEAA6B200C020E00AA7479 /* Frameworks */, + 4AAEAA70200C020E00AA7479 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ParseFacebookUtilsV4-iOS-Dynamic"; + productName = Breakpad; + productReference = 4AAEAA75200C020E00AA7479 /* ParseFacebookUtilsV4.framework */; + productType = "com.apple.product-type.framework"; + }; + 4AAEAA77200C022300AA7479 /* ParseFacebookUtilsV4-tvOS-Dynamic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4AAEAA8D200C022300AA7479 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-tvOS-Dynamic" */; + buildPhases = ( + 4AAEAA78200C022300AA7479 /* Fetch latest Dependencies */, + 4AAEAA79200C022300AA7479 /* Generate Localizable Strings */, + 4AAEAA7A200C022300AA7479 /* Headers */, + 4AAEAA81200C022300AA7479 /* Sources */, + 4AAEAA86200C022300AA7479 /* Frameworks */, + 4AAEAA8B200C022300AA7479 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ParseFacebookUtilsV4-tvOS-Dynamic"; + productName = Breakpad; + productReference = 4AAEAA90200C022300AA7479 /* ParseFacebookUtilsV4.framework */; + productType = "com.apple.product-type.framework"; + }; + 81CB98C51AB7905D00136FA5 /* ParseFacebookUtilsV4-UnitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81CB98D11AB7905D00136FA5 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-UnitTests" */; + buildPhases = ( + 81CB98C21AB7905D00136FA5 /* Sources */, + 81CB98C31AB7905D00136FA5 /* Frameworks */, + 81CB98C41AB7905D00136FA5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F52CD61A1B58311F0051AB86 /* PBXTargetDependency */, + 81CB98CE1AB7905D00136FA5 /* PBXTargetDependency */, + ); + name = "ParseFacebookUtilsV4-UnitTests"; + productName = "ParseFacebookUtilsV4-Tests"; + productReference = 81CB98C61AB7905D00136FA5 /* ParseFacebookUtilsV4-UnitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 81FE7F731C17790400E6BD34 /* ParseFacebookUtilsV4-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81FE7F881C17790400E6BD34 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-tvOS" */; + buildPhases = ( + 81FE7F741C17790400E6BD34 /* Fetch latest Dependencies */, + 81FE7F751C17790400E6BD34 /* Generate Localizable Strings */, + 81FE7F761C17790400E6BD34 /* Headers */, + 81FE7F7D1C17790400E6BD34 /* Sources */, + 81FE7F811C17790400E6BD34 /* Frameworks */, + 81FE7F861C17790400E6BD34 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ParseFacebookUtilsV4-tvOS"; + productName = Breakpad; + productReference = 81FE7F8B1C17790400E6BD34 /* ParseFacebookUtilsV4.framework */; + productType = "com.apple.product-type.framework"; + }; + D2AAC07D0554694100DB518D /* ParseFacebookUtilsV4-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-iOS" */; + buildPhases = ( + 81F035FB1BC332C00055BFDE /* Fetch latest Dependencies */, + 81B3F2291AC9CA2600A92677 /* Generate Localizable Strings */, + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + 8139B1341A7BF6B5002BEF84 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ParseFacebookUtilsV4-iOS"; + productName = Breakpad; + productReference = D2AAC07E0554694100DB518D /* ParseFacebookUtilsV4.framework */; + productType = "com.apple.product-type.framework"; + }; + F535C73A1B54B4A800A7D81E /* ParseFacebookTestApplicationV4 */ = { + isa = PBXNativeTarget; + buildConfigurationList = F535C75B1B54B4A800A7D81E /* Build configuration list for PBXNativeTarget "ParseFacebookTestApplicationV4" */; + buildPhases = ( + F535C7371B54B4A800A7D81E /* Sources */, + F535C7381B54B4A800A7D81E /* Frameworks */, + F535C7391B54B4A800A7D81E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ParseFacebookTestApplicationV4; + productName = ParseFacebookTestApplicationV4; + productReference = F535C73B1B54B4A800A7D81E /* ParseFacebookTestApplicationV4.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = PF; + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Parse, LLC"; + TargetAttributes = { + 81CB98C51AB7905D00136FA5 = { + CreatedOnToolsVersion = 6.2; + TestTargetID = F535C73A1B54B4A800A7D81E; + }; + F535C73A1B54B4A800A7D81E = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "ParseFacebookUtils" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + da, + de, + es, + fr, + it, + ja, + nl, + no, + sl, + sv, + tr, + en, + Base, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* ParseFacebookUtilsV4-iOS */, + 81FE7F731C17790400E6BD34 /* ParseFacebookUtilsV4-tvOS */, + 4AAEAA5B200C020E00AA7479 /* ParseFacebookUtilsV4-iOS-Dynamic */, + 4AAEAA77200C022300AA7479 /* ParseFacebookUtilsV4-tvOS-Dynamic */, + 81CB98C51AB7905D00136FA5 /* ParseFacebookUtilsV4-UnitTests */, + F535C73A1B54B4A800A7D81E /* ParseFacebookTestApplicationV4 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4AAEAA70200C020E00AA7479 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA71200C020E00AA7479 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4AAEAA8B200C022300AA7479 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA8C200C022300AA7479 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8139B1341A7BF6B5002BEF84 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81B3F22B1AC9CA5300A92677 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81CB98C41AB7905D00136FA5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81FE7F861C17790400E6BD34 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81FE7F871C17790400E6BD34 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F535C7391B54B4A800A7D81E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 4AAEAA5C200C020E00AA7479 /* Fetch latest Dependencies */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Fetch latest Dependencies"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ ! -d $SRCROOT/Vendor ]; then\n mkdir $SRCROOT/Vendor\nfi\n\ncd $SRCROOT/Vendor\n\nif [[ ! -d \"FBSDKCoreKit.framework\" || ! -d \"FBSDKLoginKit.framework\" ]]; then\n ARCHIVE_NAME=FBSDK.zip\n\n ARCHIVE_URL=\"https://origincache.facebook.com/developers/resources/?id=facebook-ios-sdk-current.zip\"\n curl $ARCHIVE_URL -o $ARCHIVE_NAME\n\n unzip $ARCHIVE_NAME -d fbsdk\n mv fbsdk/FBSDKCoreKit.framework .\n mv fbsdk/FBSDKLoginKit.framework .\n\n rm $ARCHIVE_NAME\n rm -r fbsdk\nfi\n"; + showEnvVarsInLog = 0; + }; + 4AAEAA5D200C020E00AA7479 /* Generate Localizable Strings */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Generate Localizable Strings"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Generate localizable strings\nfind $PROJECT_DIR -name '*.m' -print0 | xargs -0 genstrings -q -o $PROJECT_DIR/Resources\necho \"Finished converting images\""; + }; + 4AAEAA78200C022300AA7479 /* Fetch latest Dependencies */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Fetch latest Dependencies"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ ! -d $SRCROOT/Vendor/tvOS ]; then\n mkdir $SRCROOT/Vendor/tvOS\nfi\n\ncd $SRCROOT/Vendor/tvOS\n\nif [[ ! -d \"Parse.framework\" || ! -d \"Bolts.framework\" ]]; then\n ARCHIVE_NAME=Parse-tvOS.zip\n\n LATEST_TAG=$(bash -l -c \"bundle exec pod spec cat Parse\" | grep version | head -n 1 | cut -d '\"' -f 4)\n ARCHIVE_URL=\"https://github.com/ParsePlatform/Parse-SDK-iOS-OSX/releases/download/${LATEST_TAG}/${ARCHIVE_NAME}\"\n curl -L $ARCHIVE_URL -o $ARCHIVE_NAME\n\n unzip $ARCHIVE_NAME\n rm $ARCHIVE_NAME\nfi\n\nif [[ ! -d \"FBSDKCoreKit.framework\" || ! -d \"FBSDKTVOSKit.framework\" ]]; then\n ARCHIVE_NAME=FBSDK.zip\n\n ARCHIVE_URL=\"https://origincache.facebook.com/developers/resources/?id=FacebookSDKs-tvOS-current.zip\"\n curl $ARCHIVE_URL -o $ARCHIVE_NAME\n\n unzip $ARCHIVE_NAME -d fbsdk\n mv fbsdk/FBSDKCoreKit.framework .\n mv fbsdk/FBSDKTVOSKit.framework .\n\n rm $ARCHIVE_NAME\n rm -r fbsdk\nfi\n"; + showEnvVarsInLog = 0; + }; + 4AAEAA79200C022300AA7479 /* Generate Localizable Strings */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Generate Localizable Strings"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Generate localizable strings\nfind $PROJECT_DIR -name '*.m' -print0 | xargs -0 genstrings -q -o $PROJECT_DIR/Resources\necho \"Finished converting images\""; + }; + 81B3F2291AC9CA2600A92677 /* Generate Localizable Strings */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Generate Localizable Strings"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Generate localizable strings\nfind $PROJECT_DIR -name '*.m' -print0 | xargs -0 genstrings -q -o $PROJECT_DIR/Resources\necho \"Finished converting images\""; + }; + 81F035FB1BC332C00055BFDE /* Fetch latest Dependencies */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Fetch latest Dependencies"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ ! -d $SRCROOT/Vendor ]; then\n mkdir $SRCROOT/Vendor\nfi\n\ncd $SRCROOT/Vendor\n\nif [[ ! -d \"FBSDKCoreKit.framework\" || ! -d \"FBSDKLoginKit.framework\" ]]; then\n ARCHIVE_NAME=FBSDK.zip\n\n ARCHIVE_URL=\"https://origincache.facebook.com/developers/resources/?id=facebook-ios-sdk-current.zip\"\n curl $ARCHIVE_URL -o $ARCHIVE_NAME\n\n unzip $ARCHIVE_NAME -d fbsdk\n mv fbsdk/FBSDKCoreKit.framework .\n mv fbsdk/FBSDKLoginKit.framework .\n\n rm $ARCHIVE_NAME\n rm -r fbsdk\nfi\n"; + showEnvVarsInLog = 0; + }; + 81FE7F741C17790400E6BD34 /* Fetch latest Dependencies */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Fetch latest Dependencies"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ ! -d $SRCROOT/Vendor/tvOS ]; then\n mkdir $SRCROOT/Vendor/tvOS\nfi\n\ncd $SRCROOT/Vendor/tvOS\n\nif [[ ! -d \"FBSDKCoreKit.framework\" || ! -d \"FBSDKTVOSKit.framework\" ]]; then\n ARCHIVE_NAME=FBSDK.zip\n\n ARCHIVE_URL=\"https://origincache.facebook.com/developers/resources/?id=FacebookSDKs-tvOS-current.zip\"\n curl $ARCHIVE_URL -o $ARCHIVE_NAME\n\n unzip $ARCHIVE_NAME -d fbsdk\n mv fbsdk/FBSDKCoreKit.framework .\n mv fbsdk/FBSDKTVOSKit.framework .\n\n rm $ARCHIVE_NAME\n rm -r fbsdk\nfi\n"; + showEnvVarsInLog = 0; + }; + 81FE7F751C17790400E6BD34 /* Generate Localizable Strings */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Generate Localizable Strings"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Generate localizable strings\nfind $PROJECT_DIR -name '*.m' -print0 | xargs -0 genstrings -q -o $PROJECT_DIR/Resources\necho \"Finished converting images\""; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4AAEAA66200C020E00AA7479 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA67200C020E00AA7479 /* PFFacebookAuthenticationProvider.m in Sources */, + 4AAEAA68200C020E00AA7479 /* PFFacebookUtils.m in Sources */, + 4AAEAA69200C020E00AA7479 /* PFFacebookMobileAuthenticationProvider.m in Sources */, + 4AAEAA6A200C020E00AA7479 /* PFFacebookPrivateUtilities.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4AAEAA81200C022300AA7479 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAA82200C022300AA7479 /* PFFacebookDeviceAuthenticationProvider.m in Sources */, + 4AAEAA83200C022300AA7479 /* PFFacebookAuthenticationProvider.m in Sources */, + 4AAEAA84200C022300AA7479 /* PFFacebookUtils.m in Sources */, + 4AAEAA85200C022300AA7479 /* PFFacebookPrivateUtilities.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81CB98C21AB7905D00136FA5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81930A3E1BBE1A0600A5E4BB /* PFFacebookTestCase.m in Sources */, + F5E3229D1B5583A800E319F9 /* FacebookUtilsTests.m in Sources */, + F5E3229B1B549C2C00E319F9 /* FacebookAuthenticationProviderTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81FE7F7D1C17790400E6BD34 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81EA09DB1C178DED00B0F875 /* PFFacebookDeviceAuthenticationProvider.m in Sources */, + 81EA09D71C178DED00B0F875 /* PFFacebookAuthenticationProvider.m in Sources */, + 81FE7F7E1C17790400E6BD34 /* PFFacebookUtils.m in Sources */, + 81FE7F801C17790400E6BD34 /* PFFacebookPrivateUtilities.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81EA09D61C178DED00B0F875 /* PFFacebookAuthenticationProvider.m in Sources */, + 813DFC8B1AB2510300F25A08 /* PFFacebookUtils.m in Sources */, + 81EA09D01C178DED00B0F875 /* PFFacebookMobileAuthenticationProvider.m in Sources */, + 81E35FD51BAA6F8400348526 /* PFFacebookPrivateUtilities.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F535C7371B54B4A800A7D81E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 815FC59A1BBF74890006AF6E /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 81CB98CE1AB7905D00136FA5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2AAC07D0554694100DB518D /* ParseFacebookUtilsV4-iOS */; + targetProxy = 81CB98CD1AB7905D00136FA5 /* PBXContainerItemProxy */; + }; + F52CD61A1B58311F0051AB86 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F535C73A1B54B4A800A7D81E /* ParseFacebookTestApplicationV4 */; + targetProxy = F52CD6191B58311F0051AB86 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F52CD64A1B5838560051AB86 /* ParseFacebookUtilsV4-iOS.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F52CD64A1B5838560051AB86 /* ParseFacebookUtilsV4-iOS.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8121EAAB1D39862400AC0B02 /* Debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + PARSE_DIR = "$(PROJECT_DIR)/.."; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8121EAAC1D39862400AC0B02 /* Release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + PARSE_DIR = "$(PROJECT_DIR)/.."; + }; + name = Release; + }; + 4AAEAA73200C020E00AA7479 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4AAEAA92200C025100AA7479 /* ParseFacebookUtilsV4-iOS-Dynamic.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Debug; + }; + 4AAEAA74200C020E00AA7479 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4AAEAA92200C025100AA7479 /* ParseFacebookUtilsV4-iOS-Dynamic.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Release; + }; + 4AAEAA8E200C022300AA7479 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4AAEAA93200C026300AA7479 /* ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 4AAEAA8F200C022300AA7479 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4AAEAA93200C026300AA7479 /* ParseFacebookUtilsV4-tvOS-Dynamic.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 81CB98CF1AB7905D00136FA5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F52CD64B1B5838620051AB86 /* ParseFacebookUtilsV4-UnitTests.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 81CB98D01AB7905D00136FA5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F52CD64B1B5838620051AB86 /* ParseFacebookUtilsV4-UnitTests.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 81FE7F891C17790400E6BD34 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 81FE7F721C1778FC00E6BD34 /* ParseFacebookUtilsV4-tvOS.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 81FE7F8A1C17790400E6BD34 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 81FE7F721C1778FC00E6BD34 /* ParseFacebookUtilsV4-tvOS.xcconfig */; + buildSettings = { + }; + name = Release; + }; + F535C75C1B54B4A800A7D81E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F52CD64C1B58386F0051AB86 /* ParseFacebookTestApplicationV4.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Debug; + }; + F535C75D1B54B4A800A7D81E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F52CD64C1B58386F0051AB86 /* ParseFacebookTestApplicationV4.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "ParseFacebookUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4AAEAA72200C020E00AA7479 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-iOS-Dynamic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AAEAA73200C020E00AA7479 /* Debug */, + 4AAEAA74200C020E00AA7479 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4AAEAA8D200C022300AA7479 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-tvOS-Dynamic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AAEAA8E200C022300AA7479 /* Debug */, + 4AAEAA8F200C022300AA7479 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 81CB98D11AB7905D00136FA5 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-UnitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81CB98CF1AB7905D00136FA5 /* Debug */, + 81CB98D01AB7905D00136FA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 81FE7F881C17790400E6BD34 /* Build configuration list for PBXNativeTarget "ParseFacebookUtilsV4-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81FE7F891C17790400E6BD34 /* Debug */, + 81FE7F8A1C17790400E6BD34 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F535C75B1B54B4A800A7D81E /* Build configuration list for PBXNativeTarget "ParseFacebookTestApplicationV4" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F535C75C1B54B4A800A7D81E /* Debug */, + F535C75D1B54B4A800A7D81E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..04cd5dae9 --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ParseStarterProject/iOS/ParseStarterProject-Swift/ParseStarterProject-Swift.xcodeproj/xcshareddata/xcschemes/ParseStarterProject-Swift.xcscheme b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-iOS-Dynamic.xcscheme similarity index 50% rename from ParseStarterProject/iOS/ParseStarterProject-Swift/ParseStarterProject-Swift.xcodeproj/xcshareddata/xcschemes/ParseStarterProject-Swift.xcscheme rename to ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-iOS-Dynamic.xcscheme index 68e961705..a8022433e 100644 --- a/ParseStarterProject/iOS/ParseStarterProject-Swift/ParseStarterProject-Swift.xcodeproj/xcshareddata/xcschemes/ParseStarterProject-Swift.xcscheme +++ b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-iOS-Dynamic.xcscheme @@ -1,10 +1,10 @@ + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> - - - - + BlueprintIdentifier = "4AAEAA5B200C020E00AA7479" + BuildableName = "ParseFacebookUtilsV4.framework" + BlueprintName = "ParseFacebookUtilsV4-iOS-Dynamic" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> @@ -44,15 +30,6 @@ shouldUseLaunchSchemeArgsEnv = "YES"> - - - - @@ -67,16 +44,15 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + + BlueprintIdentifier = "4AAEAA5B200C020E00AA7479" + BuildableName = "ParseFacebookUtilsV4.framework" + BlueprintName = "ParseFacebookUtilsV4-iOS-Dynamic" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> - + @@ -86,16 +62,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - + + BlueprintIdentifier = "4AAEAA5B200C020E00AA7479" + BuildableName = "ParseFacebookUtilsV4.framework" + BlueprintName = "ParseFacebookUtilsV4-iOS-Dynamic" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> - + diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseWatchStarter-iOS.xcscheme b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-iOS.xcscheme similarity index 53% rename from ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseWatchStarter-iOS.xcscheme rename to ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-iOS.xcscheme index c60f780fe..af5c45524 100644 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseWatchStarter-iOS.xcscheme +++ b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-iOS.xcscheme @@ -1,10 +1,10 @@ + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + BlueprintIdentifier = "D2AAC07D0554694100DB518D" + BuildableName = "ParseFacebookUtilsV4.framework" + BlueprintName = "ParseFacebookUtilsV4-iOS" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> - - - - + BlueprintIdentifier = "F535C73A1B54B4A800A7D81E" + BuildableName = "ParseFacebookTestApplicationV4.app" + BlueprintName = "ParseFacebookTestApplicationV4" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> + BlueprintIdentifier = "81CB98C51AB7905D00136FA5" + BuildableName = "ParseFacebookUtilsV4-UnitTests.xctest" + BlueprintName = "ParseFacebookUtilsV4-UnitTests" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> @@ -69,16 +55,27 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" language = "" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + + BlueprintIdentifier = "D2AAC07D0554694100DB518D" + BuildableName = "ParseFacebookUtilsV4.framework" + BlueprintName = "ParseFacebookUtilsV4-iOS" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> @@ -95,16 +92,15 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + + BlueprintIdentifier = "D2AAC07D0554694100DB518D" + BuildableName = "ParseFacebookUtilsV4.framework" + BlueprintName = "ParseFacebookUtilsV4-iOS" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> - + @@ -114,16 +110,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - + + BlueprintIdentifier = "D2AAC07D0554694100DB518D" + BuildableName = "ParseFacebookUtilsV4.framework" + BlueprintName = "ParseFacebookUtilsV4-iOS" + ReferencedContainer = "container:ParseFacebookUtils.xcodeproj"> - + diff --git a/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-tvOS-Dynamic.xcscheme b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-tvOS-Dynamic.xcscheme new file mode 100644 index 000000000..1f3829a8c --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-tvOS-Dynamic.xcscheme @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-tvOS.xcscheme b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-tvOS.xcscheme new file mode 100644 index 000000000..c9f8b107f --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils.xcodeproj/xcshareddata/xcschemes/ParseFacebookUtilsV4-tvOS.xcscheme @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/PFFacebookAuthenticationProvider.h b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/PFFacebookAuthenticationProvider.h new file mode 100644 index 000000000..6f5159668 --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/PFFacebookAuthenticationProvider.h @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const PFFacebookUserAuthenticationType; + +@interface PFFacebookAuthenticationProvider : NSObject + +///-------------------------------------- +/// @name Init +///-------------------------------------- + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initWithApplication:(UIApplication *)application + launchOptions:(nullable NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER; ++ (instancetype)providerWithApplication:(UIApplication *)application + launchOptions:(nullable NSDictionary *)launchOptions;; + +///-------------------------------------- +/// @name Authenticate +///-------------------------------------- + +- (BFTask*> *)authenticateAsyncWithReadPermissions:(nullable NSArray *)readPermissions + publishPermissions:(nullable NSArray *)publishPermissions; +- (BFTask*> *)authenticateAsyncWithReadPermissions:(nullable NSArray *)readPermissions + publishPermissions:(nullable NSArray *)publishPermissions + fromViewComtroller:(UIViewController *)viewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/PFFacebookAuthenticationProvider.m b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/PFFacebookAuthenticationProvider.m new file mode 100644 index 000000000..95d497a5a --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/PFFacebookAuthenticationProvider.m @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFFacebookAuthenticationProvider.h" + +#import +#import + +#import "PFFacebookPrivateUtilities.h" + +NSString *const PFFacebookUserAuthenticationType = @"facebook"; + +@implementation PFFacebookAuthenticationProvider + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +- (instancetype)initWithApplication:(UIApplication *)application + launchOptions:(nullable NSDictionary *)launchOptions { + self = [super init]; + if (!self) return self; + + [[FBSDKApplicationDelegate sharedInstance] application:[UIApplication sharedApplication] + didFinishLaunchingWithOptions:launchOptions]; + + return self; +} + ++ (instancetype)providerWithApplication:(UIApplication *)application + launchOptions:(nullable NSDictionary *)launchOptions { + return [[self alloc] initWithApplication:application launchOptions:launchOptions]; +} + +///-------------------------------------- +#pragma mark - Authenticate +///-------------------------------------- + +- (BFTask*> *)authenticateAsyncWithReadPermissions:(nullable NSArray *)readPermissions + publishPermissions:(nullable NSArray *)publishPermissions { + return [self authenticateAsyncWithReadPermissions:readPermissions + publishPermissions:publishPermissions + fromViewComtroller:[PFFacebookPrivateUtilities applicationTopViewController]]; +} + +- (BFTask*> *)authenticateAsyncWithReadPermissions:(nullable NSArray *)readPermissions + publishPermissions:(nullable NSArray *)publishPermissions + fromViewComtroller:(UIViewController *)viewController { + return [BFTask taskWithError:[NSError pffb_invalidFacebookSessionError]]; +} + +///-------------------------------------- +#pragma mark - PFUserAuthenticationDelegate +///-------------------------------------- + +- (BOOL)restoreAuthenticationWithAuthData:(nullable NSDictionary *)authData { + FBSDKAccessToken *token = [PFFacebookPrivateUtilities facebookAccessTokenFromUserAuthenticationData:authData]; + if (!token) { + return !authData; // Only deauthenticate if authData was nil, otherwise - return failure (`NO`). + } + + FBSDKAccessToken *currentToken = [FBSDKAccessToken currentAccessToken]; + // Do not reset the current token if we have the same token already set. + if (![currentToken.userID isEqualToString:token.userID] || + ![currentToken.tokenString isEqualToString:token.tokenString]) { + [FBSDKAccessToken setCurrentAccessToken:token]; + } + + return YES; +} + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider.h b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider.h new file mode 100644 index 000000000..3101fcfb9 --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +#import +#import + +#import "PFFacebookAuthenticationProvider.h" + +@class BFTask<__covariant BFGenericType>; + +NS_ASSUME_NONNULL_BEGIN + +@interface PFFacebookMobileAuthenticationProvider : PFFacebookAuthenticationProvider + +@property (nonatomic, strong, readonly) FBSDKLoginManager *loginManager; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider.m b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider.m new file mode 100644 index 000000000..f95a460dd --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider.m @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFFacebookMobileAuthenticationProvider.h" +#import "PFFacebookMobileAuthenticationProvider_Private.h" + +#import +#import + +#import +#import + +#import + +#import + +#import "PFFacebookPrivateUtilities.h" + +@implementation PFFacebookMobileAuthenticationProvider + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +- (instancetype)initWithApplication:(UIApplication *)application + launchOptions:(nullable NSDictionary *)launchOptions { + self = [super initWithApplication:application launchOptions:launchOptions]; + if (!self) return self; + + _loginManager = [[FBSDKLoginManager alloc] init]; + + return self; +} + +///-------------------------------------- +#pragma mark - Authenticate +///-------------------------------------- + +- (BFTask*> *)authenticateAsyncWithReadPermissions:(nullable NSArray *)readPermissions + publishPermissions:(nullable NSArray *)publishPermissions + fromViewComtroller:(UIViewController *)viewController { + if (readPermissions && publishPermissions) { + NSString *description = @"Read permissions are not permitted to be requested with publish permissions."; + NSError *error = [NSError errorWithDomain:PFParseErrorDomain + code:kPFErrorFacebookInvalidSession + userInfo:@{ NSLocalizedDescriptionKey: description }]; + return [BFTask taskWithError:error]; + } + + BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; + FBSDKLoginManagerRequestTokenHandler resultHandler = ^(FBSDKLoginManagerLoginResult *result, NSError *error) { + if (result.isCancelled) { + [taskCompletionSource cancel]; + } else if (error) { + taskCompletionSource.error = error; + } else { + taskCompletionSource.result = [PFFacebookPrivateUtilities userAuthenticationDataFromAccessToken:result.token]; + } + }; + if (publishPermissions) { + [self.loginManager logInWithPublishPermissions:publishPermissions + fromViewController:viewController + handler:resultHandler]; + } else { + [self.loginManager logInWithReadPermissions:readPermissions + fromViewController:viewController + handler:resultHandler]; + } + return taskCompletionSource.task; +} + +///-------------------------------------- +#pragma mark - PFUserAuthenticationDelegate +///-------------------------------------- + +- (BOOL)restoreAuthenticationWithAuthData:(nullable NSDictionary *)authData { + if (!authData) { + [self.loginManager logOut]; + } + return [super restoreAuthenticationWithAuthData:authData]; +} + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider_Private.h b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider_Private.h new file mode 100644 index 000000000..79bbbb251 --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/iOS/PFFacebookMobileAuthenticationProvider_Private.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "PFFacebookMobileAuthenticationProvider.h" + +@class FBSDKAccessToken; + +@interface PFFacebookMobileAuthenticationProvider () + +@property (nonatomic, strong, readwrite) FBSDKLoginManager *loginManager; + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/tvOS/PFFacebookDeviceAuthenticationProvider.h b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/tvOS/PFFacebookDeviceAuthenticationProvider.h new file mode 100644 index 000000000..b2cf54a66 --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/tvOS/PFFacebookDeviceAuthenticationProvider.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "PFFacebookAuthenticationProvider.h" + +@interface PFFacebookDeviceAuthenticationProvider : PFFacebookAuthenticationProvider + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/tvOS/PFFacebookDeviceAuthenticationProvider.m b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/tvOS/PFFacebookDeviceAuthenticationProvider.m new file mode 100644 index 000000000..daeb6375b --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/AuthenticationProvider/tvOS/PFFacebookDeviceAuthenticationProvider.m @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFFacebookDeviceAuthenticationProvider.h" + +#import +#import + +#import +#import +#import +#import + +#import "PFFacebookPrivateUtilities.h" + +@interface PFFacebookDeviceAuthenticationProvider () { + BFTaskCompletionSource *_loginTaskCompletionSource; + FBSDKDeviceLoginViewController *_loginViewController; +} + +@end + +@implementation PFFacebookDeviceAuthenticationProvider + +///-------------------------------------- +#pragma mark - PFFacebookAuthenticationProvider +///-------------------------------------- + +- (BFTask*> *)authenticateAsyncWithReadPermissions:(nullable NSArray *)readPermissions + publishPermissions:(nullable NSArray *)publishPermissions + fromViewComtroller:(UIViewController *)viewController { + return [BFTask taskFromExecutor:[BFExecutor mainThreadExecutor] withBlock:^id _Nonnull{ + if (_loginTaskCompletionSource) { + return [NSError errorWithDomain:FBSDKErrorDomain + code:FBSDKDialogUnavailableErrorCode + userInfo:@{ NSLocalizedDescriptionKey : @"Another login attempt is already in progress." }]; + } + _loginTaskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; + _loginViewController = [[FBSDKDeviceLoginViewController alloc] init]; + _loginViewController.delegate = self; + _loginViewController.readPermissions = readPermissions; + _loginViewController.publishPermissions = publishPermissions; + + [viewController presentViewController:_loginViewController animated:YES completion:nil]; + + return _loginTaskCompletionSource.task; + }]; +} + +///-------------------------------------- +#pragma mark - PFUserAuthenticationDelegate +///-------------------------------------- + +- (BOOL)restoreAuthenticationWithAuthData:(nullable NSDictionary *)authData { + if (!authData) { + [FBSDKAccessToken setCurrentAccessToken:nil]; + } + return [super restoreAuthenticationWithAuthData:authData]; +} + +///-------------------------------------- +#pragma mark - FBSDKDeviceLoginViewController +///-------------------------------------- + +- (void)deviceLoginViewControllerDidCancel:(FBSDKDeviceLoginViewController *)viewController { + [_loginTaskCompletionSource trySetCancelled]; + _loginViewController = nil; + _loginTaskCompletionSource = nil; +} + +- (void)deviceLoginViewControllerDidFinish:(FBSDKDeviceLoginViewController *)viewController { + FBSDKAccessToken *accessToken = [FBSDKAccessToken currentAccessToken]; + NSDictionary *result = [PFFacebookPrivateUtilities userAuthenticationDataFromAccessToken:accessToken]; + [_loginTaskCompletionSource trySetResult:result]; + _loginViewController = nil; + _loginTaskCompletionSource = nil; +} + +- (void)deviceLoginViewControllerDidFail:(FBSDKDeviceLoginViewController *)viewController error:(NSError *)error { + [_loginTaskCompletionSource trySetError:error]; + _loginViewController = nil; + _loginTaskCompletionSource = nil; +} + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookPrivateUtilities.h b/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookPrivateUtilities.h new file mode 100644 index 000000000..dec0e397a --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookPrivateUtilities.h @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import +#import + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface PFFacebookPrivateUtilities : NSObject + ++ (UIViewController *)applicationTopViewController; + +///-------------------------------------- +/// @name User Authentication Data +///-------------------------------------- + ++ (NSDictionary *)userAuthenticationDataWithFacebookUserId:(NSString *)userId + accessToken:(NSString *)accessToken + expirationDate:(NSDate *)expirationDate; ++ (nullable NSDictionary *)userAuthenticationDataFromAccessToken:(FBSDKAccessToken *)token; + ++ (nullable FBSDKAccessToken *)facebookAccessTokenFromUserAuthenticationData:(nullable NSDictionary *)authData; + +@end + +@interface BFTask (ParseFacebookUtils) + +- (instancetype)pffb_continueWithMainThreadUserBlock:(PFUserResultBlock)block; +- (instancetype)pffb_continueWithMainThreadBooleanBlock:(PFBooleanResultBlock)block; +- (instancetype)pffb_continueWithMainThreadBlock:(BFContinuationBlock)block; + +@end + +@interface NSError (ParseFacebookUtils) + ++ (instancetype)pffb_invalidFacebookSessionError; + +@end + +@interface NSDateFormatter (ParseFacebookUtils) + ++ (instancetype)pffb_preciseDateFormatter; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookPrivateUtilities.m b/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookPrivateUtilities.m new file mode 100644 index 000000000..17011182e --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookPrivateUtilities.m @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFFacebookPrivateUtilities.h" + +#import + +@implementation PFFacebookPrivateUtilities + ++ (UIViewController *)applicationTopViewController { + UIViewController *viewController = [UIApplication sharedApplication].keyWindow.rootViewController; + while (viewController.presentedViewController) { + viewController = viewController.presentedViewController; + } + return viewController; +} + +///-------------------------------------- +#pragma mark - User Authentication Data +///-------------------------------------- + ++ (NSDictionary *)userAuthenticationDataWithFacebookUserId:(NSString *)userId + accessToken:(NSString *)accessToken + expirationDate:(NSDate *)expirationDate { + return @{ @"id" : userId, + @"access_token" : accessToken, + @"expiration_date" : [[NSDateFormatter pffb_preciseDateFormatter] stringFromDate:expirationDate] }; +} + ++ (nullable NSDictionary *)userAuthenticationDataFromAccessToken:(FBSDKAccessToken *)token { + if (!token.userID || !token.tokenString || !token.expirationDate) { + return nil; + } + + return [self userAuthenticationDataWithFacebookUserId:token.userID + accessToken:token.tokenString + expirationDate:token.expirationDate]; +} + ++ (nullable FBSDKAccessToken *)facebookAccessTokenFromUserAuthenticationData:(nullable NSDictionary *)authData { + NSString *accessToken = authData[@"access_token"]; + NSString *expirationDateString = authData[@"expiration_date"]; + if (!accessToken || !expirationDateString) { + return nil; + } + + NSDate *expirationDate = [[NSDateFormatter pffb_preciseDateFormatter] dateFromString:expirationDateString]; + FBSDKAccessToken *token = [[FBSDKAccessToken alloc] initWithTokenString:accessToken + permissions:nil + declinedPermissions:nil + appID:[FBSDKSettings appID] + userID:authData[@"id"] + expirationDate:expirationDate + refreshDate:nil]; + return token; +} + +@end + +@implementation BFTask (ParseFacebookUtils) + +- (instancetype)pffb_continueWithMainThreadUserBlock:(PFUserResultBlock)block { + return [self pffb_continueWithMainThreadBlock:^id(BFTask *task) { + if (block) { + block(task.result, task.error); + } + return nil; + }]; +} + +- (instancetype)pffb_continueWithMainThreadBooleanBlock:(PFBooleanResultBlock)block { + return [self pffb_continueWithMainThreadBlock:^id(BFTask *task) { + if (block) { + block([task.result boolValue], task.error); + } + return nil; + }]; +} + +- (instancetype)pffb_continueWithMainThreadBlock:(BFContinuationBlock)block { + return [self continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:block]; +} + +@end + +@implementation NSError (ParseFacebookUtils) + ++ (instancetype)pffb_invalidFacebookSessionError { + return [NSError errorWithDomain:PFParseErrorDomain + code:kPFErrorFacebookInvalidSession + userInfo:@{ NSLocalizedDescriptionKey : @"Supplied access token is missing required data." }]; +} + +@end + +@implementation NSDateFormatter (ParseFacebookUtils) + ++ (instancetype)pffb_preciseDateFormatter { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; + formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + return formatter; +} + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookUtils_Private.h b/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookUtils_Private.h new file mode 100644 index 000000000..c6899bcf4 --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/Internal/PFFacebookUtils_Private.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "PFFacebookMobileAuthenticationProvider.h" + +@interface PFFacebookUtils (Private) + ++ (PFFacebookMobileAuthenticationProvider *)_authenticationProvider; ++ (void)_setAuthenticationProvider:(PFFacebookMobileAuthenticationProvider *)provider; + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/PFFacebookUtils.h b/ParseFacebookUtils/ParseFacebookUtils/PFFacebookUtils.h new file mode 100644 index 000000000..665cb349b --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/PFFacebookUtils.h @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +#import +#import + +#import + +#if TARGET_OS_IOS +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + The `PFFacebookUtils` class provides utility functions for using Facebook authentication with `PFUser`s. + + @warning This class supports official Facebook iOS SDK v4.0+ and is available only on iOS. + */ +@interface PFFacebookUtils : NSObject + +///-------------------------------------- +/// @name Interacting With Facebook +///-------------------------------------- + +/** + Initializes Parse Facebook Utils. + + You must provide your Facebook application ID as the value for FacebookAppID in your bundle's plist file + as described here: https://developers.facebook.com/docs/getting-started/facebook-sdk-for-ios/ + + @warning You must invoke this in order to use the Facebook functionality in Parse. + + @param launchOptions The launchOptions as passed to [UIApplicationDelegate application:didFinishLaunchingWithOptions:]. + */ ++ (void)initializeFacebookWithApplicationLaunchOptions:(nullable NSDictionary *)launchOptions; + +#if TARGET_OS_IOS +/** + `FBSDKLoginManager` provides methods for configuring login behavior, default audience + and managing Facebook Access Token. + + @warning This method is available only on iOS. + + @return An instance of `FBSDKLoginManager` that is used by `PFFacebookUtils`. + */ ++ (FBSDKLoginManager *)facebookLoginManager; +#endif + +///-------------------------------------- +/// @name Logging In +///-------------------------------------- + +/** + *Asynchronously* logs in a user using Facebook with read permissions. + + This method delegates to the Facebook SDK to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @param permissions Array of read permissions to use. + + @return The task that has will a have `result` set to `PFUser` if operation succeeds. + */ ++ (BFTask *)logInInBackgroundWithReadPermissions:(nullable NSArray *)permissions; + +/** + *Asynchronously* logs in a user using Facebook with read permissions. + + This method delegates to the Facebook SDK to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @param permissions Array of read permissions to use. + @param block The block to execute when the log in completes. + It should have the following signature: `^(PFUser *user, NSError *error)`. + */ ++ (void)logInInBackgroundWithReadPermissions:(nullable NSArray *)permissions + block:(nullable PFUserResultBlock)block; + +/** + *Asynchronously* logs in a user using Facebook with publish permissions. + + This method delegates to the Facebook SDK to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @param permissions Array of publish permissions to use. + + @return The task that has will a have `result` set to `PFUser` if operation succeeds. + */ ++ (BFTask *)logInInBackgroundWithPublishPermissions:(nullable NSArray *)permissions; + +/** + *Asynchronously* logs in a user using Facebook with publish permissions. + + This method delegates to the Facebook SDK to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @param permissions Array of publish permissions to use. + @param block The block to execute when the log in completes. + It should have the following signature: `^(PFUser *user, NSError *error)`. + */ ++ (void)logInInBackgroundWithPublishPermissions:(nullable NSArray *)permissions + block:(nullable PFUserResultBlock)block; + +/** + *Asynchronously* logs in a user using given Facebook Acess Token. + + This method delegates to the Facebook SDK to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @param accessToken An instance of `FBSDKAccessToken` to use when logging in. + + @return The task that has will a have `result` set to `PFUser` if operation succeeds. + */ ++ (BFTask *)logInInBackgroundWithAccessToken:(FBSDKAccessToken *)accessToken; + +/** + *Asynchronously* logs in a user using given Facebook Acess Token. + + This method delegates to the Facebook SDK to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @param accessToken An instance of `FBSDKAccessToken` to use when logging in. + @param block The block to execute when the log in completes. + It should have the following signature: `^(PFUser *user, NSError *error)`. + */ ++ (void)logInInBackgroundWithAccessToken:(FBSDKAccessToken *)accessToken + block:(nullable PFUserResultBlock)block; + +///-------------------------------------- +/// @name Linking Users +///-------------------------------------- + +/** + *Asynchronously* links Facebook with read permissions to an existing `PFUser`. + + This method delegates to the Facebook SDK to authenticate + the user, and then automatically links the account to the `PFUser`. + It will also save any unsaved changes that were made to the `user`. + + @param user User to link to Facebook. + @param permissions Array of read permissions to use when logging in with Facebook. + + @return The task that will have a `result` set to `@YES` if operation succeeds. + */ ++ (BFTask *)linkUserInBackground:(PFUser *)user + withReadPermissions:(nullable NSArray *)permissions; + +/** + *Asynchronously* links Facebook with read permissions to an existing `PFUser`. + + This method delegates to the Facebook SDK to authenticate + the user, and then automatically links the account to the `PFUser`. + It will also save any unsaved changes that were made to the `user`. + + @param user User to link to Facebook. + @param permissions Array of read permissions to use. + @param block The block to execute when the linking completes. + It should have the following signature: `^(BOOL succeeded, NSError *error)`. + */ ++ (void)linkUserInBackground:(PFUser *)user + withReadPermissions:(nullable NSArray *)permissions + block:(nullable PFBooleanResultBlock)block; + +/** + *Asynchronously* links Facebook with publish permissions to an existing `PFUser`. + + This method delegates to the Facebook SDK to authenticate + the user, and then automatically links the account to the `PFUser`. + It will also save any unsaved changes that were made to the `user`. + + @param user User to link to Facebook. + @param permissions Array of publish permissions to use. + + @return The task that will have a `result` set to `@YES` if operation succeeds. + */ ++ (BFTask *)linkUserInBackground:(PFUser *)user + withPublishPermissions:(NSArray *)permissions; + +/** + *Asynchronously* links Facebook with publish permissions to an existing `PFUser`. + + This method delegates to the Facebook SDK to authenticate + the user, and then automatically links the account to the `PFUser`. + It will also save any unsaved changes that were made to the `user`. + + @param user User to link to Facebook. + @param permissions Array of publish permissions to use. + @param block The block to execute when the linking completes. + It should have the following signature: `^(BOOL succeeded, NSError *error)`. + */ ++ (void)linkUserInBackground:(PFUser *)user + withPublishPermissions:(NSArray *)permissions + block:(nullable PFBooleanResultBlock)block; + +/** + *Asynchronously* links Facebook Access Token to an existing `PFUser`. + + This method delegates to the Facebook SDK to authenticate + the user, and then automatically links the account to the `PFUser`. + It will also save any unsaved changes that were made to the `user`. + + @param user User to link to Facebook. + @param accessToken An instance of `FBSDKAccessToken` to use. + + @return The task that will have a `result` set to `@YES` if operation succeeds. + */ ++ (BFTask *)linkUserInBackground:(PFUser *)user withAccessToken:(FBSDKAccessToken *)accessToken; + +/** + *Asynchronously* links Facebook Access Token to an existing `PFUser`. + + This method delegates to the Facebook SDK to authenticate + the user, and then automatically links the account to the `PFUser`. + It will also save any unsaved changes that were made to the `user`. + + @param user User to link to Facebook. + @param accessToken An instance of `FBSDKAccessToken` to use. + @param block The block to execute when the linking completes. + It should have the following signature: `^(BOOL succeeded, NSError *error)`. + */ ++ (void)linkUserInBackground:(PFUser *)user + withAccessToken:(FBSDKAccessToken *)accessToken + block:(nullable PFBooleanResultBlock)block; + +///-------------------------------------- +/// @name Unlinking Users +///-------------------------------------- + +/** + Unlinks the `PFUser` from a Facebook account *asynchronously*. + + @param user User to unlink from Facebook. + @return The task, that encapsulates the work being done. + */ ++ (BFTask *)unlinkUserInBackground:(PFUser *)user; + +/** + Unlinks the `PFUser` from a Facebook account *asynchronously*. + + @param user User to unlink from Facebook. + @param block The block to execute. + It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. + */ ++ (void)unlinkUserInBackground:(PFUser *)user block:(nullable PFBooleanResultBlock)block; + +///-------------------------------------- +/// @name Getting Linked State +///-------------------------------------- + +/** + Whether the user has their account linked to Facebook. + + @param user User to check for a facebook link. The user must be logged in on this device. + + @return `YES` if the user has their account linked to Facebook, otherwise `NO`. + */ ++ (BOOL)isLinkedWithUser:(PFUser *)user; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseFacebookUtils/ParseFacebookUtils/PFFacebookUtils.m b/ParseFacebookUtils/ParseFacebookUtils/PFFacebookUtils.m new file mode 100644 index 000000000..8c10eaf72 --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/PFFacebookUtils.m @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFFacebookUtils.h" + +#import +#import +#import + +#import "PFFacebookPrivateUtilities.h" + +#if TARGET_OS_IOS +#import "PFFacebookMobileAuthenticationProvider.h" +#elif TARGET_OS_TV +#import "PFFacebookDeviceAuthenticationProvider.h" +#endif + +@implementation PFFacebookUtils + +///-------------------------------------- +#pragma mark - Authentication Provider +///-------------------------------------- + +static PFFacebookAuthenticationProvider *authenticationProvider_; + ++ (void)_assertFacebookInitialized { + if (!authenticationProvider_) { + [NSException raise:NSInternalInconsistencyException format:@"You must initialize PFFacebookUtils with a call to +initializeFacebookWithApplicationLaunchOptions"]; + } +} + ++ (PFFacebookAuthenticationProvider *)_authenticationProvider { + return authenticationProvider_; +} + ++ (void)_setAuthenticationProvider:(PFFacebookAuthenticationProvider *)provider { + authenticationProvider_ = provider; +} + +///-------------------------------------- +#pragma mark - Interacting With Facebook +///-------------------------------------- + ++ (void)initializeFacebookWithApplicationLaunchOptions:(NSDictionary *)launchOptions { + if (![Parse currentConfiguration]) { + // TODO: (nlutsenko) Remove this when Parse SDK throws on every access to Parse._currentManager + [NSException raise:NSInternalInconsistencyException format:@"PFFacebookUtils must be initialized after initializing Parse."]; + } + if (!authenticationProvider_) { + Class providerClass = nil; +#if TARGET_OS_IOS + providerClass = [PFFacebookMobileAuthenticationProvider class]; +#elif TARGET_OS_TV + providerClass = [PFFacebookDeviceAuthenticationProvider class]; +#endif + PFFacebookAuthenticationProvider *provider = [providerClass providerWithApplication:[UIApplication sharedApplication] + launchOptions:launchOptions]; + [PFUser registerAuthenticationDelegate:provider forAuthType:PFFacebookUserAuthenticationType]; + + [self _setAuthenticationProvider:provider]; + } +} + +#pragma mark iOS +#if TARGET_OS_IOS + ++ (FBSDKLoginManager *)facebookLoginManager { + PFFacebookMobileAuthenticationProvider *provider = (PFFacebookMobileAuthenticationProvider *)[self _authenticationProvider]; + return provider.loginManager; +} + +#endif + +///-------------------------------------- +#pragma mark - Logging In +///-------------------------------------- + ++ (BFTask *)logInInBackgroundWithReadPermissions:(nullable NSArray *)permissions { + return [self _logInAsyncWithReadPermissions:permissions publishPermissions:nil]; +} + ++ (void)logInInBackgroundWithReadPermissions:(nullable NSArray *)permissions + block:(nullable PFUserResultBlock)block { + [[self logInInBackgroundWithReadPermissions:permissions] pffb_continueWithMainThreadUserBlock:block]; +} + ++ (BFTask *)logInInBackgroundWithPublishPermissions:(nullable NSArray *)permissions { + return [self _logInAsyncWithReadPermissions:nil publishPermissions:permissions]; +} + ++ (void)logInInBackgroundWithPublishPermissions:(nullable NSArray *)permissions + block:(nullable PFUserResultBlock)block { + [[self logInInBackgroundWithPublishPermissions:permissions] pffb_continueWithMainThreadUserBlock:block]; +} + ++ (BFTask *)_logInAsyncWithReadPermissions:(NSArray *)readPermissions + publishPermissions:(NSArray *)publishPermissions { + [self _assertFacebookInitialized]; + + PFFacebookAuthenticationProvider *provider = [self _authenticationProvider]; + return [[provider authenticateAsyncWithReadPermissions:readPermissions + publishPermissions:publishPermissions] continueWithSuccessBlock:^id(BFTask *task) { + return [PFUser logInWithAuthTypeInBackground:PFFacebookUserAuthenticationType authData:task.result]; + }]; +} + ++ (BFTask *)logInInBackgroundWithAccessToken:(FBSDKAccessToken *)accessToken { + [self _assertFacebookInitialized]; + + NSDictionary *authData = [PFFacebookPrivateUtilities userAuthenticationDataFromAccessToken:accessToken]; + if (!authData) { + return [BFTask taskWithError:[NSError pffb_invalidFacebookSessionError]]; + } + return [[PFUser logInWithAuthTypeInBackground:PFFacebookUserAuthenticationType + authData:authData] continueWithSuccessBlock:^id(BFTask *task) { + [FBSDKAccessToken setCurrentAccessToken:accessToken]; + return task; // Return the same result. + }]; +} + ++ (void)logInInBackgroundWithAccessToken:(FBSDKAccessToken *)accessToken + block:(nullable PFUserResultBlock)block { + [[self logInInBackgroundWithAccessToken:accessToken] pffb_continueWithMainThreadUserBlock:block]; +} + +///-------------------------------------- +#pragma mark - Linking Users +///-------------------------------------- + ++ (BFTask *)linkUserInBackground:(PFUser *)user + withReadPermissions:(nullable NSArray *)permissions { + return [self _linkUserAsync:user withReadPermissions:permissions publishPermissions:nil]; +} + ++ (void)linkUserInBackground:(PFUser *)user + withReadPermissions:(nullable NSArray *)permissions + block:(nullable PFBooleanResultBlock)block { + [[self linkUserInBackground:user withReadPermissions:permissions] pffb_continueWithMainThreadBooleanBlock:block]; +} + ++ (BFTask *)linkUserInBackground:(PFUser *)user + withPublishPermissions:(NSArray *)permissions { + return [self _linkUserAsync:user withReadPermissions:nil publishPermissions:permissions]; +} + ++ (void)linkUserInBackground:(PFUser *)user + withPublishPermissions:(NSArray *)permissions + block:(nullable PFBooleanResultBlock)block { + [[self linkUserInBackground:user withPublishPermissions:permissions] pffb_continueWithMainThreadBooleanBlock:block]; +} + ++ (BFTask *)linkUserInBackground:(PFUser *)user withAccessToken:(FBSDKAccessToken *)accessToken { + [self _assertFacebookInitialized]; + + NSDictionary *authData = [PFFacebookPrivateUtilities userAuthenticationDataFromAccessToken:accessToken]; + if (!authData) { + return [BFTask taskWithError:[NSError pffb_invalidFacebookSessionError]]; + } + return [user linkWithAuthTypeInBackground:PFFacebookUserAuthenticationType authData:authData]; +} + ++ (void)linkUserInBackground:(PFUser *)user + withAccessToken:(FBSDKAccessToken *)accessToken + block:(nullable PFBooleanResultBlock)block { + [[self linkUserInBackground:user withAccessToken:accessToken] pffb_continueWithMainThreadBooleanBlock:block]; +} + ++ (BFTask *)_linkUserAsync:(PFUser *)user + withReadPermissions:(nullable NSArray *)readPermissions + publishPermissions:(nullable NSArray *)publishPermissions { + [self _assertFacebookInitialized]; + + PFFacebookAuthenticationProvider *authenticationProvider = [self _authenticationProvider]; + return [[authenticationProvider authenticateAsyncWithReadPermissions:readPermissions + publishPermissions:publishPermissions] continueWithSuccessBlock:^id(BFTask *task) { + return [user linkWithAuthTypeInBackground:PFFacebookUserAuthenticationType authData:task.result]; + }]; +} + +///-------------------------------------- +#pragma mark - Unlinking +///-------------------------------------- + ++ (BFTask *)unlinkUserInBackground:(PFUser *)user { + [self _assertFacebookInitialized]; + return [user unlinkWithAuthTypeInBackground:PFFacebookUserAuthenticationType]; +} + ++ (void)unlinkUserInBackground:(PFUser *)user block:(nullable PFBooleanResultBlock)block { + [[self unlinkUserInBackground:user] pffb_continueWithMainThreadBooleanBlock:block]; +} + +///-------------------------------------- +#pragma mark - Getting Linked State +///-------------------------------------- + ++ (BOOL)isLinkedWithUser:(PFUser *)user { + return [user isLinkedWithAuthType:PFFacebookUserAuthenticationType]; +} + +@end diff --git a/ParseFacebookUtils/ParseFacebookUtils/ParseFacebookUtilsV4.h b/ParseFacebookUtils/ParseFacebookUtils/ParseFacebookUtilsV4.h new file mode 100644 index 000000000..367bb44fb --- /dev/null +++ b/ParseFacebookUtils/ParseFacebookUtils/ParseFacebookUtilsV4.h @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import diff --git a/ParseFacebookUtils/Resources/Info-iOS.plist b/ParseFacebookUtils/Resources/Info-iOS.plist new file mode 100644 index 000000000..33c68a5c2 --- /dev/null +++ b/ParseFacebookUtils/Resources/Info-iOS.plist @@ -0,0 +1,29 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ParseFacebookUtilsV4 + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.16.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + iPhoneSimulator + iPhoneOS + + CFBundleVersion + 1.16.0 + MinimumOSVersion + 6.0 + + diff --git a/ParseFacebookUtils/Resources/Info-tvOS.plist b/ParseFacebookUtils/Resources/Info-tvOS.plist new file mode 100644 index 000000000..9527a11c0 --- /dev/null +++ b/ParseFacebookUtils/Resources/Info-tvOS.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ParseFacebookUtilsV4 + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.16.0 + CFBundleSignature + ???? + CFBundleVersion + 1.16.0 + + diff --git a/ParseFacebookUtils/Resources/Localizable.strings b/ParseFacebookUtils/Resources/Localizable.strings new file mode 100644 index 000000000..ed60b89c4 Binary files /dev/null and b/ParseFacebookUtils/Resources/Localizable.strings differ diff --git a/ParseFacebookUtils/Tests/Other/TestCase/PFFacebookTestCase.h b/ParseFacebookUtils/Tests/Other/TestCase/PFFacebookTestCase.h new file mode 100644 index 000000000..7941e897b --- /dev/null +++ b/ParseFacebookUtils/Tests/Other/TestCase/PFFacebookTestCase.h @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@import XCTest; + +@interface PFFacebookTestCase : XCTestCase + +///-------------------------------------- +/// @name XCTestCase +///-------------------------------------- + +- (void)setUp NS_REQUIRES_SUPER; +- (void)tearDown NS_REQUIRES_SUPER; + +///-------------------------------------- +/// @name Expectations +///-------------------------------------- + +- (XCTestExpectation *)currentSelectorTestExpectation; +- (void)waitForTestExpectations; + +///-------------------------------------- +/// @name Mocks +///-------------------------------------- + +- (void)registerMockObject:(id)mockObject; + +@end + +#define _PFRegisterMock(mockObject) [self registerMockObject:mockObject] +#define _PFMockShim(method, args...) ({ id mock = method(args); _PFRegisterMock(mock); mock; }) +#define _PFOCMockWarning _Pragma("GCC warning \"Please use PF mocking methods instead of OCMock ones.\"") + +#define _PFStrictClassMock(kls) [OCMockObject mockForClass:kls] +#define _PFClassMock(kls) [OCMockObject niceMockForClass:kls] +#define _PFStrictProtocolMock(proto) [OCMockObject mockForProtocol:proto] +#define _PFProtocolMock(proto) [OCMockObject niceMockForProtocol:proto] +#define _PFPartialMock(obj) [OCMockObject partialMockForObject:obj] + +#define PFStrictClassMock(...) _PFMockShim(_PFStrictClassMock, __VA_ARGS__) +#define PFClassMock(...) _PFMockShim(_PFClassMock, __VA_ARGS__) +#define PFStrictProtocolMock(...) _PFMockShim(_PFStrictProtocolMock, __VA_ARGS__) +#define PFProtocolMock(...) _PFMockShim(_PFProtocolMock, __VA_ARGS__) +#define PFPartialMock(...) _PFMockShim(_PFPartialMock, __VA_ARGS__) + +#undef OCMStrictClassMock +#undef OCMClassMock +#undef OCMStrictProtocolMock +#undef OCMProtocolMock +#undef OCMPartialMock + +#define OCMStrictClassMock _PFOCMockWarning _PFStrictClassMock +#define OCMClassMock _PFOCMockWarning _PFClassMock +#define OCMStrictProtocolMock _PFOCMockWarning _PFStrictProtocolMock +#define OCMProtocolMock _PFOCMockWarning _PFProtocolMock +#define OCMPartialMock _PFOCMockWarning _PFPartialMock + +#define PFAssertIsKindOfClass(a1, a2, description...) \ +XCTAssertTrue([a1 isKindOfClass:[a2 class]], ## description) + +#define PFAssertNotKindOfClass(a1, a2, description...) \ +XCTAssertFalse([a1 isKindOfClass:[a2 class]], ## description) + +#define PFAssertThrowsInconsistencyException(expression, ...) \ +XCTAssertThrowsSpecificNamed(expression, NSException, NSInternalInconsistencyException, __VA_ARGS__) + +#define PFAssertThrowsInvalidArgumentException(expression, ...) \ +XCTAssertThrowsSpecificNamed(expression, NSException, NSInvalidArgumentException, __VA_ARGS__) + +#define PFAssertStringContains(a, b) XCTAssertTrue([(a) rangeOfString:(b)].location != NSNotFound) diff --git a/ParseFacebookUtils/Tests/Other/TestCase/PFFacebookTestCase.m b/ParseFacebookUtils/Tests/Other/TestCase/PFFacebookTestCase.m new file mode 100644 index 000000000..a5414a149 --- /dev/null +++ b/ParseFacebookUtils/Tests/Other/TestCase/PFFacebookTestCase.m @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFFacebookTestCase.h" + +@implementation PFFacebookTestCase { + NSMutableArray *_mocks; + dispatch_queue_t _mockQueue; +} + +///-------------------------------------- +#pragma mark - XCTestCase +///-------------------------------------- + +- (void)setUp { + [super setUp]; + + _mocks = [[NSMutableArray alloc] init]; + _mockQueue = dispatch_queue_create("com.parse.tests.mock.queue", DISPATCH_QUEUE_SERIAL); +} + +- (void)tearDown { + dispatch_sync(_mockQueue, ^{ + [_mocks makeObjectsPerformSelector:@selector(stopMocking)]; + }); + + _mocks = nil; + _mockQueue = nil; + + [super tearDown]; +} + +///-------------------------------------- +#pragma mark - Helpers +///-------------------------------------- + +- (XCTestExpectation *)currentSelectorTestExpectation { + NSInvocation *invocation = self.invocation; + NSString *selectorName = invocation ? NSStringFromSelector(invocation.selector) : @"testExpectation"; + return [self expectationWithDescription:selectorName]; +} + +- (void)waitForTestExpectations { + [self waitForExpectationsWithTimeout:10.0 handler:nil]; +} + +///-------------------------------------- +#pragma mark - Mock Registration +///-------------------------------------- + +- (void)registerMockObject:(id)mockObject { + dispatch_sync(_mockQueue, ^{ + [_mocks addObject:mockObject]; + }); +} + +@end diff --git a/ParseFacebookUtils/Tests/Resources/Info.plist b/ParseFacebookUtils/Tests/Resources/Info.plist new file mode 100644 index 000000000..ba72822e8 --- /dev/null +++ b/ParseFacebookUtils/Tests/Resources/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/ParseFacebookUtils/Tests/TestApplication/Classes/main.m b/ParseFacebookUtils/Tests/TestApplication/Classes/main.m new file mode 100644 index 000000000..453fc1227 --- /dev/null +++ b/ParseFacebookUtils/Tests/TestApplication/Classes/main.m @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface AppDelegate : NSObject + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].applicationFrame]; + window.rootViewController = [[UIViewController alloc] init]; + [window makeKeyAndVisible]; + return YES; +} + +@end + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/ParseFacebookUtils/Tests/TestApplication/Resources/Info.plist b/ParseFacebookUtils/Tests/TestApplication/Resources/Info.plist new file mode 100644 index 000000000..6dcc65160 --- /dev/null +++ b/ParseFacebookUtils/Tests/TestApplication/Resources/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ParseFacebookUtils/Tests/Unit/FacebookAuthenticationProviderTests.m b/ParseFacebookUtils/Tests/Unit/FacebookAuthenticationProviderTests.m new file mode 100644 index 000000000..a5061ab54 --- /dev/null +++ b/ParseFacebookUtils/Tests/Unit/FacebookAuthenticationProviderTests.m @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +@import Bolts.BFTask; + +@import FBSDKCoreKit.FBSDKAccessToken; +@import FBSDKLoginKit.FBSDKLoginManagerLoginResult; + +#import "PFFacebookMobileAuthenticationProvider_Private.h" +#import "PFFacebookTestCase.h" + +@interface FacebookAuthenticationProviderTests : PFFacebookTestCase + +@end + +@implementation FacebookAuthenticationProviderTests + +- (void)testAuthType { + XCTAssertEqualObjects(PFFacebookUserAuthenticationType, @"facebook"); +} + +- (void)testAuthenticateRead { + NSDictionary *expectedAuthData = @{ @"id" : @"fbId", + @"access_token" : @"token", + @"expiration_date" : @"1970-01-01T00:22:17.000Z" }; + + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + + OCMStub([mockedLoginManager logInWithReadPermissions:@[ @"read" ] + fromViewController:OCMOCK_ANY + handler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained FBSDKLoginManagerRequestTokenHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + FBSDKAccessToken *token = [[FBSDKAccessToken alloc] initWithTokenString:@"token" + permissions:@[ @"read" ] + declinedPermissions:nil + appID:@"appId" + userID:@"fbId" + expirationDate:[NSDate dateWithTimeIntervalSince1970:1337] + refreshDate:[NSDate dateWithTimeIntervalSince1970:1337]]; + FBSDKLoginManagerLoginResult *result = [[FBSDKLoginManagerLoginResult alloc] initWithToken:token + isCancelled:NO + grantedPermissions:[NSSet setWithObject:@"read"] + declinedPermissions:nil]; + + handler(result, nil); + }); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[provider authenticateAsyncWithReadPermissions:@[ @"read" ] + publishPermissions:nil] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.result, expectedAuthData); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testAuthenticatePublish { + NSDictionary *expectedAuthData = @{ @"id" : @"fbId", + @"access_token" : @"token", + @"expiration_date" : @"1970-01-01T00:22:17.000Z" }; + + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + + OCMStub([mockedLoginManager logInWithPublishPermissions:@[ @"publish" ] + fromViewController:OCMOCK_ANY + handler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained FBSDKLoginManagerRequestTokenHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + FBSDKAccessToken *token = [[FBSDKAccessToken alloc] initWithTokenString:@"token" + permissions:@[ @"publish" ] + declinedPermissions:nil + appID:@"appId" + userID:@"fbId" + expirationDate:[NSDate dateWithTimeIntervalSince1970:1337] + refreshDate:[NSDate dateWithTimeIntervalSince1970:1337]]; + FBSDKLoginManagerLoginResult *result = [[FBSDKLoginManagerLoginResult alloc] initWithToken:token + isCancelled:NO + grantedPermissions:[NSSet setWithObject:@"publish"] + declinedPermissions:nil]; + + handler(result, nil); + }); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[provider authenticateAsyncWithReadPermissions:nil + publishPermissions:@[ @"publish" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.result, expectedAuthData); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testAuthenticateBoth { + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[provider authenticateAsyncWithReadPermissions:@[ @"read" ] publishPermissions:@[ @"publish" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertNotNil(task.error); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testAuthenticateCancel { + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + + OCMStub([mockedLoginManager logInWithPublishPermissions:@[ @"publish" ] + fromViewController:OCMOCK_ANY + handler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained FBSDKLoginManagerRequestTokenHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + FBSDKLoginManagerLoginResult *result = [[FBSDKLoginManagerLoginResult alloc] initWithToken:nil + isCancelled:YES + grantedPermissions:nil + declinedPermissions:[NSSet setWithObject:@"publish"]]; + + handler(result, nil); + }); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[provider authenticateAsyncWithReadPermissions:nil publishPermissions:@[ @"publish" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertTrue(task.cancelled); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testAuthenticateError { + NSError *expectedError = [NSError errorWithDomain:@"FBSDK" code:1337 userInfo:nil]; + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + + OCMStub([mockedLoginManager logInWithPublishPermissions:@[ @"publish" ] + fromViewController:OCMOCK_ANY + handler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained FBSDKLoginManagerRequestTokenHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + handler(nil, expectedError); + }); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[provider authenticateAsyncWithReadPermissions:nil publishPermissions:@[ @"publish" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.error, expectedError); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testAuthenticateInvalidResults { + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + + OCMStub([mockedLoginManager logInWithPublishPermissions:@[ @"publish" ] + fromViewController:OCMOCK_ANY + handler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained FBSDKLoginManagerRequestTokenHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + FBSDKAccessToken *token = [[FBSDKAccessToken alloc] initWithTokenString:nil + permissions:@[ @"publish" ] + declinedPermissions:nil + appID:@"appId" + userID:nil + expirationDate:nil + refreshDate:nil]; + FBSDKLoginManagerLoginResult *result = [[FBSDKLoginManagerLoginResult alloc] initWithToken:token + isCancelled:NO + grantedPermissions:[NSSet setWithObject:@"publish"] + declinedPermissions:nil]; + + handler(result, nil); + }); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[provider authenticateAsyncWithReadPermissions:nil publishPermissions:@[ @"publish" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertNil(task.result); + XCTAssertFalse(task.faulted); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testReauthenticate { + NSDictionary *authData = @{ @"id" : @"fbId", + @"access_token" : @"token", + @"expiration_date" : @"1970-01-01T00:22:17.000Z" }; + + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTAssertTrue([provider restoreAuthenticationWithAuthData:authData]); +} + +- (void)testRestoreAuthNil { + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + OCMExpect([mockedLoginManager logOut]); + + PFFacebookMobileAuthenticationProvider *provider = [[PFFacebookMobileAuthenticationProvider alloc] initWithApplication:[UIApplication sharedApplication] launchOptions:nil]; + provider.loginManager = mockedLoginManager; + + XCTAssertTrue([provider restoreAuthenticationWithAuthData:nil]); + + OCMVerifyAll(mockedLoginManager); +} + +@end diff --git a/ParseFacebookUtils/Tests/Unit/FacebookUtilsTests.m b/ParseFacebookUtils/Tests/Unit/FacebookUtilsTests.m new file mode 100644 index 000000000..00645da20 --- /dev/null +++ b/ParseFacebookUtils/Tests/Unit/FacebookUtilsTests.m @@ -0,0 +1,272 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +@import Parse; + +#import + +#import "PFFacebookTestCase.h" +#import "PFFacebookUtils_Private.h" +#import "PFFacebookPrivateUtilities.h" + +///-------------------------------------- +#pragma mark - FacebookUtilsTests +///-------------------------------------- + +@interface FacebookUtilsTests : PFFacebookTestCase + +@end + +@implementation FacebookUtilsTests + +///-------------------------------------- +#pragma mark - XCTestCase +///-------------------------------------- + +- (void)tearDown { + [PFFacebookUtils _setAuthenticationProvider:nil]; + + [super tearDown]; +} + +///-------------------------------------- +#pragma mark - Helpers +///-------------------------------------- + +- (NSDictionary *)sampleAuthData { + return @{ @"id" : @"fbId", + @"auth_token" : @"token", + @"expiration_date" : @"1970-01-01T00:22:17.000Z" }; +} + +///-------------------------------------- +#pragma mark - Tests +///-------------------------------------- + +- (void)testInitialize { + id userMock = PFStrictClassMock([PFUser class]); + OCMExpect(ClassMethod([userMock registerAuthenticationDelegate:[OCMArg checkWithBlock:^BOOL(id obj) { + return (obj != nil); + }] forAuthType:@"facebook"])); + + XCTAssertThrows([PFFacebookUtils unlinkUserInBackground:userMock]); + + XCTAssertThrows([PFFacebookUtils initializeFacebookWithApplicationLaunchOptions:nil]); + + id parseMock = PFStrictClassMock([Parse class]); + id configurationMock = PFStrictClassMock([ParseClientConfiguration class]); + OCMStub(ClassMethod([parseMock currentConfiguration])).andReturn(configurationMock); + + XCTAssertNoThrow([PFFacebookUtils initializeFacebookWithApplicationLaunchOptions:nil]); + XCTAssertNotNil([PFFacebookUtils _authenticationProvider]); + XCTAssertNoThrow([PFFacebookUtils initializeFacebookWithApplicationLaunchOptions:nil]); + + OCMVerifyAll(userMock); +} + +- (void)testLoginManager { + id mockedLoginManager = PFStrictClassMock([FBSDKLoginManager class]); + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + + OCMStub([mockedAuthProvider loginManager]).andReturn(mockedLoginManager); + + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + XCTAssertEqualObjects(mockedLoginManager, [PFFacebookUtils facebookLoginManager]); +} + +- (void)testLoginReadPermissions { + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + OCMStub([mockedAuthProvider authenticateAsyncWithReadPermissions:@[ @"read" ] publishPermissions:nil]).andReturn([BFTask taskWithResult:@{}]); + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + + id userMock = PFStrictClassMock([PFUser class]); + OCMStub(ClassMethod([userMock logInWithAuthTypeInBackground:@"facebook" authData:[OCMArg isNotNil]])).andReturn([BFTask taskWithResult:userMock]); + + XCTestExpectation *taskExpecatation = [self expectationWithDescription:@"task"]; + [[PFFacebookUtils logInInBackgroundWithReadPermissions:@[ @"read" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertEqual(task.result, userMock); + [taskExpecatation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; + + XCTestExpectation *blockExpecatation = [self expectationWithDescription:@"block"]; + [PFFacebookUtils logInInBackgroundWithReadPermissions:@[ @"read" ] block:^(PFUser *resultUser, NSError *error) { + XCTAssertEqual(resultUser, userMock); + XCTAssertNil(error); + [blockExpecatation fulfill]; + }]; + [self waitForTestExpectations]; +} + +- (void)testLoginWritePermissions { + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + OCMStub([mockedAuthProvider authenticateAsyncWithReadPermissions:nil publishPermissions:@[ @"publish" ]]).andReturn([BFTask taskWithResult:@{}]); + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + + id userMock = PFStrictClassMock([PFUser class]); + OCMStub(ClassMethod([userMock logInWithAuthTypeInBackground:@"facebook" authData:[OCMArg isNotNil]])).andReturn([BFTask taskWithResult:userMock]); + + XCTestExpectation *taskExpecatation = [self expectationWithDescription:@"task"]; + [[PFFacebookUtils logInInBackgroundWithPublishPermissions:@[ @"publish" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertEqual(task.result, userMock); + [taskExpecatation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; + + XCTestExpectation *blockExpecatation = [self expectationWithDescription:@"block"]; + [PFFacebookUtils logInInBackgroundWithPublishPermissions:@[ @"publish" ] block:^(PFUser *resultUser, NSError *error) { + XCTAssertEqual(resultUser, userMock); + XCTAssertNil(error); + [blockExpecatation fulfill]; + }]; + [self waitForTestExpectations]; +} + +- (void)testLoginWithAccessToken { + id mockedPrivateUtilities = PFStrictClassMock([PFFacebookPrivateUtilities class]); + id mockedAccessToken = PFStrictClassMock([FBSDKAccessToken class]); + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + + // NOTE: (richardross) Until we decouple user login with auth data, we can only mock error cases here. + OCMStub(ClassMethod([mockedPrivateUtilities userAuthenticationDataFromAccessToken:mockedAccessToken])).andReturn(nil); + + XCTestExpectation *taskExpectation = [self expectationWithDescription:@"task"]; + [[PFFacebookUtils logInInBackgroundWithAccessToken:mockedAccessToken] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.error.domain, PFParseErrorDomain); + XCTAssertEqual(task.error.code, kPFErrorFacebookInvalidSession); + [taskExpectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; + + XCTestExpectation *blockExpectation = [self expectationWithDescription:@"block"]; + [PFFacebookUtils logInInBackgroundWithAccessToken:mockedAccessToken block:^(PFUser *user, NSError *error) { + XCTAssertNil(user); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqual(error.code, kPFErrorFacebookInvalidSession); + [blockExpectation fulfill]; + }]; + [self waitForTestExpectations]; +} + +- (void)testLinkWithReadPermissions { + id mockedUser = PFStrictClassMock([PFUser class]); + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + + OCMStub([mockedAuthProvider authenticateAsyncWithReadPermissions:@[ @"read" ] publishPermissions:nil]).andReturn([BFTask taskWithResult:@{}]); + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + + OCMStub([mockedUser linkWithAuthTypeInBackground:@"facebook" authData:[OCMArg isNotNil]]).andReturn([BFTask taskWithResult:@YES]); + + XCTestExpectation *taskExpecatation = [self expectationWithDescription:@"task"]; + [[PFFacebookUtils linkUserInBackground:mockedUser withReadPermissions:@[ @"read" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.result, @YES); + [taskExpecatation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; + + XCTestExpectation *blockExpectation = [self expectationWithDescription:@"block"]; + [PFFacebookUtils linkUserInBackground:mockedUser withReadPermissions:@[ @"read" ] block:^(BOOL result, NSError *error) { + XCTAssertTrue(result); + [blockExpectation fulfill]; + }]; + [self waitForTestExpectations]; +} + +- (void)testLinkWithWritePermissions { + id mockedUser = PFStrictClassMock([PFUser class]); + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + + OCMStub([mockedAuthProvider authenticateAsyncWithReadPermissions:nil publishPermissions:@[ @"publish" ]]).andReturn([BFTask taskWithResult:@{}]); + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + OCMStub([mockedUser linkWithAuthTypeInBackground:@"facebook" authData:[OCMArg isNotNil]]).andReturn([BFTask taskWithResult:@YES]); + + XCTestExpectation *taskExpecation = [self expectationWithDescription:@"task"]; + [[PFFacebookUtils linkUserInBackground:mockedUser withPublishPermissions:@[ @"publish" ]] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.result, @YES); + [taskExpecation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; + + XCTestExpectation *blockExpectation = [self expectationWithDescription:@"block"]; + [PFFacebookUtils linkUserInBackground:mockedUser withPublishPermissions:@[ @"publish" ] block:^(BOOL result, NSError *error) { + XCTAssertTrue(result); + [blockExpectation fulfill]; + }]; + [self waitForTestExpectations]; +} + +- (void)testLinkWithAccessToken { + id mockedPrivateUtilities = PFStrictClassMock([PFFacebookPrivateUtilities class]); + id mockedAccessToken = PFStrictClassMock([FBSDKAccessToken class]); + id mockedUser = PFStrictClassMock([PFUser class]); + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + + NSDictionary *sampleAuthData = [self sampleAuthData]; + OCMStub(ClassMethod([mockedPrivateUtilities userAuthenticationDataFromAccessToken:mockedAccessToken])).andReturn(sampleAuthData); + + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + [OCMStub([mockedUser linkWithAuthTypeInBackground:@"facebook" authData:sampleAuthData]) andReturn:[BFTask taskWithResult:@YES]]; + + XCTestExpectation *taskExpecatation = [self expectationWithDescription:@"block"]; + [[PFFacebookUtils linkUserInBackground:mockedUser withAccessToken:mockedAccessToken] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.result, @YES); + [taskExpecatation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; + + XCTestExpectation *blockExpectation = [self expectationWithDescription:@"block"]; + [PFFacebookUtils linkUserInBackground:mockedUser withAccessToken:mockedAccessToken block:^(BOOL result, NSError *error) { + XCTAssertTrue(result); + [blockExpectation fulfill]; + }]; + [self waitForTestExpectations]; +} + +- (void)testUnlink { + id mockedLinkedUser = PFStrictClassMock([PFUser class]); + id mockedAuthProvider = PFStrictClassMock([PFFacebookMobileAuthenticationProvider class]); + + [PFFacebookUtils _setAuthenticationProvider:mockedAuthProvider]; + [OCMStub([mockedLinkedUser unlinkWithAuthTypeInBackground:@"facebook"]) andReturn:[BFTask taskWithResult:@YES]]; + + XCTestExpectation *taskExpecatation = [self expectationWithDescription:@"block"]; + [[PFFacebookUtils unlinkUserInBackground:mockedLinkedUser] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.result, @YES); + [taskExpecatation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; + + XCTestExpectation *blockExpectation = [self expectationWithDescription:@"block"]; + [PFFacebookUtils unlinkUserInBackground:mockedLinkedUser block:^(BOOL result, NSError *error) { + XCTAssertTrue(result); + [blockExpectation fulfill]; + }]; + [self waitForTestExpectations]; +} + +- (void)testIsLinked { + id mockedLinkedUser = PFStrictClassMock([PFUser class]); + id mockedUnlinkedUser = PFStrictClassMock([PFUser class]); + + OCMStub([mockedLinkedUser isLinkedWithAuthType:@"facebook"]).andReturn(YES); + OCMStub([mockedUnlinkedUser isLinkedWithAuthType:@"facebook"]).andReturn(NO); + + XCTAssertTrue([PFFacebookUtils isLinkedWithUser:mockedLinkedUser]); + XCTAssertFalse([PFFacebookUtils isLinkedWithUser:mockedUnlinkedUser]); +} + +@end diff --git a/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/ParseOSXStarterProject-Swift.xcodeproj/xcshareddata/xcschemes/ParseOSXStarterProject-Swift.xcscheme b/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/ParseOSXStarterProject-Swift.xcodeproj/xcshareddata/xcschemes/ParseOSXStarterProject-Swift.xcscheme deleted file mode 100644 index 1302fb196..000000000 --- a/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/ParseOSXStarterProject-Swift.xcodeproj/xcshareddata/xcschemes/ParseOSXStarterProject-Swift.xcscheme +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist b/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist index 6cca4990a..55c8c07e3 100644 --- a/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist +++ b/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSMainNibFile diff --git a/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist b/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist index cde97274e..bdb3a1e4b 100644 --- a/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist +++ b/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSMainNibFile diff --git a/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist b/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist index 07bbf7315..ecf973294 100644 --- a/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist +++ b/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist b/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist index b071a05a0..f7f03cd45 100644 --- a/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist +++ b/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist @@ -19,11 +19,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 LSRequiresIPhoneOS NSMainNibFile diff --git a/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist b/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist index 509b99d60..c1cb3c95f 100644 --- a/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist +++ b/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 LSRequiresIPhoneOS UIMainStoryboardFile diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist index e7fe91301..667c67427 100644 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist +++ b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 NSExtension NSExtensionAttributes diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseWatchStarter-watchOS.xcscheme b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseWatchStarter-watchOS.xcscheme deleted file mode 100644 index 1f26c0d2f..000000000 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseWatchStarter-watchOS.xcscheme +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist index 1c4d7cd67..51542af38 100644 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist +++ b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist b/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist index 1a8b8a824..753250ca8 100644 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist +++ b/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.4 + 1.16.0 CFBundleSignature ???? CFBundleVersion - 1.15.4 + 1.16.0 LSRequiresIPhoneOS UIMainStoryboardFile diff --git a/ParseTwitterUtils/CHANGELOG.md b/ParseTwitterUtils/CHANGELOG.md new file mode 100644 index 000000000..d44df0325 --- /dev/null +++ b/ParseTwitterUtils/CHANGELOG.md @@ -0,0 +1,55 @@ +# Change Log + +## [1.11.0](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.11.0) (2016-06-29) +[Full Changelog](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/compare/1.10.0...1.11.0) + +**Implemented enhancements:** + +- Add support for Carthage. [\#33](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/33) ([nlutsenko](https://github.com/nlutsenko)) +- Fix analyzer and build warnings. [\#32](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/32) ([nlutsenko](https://github.com/nlutsenko)) +- Update all dependencies and project to Xcode 7.3. [\#28](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/28) ([nlutsenko](https://github.com/nlutsenko)) +- Add support for using `PF_Twitter` without a linking or logging into a Parse account [\#27](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/27) ([zadr](https://github.com/zadr)) +- Removed `Parse.clientKey` requirement for initializing ParseTwitterUtils. [\#26](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/26) ([aphex3k](https://github.com/aphex3k)) + +**Merged pull requests:** + +- Update CocoaPods and RubyGems dependencies. [\#31](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/31) ([nlutsenko](https://github.com/nlutsenko)) + +## [1.10.0](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.10.0) (2016-01-08) +[Full Changelog](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/compare/1.9.1...1.10.0) + +**Implemented enhancements:** + +- Remove all usages of PF\_GENERIC macro. [\#19](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/19) ([nlutsenko](https://github.com/nlutsenko)) +- Add custom packaging into a precompiled binary via Rakefile. [\#16](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/16) ([nlutsenko](https://github.com/nlutsenko)) +- Update all of the public documentation to new style. [\#15](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/15) ([nlutsenko](https://github.com/nlutsenko)) + +**Merged pull requests:** + +- ParseTwitterUtils 1.10.0 [\#21](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/21) ([nlutsenko](https://github.com/nlutsenko)) +- Update project to Xcode 7.2. [\#20](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/20) ([nlutsenko](https://github.com/nlutsenko)) +- Use Xcode 7.2 for Travis-CI. [\#17](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/17) ([nlutsenko](https://github.com/nlutsenko)) +- Update xctoolchain to latest. [\#14](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/14) ([richardjrossiii](https://github.com/richardjrossiii)) + +## [1.9.1](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.9.1) (2015-11-18) +[Full Changelog](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/compare/1.9.0...1.9.1) + +**Implemented enhancements:** + +- Fix warnings in PFOAuth1FlowDialog. [\#10](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/10) ([nlutsenko](https://github.com/nlutsenko)) +- Remove all usage of custom nullability macros. [\#8](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/8) ([nlutsenko](https://github.com/nlutsenko)) +- Update everything to use Xcode 7.1. [\#6](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/6) ([nlutsenko](https://github.com/nlutsenko)) +- Added safeguard assertion for initialization of PFTwitterUtils before Parse is initialized. [\#4](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/4) ([nlutsenko](https://github.com/nlutsenko)) +- Include and automate localized strings generation. [\#2](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/2) ([nlutsenko](https://github.com/nlutsenko)) + +**Merged pull requests:** + +- ParseTwitterUtils 1.9.1 [\#13](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/13) ([nlutsenko](https://github.com/nlutsenko)) +- Update Gemfile. [\#9](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/9) ([nlutsenko](https://github.com/nlutsenko)) +- Update xctoolchain to latest. [\#5](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/5) ([nlutsenko](https://github.com/nlutsenko)) +- Use shared things from xctoolchain. [\#3](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/3) ([nlutsenko](https://github.com/nlutsenko)) + +## [1.9.0](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.9.0) (2015-10-08) + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/ParseTwitterUtils/Configurations/ParseTwitterTestApplication.xcconfig b/ParseTwitterUtils/Configurations/ParseTwitterTestApplication.xcconfig new file mode 100644 index 000000000..20baf58fa --- /dev/null +++ b/ParseTwitterUtils/Configurations/ParseTwitterTestApplication.xcconfig @@ -0,0 +1,18 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/Application.xcconfig" + +PRODUCT_NAME = ParseTwitterTestApplication +PRODUCT_BUNDLE_IDENTIFIER = com.parse.twitterutils.testapplication + +IPHONEOS_DEPLOYMENT_TARGET = 8.0 + +INFOPLIST_FILE = $(SRCROOT)/Tests/TestApplication/Resources/Info.plist diff --git a/ParseTwitterUtils/Configurations/ParseTwitterUtils-Tests.xcconfig b/ParseTwitterUtils/Configurations/ParseTwitterUtils-Tests.xcconfig new file mode 100644 index 000000000..dddecf867 --- /dev/null +++ b/ParseTwitterUtils/Configurations/ParseTwitterUtils-Tests.xcconfig @@ -0,0 +1,20 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/LogicTests.xcconfig" + +PRODUCT_NAME = ParseTwitterUtils-Tests +IPHONEOS_DEPLOYMENT_TARGET = 8.0 + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(BUILT_PRODUCTS_DIR)/OCMock + +INFOPLIST_FILE = $(PROJECT_DIR)/Tests/Resources/Info.plist + +TEST_HOST = $(BUILT_PRODUCTS_DIR)/ParseTwitterTestApplication.app/ParseTwitterTestApplication diff --git a/ParseTwitterUtils/Configurations/ParseTwitterUtils-iOS-Dynamic.xcconfig b/ParseTwitterUtils/Configurations/ParseTwitterUtils-iOS-Dynamic.xcconfig new file mode 100644 index 000000000..a51fef512 --- /dev/null +++ b/ParseTwitterUtils/Configurations/ParseTwitterUtils-iOS-Dynamic.xcconfig @@ -0,0 +1,22 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/DynamicFramework.xcconfig" + +PRODUCT_NAME = ParseTwitterUtils +PRODUCT_BUNDLE_IDENTIFIER = com.parse.twitterutils.ios + +IPHONEOS_DEPLOYMENT_TARGET = 8.0 + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info.plist + +// TODO: (nlutsenko) Cleanup source code so we can safely ignore local variable shadow warnings. +GCC_WARN_SHADOW = NO + diff --git a/ParseTwitterUtils/Configurations/ParseTwitterUtils-iOS.xcconfig b/ParseTwitterUtils/Configurations/ParseTwitterUtils-iOS.xcconfig new file mode 100644 index 000000000..dc28e59ce --- /dev/null +++ b/ParseTwitterUtils/Configurations/ParseTwitterUtils-iOS.xcconfig @@ -0,0 +1,20 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/StaticFramework.xcconfig" + +PRODUCT_NAME = ParseTwitterUtils +PRODUCT_BUNDLE_IDENTIFIER = com.parse.twitterutils.ios + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info.plist + +// TODO: (nlutsenko) Cleanup source code so we can safely ignore local variable shadow warnings. +GCC_WARN_SHADOW = NO + diff --git a/ParseTwitterUtils/Configurations/Shared b/ParseTwitterUtils/Configurations/Shared new file mode 120000 index 000000000..657caabf0 --- /dev/null +++ b/ParseTwitterUtils/Configurations/Shared @@ -0,0 +1 @@ +../../Vendor/xctoolchain/Configurations \ No newline at end of file diff --git a/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/project.pbxproj b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/project.pbxproj new file mode 100644 index 000000000..5021f8e4f --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/project.pbxproj @@ -0,0 +1,921 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 06D00BAB1BC78F23005BAA6F /* ParseTwitterUtils.strings in Resources */ = {isa = PBXBuildFile; fileRef = 06D00BAD1BC78F23005BAA6F /* ParseTwitterUtils.strings */; }; + 06D00BB51BC790F3005BAA6F /* PFTwitterLocalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 06D00BB41BC790F3005BAA6F /* PFTwitterLocalization.h */; }; + 4A0ECBDC200D41B600BA84A3 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0ECBDD200D41B600BA84A3 /* Parse.framework */; }; + 4A0ECBDE200D41C400BA84A3 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0ECBDF200D41C400BA84A3 /* Parse.framework */; }; + 4A0ECBE0200D435300BA84A3 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0ECBDD200D41B600BA84A3 /* Parse.framework */; }; + 4A0ECBE1200D437800BA84A3 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94FE200D12C9005D8F4B /* Bolts.framework */; }; + 4A9A94F8200D12A2005D8F4B /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94FA200D12A2005D8F4B /* Bolts.framework */; }; + 4A9A94FC200D12C9005D8F4B /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94FE200D12C9005D8F4B /* Bolts.framework */; }; + 4AAEAACA200C2BBB00AA7479 /* OCMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAEAACB200C2BBB00AA7479 /* OCMock.framework */; }; + 8135E4951B4B6A0E0092F452 /* PF_Twitter_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E48E1B4B6A0E0092F452 /* PF_Twitter_Private.h */; }; + 8135E4961B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E48F1B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.h */; }; + 8135E4971B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8135E4901B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.m */; }; + 8135E4981B4B6A0E0092F452 /* PF_Twitter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E4911B4B6A0E0092F452 /* PF_Twitter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8135E4991B4B6A0E0092F452 /* PF_Twitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8135E4921B4B6A0E0092F452 /* PF_Twitter.m */; }; + 8135E49A1B4B6A0E0092F452 /* PFTwitterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E4931B4B6A0E0092F452 /* PFTwitterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8135E49B1B4B6A0E0092F452 /* PFTwitterUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 8135E4941B4B6A0E0092F452 /* PFTwitterUtils.m */; }; + 813DFC981AB2526000F25A08 /* third_party_licenses.txt in Resources */ = {isa = PBXBuildFile; fileRef = 813DFC971AB2526000F25A08 /* third_party_licenses.txt */; }; + 813E54A41BB5DDEF00C727E8 /* PFTwitterUtils_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 813E54A31BB5DDEF00C727E8 /* PFTwitterUtils_Private.h */; }; + 813E54A91BB5E5FA00C727E8 /* PFTwitterPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 813E54A61BB5E52000C727E8 /* PFTwitterPrivateUtilities.m */; }; + 813E54AA1BB5E5FF00C727E8 /* PFTwitterPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 813E54A51BB5E52000C727E8 /* PFTwitterPrivateUtilities.h */; }; + 815F18401B4B730E0066E996 /* PFOAuth1FlowDialog.h in Headers */ = {isa = PBXBuildFile; fileRef = 815F183E1B4B730E0066E996 /* PFOAuth1FlowDialog.h */; }; + 815F18411B4B730E0066E996 /* PFOAuth1FlowDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 815F183F1B4B730E0066E996 /* PFOAuth1FlowDialog.m */; }; + 81665C731BBDE27D00AE923F /* OAuth1FlowDialogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81665C6E1BBDE27D00AE923F /* OAuth1FlowDialogTests.m */; }; + 81665C741BBDE27D00AE923F /* OAuthCoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81665C6F1BBDE27D00AE923F /* OAuthCoreTests.m */; }; + 81665C751BBDE27D00AE923F /* TwitterAuthenticationProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81665C701BBDE27D00AE923F /* TwitterAuthenticationProviderTests.m */; }; + 81665C761BBDE27D00AE923F /* TwitterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81665C711BBDE27D00AE923F /* TwitterTests.m */; }; + 81665C771BBDE27D00AE923F /* TwitterUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81665C721BBDE27D00AE923F /* TwitterUtilsTests.m */; }; + 81665C7C1BBDE2EE00AE923F /* PFTwitterTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 81665C7B1BBDE2EE00AE923F /* PFTwitterTestCase.m */; }; + 8166FB921B4F1DC5003841A2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8166FB8F1B4F1DC5003841A2 /* main.m */; }; + 816831131D1E16CC00315E21 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 816831121D1E16CC00315E21 /* UIKit.framework */; }; + 816831161D1E16D000315E21 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 816831151D1E16D000315E21 /* Accounts.framework */; }; + 816831191D1E16D500315E21 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 816831181D1E16D500315E21 /* CoreGraphics.framework */; }; + 8168311D1D1E16DC00315E21 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8168311C1D1E16DC00315E21 /* Security.framework */; }; + 816831201D1E16E200315E21 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8168311F1D1E16E200315E21 /* StoreKit.framework */; }; + 816831241D1E16E700315E21 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 816831231D1E16E700315E21 /* libsqlite3.tbd */; }; + 816831271D1E16EB00315E21 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 816831261D1E16EB00315E21 /* CoreLocation.framework */; }; + 8168312B1D1E16F000315E21 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8168312A1D1E16F000315E21 /* SystemConfiguration.framework */; }; + 8168312F1D1E16F700315E21 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8168312E1D1E16F700315E21 /* AudioToolbox.framework */; }; + 816831331D1E16FE00315E21 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 816831321D1E16FE00315E21 /* Social.framework */; }; + 816832521D1E1A7900315E21 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8168311C1D1E16DC00315E21 /* Security.framework */; }; + 816832591D1E1A7E00315E21 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8168312A1D1E16F000315E21 /* SystemConfiguration.framework */; }; + 8168325E1D1E1A8600315E21 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 816831231D1E16E700315E21 /* libsqlite3.tbd */; }; + 8168326A1D1E1A8B00315E21 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8168312E1D1E16F700315E21 /* AudioToolbox.framework */; }; + 817A37CB1B4B741A00129AFA /* PF_OAuthCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 817A37C91B4B741A00129AFA /* PF_OAuthCore.h */; }; + 817A37CC1B4B741A00129AFA /* PF_OAuthCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 817A37CA1B4B741A00129AFA /* PF_OAuthCore.m */; }; + 819DAAD61BB5EC79002BDE2B /* PFTwitterAlertView.h in Headers */ = {isa = PBXBuildFile; fileRef = 819DAAD41BB5EC79002BDE2B /* PFTwitterAlertView.h */; }; + 819DAAD71BB5EC79002BDE2B /* PFTwitterAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 819DAAD51BB5EC79002BDE2B /* PFTwitterAlertView.m */; }; + 81CB98CC1AB7905D00136FA5 /* ParseTwitterUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AAC07E0554694100DB518D /* ParseTwitterUtils.framework */; }; + 81D342A11B4C7DA500B6C124 /* ParseTwitterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 81D342A01B4C7DA500B6C124 /* ParseTwitterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81ECACBB1D1E14E000FA7673 /* PFOAuth1FlowDialog.h in Headers */ = {isa = PBXBuildFile; fileRef = 815F183E1B4B730E0066E996 /* PFOAuth1FlowDialog.h */; }; + 81ECACBC1D1E14E000FA7673 /* PF_OAuthCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 817A37C91B4B741A00129AFA /* PF_OAuthCore.h */; }; + 81ECACBD1D1E14E000FA7673 /* PFTwitterUtils_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 813E54A31BB5DDEF00C727E8 /* PFTwitterUtils_Private.h */; }; + 81ECACBE1D1E14E000FA7673 /* ParseTwitterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 81D342A01B4C7DA500B6C124 /* ParseTwitterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81ECACBF1D1E14E000FA7673 /* PFTwitterPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 813E54A51BB5E52000C727E8 /* PFTwitterPrivateUtilities.h */; }; + 81ECACC01D1E14E000FA7673 /* PFTwitterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E4931B4B6A0E0092F452 /* PFTwitterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81ECACC11D1E14E000FA7673 /* PFTwitterLocalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 06D00BB41BC790F3005BAA6F /* PFTwitterLocalization.h */; }; + 81ECACC21D1E14E000FA7673 /* PF_Twitter_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E48E1B4B6A0E0092F452 /* PF_Twitter_Private.h */; }; + 81ECACC31D1E14E000FA7673 /* PFTwitterAuthenticationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E48F1B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.h */; }; + 81ECACC41D1E14E000FA7673 /* PFTwitterAlertView.h in Headers */ = {isa = PBXBuildFile; fileRef = 819DAAD41BB5EC79002BDE2B /* PFTwitterAlertView.h */; }; + 81ECACC51D1E14E000FA7673 /* PF_Twitter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8135E4911B4B6A0E0092F452 /* PF_Twitter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81ECACC71D1E14E000FA7673 /* PFTwitterAuthenticationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8135E4901B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.m */; }; + 81ECACC81D1E14E000FA7673 /* PFTwitterUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 8135E4941B4B6A0E0092F452 /* PFTwitterUtils.m */; }; + 81ECACC91D1E14E000FA7673 /* PF_Twitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8135E4921B4B6A0E0092F452 /* PF_Twitter.m */; }; + 81ECACCA1D1E14E000FA7673 /* PFOAuth1FlowDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 815F183F1B4B730E0066E996 /* PFOAuth1FlowDialog.m */; }; + 81ECACCB1D1E14E000FA7673 /* PFTwitterPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 813E54A61BB5E52000C727E8 /* PFTwitterPrivateUtilities.m */; }; + 81ECACCC1D1E14E000FA7673 /* PF_OAuthCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 817A37CA1B4B741A00129AFA /* PF_OAuthCore.m */; }; + 81ECACCD1D1E14E000FA7673 /* PFTwitterAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 819DAAD51BB5EC79002BDE2B /* PFTwitterAlertView.m */; }; + 81ECACD21D1E14E000FA7673 /* third_party_licenses.txt in Resources */ = {isa = PBXBuildFile; fileRef = 813DFC971AB2526000F25A08 /* third_party_licenses.txt */; }; + 81ECACD31D1E14E000FA7673 /* ParseTwitterUtils.strings in Resources */ = {isa = PBXBuildFile; fileRef = 06D00BAD1BC78F23005BAA6F /* ParseTwitterUtils.strings */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 8166FB941B4F1E9A003841A2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8166FB661B4F1D77003841A2; + remoteInfo = ParseTwitterTestApplication; + }; + 81CB98CD1AB7905D00136FA5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2AAC07D0554694100DB518D; + remoteInfo = "ParseFacebookUtils_v4-iOS"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 06D00BAE1BC78F29005BAA6F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ParseTwitterUtils.strings; sourceTree = ""; }; + 06D00BB41BC790F3005BAA6F /* PFTwitterLocalization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFTwitterLocalization.h; sourceTree = ""; }; + 4A0ECBDD200D41B600BA84A3 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A0ECBDF200D41C400BA84A3 /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94FA200D12A2005D8F4B /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94FE200D12C9005D8F4B /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AAEAACB200C2BBB00AA7479 /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8135E48E1B4B6A0E0092F452 /* PF_Twitter_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PF_Twitter_Private.h; sourceTree = ""; }; + 8135E48F1B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFTwitterAuthenticationProvider.h; sourceTree = ""; }; + 8135E4901B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFTwitterAuthenticationProvider.m; sourceTree = ""; }; + 8135E4911B4B6A0E0092F452 /* PF_Twitter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PF_Twitter.h; sourceTree = ""; }; + 8135E4921B4B6A0E0092F452 /* PF_Twitter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PF_Twitter.m; sourceTree = ""; }; + 8135E4931B4B6A0E0092F452 /* PFTwitterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFTwitterUtils.h; sourceTree = ""; }; + 8135E4941B4B6A0E0092F452 /* PFTwitterUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFTwitterUtils.m; sourceTree = ""; }; + 813DFC971AB2526000F25A08 /* third_party_licenses.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = third_party_licenses.txt; sourceTree = SOURCE_ROOT; }; + 813E54A31BB5DDEF00C727E8 /* PFTwitterUtils_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFTwitterUtils_Private.h; sourceTree = ""; }; + 813E54A51BB5E52000C727E8 /* PFTwitterPrivateUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFTwitterPrivateUtilities.h; sourceTree = ""; }; + 813E54A61BB5E52000C727E8 /* PFTwitterPrivateUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFTwitterPrivateUtilities.m; sourceTree = ""; }; + 815462231D37FE6E0069489A /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; + 815462251D37FE6E0069489A /* iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = iOS.xcconfig; sourceTree = ""; }; + 815462261D37FE6E0069489A /* macOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = ""; }; + 815462271D37FE6E0069489A /* tvOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = tvOS.xcconfig; sourceTree = ""; }; + 815462281D37FE6E0069489A /* watchOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = watchOS.xcconfig; sourceTree = ""; }; + 8154622A1D37FE6E0069489A /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = ""; }; + 8154622B1D37FE6E0069489A /* DynamicFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DynamicFramework.xcconfig; sourceTree = ""; }; + 8154622C1D37FE6E0069489A /* LogicTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = LogicTests.xcconfig; sourceTree = ""; }; + 8154622D1D37FE6E0069489A /* StaticFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticFramework.xcconfig; sourceTree = ""; }; + 8154622F1D37FE6E0069489A /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 815462301D37FE6E0069489A /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 815462311D37FE6E0069489A /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 815F183E1B4B730E0066E996 /* PFOAuth1FlowDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFOAuth1FlowDialog.h; sourceTree = ""; }; + 815F183F1B4B730E0066E996 /* PFOAuth1FlowDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFOAuth1FlowDialog.m; sourceTree = ""; }; + 81665C6E1BBDE27D00AE923F /* OAuth1FlowDialogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OAuth1FlowDialogTests.m; sourceTree = ""; }; + 81665C6F1BBDE27D00AE923F /* OAuthCoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OAuthCoreTests.m; sourceTree = ""; }; + 81665C701BBDE27D00AE923F /* TwitterAuthenticationProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TwitterAuthenticationProviderTests.m; sourceTree = ""; }; + 81665C711BBDE27D00AE923F /* TwitterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TwitterTests.m; sourceTree = ""; }; + 81665C721BBDE27D00AE923F /* TwitterUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TwitterUtilsTests.m; sourceTree = ""; }; + 81665C7A1BBDE2EE00AE923F /* PFTwitterTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFTwitterTestCase.h; sourceTree = ""; }; + 81665C7B1BBDE2EE00AE923F /* PFTwitterTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFTwitterTestCase.m; sourceTree = ""; }; + 8166FB671B4F1D77003841A2 /* ParseTwitterTestApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ParseTwitterTestApplication.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8166FB8F1B4F1DC5003841A2 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8166FB911B4F1DC5003841A2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 816831121D1E16CC00315E21 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 816831151D1E16D000315E21 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/Accounts.framework; sourceTree = DEVELOPER_DIR; }; + 816831181D1E16D500315E21 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + 8168311C1D1E16DC00315E21 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + 8168311F1D1E16E200315E21 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; + 816831231D1E16E700315E21 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 816831261D1E16EB00315E21 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; }; + 8168312A1D1E16F000315E21 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + 8168312E1D1E16F700315E21 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; + 816831321D1E16FE00315E21 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/Social.framework; sourceTree = DEVELOPER_DIR; }; + 817A37C91B4B741A00129AFA /* PF_OAuthCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PF_OAuthCore.h; sourceTree = ""; }; + 817A37CA1B4B741A00129AFA /* PF_OAuthCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PF_OAuthCore.m; sourceTree = ""; }; + 81930A391BBDE76E00A5E4BB /* PFTwitterTestMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFTwitterTestMacros.h; sourceTree = ""; }; + 819DAAD41BB5EC79002BDE2B /* PFTwitterAlertView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFTwitterAlertView.h; sourceTree = ""; }; + 819DAAD51BB5EC79002BDE2B /* PFTwitterAlertView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFTwitterAlertView.m; sourceTree = ""; }; + 81CB98C61AB7905D00136FA5 /* ParseTwitterUtils-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ParseTwitterUtils-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 81CB98D31AB7906D00136FA5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Resources/Info.plist; sourceTree = ""; }; + 81D342A01B4C7DA500B6C124 /* ParseTwitterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParseTwitterUtils.h; sourceTree = ""; }; + 81ECACD71D1E14E000FA7673 /* ParseTwitterUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseTwitterUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 81ECACD91D1E14F300FA7673 /* ParseTwitterUtils-iOS-Dynamic.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseTwitterUtils-iOS-Dynamic.xcconfig"; sourceTree = ""; }; + D2AAC07E0554694100DB518D /* ParseTwitterUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseTwitterUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F51535531B57453700C49F56 /* ParseTwitterUtils-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseTwitterUtils-iOS.xcconfig"; sourceTree = ""; }; + F51535541B57454500C49F56 /* ParseTwitterUtils-Tests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseTwitterUtils-Tests.xcconfig"; sourceTree = ""; }; + F51535551B57455200C49F56 /* ParseTwitterTestApplication.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ParseTwitterTestApplication.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8166FB641B4F1D77003841A2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81CB98C31AB7905D00136FA5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AAEAACA200C2BBB00AA7479 /* OCMock.framework in Frameworks */, + 8168326A1D1E1A8B00315E21 /* AudioToolbox.framework in Frameworks */, + 8168325E1D1E1A8600315E21 /* libsqlite3.tbd in Frameworks */, + 816832591D1E1A7E00315E21 /* SystemConfiguration.framework in Frameworks */, + 816832521D1E1A7900315E21 /* Security.framework in Frameworks */, + 4A0ECBE0200D435300BA84A3 /* Parse.framework in Frameworks */, + 81CB98CC1AB7905D00136FA5 /* ParseTwitterUtils.framework in Frameworks */, + 4A0ECBE1200D437800BA84A3 /* Bolts.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81ECACCE1D1E14E000FA7673 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A0ECBDE200D41C400BA84A3 /* Parse.framework in Frameworks */, + 4A9A94F8200D12A2005D8F4B /* Bolts.framework in Frameworks */, + 816831331D1E16FE00315E21 /* Social.framework in Frameworks */, + 8168312F1D1E16F700315E21 /* AudioToolbox.framework in Frameworks */, + 8168312B1D1E16F000315E21 /* SystemConfiguration.framework in Frameworks */, + 816831271D1E16EB00315E21 /* CoreLocation.framework in Frameworks */, + 816831241D1E16E700315E21 /* libsqlite3.tbd in Frameworks */, + 816831201D1E16E200315E21 /* StoreKit.framework in Frameworks */, + 8168311D1D1E16DC00315E21 /* Security.framework in Frameworks */, + 816831191D1E16D500315E21 /* CoreGraphics.framework in Frameworks */, + 816831161D1E16D000315E21 /* Accounts.framework in Frameworks */, + 816831131D1E16CC00315E21 /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A0ECBDC200D41B600BA84A3 /* Parse.framework in Frameworks */, + 4A9A94FC200D12C9005D8F4B /* Bolts.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC07E0554694100DB518D /* ParseTwitterUtils.framework */, + 81CB98C61AB7905D00136FA5 /* ParseTwitterUtils-Tests.xctest */, + 8166FB671B4F1D77003841A2 /* ParseTwitterTestApplication.app */, + 81ECACD71D1E14E000FA7673 /* ParseTwitterUtils.framework */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* Breakpad */ = { + isa = PBXGroup; + children = ( + F51535381B57451200C49F56 /* Configurations */, + 8135E48C1B4B6A0E0092F452 /* ParseTwitterUtils */, + 813DFC961AB2524C00F25A08 /* Resources */, + 81CB98D21AB7906D00136FA5 /* Tests */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + 8EF82AB7049F0059A572B973 /* Pods */, + ); + indentWidth = 4; + name = Breakpad; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4A0ECBDF200D41C400BA84A3 /* Parse.framework */, + 4A0ECBDD200D41B600BA84A3 /* Parse.framework */, + 4A9A94FE200D12C9005D8F4B /* Bolts.framework */, + 4A9A94FA200D12A2005D8F4B /* Bolts.framework */, + 4AAEAACB200C2BBB00AA7479 /* OCMock.framework */, + 813DFC8F1AB2513D00F25A08 /* User Frameworks */, + 813DFC8E1AB2513300F25A08 /* System Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8135E48C1B4B6A0E0092F452 /* ParseTwitterUtils */ = { + isa = PBXGroup; + children = ( + 8135E48D1B4B6A0E0092F452 /* Internal */, + 81D342A01B4C7DA500B6C124 /* ParseTwitterUtils.h */, + 8135E4931B4B6A0E0092F452 /* PFTwitterUtils.h */, + 8135E4941B4B6A0E0092F452 /* PFTwitterUtils.m */, + 8135E4911B4B6A0E0092F452 /* PF_Twitter.h */, + 8135E4921B4B6A0E0092F452 /* PF_Twitter.m */, + ); + path = ParseTwitterUtils; + sourceTree = ""; + }; + 8135E48D1B4B6A0E0092F452 /* Internal */ = { + isa = PBXGroup; + children = ( + 815F183D1B4B730E0066E996 /* Dialog */, + 817A37C81B4B741A00129AFA /* OAuthCore */, + 8135E48E1B4B6A0E0092F452 /* PF_Twitter_Private.h */, + 813E54A31BB5DDEF00C727E8 /* PFTwitterUtils_Private.h */, + 8135E48F1B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.h */, + 8135E4901B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.m */, + 813E54A51BB5E52000C727E8 /* PFTwitterPrivateUtilities.h */, + 813E54A61BB5E52000C727E8 /* PFTwitterPrivateUtilities.m */, + 819DAAD41BB5EC79002BDE2B /* PFTwitterAlertView.h */, + 819DAAD51BB5EC79002BDE2B /* PFTwitterAlertView.m */, + 06D00BB41BC790F3005BAA6F /* PFTwitterLocalization.h */, + ); + path = Internal; + sourceTree = ""; + }; + 813DFC8E1AB2513300F25A08 /* System Frameworks */ = { + isa = PBXGroup; + children = ( + 816831321D1E16FE00315E21 /* Social.framework */, + 8168312E1D1E16F700315E21 /* AudioToolbox.framework */, + 8168312A1D1E16F000315E21 /* SystemConfiguration.framework */, + 816831261D1E16EB00315E21 /* CoreLocation.framework */, + 816831231D1E16E700315E21 /* libsqlite3.tbd */, + 8168311F1D1E16E200315E21 /* StoreKit.framework */, + 8168311C1D1E16DC00315E21 /* Security.framework */, + 816831181D1E16D500315E21 /* CoreGraphics.framework */, + 816831151D1E16D000315E21 /* Accounts.framework */, + 816831121D1E16CC00315E21 /* UIKit.framework */, + ); + name = "System Frameworks"; + sourceTree = ""; + }; + 813DFC8F1AB2513D00F25A08 /* User Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = "User Frameworks"; + sourceTree = ""; + }; + 813DFC961AB2524C00F25A08 /* Resources */ = { + isa = PBXGroup; + children = ( + 813DFC971AB2526000F25A08 /* third_party_licenses.txt */, + 06D00BAD1BC78F23005BAA6F /* ParseTwitterUtils.strings */, + ); + path = Resources; + sourceTree = ""; + }; + 815462221D37FE6E0069489A /* Shared */ = { + isa = PBXGroup; + children = ( + 815462231D37FE6E0069489A /* Common.xcconfig */, + 815462241D37FE6E0069489A /* Platform */, + 815462291D37FE6E0069489A /* Product */, + 8154622E1D37FE6E0069489A /* Project */, + 815462311D37FE6E0069489A /* Warnings.xcconfig */, + ); + path = Shared; + sourceTree = ""; + }; + 815462241D37FE6E0069489A /* Platform */ = { + isa = PBXGroup; + children = ( + 815462251D37FE6E0069489A /* iOS.xcconfig */, + 815462261D37FE6E0069489A /* macOS.xcconfig */, + 815462271D37FE6E0069489A /* tvOS.xcconfig */, + 815462281D37FE6E0069489A /* watchOS.xcconfig */, + ); + path = Platform; + sourceTree = ""; + }; + 815462291D37FE6E0069489A /* Product */ = { + isa = PBXGroup; + children = ( + 8154622A1D37FE6E0069489A /* Application.xcconfig */, + 8154622B1D37FE6E0069489A /* DynamicFramework.xcconfig */, + 8154622C1D37FE6E0069489A /* LogicTests.xcconfig */, + 8154622D1D37FE6E0069489A /* StaticFramework.xcconfig */, + ); + path = Product; + sourceTree = ""; + }; + 8154622E1D37FE6E0069489A /* Project */ = { + isa = PBXGroup; + children = ( + 8154622F1D37FE6E0069489A /* Debug.xcconfig */, + 815462301D37FE6E0069489A /* Release.xcconfig */, + ); + path = Project; + sourceTree = ""; + }; + 815F183D1B4B730E0066E996 /* Dialog */ = { + isa = PBXGroup; + children = ( + 815F183E1B4B730E0066E996 /* PFOAuth1FlowDialog.h */, + 815F183F1B4B730E0066E996 /* PFOAuth1FlowDialog.m */, + ); + path = Dialog; + sourceTree = ""; + }; + 81665C6D1BBDE27D00AE923F /* Unit */ = { + isa = PBXGroup; + children = ( + 81665C6E1BBDE27D00AE923F /* OAuth1FlowDialogTests.m */, + 81665C6F1BBDE27D00AE923F /* OAuthCoreTests.m */, + 81665C701BBDE27D00AE923F /* TwitterAuthenticationProviderTests.m */, + 81665C711BBDE27D00AE923F /* TwitterTests.m */, + 81665C721BBDE27D00AE923F /* TwitterUtilsTests.m */, + ); + path = Unit; + sourceTree = ""; + }; + 81665C781BBDE2C000AE923F /* Other */ = { + isa = PBXGroup; + children = ( + 81665C791BBDE2EE00AE923F /* TestCase */, + 81930A391BBDE76E00A5E4BB /* PFTwitterTestMacros.h */, + ); + path = Other; + sourceTree = ""; + }; + 81665C791BBDE2EE00AE923F /* TestCase */ = { + isa = PBXGroup; + children = ( + 81665C7A1BBDE2EE00AE923F /* PFTwitterTestCase.h */, + 81665C7B1BBDE2EE00AE923F /* PFTwitterTestCase.m */, + ); + path = TestCase; + sourceTree = ""; + }; + 8166FB8D1B4F1DC5003841A2 /* TestApplication */ = { + isa = PBXGroup; + children = ( + 8166FB8E1B4F1DC5003841A2 /* Classes */, + 8166FB901B4F1DC5003841A2 /* Resources */, + ); + path = TestApplication; + sourceTree = ""; + }; + 8166FB8E1B4F1DC5003841A2 /* Classes */ = { + isa = PBXGroup; + children = ( + 8166FB8F1B4F1DC5003841A2 /* main.m */, + ); + path = Classes; + sourceTree = ""; + }; + 8166FB901B4F1DC5003841A2 /* Resources */ = { + isa = PBXGroup; + children = ( + 8166FB911B4F1DC5003841A2 /* Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 817A37C81B4B741A00129AFA /* OAuthCore */ = { + isa = PBXGroup; + children = ( + 817A37C91B4B741A00129AFA /* PF_OAuthCore.h */, + 817A37CA1B4B741A00129AFA /* PF_OAuthCore.m */, + ); + path = OAuthCore; + sourceTree = ""; + }; + 81CB98D21AB7906D00136FA5 /* Tests */ = { + isa = PBXGroup; + children = ( + 81665C6D1BBDE27D00AE923F /* Unit */, + 81665C781BBDE2C000AE923F /* Other */, + 81CB98D71AB7907500136FA5 /* Resources */, + 8166FB8D1B4F1DC5003841A2 /* TestApplication */, + ); + path = Tests; + sourceTree = ""; + }; + 81CB98D71AB7907500136FA5 /* Resources */ = { + isa = PBXGroup; + children = ( + 81CB98D31AB7906D00136FA5 /* Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 8EF82AB7049F0059A572B973 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + name = Pods; + sourceTree = ""; + }; + F51535381B57451200C49F56 /* Configurations */ = { + isa = PBXGroup; + children = ( + F51535531B57453700C49F56 /* ParseTwitterUtils-iOS.xcconfig */, + 81ECACD91D1E14F300FA7673 /* ParseTwitterUtils-iOS-Dynamic.xcconfig */, + F51535541B57454500C49F56 /* ParseTwitterUtils-Tests.xcconfig */, + F51535551B57455200C49F56 /* ParseTwitterTestApplication.xcconfig */, + 815462221D37FE6E0069489A /* Shared */, + ); + path = Configurations; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 81ECACBA1D1E14E000FA7673 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 81ECACBB1D1E14E000FA7673 /* PFOAuth1FlowDialog.h in Headers */, + 81ECACBC1D1E14E000FA7673 /* PF_OAuthCore.h in Headers */, + 81ECACBD1D1E14E000FA7673 /* PFTwitterUtils_Private.h in Headers */, + 81ECACBE1D1E14E000FA7673 /* ParseTwitterUtils.h in Headers */, + 81ECACBF1D1E14E000FA7673 /* PFTwitterPrivateUtilities.h in Headers */, + 81ECACC01D1E14E000FA7673 /* PFTwitterUtils.h in Headers */, + 81ECACC11D1E14E000FA7673 /* PFTwitterLocalization.h in Headers */, + 81ECACC21D1E14E000FA7673 /* PF_Twitter_Private.h in Headers */, + 81ECACC31D1E14E000FA7673 /* PFTwitterAuthenticationProvider.h in Headers */, + 81ECACC41D1E14E000FA7673 /* PFTwitterAlertView.h in Headers */, + 81ECACC51D1E14E000FA7673 /* PF_Twitter.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 815F18401B4B730E0066E996 /* PFOAuth1FlowDialog.h in Headers */, + 817A37CB1B4B741A00129AFA /* PF_OAuthCore.h in Headers */, + 813E54A41BB5DDEF00C727E8 /* PFTwitterUtils_Private.h in Headers */, + 81D342A11B4C7DA500B6C124 /* ParseTwitterUtils.h in Headers */, + 813E54AA1BB5E5FF00C727E8 /* PFTwitterPrivateUtilities.h in Headers */, + 8135E49A1B4B6A0E0092F452 /* PFTwitterUtils.h in Headers */, + 06D00BB51BC790F3005BAA6F /* PFTwitterLocalization.h in Headers */, + 8135E4951B4B6A0E0092F452 /* PF_Twitter_Private.h in Headers */, + 8135E4961B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.h in Headers */, + 819DAAD61BB5EC79002BDE2B /* PFTwitterAlertView.h in Headers */, + 8135E4981B4B6A0E0092F452 /* PF_Twitter.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 8166FB661B4F1D77003841A2 /* ParseTwitterTestApplication */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8166FB8B1B4F1D77003841A2 /* Build configuration list for PBXNativeTarget "ParseTwitterTestApplication" */; + buildPhases = ( + 8166FB631B4F1D77003841A2 /* Sources */, + 8166FB641B4F1D77003841A2 /* Frameworks */, + 8166FB651B4F1D77003841A2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ParseTwitterTestApplication; + productName = ParseTwitterTestApplication; + productReference = 8166FB671B4F1D77003841A2 /* ParseTwitterTestApplication.app */; + productType = "com.apple.product-type.application"; + }; + 81CB98C51AB7905D00136FA5 /* ParseTwitterUtils-Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81CB98D11AB7905D00136FA5 /* Build configuration list for PBXNativeTarget "ParseTwitterUtils-Tests" */; + buildPhases = ( + 81CB98C21AB7905D00136FA5 /* Sources */, + 81CB98C31AB7905D00136FA5 /* Frameworks */, + 81CB98C41AB7905D00136FA5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 81CB98CE1AB7905D00136FA5 /* PBXTargetDependency */, + 8166FB951B4F1E9A003841A2 /* PBXTargetDependency */, + ); + name = "ParseTwitterUtils-Tests"; + productName = "ParseFacebookUtilsV4-Tests"; + productReference = 81CB98C61AB7905D00136FA5 /* ParseTwitterUtils-Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 81ECACB71D1E14E000FA7673 /* ParseTwitterUtils-iOS-Dynamic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81ECACD41D1E14E000FA7673 /* Build configuration list for PBXNativeTarget "ParseTwitterUtils-iOS-Dynamic" */; + buildPhases = ( + 81ECACB91D1E14E000FA7673 /* Generate Localizable Strings */, + 81ECACBA1D1E14E000FA7673 /* Headers */, + 81ECACC61D1E14E000FA7673 /* Sources */, + 81ECACCE1D1E14E000FA7673 /* Frameworks */, + 81ECACD11D1E14E000FA7673 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ParseTwitterUtils-iOS-Dynamic"; + productName = Breakpad; + productReference = 81ECACD71D1E14E000FA7673 /* ParseTwitterUtils.framework */; + productType = "com.apple.product-type.framework"; + }; + D2AAC07D0554694100DB518D /* ParseTwitterUtils-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "ParseTwitterUtils-iOS" */; + buildPhases = ( + 81B3F2291AC9CA2600A92677 /* Generate Localizable Strings */, + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + 8139B1341A7BF6B5002BEF84 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ParseTwitterUtils-iOS"; + productName = Breakpad; + productReference = D2AAC07E0554694100DB518D /* ParseTwitterUtils.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = PF; + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Parse, LLC"; + TargetAttributes = { + 8166FB661B4F1D77003841A2 = { + CreatedOnToolsVersion = 6.4; + }; + 81CB98C51AB7905D00136FA5 = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 8166FB661B4F1D77003841A2; + }; + }; + }; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "ParseTwitterUtils" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + en, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* ParseTwitterUtils-iOS */, + 81ECACB71D1E14E000FA7673 /* ParseTwitterUtils-iOS-Dynamic */, + 81CB98C51AB7905D00136FA5 /* ParseTwitterUtils-Tests */, + 8166FB661B4F1D77003841A2 /* ParseTwitterTestApplication */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8139B1341A7BF6B5002BEF84 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 813DFC981AB2526000F25A08 /* third_party_licenses.txt in Resources */, + 06D00BAB1BC78F23005BAA6F /* ParseTwitterUtils.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8166FB651B4F1D77003841A2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81CB98C41AB7905D00136FA5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81ECACD11D1E14E000FA7673 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81ECACD21D1E14E000FA7673 /* third_party_licenses.txt in Resources */, + 81ECACD31D1E14E000FA7673 /* ParseTwitterUtils.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 81B3F2291AC9CA2600A92677 /* Generate Localizable Strings */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Generate Localizable Strings"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Generate localizable strings\nLOCALIZATION_FOLDER=$PROJECT_DIR/Resources/en.lproj\nLOCALIZATION_MACRO=PFTWLocalizedString\n\nfind $PROJECT_DIR/ParseTwitterUtils -name '*.m' -print0 | xargs -0 xcrun extractLocStrings -s $LOCALIZATION_MACRO -q -o $LOCALIZATION_FOLDER\n\nmv $LOCALIZATION_FOLDER/Localizable.strings $LOCALIZATION_FOLDER/ParseTwitterUtils.strings\n"; + showEnvVarsInLog = 0; + }; + 81ECACB91D1E14E000FA7673 /* Generate Localizable Strings */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Generate Localizable Strings"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Generate localizable strings\nLOCALIZATION_FOLDER=$PROJECT_DIR/Resources/en.lproj\nLOCALIZATION_MACRO=PFTWLocalizedString\n\nfind $PROJECT_DIR/ParseTwitterUtils -name '*.m' -print0 | xargs -0 xcrun extractLocStrings -s $LOCALIZATION_MACRO -q -o $LOCALIZATION_FOLDER\n\nmv $LOCALIZATION_FOLDER/Localizable.strings $LOCALIZATION_FOLDER/ParseTwitterUtils.strings\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8166FB631B4F1D77003841A2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8166FB921B4F1DC5003841A2 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81CB98C21AB7905D00136FA5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81665C751BBDE27D00AE923F /* TwitterAuthenticationProviderTests.m in Sources */, + 81665C741BBDE27D00AE923F /* OAuthCoreTests.m in Sources */, + 81665C731BBDE27D00AE923F /* OAuth1FlowDialogTests.m in Sources */, + 81665C771BBDE27D00AE923F /* TwitterUtilsTests.m in Sources */, + 81665C761BBDE27D00AE923F /* TwitterTests.m in Sources */, + 81665C7C1BBDE2EE00AE923F /* PFTwitterTestCase.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81ECACC61D1E14E000FA7673 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81ECACC71D1E14E000FA7673 /* PFTwitterAuthenticationProvider.m in Sources */, + 81ECACC81D1E14E000FA7673 /* PFTwitterUtils.m in Sources */, + 81ECACC91D1E14E000FA7673 /* PF_Twitter.m in Sources */, + 81ECACCA1D1E14E000FA7673 /* PFOAuth1FlowDialog.m in Sources */, + 81ECACCB1D1E14E000FA7673 /* PFTwitterPrivateUtilities.m in Sources */, + 81ECACCC1D1E14E000FA7673 /* PF_OAuthCore.m in Sources */, + 81ECACCD1D1E14E000FA7673 /* PFTwitterAlertView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8135E4971B4B6A0E0092F452 /* PFTwitterAuthenticationProvider.m in Sources */, + 8135E49B1B4B6A0E0092F452 /* PFTwitterUtils.m in Sources */, + 8135E4991B4B6A0E0092F452 /* PF_Twitter.m in Sources */, + 815F18411B4B730E0066E996 /* PFOAuth1FlowDialog.m in Sources */, + 813E54A91BB5E5FA00C727E8 /* PFTwitterPrivateUtilities.m in Sources */, + 817A37CC1B4B741A00129AFA /* PF_OAuthCore.m in Sources */, + 819DAAD71BB5EC79002BDE2B /* PFTwitterAlertView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 8166FB951B4F1E9A003841A2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8166FB661B4F1D77003841A2 /* ParseTwitterTestApplication */; + targetProxy = 8166FB941B4F1E9A003841A2 /* PBXContainerItemProxy */; + }; + 81CB98CE1AB7905D00136FA5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2AAC07D0554694100DB518D /* ParseTwitterUtils-iOS */; + targetProxy = 81CB98CD1AB7905D00136FA5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 06D00BAD1BC78F23005BAA6F /* ParseTwitterUtils.strings */ = { + isa = PBXVariantGroup; + children = ( + 06D00BAE1BC78F29005BAA6F /* en */, + ); + name = ParseTwitterUtils.strings; + path = Resources; + sourceTree = SOURCE_ROOT; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F51535531B57453700C49F56 /* ParseTwitterUtils-iOS.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F51535531B57453700C49F56 /* ParseTwitterUtils-iOS.xcconfig */; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8154622F1D37FE6E0069489A /* Debug.xcconfig */; + buildSettings = { + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + ENABLE_TESTABILITY = YES; + ONLY_ACTIVE_ARCH = YES; + PARSE_DIR = "$(PROJECT_DIR)/.."; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 815462301D37FE6E0069489A /* Release.xcconfig */; + buildSettings = { + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + PARSE_DIR = "$(PROJECT_DIR)/.."; + }; + name = Release; + }; + 8166FB871B4F1D77003841A2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F51535551B57455200C49F56 /* ParseTwitterTestApplication.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 8166FB881B4F1D77003841A2 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F51535551B57455200C49F56 /* ParseTwitterTestApplication.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 81CB98CF1AB7905D00136FA5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F51535541B57454500C49F56 /* ParseTwitterUtils-Tests.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + }; + name = Debug; + }; + 81CB98D01AB7905D00136FA5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F51535541B57454500C49F56 /* ParseTwitterUtils-Tests.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + }; + name = Release; + }; + 81ECACD51D1E14E000FA7673 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 81ECACD91D1E14F300FA7673 /* ParseTwitterUtils-iOS-Dynamic.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 81ECACD61D1E14E000FA7673 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 81ECACD91D1E14F300FA7673 /* ParseTwitterUtils-iOS-Dynamic.xcconfig */; + buildSettings = { + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "ParseTwitterUtils-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "ParseTwitterUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8166FB8B1B4F1D77003841A2 /* Build configuration list for PBXNativeTarget "ParseTwitterTestApplication" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8166FB871B4F1D77003841A2 /* Debug */, + 8166FB881B4F1D77003841A2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 81CB98D11AB7905D00136FA5 /* Build configuration list for PBXNativeTarget "ParseTwitterUtils-Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81CB98CF1AB7905D00136FA5 /* Debug */, + 81CB98D01AB7905D00136FA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 81ECACD41D1E14E000FA7673 /* Build configuration list for PBXNativeTarget "ParseTwitterUtils-iOS-Dynamic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81ECACD51D1E14E000FA7673 /* Debug */, + 81ECACD61D1E14E000FA7673 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..f1755b4ab --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ParseStarterProject/OSX/ParseOSXStarterProject/ParseOSXStarterProject.xcodeproj/xcshareddata/xcschemes/ParseOSXStarterProject.xcscheme b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS-Dynamic.xcscheme similarity index 51% rename from ParseStarterProject/OSX/ParseOSXStarterProject/ParseOSXStarterProject.xcodeproj/xcshareddata/xcschemes/ParseOSXStarterProject.xcscheme rename to ParseTwitterUtils/ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS-Dynamic.xcscheme index 98232d265..6585fb87f 100644 --- a/ParseStarterProject/OSX/ParseOSXStarterProject/ParseOSXStarterProject.xcodeproj/xcshareddata/xcschemes/ParseOSXStarterProject.xcscheme +++ b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS-Dynamic.xcscheme @@ -1,37 +1,23 @@ + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> - - - - + BlueprintIdentifier = "81ECACB71D1E14E000FA7673" + BuildableName = "ParseTwitterUtils.framework" + BlueprintName = "ParseTwitterUtils-iOS-Dynamic" + ReferencedContainer = "container:ParseTwitterUtils.xcodeproj"> @@ -47,10 +33,10 @@ + BlueprintIdentifier = "81ECACB71D1E14E000FA7673" + BuildableName = "ParseTwitterUtils.framework" + BlueprintName = "ParseTwitterUtils-iOS-Dynamic" + ReferencedContainer = "container:ParseTwitterUtils.xcodeproj"> @@ -67,16 +53,15 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + + BlueprintIdentifier = "81ECACB71D1E14E000FA7673" + BuildableName = "ParseTwitterUtils.framework" + BlueprintName = "ParseTwitterUtils-iOS-Dynamic" + ReferencedContainer = "container:ParseTwitterUtils.xcodeproj"> - + @@ -86,16 +71,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - + + BlueprintIdentifier = "81ECACB71D1E14E000FA7673" + BuildableName = "ParseTwitterUtils.framework" + BlueprintName = "ParseTwitterUtils-iOS-Dynamic" + ReferencedContainer = "container:ParseTwitterUtils.xcodeproj"> - + diff --git a/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS.xcscheme b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS.xcscheme new file mode 100644 index 000000000..052acf3d7 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS.xcscheme @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.h new file mode 100644 index 000000000..9adc38caf --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.h @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +@class PFOAuth1FlowDialog; + +@protocol PFOAuth1FlowDialogDataSource + +/** + Asks if a link touched by a user should be opened in an external browser. + + If a user touches a link, the default behavior is to open the link in the Safari browser, + which will cause your app to quit. You may want to prevent this from happening, open the link + in your own internal browser, or perhaps warn the user that they are about to leave your app. + If so, implement this method on your delegate and return NO. If you warn the user, you + should hold onto the URL and once you have received their acknowledgement open the URL yourself + using [[UIApplication sharedApplication] openURL:]. + */ +- (BOOL)dialog:(PFOAuth1FlowDialog *)dialog shouldOpenURLInExternalBrowser:(NSURL *)url; + +@end + +typedef void (^PFOAuth1FlowDialogCompletion)(BOOL succeeded, NSURL *url, NSError *error); + +/** + To allow for greater mockability, this protocol exposes all of the methods implemented by PFOAuth1FlowDialog. + */ +@protocol PFOAuth1FlowDialogInterface + +@property (nonatomic, weak) id dataSource; +@property (nonatomic, strong) PFOAuth1FlowDialogCompletion completion; + +@property (nonatomic, copy) NSDictionary *queryParameters; +@property (nonatomic, copy) NSString *redirectURLPrefix; + +/** + The title that is shown in the header atop the view. + */ +@property (nonatomic, copy) NSString *title; + ++ (instancetype)dialogWithURL:(NSURL *)url queryParameters:(NSDictionary *)queryParameters; + +/** + The view will be added to the top of the current key window. + */ +- (void)showAnimated:(BOOL)animated; + +/** + Hides the view. + This method does not call the completion block. + */ +- (void)dismissAnimated:(BOOL)animated; + +/** + Displays a URL in the dialog. + */ +- (void)loadURL:(NSURL *)url queryParameters:(NSDictionary *)parameters; + +@end + +@interface PFOAuth1FlowDialog : UIView { +@public + // Ensures that UI elements behind the dialog are disabled. + UIView *_modalBackgroundView; + + NSURL *_baseURL; + NSURL *_loadingURL; + + UILabel *_titleLabel; + UIButton *_closeButton; + UIWebView *_webView; + UIActivityIndicatorView *_activityIndicator; + + UIInterfaceOrientation _orientation; + BOOL _showingKeyboard; +} + +- (instancetype)initWithURL:(NSURL *)url queryParameters:(NSDictionary *)parameters; + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.m b/ParseTwitterUtils/ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.m new file mode 100644 index 000000000..47d1d37a2 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.m @@ -0,0 +1,613 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFOAuth1FlowDialog.h" + +#import + +@implementation PFOAuth1FlowDialog + +@synthesize dataSource = _dataSource; +@synthesize completion = _completion; + +@synthesize queryParameters = _queryParameters; +@synthesize redirectURLPrefix = _redirectURLPrefix; + +static NSString *const PFOAuth1FlowDialogDefaultTitle = @"Connect to Service"; + +static const CGFloat PFOAuth1FlowDialogBorderGreyColorComponents[4] = {0.3f, 0.3f, 0.3f, 0.8f}; +static const CGFloat PFOAuth1FlowDialogBorderBlackColorComponents[4] = {0.3f, 0.3f, 0.3f, 1.0f}; + +static const NSTimeInterval PFOAuth1FlowDialogAnimationDuration = 0.3; + +static const UIEdgeInsets PFOAuth1FlowDialogContentInsets = { + .top = 10.0f, + .left = 10.0f, + .bottom = 10.0f, + .right = 10.0f, +}; + +static const UIEdgeInsets PFOAuth1FlowDialogTitleInsets = {.top = 4.0f, .left = 8.0f, .bottom = 4.0f, .right = 8.0f}; + +static const CGFloat PFOAuth1FlowDialogScreenInset = 10.0f; + +static BOOL PFOAuth1FlowDialogScreenHasAutomaticRotation() { + static BOOL automaticRotation; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + automaticRotation = [[UIScreen mainScreen] respondsToSelector:NSSelectorFromString(@"coordinateSpace")]; + }); + return automaticRotation; +} + +static BOOL PFOAuth1FlowDialogIsDevicePad() { + static BOOL isPad; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + isPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad); + }); + return isPad; +} + +static CGFloat PFTFloatRound(CGFloat value, NSRoundingMode mode) { + switch (mode) { + case NSRoundPlain: + case NSRoundBankers: +#if CGFLOAT_IS_DOUBLE + value = round(value); +#else + value = roundf(value); +#endif + case NSRoundDown: +#if CGFLOAT_IS_DOUBLE + value = floor(value); +#else + value = floorf(value); +#endif + case NSRoundUp: +#if CGFLOAT_IS_DOUBLE + value = ceil(value); +#else + value = ceilf(value); +#endif + default: break; + } + return value; +} + +#pragma mark - +#pragma mark Class + ++ (void)_fillRect:(CGRect)rect withColorComponents:(const CGFloat *)colorComponents radius:(CGFloat)radius { + CGContextRef context = UIGraphicsGetCurrentContext(); + + if (colorComponents) { + CGContextSaveGState(context); + CGContextSetFillColor(context, colorComponents); + if (radius != 0.0f) { + UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; + CGContextAddPath(context, [bezierPath CGPath]); + CGContextFillPath(context); + } else { + CGContextFillRect(context, rect); + } + CGContextRestoreGState(context); + } +} + ++ (void)_strokeRect:(CGRect)rect withColorComponents:(const CGFloat *)strokeColor { + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextSaveGState(context); + { + CGContextSetStrokeColor(context, strokeColor); + CGContextSetLineWidth(context, 1.0f); + CGContextStrokeRect(context, rect); + } + CGContextRestoreGState(context); +} + ++ (NSURL *)_urlFromBaseURL:(NSURL *)baseURL queryParameters:(NSDictionary *)params { + if ([params count] > 0) { + NSMutableArray *parameterPairs = [NSMutableArray array]; + [params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFStringRef escapedString = CFURLCreateStringByAddingPercentEscapes( + NULL, /* allocator */ + (CFStringRef)obj, + NULL, /* charactersToLeaveUnescaped */ + (CFStringRef)@"!*'();:@&=+$,/?%#[]", + kCFStringEncodingUTF8); + [parameterPairs addObject:[NSString stringWithFormat:@"%@=%@", key, CFBridgingRelease(escapedString)]]; + }]; + + NSString *query = [parameterPairs componentsJoinedByString:@"&"]; + NSString *url = [NSString stringWithFormat:@"%@?%@", [baseURL absoluteString], query]; + + return [NSURL URLWithString:url]; + } + + return baseURL; +} + ++ (UIImage *)_closeButtonImage { + CGRect imageRect = CGRectZero; + imageRect.size = CGSizeMake(30.0f, 30.0f); + + UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGRect outerRingRect = CGRectInset(imageRect, 2.0f, 2.0f); + + [[UIColor whiteColor] set]; + CGContextFillEllipseInRect(context, outerRingRect); + + CGRect innerRingRect = CGRectInset(outerRingRect, 2.0f, 2.0f); + + [[UIColor blackColor] set]; + CGContextFillEllipseInRect(context, innerRingRect); + + CGRect crossRect = CGRectInset(innerRingRect, 6.0f, 6.0f); + + CGContextBeginPath(context); + + [[UIColor whiteColor] setStroke]; + CGContextSetLineWidth(context, 3.0f); + + CGContextMoveToPoint(context, CGRectGetMinX(crossRect), CGRectGetMinY(crossRect)); + CGContextAddLineToPoint(context, CGRectGetMaxX(crossRect), CGRectGetMaxY(crossRect)); + + CGContextMoveToPoint(context, CGRectGetMaxX(crossRect), CGRectGetMinY(crossRect)); + CGContextAddLineToPoint(context, CGRectGetMinX(crossRect), CGRectGetMaxY(crossRect)); + + CGContextStrokePath(context); + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + +#pragma mark - +#pragma mark Init + +- (instancetype)init { + self = [super initWithFrame:CGRectZero]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + self.autoresizesSubviews = YES; + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.contentMode = UIViewContentModeRedraw; + + _orientation = UIInterfaceOrientationPortrait; + + _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _closeButton.showsTouchWhenHighlighted = YES; + _closeButton.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin); + [_closeButton setImage:[[self class] _closeButtonImage] forState:UIControlStateNormal]; + [_closeButton addTarget:self + action:@selector(_cancelButtonAction) + forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_closeButton]; + + CGFloat titleLabelFontSize = (PFOAuth1FlowDialogIsDevicePad() ? 18.0f : 14.0f); + _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + _titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; + _titleLabel.backgroundColor = [UIColor clearColor]; + _titleLabel.text = PFOAuth1FlowDialogDefaultTitle; + _titleLabel.textColor = [UIColor whiteColor]; + _titleLabel.font = [UIFont boldSystemFontOfSize:titleLabelFontSize]; + [self addSubview:_titleLabel]; + + _webView = [[UIWebView alloc] initWithFrame:CGRectZero]; + _webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _webView.delegate = self; + [self addSubview:_webView]; + + _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: + UIActivityIndicatorViewStyleWhiteLarge]; + _activityIndicator.color = [UIColor grayColor]; + _activityIndicator.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin | + UIViewAutoresizingFlexibleBottomMargin | + UIViewAutoresizingFlexibleLeftMargin | + UIViewAutoresizingFlexibleRightMargin); + [self addSubview:_activityIndicator]; + + _modalBackgroundView = [[UIView alloc] init]; + } + return self; +} + +- (instancetype)initWithURL:(NSURL *)url queryParameters:(NSDictionary *)parameters { + self = [self init]; + if (self) { + _baseURL = url; + _queryParameters = [parameters mutableCopy]; + } + return self; +} + ++ (instancetype)dialogWithURL:(NSURL *)url queryParameters:(NSDictionary *)queryParameters { + return [[self alloc] initWithURL:url queryParameters:queryParameters]; +} + +#pragma mark - +#pragma mark Dealloc + +- (void)dealloc { + _webView.delegate = nil; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - +#pragma mark UIView + +- (void)drawRect:(CGRect)rect { + [super drawRect:rect]; + + [[self class] _fillRect:self.bounds withColorComponents:PFOAuth1FlowDialogBorderGreyColorComponents radius:10.0f]; + [[self class] _strokeRect:_webView.frame withColorComponents:PFOAuth1FlowDialogBorderBlackColorComponents]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + const CGRect bounds = self.bounds; + const CGRect contentRect = UIEdgeInsetsInsetRect(bounds, PFOAuth1FlowDialogContentInsets); + + CGRect titleLabelBoundingRect = UIEdgeInsetsInsetRect(contentRect, PFOAuth1FlowDialogTitleInsets); + CGSize titleLabelSize = [_titleLabel sizeThatFits:titleLabelBoundingRect.size]; + titleLabelBoundingRect.size.width = (CGRectGetMaxX(contentRect) - + PFOAuth1FlowDialogTitleInsets.right - + titleLabelSize.height); + titleLabelSize = [_titleLabel sizeThatFits:titleLabelBoundingRect.size]; + + CGRect titleLabelFrame = titleLabelBoundingRect; + titleLabelFrame.size.height = titleLabelSize.height; + titleLabelFrame.size.width = CGRectGetWidth(titleLabelBoundingRect); + _titleLabel.frame = titleLabelFrame; + + CGRect closeButtonFrame = contentRect; + closeButtonFrame.size.height = (CGRectGetHeight(titleLabelFrame) + + PFOAuth1FlowDialogTitleInsets.top + + PFOAuth1FlowDialogTitleInsets.bottom); + closeButtonFrame.size.width = CGRectGetHeight(closeButtonFrame); + closeButtonFrame.origin.x = CGRectGetMaxX(contentRect) - CGRectGetWidth(closeButtonFrame); + _closeButton.frame = closeButtonFrame; + + CGRect webViewFrame = contentRect; + if (!_showingKeyboard || PFOAuth1FlowDialogIsDevicePad() || UIInterfaceOrientationIsPortrait(_orientation)) { + webViewFrame.origin.y = CGRectGetMaxY(titleLabelFrame) + PFOAuth1FlowDialogTitleInsets.bottom; + webViewFrame.size.height = CGRectGetMaxY(contentRect) - CGRectGetMinY(webViewFrame); + } + _webView.frame = webViewFrame; + + [_activityIndicator sizeToFit]; + _activityIndicator.center = _webView.center; +} + +#pragma mark - +#pragma mark Accessors + +- (NSString *)title { + return _titleLabel.text; +} + +- (void)setTitle:(NSString *)title { + _titleLabel.text = title; + + [self setNeedsLayout]; +} + +#pragma mark - +#pragma mark Present / Dismiss + +- (void)showAnimated:(BOOL)animated { + [self load]; + [self _sizeToFitOrientation]; + + [_activityIndicator startAnimating]; + + UIWindow *window = [UIApplication sharedApplication].keyWindow; + _modalBackgroundView.frame = window.bounds; + [_modalBackgroundView addSubview:self]; + [window addSubview:_modalBackgroundView]; + + CGAffineTransform transform = [self _transformForOrientation:_orientation]; + if (animated) { + self.transform = CGAffineTransformScale(transform, 0.001f, 0.001f); + + NSTimeInterval animationStepDuration = PFOAuth1FlowDialogAnimationDuration / 2.0f; + + [UIView animateWithDuration:animationStepDuration + animations:^{ + self.transform = CGAffineTransformScale(transform, 1.1f, 1.1f); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:animationStepDuration + animations:^{ + self.transform = CGAffineTransformScale(transform, 0.9f, 0.9f); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:animationStepDuration + animations:^{ + self.transform = transform; + }]; + }]; + + }]; + } else { + self.transform = transform; + } + + [self _addObservers]; +} + +- (void)dismissAnimated:(BOOL)animated { + _loadingURL = nil; + + __weak typeof(self) wself = self; + dispatch_block_t completionBlock = ^{ + __strong typeof(wself) sself = wself; + [sself _removeObservers]; + [sself removeFromSuperview]; + [_modalBackgroundView removeFromSuperview]; + }; + + if (animated) { + [UIView animateWithDuration:PFOAuth1FlowDialogAnimationDuration + animations:^{ + typeof(wself) sself = wself; + sself.alpha = 0.0f; + } + completion:^(BOOL finished) { + completionBlock(); + }]; + } else { + completionBlock(); + } +} + +- (void)_dismissWithSuccess:(BOOL)success url:(NSURL *)url error:(NSError *)error { + if (!self.completion) { + return; + } + + PFOAuth1FlowDialogCompletion completion = self.completion; + self.completion = nil; + + dispatch_async(dispatch_get_main_queue(), ^{ + completion(success, url, error); + }); + + [self dismissAnimated:YES]; +} + +- (void)_cancelButtonAction { + [self _dismissWithSuccess:NO url:nil error:nil]; +} + +#pragma mark - +#pragma mark Public + +- (void)load { + [self loadURL:_baseURL queryParameters:self.queryParameters]; +} + +- (void)loadURL:(NSURL *)url queryParameters:(NSDictionary *)parameters { + _loadingURL = [[self class] _urlFromBaseURL:url queryParameters:parameters]; + + NSURLRequest *request = [NSURLRequest requestWithURL:_loadingURL]; + [_webView loadRequest:request]; +} + +#pragma mark - +#pragma mark UIWebViewDelegate + +- (BOOL)webView:(UIWebView *)webView +shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType { + NSURL *url = request.URL; + + if ([url.absoluteString hasPrefix:self.redirectURLPrefix]) { + [self _dismissWithSuccess:YES url:url error:nil]; + return NO; + } else if ([_loadingURL isEqual:url]) { + return YES; + } else if (navigationType == UIWebViewNavigationTypeLinkClicked) { + if ([self.dataSource dialog:self shouldOpenURLInExternalBrowser:url]) { + [[UIApplication sharedApplication] openURL:url]; + } else { + return YES; + } + } + + return YES; +} + +- (void)webViewDidStartLoad:(UIWebView *)webView { + [[PFNetworkActivityIndicatorManager sharedManager] incrementActivityCount]; +} + +- (void)webViewDidFinishLoad:(UIWebView *)webView { + [[PFNetworkActivityIndicatorManager sharedManager] decrementActivityCount]; + + [_activityIndicator stopAnimating]; + self.title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"]; +} + +- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { + [[PFNetworkActivityIndicatorManager sharedManager] decrementActivityCount]; + + // 102 == WebKitErrorFrameLoadInterruptedByPolicyChange + if (!([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102)) { + [self _dismissWithSuccess:NO url:nil error:error]; + } +} + +#pragma mark - +#pragma mark Observers + +- (void)_addObservers { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_deviceOrientationDidChange:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)_removeObservers { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIDeviceOrientationDidChangeNotification + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIKeyboardWillHideNotification + object:nil]; +} + +#pragma mark - +#pragma mark UIDeviceOrientationDidChangeNotification + +- (void)_deviceOrientationDidChange:(NSNotification *)notification { + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if ([self _shouldRotateToOrientation:orientation]) { + NSTimeInterval duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration; + [UIView animateWithDuration:duration + animations:^{ + [self _sizeToFitOrientation]; + }]; + } +} + +- (BOOL)_shouldRotateToOrientation:(UIInterfaceOrientation)orientation { + if (orientation == _orientation) { + return NO; + } + + return (orientation == UIDeviceOrientationLandscapeLeft || + orientation == UIDeviceOrientationLandscapeRight || + orientation == UIDeviceOrientationPortrait || + orientation == UIDeviceOrientationPortraitUpsideDown); +} + +- (CGAffineTransform)_transformForOrientation:(UIInterfaceOrientation)orientation { + // No manual rotation required on iOS 8 + // There is coordinateSpace method, since iOS 8 + if (PFOAuth1FlowDialogScreenHasAutomaticRotation()) { + return CGAffineTransformIdentity; + } + + switch (orientation) { + case UIInterfaceOrientationLandscapeLeft: + return CGAffineTransformMakeRotation((CGFloat)(-M_PI / 2.0f)); + break; + case UIInterfaceOrientationLandscapeRight: + return CGAffineTransformMakeRotation((CGFloat)(M_PI / 2.0f)); + break; + case UIInterfaceOrientationPortraitUpsideDown: + return CGAffineTransformMakeRotation((CGFloat)-M_PI); + break; + case UIInterfaceOrientationPortrait: + case UIInterfaceOrientationUnknown: + default: + break; + } + + return CGAffineTransformIdentity; +} + +- (void)_sizeToFitOrientation { + _orientation = [UIApplication sharedApplication].statusBarOrientation; + CGAffineTransform transform = [self _transformForOrientation:_orientation]; + + CGRect bounds = [UIScreen mainScreen].applicationFrame; + CGRect transformedBounds = CGRectApplyAffineTransform(bounds, transform); + transformedBounds.origin = CGPointZero; + + CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); + + CGFloat scale = (PFOAuth1FlowDialogIsDevicePad() ? 0.6f : 1.0f); + + CGFloat width = PFTFloatRound((scale * CGRectGetWidth(transformedBounds)) - PFOAuth1FlowDialogScreenInset * 2.0f, NSRoundDown); + CGFloat height = PFTFloatRound((scale * CGRectGetHeight(transformedBounds)) - PFOAuth1FlowDialogScreenInset * 2.0f, NSRoundDown); + + self.transform = transform; + self.center = center; + self.bounds = CGRectMake(0.0f, 0.0f, width, height); + + [self setNeedsLayout]; +} + +#pragma mark - +#pragma mark UIKeyboardNotifications + +- (void)_keyboardWillShow:(NSNotification *)notification { + _showingKeyboard = YES; + + if (PFOAuth1FlowDialogIsDevicePad()) { + // On the iPad the screen is large enough that we don't need to + // resize the dialog to accomodate the keyboard popping up + return; + } + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsLandscape(orientation)) { + NSDictionary *userInfo = [notification userInfo]; + NSTimeInterval animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve animationCurve = [[notification userInfo][UIKeyboardAnimationCurveUserInfoKey] intValue]; + + [UIView animateWithDuration:animationDuration + delay:0.0 + options:animationCurve << 16 | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + [self setNeedsLayout]; + [self layoutIfNeeded]; + + [self setNeedsDisplay]; + } + completion:nil]; + } +} + +- (void)_keyboardWillHide:(NSNotification *)notification { + _showingKeyboard = NO; + + if (PFOAuth1FlowDialogIsDevicePad()) { + return; + } + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsLandscape(orientation)) { + NSDictionary *userInfo = [notification userInfo]; + NSTimeInterval animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve animationCurve = [[notification userInfo][UIKeyboardAnimationCurveUserInfoKey] intValue]; + + [UIView animateWithDuration:animationDuration + delay:0.0 + options:animationCurve << 16 | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + [self setNeedsLayout]; + [self layoutIfNeeded]; + + [self setNeedsDisplay]; + } + completion:nil]; + } +} + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.h new file mode 100644 index 000000000..a44f469cb --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface PFOAuthConfiguration : NSObject + +@property (nonatomic, strong) NSURL *url; +@property (nonatomic, copy) NSString *method; +@property (nullable, nonatomic, strong) NSData *body; + +@property (nonatomic, copy) NSString *consumerKey; +@property (nonatomic, copy) NSString *consumerSecret; + +@property (nullable, nonatomic, copy) NSString *token; +@property (nullable, nonatomic, copy) NSString *tokenSecret; + +@property (nullable, nonatomic, copy) NSDictionary *additionalParameters; + +@property (nonatomic, copy) NSString *nonce; +@property (nonatomic, strong) NSDate *timestampDate; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + ++ (instancetype)configurationForURL:(NSURL *)url + method:(NSString *)method + body:(nullable NSData *)body + additionalParameters:(nullable NSDictionary *)additionalParams + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + token:(nullable NSString *)token + tokenSecret:(nullable NSString *)tokenSecret; + +@end + +@interface PFOAuth : NSObject + ++ (NSString *)authorizationHeaderFromConfiguration:(PFOAuthConfiguration *)configuration; + +@end + +@interface NSURL (PF_OAuthAdditions) + ++ (NSDictionary *)PF_ab_parseURLQueryString:(NSString *)query; + +@end + +@interface NSString (PF_OAuthAdditions) + +- (NSString *)PF_ab_RFC3986EncodedString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.m b/ParseTwitterUtils/ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.m new file mode 100644 index 000000000..987ebcc52 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.m @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PF_OAuthCore.h" + +#import + +static NSData *PF_HMAC_SHA1(NSString *data, NSString *key) { + unsigned char buf[CC_SHA1_DIGEST_LENGTH]; + CCHmac(kCCHmacAlgSHA1, [key UTF8String], [key length], [data UTF8String], [data length], buf); + return [NSData dataWithBytes:buf length:CC_SHA1_DIGEST_LENGTH]; +} + +@implementation PFOAuthConfiguration + +- (instancetype)init { + self = [super init]; + if (!self) return nil; + + _nonce = [[NSUUID UUID] UUIDString]; + _timestampDate = [NSDate date]; + + return self; +} + ++ (instancetype)configurationForURL:(NSURL *)url + method:(NSString *)method + body:(nullable NSData *)body + additionalParameters:(nullable NSDictionary *)additionalParams + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + token:(nullable NSString *)token + tokenSecret:(nullable NSString *)tokenSecret { + PFOAuthConfiguration *configuration = [[self alloc] init]; + configuration.url = url; + configuration.method = method; + configuration.body = body; + configuration.additionalParameters = additionalParams; + configuration.consumerKey = consumerKey; + configuration.consumerSecret = consumerSecret; + configuration.token = token; + configuration.tokenSecret = tokenSecret; + return configuration; +} + +@end + +@implementation PFOAuth + ++ (NSString *)authorizationHeaderFromConfiguration:(PFOAuthConfiguration *)configuration { + NSString *authTimeStamp = [NSString stringWithFormat:@"%llu", + (unsigned long long)floor([configuration.timestampDate timeIntervalSince1970])]; + NSString *authSignatureMethod = @"HMAC-SHA1"; + NSString *authVersion = @"1.0"; + NSURL *url = configuration.url; + + // Don't use -mutableCopy here, as that will return nil if `additionalParams` is nil. + NSMutableDictionary *oAuthAuthorizationParameters = [NSMutableDictionary dictionaryWithDictionary:configuration.additionalParameters]; + + oAuthAuthorizationParameters[@"oauth_nonce"] = configuration.nonce; + oAuthAuthorizationParameters[@"oauth_timestamp"] = authTimeStamp; + oAuthAuthorizationParameters[@"oauth_signature_method"] = authSignatureMethod; + oAuthAuthorizationParameters[@"oauth_version"] = authVersion; + oAuthAuthorizationParameters[@"oauth_consumer_key"] = configuration.consumerKey; + + if (configuration.token) { + oAuthAuthorizationParameters[@"oauth_token"] = configuration.token; + } + + // get query and body parameters + NSDictionary *additionalQueryParameters = [NSURL PF_ab_parseURLQueryString:[url query]]; + NSDictionary *additionalBodyParameters = nil; + if (configuration.body) { + NSString *string = [[NSString alloc] initWithData:configuration.body encoding:NSUTF8StringEncoding]; + additionalBodyParameters = [NSURL PF_ab_parseURLQueryString:string]; + } + + // combine all parameters + NSMutableDictionary *parameters = [oAuthAuthorizationParameters mutableCopy]; + [parameters addEntriesFromDictionary:additionalQueryParameters]; + if (additionalBodyParameters) { + [parameters addEntriesFromDictionary:additionalBodyParameters]; + } + + NSArray *sortedKeys = [[parameters allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2] ?: [parameters[obj1] compare:parameters[obj2]]; + }]; + + NSMutableArray *parameterArray = [NSMutableArray array]; + for (NSString *key in sortedKeys) { + [parameterArray addObject:[NSString stringWithFormat:@"%@=%@", key, [parameters[key] PF_ab_RFC3986EncodedString]]]; + } + + NSString *normalizedParameterString = [parameterArray componentsJoinedByString:@"&"]; + NSString *normalizedURLString = [NSString stringWithFormat:@"%@://%@%@", [url scheme], [url host], [url path]]; + + NSString *signatureBaseString = [NSString stringWithFormat:@"%@&%@&%@", + [configuration.method PF_ab_RFC3986EncodedString], + [normalizedURLString PF_ab_RFC3986EncodedString], + [normalizedParameterString PF_ab_RFC3986EncodedString]]; + + NSString *key = [NSString stringWithFormat:@"%@&%@", + [configuration.consumerSecret PF_ab_RFC3986EncodedString], + (configuration.tokenSecret ? [configuration.tokenSecret PF_ab_RFC3986EncodedString] : @"")]; + + NSData *signature = PF_HMAC_SHA1(signatureBaseString, key); + NSString *base64Signature = [signature base64EncodedStringWithOptions:0]; + + oAuthAuthorizationParameters[@"oauth_signature"] = base64Signature; + + NSMutableArray *authorizationHeaderItems = [NSMutableArray array]; + [oAuthAuthorizationParameters enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + [authorizationHeaderItems addObject:[NSString stringWithFormat:@"%@=\"%@\"", + [key PF_ab_RFC3986EncodedString], + [value PF_ab_RFC3986EncodedString]]]; + }]; + + NSString *authorizationHeaderString = [authorizationHeaderItems componentsJoinedByString:@", "]; + authorizationHeaderString = [NSString stringWithFormat:@"OAuth %@", authorizationHeaderString]; + + return authorizationHeaderString; +} + +@end + +@implementation NSURL (OAuthAdditions) + ++ (NSDictionary *)PF_ab_parseURLQueryString:(NSString *)query { + // Use NSURLComponents if available. + if ([NSURLComponents class] != nil && [NSURLComponents instancesRespondToSelector:@selector(queryItems)]) { + NSURLComponents *components = [[NSURLComponents alloc] init]; + [components setQuery:query]; + + NSArray *queryItems = components.queryItems; + + NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:components.queryItems.count]; + for (NSURLQueryItem *item in queryItems) { + dictionary[item.name] = [item.value stringByRemovingPercentEncoding]; + } + return dictionary; + } + + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSArray *pairs = [query componentsSeparatedByString:@"&"]; + for (NSString *pair in pairs) { + NSArray *keyValue = [pair componentsSeparatedByString:@"="]; + if ([keyValue count] == 2) { + NSString *key = [keyValue objectAtIndex:0]; + NSString *value = [keyValue objectAtIndex:1]; + value = [value stringByRemovingPercentEncoding]; + if (key && value) + [dict setObject:value forKey:key]; + } + } + return [NSDictionary dictionaryWithDictionary:dict]; +} + +@end + +@implementation NSString (OAuthAdditions) + +- (NSString *)PF_ab_RFC3986EncodedString // UTF-8 encodes prior to URL encoding +{ + NSMutableString *result = [NSMutableString string]; + const char *p = [self UTF8String]; + unsigned char c; + + for (; (c = *p); p++) { + switch (c) { + case '0' ... '9': + case 'A' ... 'Z': + case 'a' ... 'z': + case '.': + case '-': + case '~': + case '_': + [result appendFormat:@"%c", c]; + break; + default: + [result appendFormat:@"%%%02X", c]; + } + } + return result; +} + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAlertView.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAlertView.h new file mode 100644 index 000000000..3b8c81562 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAlertView.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +typedef void (^PFTwitterAlertViewCompletion)(NSUInteger selectedOtherButtonIndex); + +@interface PFTwitterAlertView : NSObject + ++ (void)showAlertWithTitle:(NSString *)title + message:(NSString *)message + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(NSArray *)otherButtonTitles + completion:(PFTwitterAlertViewCompletion)completion NS_EXTENSION_UNAVAILABLE_IOS(""); + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAlertView.m b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAlertView.m new file mode 100644 index 000000000..e42371b08 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAlertView.m @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFTwitterAlertView.h" + +@interface PFTwitterAlertView () + +@property (nonatomic, copy) PFTwitterAlertViewCompletion completion; + +@end + +@implementation PFTwitterAlertView + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + ++ (void)showAlertWithTitle:(NSString *)title + message:(NSString *)message + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(NSArray *)otherButtonTitles + completion:(PFTwitterAlertViewCompletion)completion { + if ([UIAlertController class] != nil) { + __block UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + void (^alertActionHandler)(UIAlertAction *) = [^(UIAlertAction *action) { + if (completion) { + // This block intentionally retains alertController, and releases it afterwards. + if (action.style == UIAlertActionStyleCancel) { + completion(NSNotFound); + } else { + NSUInteger index = [alertController.actions indexOfObject:action]; + completion(index - 1); + } + } + alertController = nil; + } copy]; + + [alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle + style:UIAlertActionStyleCancel + handler:alertActionHandler]]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle + style:UIAlertActionStyleDefault + handler:alertActionHandler]]; + } + + UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + UIViewController *viewController = keyWindow.rootViewController; + while (viewController.presentedViewController) { + viewController = viewController.presentedViewController; + } + + [viewController presentViewController:alertController animated:YES completion:nil]; + } else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + __block PFTwitterAlertView *pfAlertView = [[self alloc] init]; + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:nil + cancelButtonTitle:cancelButtonTitle + otherButtonTitles:nil]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertView addButtonWithTitle:buttonTitle]; + } + + pfAlertView.completion = ^(NSUInteger index) { + if (completion) { + completion(index); + } + + pfAlertView = nil; + }; + + alertView.delegate = pfAlertView; + [alertView show]; +#endif + } +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + +///-------------------------------------- +#pragma mark - UIAlertViewDelegate +///-------------------------------------- + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if (self.completion) { + if (buttonIndex == alertView.cancelButtonIndex) { + self.completion(NSNotFound); + } else { + self.completion(buttonIndex - 1); + } + } +} + +#endif + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.h new file mode 100644 index 000000000..30d95e442 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@class BFTask<__covariant BFGenericType>; +@class PF_Twitter; + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const PFTwitterUserAuthenticationType; + +@interface PFTwitterAuthenticationProvider : NSObject + +@property (nonatomic, strong, readonly) PF_Twitter *twitter; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithTwitter:(PF_Twitter *)twitter NS_DESIGNATED_INITIALIZER; ++ (instancetype)providerWithTwitter:(PF_Twitter *)twitter; + +- (BFTask *)authenticateAsync; + +- (NSDictionary *)authDataWithTwitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + secret:(NSString *)authTokenSecret; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.m b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.m new file mode 100644 index 000000000..6e352319e --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.m @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFTwitterAuthenticationProvider.h" + +#import + +#import + +#import "PFTwitterPrivateUtilities.h" +#import "PF_Twitter.h" + +NSString *const PFTwitterUserAuthenticationType = @"twitter"; + +static NSString *const _PFTwitterAuthDataIdKey = @"id"; +static NSString *const _PFTwitterAuthDataScreenNameKey = @"screen_name"; +static NSString *const _PFTwitterAuthDataAuthTokenKey = @"auth_token"; +static NSString *const _PFTwitterAuthDataAuthTokenSecretKey = @"auth_token_secret"; +static NSString *const _PFTwitterAuthDataConsumerKeyKey = @"consumer_key"; +static NSString *const _PFTwitterAuthDataConsumerSecretKey = @"consumer_secret"; + +@implementation PFTwitterAuthenticationProvider + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +- (instancetype)init { + PFTWConsistencyAssert(NO, @"%@ is not a designated initializer for instances of %@.", + NSStringFromSelector(_cmd), NSStringFromClass([self class])); + return nil; +} + +- (instancetype)initWithTwitter:(PF_Twitter *)twitter { + self = [super init]; + if (!self) return nil; + + _twitter = twitter; + + return self; +} + ++ (instancetype)providerWithTwitter:(PF_Twitter *)twitter { + return [[self alloc] initWithTwitter:twitter]; +} + +///-------------------------------------- +#pragma mark - Auth Data +///-------------------------------------- + +- (NSDictionary *)authDataWithTwitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + secret:(NSString *)authTokenSecret { + NSDictionary *authData = @{_PFTwitterAuthDataIdKey : twitterId, + _PFTwitterAuthDataScreenNameKey : screenName, + _PFTwitterAuthDataAuthTokenKey : authToken, + _PFTwitterAuthDataAuthTokenSecretKey : authTokenSecret, + _PFTwitterAuthDataConsumerKeyKey : self.twitter.consumerKey, + _PFTwitterAuthDataConsumerSecretKey : self.twitter.consumerSecret}; + return authData; +} + +///-------------------------------------- +#pragma mark - Authentication +///-------------------------------------- + +- (BFTask *)authenticateAsync { + return [[self.twitter authorizeInBackground] continueWithSuccessBlock:^id(BFTask *task) { + NSDictionary *authData = [self authDataWithTwitterId:self.twitter.userId + screenName:self.twitter.screenName + authToken:self.twitter.authToken + secret:self.twitter.authTokenSecret]; + return [BFTask taskWithResult:authData]; + }]; +} + +///-------------------------------------- +#pragma mark - PFUserAuthenticationDelegate +///-------------------------------------- + +- (BOOL)restoreAuthenticationWithAuthData:(NSDictionary *)authData { + // If authData is nil, this is an unlink operation, which should succeed. + if (!authData) { + self.twitter.userId = nil; + self.twitter.authToken = nil; + self.twitter.authTokenSecret = nil; + self.twitter.screenName = nil; + return YES; + } + + // Check that the authData contains the required fields, and if so, synchronize. + NSString *userId = authData[_PFTwitterAuthDataIdKey]; + NSString *screenName = authData[_PFTwitterAuthDataScreenNameKey]; + NSString *authToken = authData[_PFTwitterAuthDataAuthTokenKey]; + NSString *authTokenSecret = authData[_PFTwitterAuthDataAuthTokenSecretKey]; + if (userId && screenName && authToken && authTokenSecret) { + self.twitter.userId = userId; + self.twitter.screenName = screenName; + self.twitter.authToken = authToken; + self.twitter.authTokenSecret = authTokenSecret; + return YES; + } + return NO; +} + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterLocalization.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterLocalization.h new file mode 100644 index 000000000..09ece8bbb --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterLocalization.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#ifndef PFTwitterLocalization_h +#define PFTwitterLocalization_h + +#define PFTWLocalizedString(key, comment) \ +[[NSBundle bundleForClass:[self class]] localizedStringForKey:key value:nil table:@"ParseTwitterUtils"] + +#endif diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.h new file mode 100644 index 000000000..f556d2bb8 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +#import + +@interface PFTwitterPrivateUtilities : NSObject + ++ (void)safePerformSelector:(SEL)selector onTarget:(id)target withObject:(id)object object:(id)anotherObject; + +@end + +@interface BFTask (ParseTwitterUtils) + +- (id)pftw_waitForResult:(NSError **)error; + +//TODO: (nlutsenko) Look into killing this and replacing with generic continueWithBlock: +- (instancetype)pftw_continueAsyncWithBlock:(BFContinuationBlock)block; + +- (instancetype)pftw_continueWithMainThreadUserBlock:(PFUserResultBlock)block; +- (instancetype)pftw_continueWithMainThreadBooleanBlock:(PFBooleanResultBlock)block; +- (instancetype)pftw_continueWithMainThreadBlock:(BFContinuationBlock)block; + +@end + +/** + Raises an `NSInternalInconsistencyException` if the `condition` does not pass. + Use `description` to supply the way to fix the exception. + */ +#define PFTWConsistencyAssert(condition, description, ...) \ +do { \ + if (!(condition)) { \ + [NSException raise:NSInternalInconsistencyException \ + format:description, ##__VA_ARGS__]; \ + } \ +} while(0) diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.m b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.m new file mode 100644 index 000000000..7c02aff35 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.m @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFTwitterPrivateUtilities.h" + +#import + +@implementation PFTwitterPrivateUtilities + ++ (void)safePerformSelector:(SEL)selector + onTarget:(id)target + withObject:(id)object + object:(id)anotherObject { + if (target == nil || selector == nil || ![target respondsToSelector:selector]) { + return; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [target performSelector:selector withObject:object withObject:anotherObject]; +#pragma clang diagnostic pop +} + +@end + +@implementation BFTask (ParseTwitterUtils) + +- (id)pftw_waitForResult:(NSError **)error { + [self waitUntilFinished]; + + if (self.cancelled) { + return nil; + } else if (self.error && error) { + *error = self.error; + } + return self.result; +} + +- (instancetype)pftw_continueAsyncWithBlock:(BFContinuationBlock)block { + BFExecutor *executor = [BFExecutor executorWithDispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + return [self continueWithExecutor:executor withBlock:block]; +} + +- (instancetype)pftw_continueWithMainThreadUserBlock:(PFUserResultBlock)block { + return [self pftw_continueWithMainThreadBlock:^id(BFTask *task) { + if (block) { + block(task.result, task.error); + } + return nil; + }]; +} + +- (instancetype)pftw_continueWithMainThreadBooleanBlock:(PFBooleanResultBlock)block { + return [self pftw_continueWithMainThreadBlock:^id(BFTask *task) { + if (block) { + block([task.result boolValue], task.error); + } + return nil; + }]; +} + +- (instancetype)pftw_continueWithMainThreadBlock:(BFContinuationBlock)block { + return [self continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:block]; +} + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterUtils_Private.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterUtils_Private.h new file mode 100644 index 000000000..aa88c8804 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PFTwitterUtils_Private.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFTwitterUtils.h" + +@class PFTwitterAuthenticationProvider; + +@interface PFTwitterUtils () + ++ (PFTwitterAuthenticationProvider *)_authenticationProvider; ++ (void)_setAuthenticationProvider:(PFTwitterAuthenticationProvider *)provider; + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/Internal/PF_Twitter_Private.h b/ParseTwitterUtils/ParseTwitterUtils/Internal/PF_Twitter_Private.h new file mode 100644 index 000000000..2136290e8 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/Internal/PF_Twitter_Private.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "PF_Twitter.h" + +@class ACAccount; +@class ACAccountStore; +@protocol PFOAuth1FlowDialogInterface; + +NS_ASSUME_NONNULL_BEGIN + +@interface PF_Twitter () + +@property (nonatomic, strong, readonly) ACAccountStore *accountStore; +@property (nonatomic, strong, readonly) NSURLSession *urlSession; +@property (nonatomic, strong, readonly) Class oauthDialogClass; + +- (instancetype)initWithAccountStore:(ACAccountStore *)accountStore + urlSession:(NSURLSession *)urlSession + dialogClass:(Class)dialogClass; + +/** + Obtain access to the local twitter account. + Returns the twitter account if access is obtained. Otherwise, returns null. + */ +- (BFTask *)_getLocalTwitterAccountAsync; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseTwitterUtils/ParseTwitterUtils/PFTwitterUtils.h b/ParseTwitterUtils/ParseTwitterUtils/PFTwitterUtils.h new file mode 100644 index 000000000..346f004ed --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/PFTwitterUtils.h @@ -0,0 +1,320 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class BFTask<__covariant BFGenericType>; +@class PF_Twitter; + +/** + The `PFTwitterUtils` class provides utility functions for working with Twitter in a Parse application. + + This class is currently for iOS only. + */ +@interface PFTwitterUtils : NSObject + +///-------------------------------------- +/// @name Interacting With Twitter +///-------------------------------------- + +/** + Gets the instance of the `PF_Twitter` object that Parse uses. + + @return An instance of `PF_Twitter` object. + */ ++ (nullable PF_Twitter *)twitter; + +/** + Initializes the Twitter singleton. + + @warning You must invoke this in order to use the Twitter functionality in Parse. + + @param consumerKey Your Twitter application's consumer key. + @param consumerSecret Your Twitter application's consumer secret. + */ ++ (void)initializeWithConsumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret; + +/** + Whether the user has their account linked to Twitter. + + @param user User to check for a Twitter link. The user must be logged in on this device. + + @return `YES` if the user has their account linked to Twitter, otherwise `NO`. + */ ++ (BOOL)isLinkedWithUser:(nullable PFUser *)user; + +///-------------------------------------- +/// @name Logging In & Creating Twitter-Linked Users +///-------------------------------------- + +/** + *Asynchronously* logs in a user using Twitter. + + This method delegates to Twitter to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @return The task, that encapsulates the work being done. + */ ++ (BFTask *)logInInBackground; + +/** + *Asynchronously* logs in a user using Twitter. + + This method delegates to Twitter to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) `PFUser`. + + @param block The block to execute. + It should have the following argument signature: `^(PFUser *user, NSError *error)`. + */ ++ (void)logInWithBlock:(nullable PFUserResultBlock)block; + +/* + *Asynchronously* Logs in a user using Twitter. + + This method delegates to Twitter to authenticate the user, + and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. + + @param target Target object for the selector + @param selector The selector that will be called when the asynchrounous request is complete. + It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError **)error`. + */ ++ (void)logInWithTarget:(nullable id)target selector:(nullable SEL)selector; + +/** + *Asynchronously* logs in a user using Twitter. + + Allows you to handle user login to Twitter, then provide authentication + data to log in (or create, in the case where it is a new user) the `PFUser`. + + @param twitterId The id of the Twitter user being linked. + @param screenName The screen name of the Twitter user being linked. + @param authToken The auth token for the user's session. + @param authTokenSecret The auth token secret for the user's session. + + @return The task, that encapsulates the work being done. + */ ++ (BFTask *)logInWithTwitterIdInBackground:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret; + +/** + Logs in a user using Twitter. + + Allows you to handle user login to Twitter, then provide authentication data + to log in (or create, in the case where it is a new user) the `PFUser`. + + @param twitterId The id of the Twitter user being linked + @param screenName The screen name of the Twitter user being linked + @param authToken The auth token for the user's session + @param authTokenSecret The auth token secret for the user's session + @param block The block to execute. + It should have the following argument signature: `^(PFUser *user, NSError *error)`. + */ ++ (void)logInWithTwitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + block:(nullable PFUserResultBlock)block; + +/* + Logs in a user using Twitter. + + Allows you to handle user login to Twitter, then provide authentication data + to log in (or create, in the case where it is a new user) the `PFUser`. + + @param twitterId The id of the Twitter user being linked. + @param screenName The screen name of the Twitter user being linked. + @param authToken The auth token for the user's session. + @param authTokenSecret The auth token secret for the user's session. + @param target Target object for the selector. + @param selector The selector that will be called when the asynchronous request is complete. + It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError *)error`. + */ ++ (void)logInWithTwitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + target:(nullable id)target + selector:(nullable SEL)selector; + +///-------------------------------------- +/// @name Linking Users with Twitter +///-------------------------------------- + +/** + *Asynchronously* links Twitter to an existing PFUser. + + This method delegates to Twitter to authenticate the user, + and then automatically links the account to the `PFUser`. + + @param user User to link to Twitter. + + @deprecated Please use `[PFTwitterUtils linkUserInBackground:]` instead. + */ ++ (void)linkUser:(PFUser *)user PARSE_DEPRECATED("Please use +linkUserInBackground: instead."); + +/** + *Asynchronously* links Twitter to an existing `PFUser`. + + This method delegates to Twitter to authenticate the user, + and then automatically links the account to the `PFUser`. + + @param user User to link to Twitter. + + @return The task, that encapsulates the work being done. + */ ++ (BFTask *)linkUserInBackground:(PFUser *)user; + +/** + *Asynchronously* links Twitter to an existing `PFUser`. + + This method delegates to Twitter to authenticate the user, + and then automatically links the account to the `PFUser`. + + @param user User to link to Twitter. + @param block The block to execute. + It should have the following argument signature: `^(BOOL success, NSError *error)`. + */ ++ (void)linkUser:(PFUser *)user block:(nullable PFBooleanResultBlock)block; + +/* + *Asynchronously* links Twitter to an existing `PFUser`. + + This method delegates to Twitter to authenticate the user, + and then automatically links the account to the `PFUser`. + + @param user User to link to Twitter. + @param target Target object for the selector + @param selector The selector that will be called when the asynchrounous request is complete. + It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. + */ ++ (void)linkUser:(PFUser *)user target:(nullable id)target selector:(nullable SEL)selector; + +/** + *Asynchronously* links Twitter to an existing PFUser asynchronously. + + Allows you to handle user login to Twitter, + then provide authentication data to link the account to the `PFUser`. + + @param user User to link to Twitter. + @param twitterId The id of the Twitter user being linked. + @param screenName The screen name of the Twitter user being linked. + @param authToken The auth token for the user's session. + @param authTokenSecret The auth token secret for the user's session. + @return The task, that encapsulates the work being done. + */ ++ (BFTask *)linkUserInBackground:(PFUser *)user + twitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret; + +/** + *Asynchronously* links Twitter to an existing `PFUser`. + + @discussionAllows you to handle user login to Twitter, + then provide authentication data to link the account to the `PFUser`. + + @param user User to link to Twitter. + @param twitterId The id of the Twitter user being linked. + @param screenName The screen name of the Twitter user being linked. + @param authToken The auth token for the user's session. + @param authTokenSecret The auth token secret for the user's session. + @param block The block to execute. + It should have the following argument signature: `^(BOOL success, NSError *error)`. + */ ++ (void)linkUser:(PFUser *)user + twitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + block:(nullable PFBooleanResultBlock)block; + +/* + Links Twitter to an existing `PFUser`. + + This method allows you to handle user login to Twitter, + then provide authentication data to link the account to the `PFUser`. + + @param user User to link to Twitter. + @param twitterId The id of the Twitter user being linked. + @param screenName The screen name of the Twitter user being linked. + @param authToken The auth token for the user's session. + @param authTokenSecret The auth token secret for the user's session. + @param target Target object for the selector. + @param selector The selector that will be called when the asynchronous request is complete. + It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. + */ ++ (void)linkUser:(PFUser *)user + twitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + target:(nullable id)target + selector:(nullable SEL)selector; + +///-------------------------------------- +/// @name Unlinking Users from Twitter +///-------------------------------------- + +/** + *Synchronously* unlinks the `PFUser` from a Twitter account. + + @param user User to unlink from Twitter. + + @return Returns true if the unlink was successful. + */ ++ (BOOL)unlinkUser:(PFUser *)user; + +/** + *Synchronously* unlinks the PFUser from a Twitter account. + + @param user User to unlink from Twitter. + @param error Error object to set on error. + + @return Returns `YES` if the unlink was successful, otherwise `NO`. + */ ++ (BOOL)unlinkUser:(PFUser *)user error:(NSError **)error; + +/** + Makes an *asynchronous* request to unlink a user from a Twitter account. + + @param user User to unlink from Twitter. + + @return The task, that encapsulates the work being done. + */ ++ (BFTask *)unlinkUserInBackground:(PFUser *)user; + +/** + Makes an *asynchronous* request to unlink a user from a Twitter account. + + @param user User to unlink from Twitter. + @param block The block to execute. + It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. + */ ++ (void)unlinkUserInBackground:(PFUser *)user block:(nullable PFBooleanResultBlock)block; + +/* + Makes an *asynchronous* request to unlink a user from a Twitter account. + + @param user User to unlink from Twitter + @param target Target object for the selector + @param selector The selector that will be called when the asynchrounous request is complete. + */ ++ (void)unlinkUserInBackground:(PFUser *)user target:(nullable id)target selector:(nullable SEL)selector; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseTwitterUtils/ParseTwitterUtils/PFTwitterUtils.m b/ParseTwitterUtils/ParseTwitterUtils/PFTwitterUtils.m new file mode 100644 index 000000000..4444916d0 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/PFTwitterUtils.m @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFTwitterUtils.h" + +#import +#import + +#import + +#import "PFTwitterAuthenticationProvider.h" +#import "PFTwitterPrivateUtilities.h" +#import "PF_Twitter.h" + +@implementation PFTwitterUtils + +///-------------------------------------- +#pragma mark - Authentication Provider +///-------------------------------------- + +static PFTwitterAuthenticationProvider *authenticationProvider_; + ++ (PFTwitterAuthenticationProvider *)_authenticationProvider { + return authenticationProvider_; +} + ++ (void)_setAuthenticationProvider:(PFTwitterAuthenticationProvider *)provider { + authenticationProvider_ = provider; +} + +///-------------------------------------- +#pragma mark - Initialize +///-------------------------------------- + ++ (void)_assertTwitterInitialized { + PFTWConsistencyAssert([self _authenticationProvider], + @"You must call PFTwitterUtils initializeWithConsumerKey:consumerSecret: to use PFTwitterUtils."); +} + ++ (void)_assertParseInitialized { + PFTWConsistencyAssert([Parse getApplicationId], + @"PFTwitterUtils should be initialized after setting up Parse."); +} + ++ (void)initializeWithConsumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret { + [self _assertParseInitialized]; + if (![self _authenticationProvider]) { + PF_Twitter *twitter = [[PF_Twitter alloc] init]; + twitter.consumerKey = consumerKey; + twitter.consumerSecret = consumerSecret; + + PFTwitterAuthenticationProvider *provider = [[PFTwitterAuthenticationProvider alloc] initWithTwitter:twitter]; + [PFUser registerAuthenticationDelegate:provider forAuthType:PFTwitterUserAuthenticationType]; + + [self _setAuthenticationProvider:provider]; + } +} + ++ (PF_Twitter *)twitter { + return [self _authenticationProvider].twitter; +} + ++ (BOOL)isLinkedWithUser:(PFUser *)user { + return [user isLinkedWithAuthType:PFTwitterUserAuthenticationType]; +} + ++ (BOOL)unlinkUser:(PFUser *)user { + return [self unlinkUser:user error:nil]; +} + ++ (BOOL)unlinkUser:(PFUser *)user error:(NSError **)error { + return [[[self unlinkUserInBackground:user] pftw_waitForResult:error] boolValue]; +} + ++ (BFTask *)unlinkUserInBackground:(PFUser *)user { + [self _assertTwitterInitialized]; + return [user unlinkWithAuthTypeInBackground:PFTwitterUserAuthenticationType]; +} + ++ (void)unlinkUserInBackground:(PFUser *)user block:(PFBooleanResultBlock)block { + [[self unlinkUserInBackground:user] pftw_continueWithMainThreadBooleanBlock:block]; +} + ++ (void)unlinkUserInBackground:(PFUser *)user target:(id)target selector:(SEL)selector { + [PFTwitterUtils unlinkUserInBackground:user block:^(BOOL succeeded, NSError *error) { + [PFTwitterPrivateUtilities safePerformSelector:selector onTarget:target withObject:@(succeeded) object:error]; + }]; +} + ++ (void)linkUser:(PFUser *)user { + // This is misnamed `*InBackground` method. Left as is for backward compatability. + [self linkUserInBackground:user]; +} + ++ (BFTask *)linkUserInBackground:(PFUser *)user { + [self _assertTwitterInitialized]; + + PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; + return [[provider authenticateAsync] continueWithSuccessBlock:^id(BFTask *task) { + return [user linkWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:task.result]; + }]; +} + ++ (void)linkUser:(PFUser *)user block:(PFBooleanResultBlock)block { + [[self linkUserInBackground:user] pftw_continueWithMainThreadBooleanBlock:block]; +} + ++ (void)linkUser:(PFUser *)user target:(id)target selector:(SEL)selector { + [PFTwitterUtils linkUser:user block:^(BOOL succeeded, NSError *error) { + [PFTwitterPrivateUtilities safePerformSelector:selector onTarget:target withObject:@(succeeded) object:error]; + }]; +} + ++ (BFTask *)linkUserInBackground:(PFUser *)user + twitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret { + [self _assertTwitterInitialized]; + + PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; + NSDictionary *authData = [provider authDataWithTwitterId:twitterId + screenName:screenName + authToken:authToken + secret:authTokenSecret]; + return [user linkWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:authData]; +} + ++ (void)linkUser:(PFUser *)user + twitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + block:(PFBooleanResultBlock)block { + [[self linkUserInBackground:user + twitterId:twitterId + screenName:screenName + authToken:authToken + authTokenSecret:authTokenSecret] pftw_continueWithMainThreadBooleanBlock:block]; +} + ++ (void)linkUser:(PFUser *)user + twitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + target:(id)target + selector:(SEL)selector { + [PFTwitterUtils linkUser:user + twitterId:twitterId + screenName:screenName + authToken:authToken + authTokenSecret:authTokenSecret + block:^(BOOL succeeded, NSError *error) { + [PFTwitterPrivateUtilities safePerformSelector:selector + onTarget:target + withObject:@(succeeded) + object:error]; + }]; +} + ++ (BFTask *)logInInBackground { + [self _assertTwitterInitialized]; + + PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; + return [[provider authenticateAsync] continueWithSuccessBlock:^id(BFTask *task) { + return [PFUser logInWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:task.result]; + }]; +} + ++ (void)logInWithBlock:(PFUserResultBlock)block { + [[self logInInBackground] pftw_continueWithMainThreadUserBlock:block]; +} + ++ (void)logInWithTarget:(id)target selector:(SEL)selector { + [self logInWithBlock:^(PFUser *user, NSError *error) { + [PFTwitterPrivateUtilities safePerformSelector:selector onTarget:target withObject:user object:error]; + }]; +} + ++ (BFTask *)logInWithTwitterIdInBackground:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret { + [self _assertTwitterInitialized]; + + PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; + NSDictionary *authData = [provider authDataWithTwitterId:twitterId + screenName:screenName + authToken:authToken + secret:authTokenSecret]; + return [PFUser logInWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:authData]; +} + ++ (void)logInWithTwitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + block:(PFUserResultBlock)block { + [[self logInWithTwitterIdInBackground:twitterId + screenName:screenName + authToken:authToken + authTokenSecret:authTokenSecret] pftw_continueWithMainThreadUserBlock:block]; +} + ++ (void)logInWithTwitterId:(NSString *)twitterId + screenName:(NSString *)screenName + authToken:(NSString *)authToken + authTokenSecret:(NSString *)authTokenSecret + target:(id)target + selector:(SEL)selector { + [self logInWithTwitterId:twitterId + screenName:screenName + authToken:authToken + authTokenSecret:authTokenSecret + block:^(PFUser *user, NSError *error) { + [PFTwitterPrivateUtilities safePerformSelector:selector + onTarget:target + withObject:user + object:error]; + }]; +} + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/PF_Twitter.h b/ParseTwitterUtils/ParseTwitterUtils/PF_Twitter.h new file mode 100644 index 000000000..6f0df5404 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/PF_Twitter.h @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class BFTask<__covariant BFGenericType>; + +/** + The `PF_Twitter` class is a simple interface for interacting with the Twitter REST API, + automating sign-in and OAuth signing of requests against the API. + */ +@interface PF_Twitter : NSObject + +/** + Initializes an instance of `PF_Twitter` configured to access device Twitter accounts with Accounts.framework, + and remote access to Twitter accounts - and, if no accounts are found locally - through a built-in webview. + + After setting `consumerKey` and `consumerSecret`, authorization to Twitter accounts can be requested with +`authorizeInBackground`, and then revoked with its opposite, `deauthorizeInBackground`. + */ +- (instancetype)init; + +/** + Consumer key of the application that is used to authorize with Twitter. + */ +@property (nullable, nonatomic, copy) NSString *consumerKey; + +/** + Consumer secret of the application that is used to authorize with Twitter. + */ +@property (nullable, nonatomic, copy) NSString *consumerSecret; + +/** + Auth token for the current user. + */ +@property (nullable, nonatomic, copy) NSString *authToken; + +/** + Auth token secret for the current user. + */ +@property (nullable, nonatomic, copy) NSString *authTokenSecret; + +/** + Twitter user id of the currently signed in user. + */ +@property (nullable, nonatomic, copy) NSString *userId; + +/** + Twitter screen name of the currently signed in user. + */ +@property (nullable, nonatomic, copy) NSString *screenName; + +/** + Displays an auth dialog and populates the authToken, authTokenSecret, userId, and screenName properties + if the Twitter user grants permission to the application. + + @return The task, that encapsulates the work being done. + */ +- (BFTask *)authorizeInBackground; + +/** + Invalidates an OAuth token for the current user, if the Twitter user has granted permission to the + current application. + + @return The task, that encapsulates the work being done. + */ +- (BFTask *)deauthorizeInBackground; + +/** + Displays an auth dialog and populates the authToken, authTokenSecret, userId, and screenName properties + if the Twitter user grants permission to the application. + + @param success Invoked upon successful authorization. + @param failure Invoked upon an error occurring in the authorization process. + @param cancel Invoked when the user cancels authorization. + */ +- (void)authorizeWithSuccess:(nullable void (^)(void))success + failure:(nullable void (^)(NSError *__nullable error))failure + cancel:(nullable void (^)(void))cancel; + +/** + Adds a 3-legged OAuth signature to an `NSMutableURLRequest` based + upon the properties set for the Twitter object. + + Use this function to sign requests being made to the Twitter API. + + @param request Request to sign. + */ +- (void)signRequest:(nullable NSMutableURLRequest *)request; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseTwitterUtils/ParseTwitterUtils/PF_Twitter.m b/ParseTwitterUtils/ParseTwitterUtils/PF_Twitter.m new file mode 100644 index 000000000..6e87e7b77 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/PF_Twitter.m @@ -0,0 +1,498 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PF_Twitter.h" +#import "PF_Twitter_Private.h" + +#import +#import +#import + +#import +#import + +#import + +#import "PFOAuth1FlowDialog.h" +#import "PFTwitterAlertView.h" +#import "PFTwitterPrivateUtilities.h" +#import "PF_OAuthCore.h" +#import "PFTwitterLocalization.h" + +@implementation PF_Twitter + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +- (instancetype)init { + return [self initWithAccountStore:[[ACAccountStore alloc] init] + urlSession:[NSURLSession sharedSession] + dialogClass:[PFOAuth1FlowDialog class]]; +} + +- (instancetype)initWithAccountStore:(ACAccountStore *)accountStore + urlSession:(NSURLSession *)urlSession + dialogClass:(Class)dialogClass { + self = [super init]; + if (!self) return nil; + + _accountStore = accountStore; + _urlSession = urlSession; + _oauthDialogClass = dialogClass; + + PFTWConsistencyAssert(_oauthDialogClass == nil || + [(id)_oauthDialogClass conformsToProtocol:@protocol(PFOAuth1FlowDialogInterface)], + @"OAuth Dialog class must conform to the Dialog Interface protocol!"); + + return self; +} + +///-------------------------------------- +#pragma mark - Authorize +///-------------------------------------- + +- (BFTask *)authorizeInBackground { + if (self.consumerKey.length == 0 || self.consumerSecret.length == 0) { + //TODO: (nlutsenko) This doesn't look right, maybe we should add additional error code? + return [BFTask taskWithError:[NSError errorWithDomain:PFParseErrorDomain code:1 userInfo:nil]]; + } + + return [[self _performReverseAuthAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + dispatch_async(dispatch_get_main_queue(), ^{ + // if reverse auth was successful then return + if (task.cancelled) { + [source cancel]; + return; + } else if (!task.error && !task.result) { + source.result = nil; + return; + } + + // fallback to the webview auth + [[self _performWebViewAuthAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { + NSError *error = task.error; + if (task.cancelled) { + [source cancel]; + } else if (!error) { + [source setResult:task.result]; + } else { + [source setError:error]; + } + return nil; + }]; + }); + return source.task; + }]; +} +- (BFTask *)deauthorizeInBackground { + if (self.consumerKey.length == 0 || self.consumerSecret.length == 0) { + //TODO: (nlutsenko) This doesn't look right, maybe we should add additional error code? + return [BFTask taskWithError:[NSError errorWithDomain:PFParseErrorDomain code:1 userInfo:nil]]; + } + + return [[self _performDeauthAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + if (task.cancelled) { + [source cancel]; + } else if (!task.error && !task.result) { + source.result = nil; + } else if (task.error) { + [source trySetError:task.error]; + } else if (task.result) { + [self setLoginResultValues:nil]; + + [source trySetResult:task.result]; + } + return source.task; + }]; +} + +- (void)authorizeWithSuccess:(void (^)(void))success + failure:(void (^)(NSError *error))failure + cancel:(void (^)(void))cancel { + [[self authorizeInBackground] continueWithExecutor:[BFExecutor mainThreadExecutor] + withBlock:^id(BFTask *task) { + if (task.error) { + failure(task.error); + } else if (task.cancelled) { + cancel(); + } else { + success(); + } + return nil; + }]; +} + +// Displays the web view dialog +- (BFTask *)_showWebViewDialogAsync:(NSString *)requestToken requestSecret:(NSString *)requestSecret { + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + + static NSString *twitterAuthURLString = @"https://api.twitter.com/oauth/authenticate"; + + PFOAuth1FlowDialog *dialog = [_oauthDialogClass dialogWithURL:[NSURL URLWithString:twitterAuthURLString] + queryParameters:@{ @"oauth_token" : requestToken }]; + dialog.redirectURLPrefix = @"http://twitter-oauth.callback"; + dialog.completion = ^(BOOL succeeded, NSURL *url, NSError *error) { + // In case of error + if (error) { + source.error = error; + return; + } + // In case the dialog was cancelled + if (!succeeded) { + [source cancel]; + return; + } + + // Handle URL received. + NSDictionary *authQueryParams = [NSURL PF_ab_parseURLQueryString:[url query]]; + NSString *verifier = [authQueryParams objectForKey:@"oauth_verifier"]; + NSString *token = [authQueryParams objectForKey:@"oauth_token"]; + + [[self _getAccessTokenForWebAuthAsync:verifier requestSecret:requestSecret token:token] + pftw_continueAsyncWithBlock:^id (BFTask *task) { + NSError *error = task.error; + if (!error) { + NSDictionary *accessResult = (NSDictionary*) task.result; + [self setLoginResultValues:accessResult]; + source.result = nil; + } else { + source.error = error; + } + return nil; + }]; + }; + [dialog showAnimated:YES]; + + return source.task; +} + +/** + Get the request token for the authentication. This is the first step in auth. + if isReverseAuth is YES then get the request token for reverse auth mode. Otherwise, get the request token for web auth mode. + */ +- (BFTask *)_getRequestTokenAsync:(BOOL)isReverseAuth { + NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/oauth/request_token"]; + NSMutableDictionary *params = nil; + NSData *body = nil; + + if (isReverseAuth) { + body = [[NSString stringWithFormat:@"x_auth_mode=%@", @"reverse_auth"] dataUsingEncoding:NSUTF8StringEncoding]; + } else { + params = [NSMutableDictionary dictionary]; + [params setObject:@"http://twitter-oauth.callback" forKey:@"oauth_callback"]; + } + + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:url + method:@"POST" + body:body + additionalParameters:params + consumerKey:_consumerKey + consumerSecret:_consumerSecret + token:nil + tokenSecret:nil]; + NSString *header = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + [request setHTTPMethod:@"POST"]; + [request addValue:header forHTTPHeaderField:@"Authorization"]; + [request setHTTPBody:body]; + + BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; + [[self.urlSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + [taskCompletionSource trySetError:error]; + } else { + [taskCompletionSource trySetResult:data]; + } + }] resume]; + return taskCompletionSource.task; +} + +// Get the access token for web authentication +- (BFTask *)_getAccessTokenForWebAuthAsync:(NSString *)verifier + requestSecret:(NSString *)requestSecret + token:(NSString *)token { + NSURL *accessTokenURL = [NSURL URLWithString:@"https://api.twitter.com/oauth/access_token"]; + NSData *body = [[NSString stringWithFormat:@"oauth_verifier=%@", verifier] dataUsingEncoding:NSUTF8StringEncoding]; + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:accessTokenURL + method:@"POST" + body:body + additionalParameters:nil + consumerKey:_consumerKey + consumerSecret:_consumerSecret + token:token + tokenSecret:requestSecret]; + NSString *accessTokenHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + NSMutableURLRequest *accessRequest = [NSMutableURLRequest requestWithURL:accessTokenURL]; + [accessRequest setHTTPMethod:@"POST"]; + [accessRequest addValue:accessTokenHeader forHTTPHeaderField:@"Authorization"]; + [accessRequest setHTTPBody:body]; + + BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; + [[self.urlSession dataTaskWithRequest:accessRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + [taskCompletionSource trySetError:error]; + } else { + NSString *accessResponseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSDictionary *accessResponseValues = [NSURL PF_ab_parseURLQueryString:accessResponseString]; + [taskCompletionSource trySetResult:accessResponseValues]; + } + }] resume]; + return taskCompletionSource.task; +} + +/** + Get the access token for reverse authentication. + If the Task is successful then, Task result is dictionary containing logged in user's Auth token, Screenname and other attributes. + */ +- (BFTask *)_getAccessTokenForReverseAuthAsync:(NSString *)signedReverseAuthSignature + localTwitterAccount:(ACAccount *)localTwitterAccount { + + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + if (!signedReverseAuthSignature || + [signedReverseAuthSignature length] == 0 || + !localTwitterAccount) { + + source.error = [NSError errorWithDomain:PFParseErrorDomain code:1 userInfo:nil]; + return source.task; + } + + NSDictionary *params = @{ @"x_reverse_auth_parameters" : signedReverseAuthSignature, + @"x_reverse_auth_target" : _consumerKey }; + + NSURL *accessTokenUrl = [NSURL URLWithString:@"https://api.twitter.com/oauth/access_token"]; + SLRequest *accessRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter + requestMethod:SLRequestMethodPOST + URL:accessTokenUrl + parameters:params]; + + [accessRequest setAccount:localTwitterAccount]; + [accessRequest performRequestWithHandler:^(NSData *data, NSHTTPURLResponse *urlResponse, NSError *error) { + if (error) { + [source setError:error]; + } else { + NSString *accessResponseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSDictionary *accessResponseValues = [NSURL PF_ab_parseURLQueryString:accessResponseString]; + [source setResult:accessResponseValues]; + } + }]; + + return source.task; +} + +// Set the result parameters from the data returned om succesful login +- (void)setLoginResultValues:(NSDictionary *)resultData { + self.authToken = [resultData objectForKey:@"oauth_token"]; + self.authTokenSecret = [resultData objectForKey:@"oauth_token_secret"]; + self.userId = [resultData objectForKey:@"user_id"]; + self.screenName = [resultData objectForKey:@"screen_name"]; +} + +// Performs the Reverse auth for the the twitter account setup on the device. +- (BFTask *)_performReverseAuthAsync { + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + + // get permission to access the account if its setup and available. + [[self _getLocalTwitterAccountAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { + + if (task.error) { + source.error = task.error; + return source.task; + } + + if (task.cancelled) { + [source cancel]; + return source.task; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + ACAccount *localTwitterAccount = (ACAccount*) task.result; + + if(!localTwitterAccount) { + source.error = [NSError errorWithDomain:PFParseErrorDomain code:2 userInfo:nil]; + return; + } + + // continue with reverse auth since its permitted + [[self _getRequestTokenAsync:YES] pftw_continueAsyncWithBlock:^id(BFTask *task) { + if (task.error) { + source.error = task.error; + return source.task; + } + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *requestTokenResponse = [[NSString alloc] initWithData:task.result encoding:NSUTF8StringEncoding]; + + [[self _getAccessTokenForReverseAuthAsync:requestTokenResponse + localTwitterAccount:localTwitterAccount] pftw_continueAsyncWithBlock:^id(BFTask *task) { + NSError *error = task.error; + if (!error) { + NSDictionary *accessResult = (NSDictionary*) task.result; + [self setLoginResultValues:accessResult]; + source.result = nil; + } else { + source.error = task.error; + } + return nil; + }]; + }); + return nil; + }]; + + }); + return nil; + }]; + + return source.task; +} + +- (BFTask *)_performWebViewAuthAsync { + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + + [[self _getRequestTokenAsync:NO] pftw_continueAsyncWithBlock:^id(BFTask *task) { + if (task.error || task.isCancelled) { + if (task.error) { + [source setError:task.error]; + } else { + [source cancel]; + } + return task; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *requestTokenResponse = [[NSString alloc] initWithData:task.result encoding:NSUTF8StringEncoding]; + + NSDictionary *requestTokenParsed = [NSURL PF_ab_parseURLQueryString:requestTokenResponse]; + NSString *requestToken = requestTokenParsed[@"oauth_token"]; + NSString *requestSecret = requestTokenParsed[@"oauth_token_secret"]; + + // If one of these is missing, then we failed to get a token. + if (!requestToken || !requestSecret) { + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Failed to request authorization token from Twitter." }; + NSError *error = [NSError errorWithDomain:PFParseErrorDomain code:2 userInfo:userInfo]; + [source setError:error]; + return; + } + + // show the webview dialog for auth token + [[self _showWebViewDialogAsync:requestToken + requestSecret:requestSecret] pftw_continueAsyncWithBlock:^id(BFTask *task) { + NSError *error = task.error; + if (task.isCancelled) { + [source cancel]; + } else if (!error) { + [source setResult:task.result]; + } else { + [source setError:error]; + } + return nil; + }]; + }); + return nil; + }]; + + return source.task; +} + +- (BFTask *)_getLocalTwitterAccountAsync { + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + + // If no twitter accounts present in the system, then no need to ask for permission to the user + if (![SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) { + [source setResult:nil]; + return source.task; + } + + ACAccountType *twitterType = [_accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]; + [_accountStore requestAccessToAccountsWithType:twitterType options:nil completion:^(BOOL granted, NSError *error) { + if (error) { + [source setError:error]; + return; + } + + if (!granted) { + [source setResult:nil]; + return; + } + + NSArray *accounts = [_accountStore accountsWithAccountType:twitterType]; + + // No accounts - provide an empty result + if ([accounts count] == 0) { + [source setResult:nil]; + return; + } + + // Finish if there is only 1 account + if ([accounts count] == 1) { + [source setResult:accounts[0]]; + return; + } + + NSArray *usernames = [accounts valueForKey:@"accountDescription"]; + + // Call async on the main thread, as the completion isn't executed on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + [PFTwitterAlertView showAlertWithTitle:PFTWLocalizedString(@"Select a Twitter Account", @"Select a Twitter Account") + message:nil + cancelButtonTitle:PFTWLocalizedString(@"Cancel", @"Cancel") + otherButtonTitles:usernames + completion:^(NSUInteger buttonIndex) { + if (buttonIndex == NSNotFound) { + [source cancel]; + } else { + ACAccount *account = accounts[buttonIndex]; + [source setResult:account]; + } + }]; + }); + }]; + + return source.task; +} + +- (BFTask *)_performDeauthAsync { + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; + request.URL = [NSURL URLWithString:@"https://api.twitter.com/oauth2/invalidate_token"]; + request.HTTPMethod = @"POST"; + + [self signRequest:request]; + + BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; + + [[self.urlSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + [taskCompletionSource trySetError:error]; + } else { + [taskCompletionSource trySetResult:data]; + } + }] resume]; + + return taskCompletionSource.task; +} + +///-------------------------------------- +#pragma mark - Sign Request +///-------------------------------------- + +- (void)signRequest:(NSMutableURLRequest *)request { + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:request.URL + method:request.HTTPMethod ?: @"GET" + body:request.HTTPBody + additionalParameters:nil + consumerKey:_consumerKey + consumerSecret:_consumerSecret + token:_authToken + tokenSecret:_authTokenSecret]; + NSString *header = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + [request addValue:header forHTTPHeaderField:@"Authorization"]; +} + +@end diff --git a/ParseTwitterUtils/ParseTwitterUtils/ParseTwitterUtils.h b/ParseTwitterUtils/ParseTwitterUtils/ParseTwitterUtils.h new file mode 100644 index 000000000..2c49673a1 --- /dev/null +++ b/ParseTwitterUtils/ParseTwitterUtils/ParseTwitterUtils.h @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import diff --git a/ParseTwitterUtils/Resources/Info.plist b/ParseTwitterUtils/Resources/Info.plist new file mode 100644 index 000000000..c6594d610 --- /dev/null +++ b/ParseTwitterUtils/Resources/Info.plist @@ -0,0 +1,29 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ParseTwitterUtils + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.16.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + iPhoneSimulator + iPhoneOS + + CFBundleVersion + 1.16.0 + MinimumOSVersion + 6.0 + + diff --git a/ParseTwitterUtils/Resources/en.lproj/ParseTwitterUtils.strings b/ParseTwitterUtils/Resources/en.lproj/ParseTwitterUtils.strings new file mode 100644 index 000000000..2ebc81b9e Binary files /dev/null and b/ParseTwitterUtils/Resources/en.lproj/ParseTwitterUtils.strings differ diff --git a/ParseTwitterUtils/Tests/Other/PFTwitterTestMacros.h b/ParseTwitterUtils/Tests/Other/PFTwitterTestMacros.h new file mode 100644 index 000000000..5c1461e7c --- /dev/null +++ b/ParseTwitterUtils/Tests/Other/PFTwitterTestMacros.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#ifndef PFTwitterTestMacros_h +#define PFTwitterTestMacros_h + +/** + To prevent retain cycles by OCMock, this macro allows us to capture a weak reference to return from a stubbed method. + */ +#define andReturnWeak(variable) _andDo( \ + ({ \ + __weak typeof(variable) variable ## _weak = (variable); \ + ^(NSInvocation *invocation) { \ + __autoreleasing typeof(variable) variable ## _block = variable ## _weak; \ + [invocation setReturnValue:&(variable ## _block)]; \ + }; \ + }) \ +) + +#endif /* PFTwitterTestMacros_h */ diff --git a/ParseTwitterUtils/Tests/Other/TestCase/PFTwitterTestCase.h b/ParseTwitterUtils/Tests/Other/TestCase/PFTwitterTestCase.h new file mode 100644 index 000000000..05411f71d --- /dev/null +++ b/ParseTwitterUtils/Tests/Other/TestCase/PFTwitterTestCase.h @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@import XCTest; + +@interface PFTwitterTestCase : XCTestCase + +///-------------------------------------- +/// @name XCTestCase +///-------------------------------------- + +- (void)setUp NS_REQUIRES_SUPER; +- (void)tearDown NS_REQUIRES_SUPER; + +///-------------------------------------- +/// @name Expectations +///-------------------------------------- + +- (XCTestExpectation *)currentSelectorTestExpectation; +- (void)waitForTestExpectations; + +///-------------------------------------- +/// @name Mocks +///-------------------------------------- + +- (void)registerMockObject:(id)mockObject; + +@end + +#define _PFRegisterMock(mockObject) [self registerMockObject:mockObject] +#define _PFMockShim(method, args...) ({ id mock = method(args); _PFRegisterMock(mock); mock; }) +#define _PFOCMockWarning _Pragma("GCC warning \"Please use PF mocking methods instead of OCMock ones.\"") + +#define _PFStrictClassMock(kls) [OCMockObject mockForClass:kls] +#define _PFClassMock(kls) [OCMockObject niceMockForClass:kls] +#define _PFStrictProtocolMock(proto) [OCMockObject mockForProtocol:proto] +#define _PFProtocolMock(proto) [OCMockObject niceMockForProtocol:proto] +#define _PFPartialMock(obj) [OCMockObject partialMockForObject:obj] + +#define PFStrictClassMock(...) _PFMockShim(_PFStrictClassMock, __VA_ARGS__) +#define PFClassMock(...) _PFMockShim(_PFClassMock, __VA_ARGS__) +#define PFStrictProtocolMock(...) _PFMockShim(_PFStrictProtocolMock, __VA_ARGS__) +#define PFProtocolMock(...) _PFMockShim(_PFProtocolMock, __VA_ARGS__) +#define PFPartialMock(...) _PFMockShim(_PFPartialMock, __VA_ARGS__) + +#undef OCMStrictClassMock +#undef OCMClassMock +#undef OCMStrictProtocolMock +#undef OCMProtocolMock +#undef OCMPartialMock + +#define OCMStrictClassMock _PFOCMockWarning _PFStrictClassMock +#define OCMClassMock _PFOCMockWarning _PFClassMock +#define OCMStrictProtocolMock _PFOCMockWarning _PFStrictProtocolMock +#define OCMProtocolMock _PFOCMockWarning _PFProtocolMock +#define OCMPartialMock _PFOCMockWarning _PFPartialMock + +#define PFAssertIsKindOfClass(a1, a2, description...) \ +XCTAssertTrue([a1 isKindOfClass:[a2 class]], ## description) + +#define PFAssertNotKindOfClass(a1, a2, description...) \ +XCTAssertFalse([a1 isKindOfClass:[a2 class]], ## description) + +#define PFAssertThrowsInconsistencyException(expression, ...) \ +XCTAssertThrowsSpecificNamed(expression, NSException, NSInternalInconsistencyException, __VA_ARGS__) + +#define PFAssertThrowsInvalidArgumentException(expression, ...) \ +XCTAssertThrowsSpecificNamed(expression, NSException, NSInvalidArgumentException, __VA_ARGS__) + +#define PFAssertStringContains(a, b) XCTAssertTrue([(a) rangeOfString:(b)].location != NSNotFound) diff --git a/ParseTwitterUtils/Tests/Other/TestCase/PFTwitterTestCase.m b/ParseTwitterUtils/Tests/Other/TestCase/PFTwitterTestCase.m new file mode 100644 index 000000000..9afe39c68 --- /dev/null +++ b/ParseTwitterUtils/Tests/Other/TestCase/PFTwitterTestCase.m @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFTwitterTestCase.h" + +@implementation PFTwitterTestCase { + NSMutableArray *_mocks; + dispatch_queue_t _mockQueue; +} + +///-------------------------------------- +#pragma mark - XCTestCase +///-------------------------------------- + +- (void)setUp { + [super setUp]; + + _mocks = [[NSMutableArray alloc] init]; + _mockQueue = dispatch_queue_create("com.parse.tests.mock.queue", DISPATCH_QUEUE_SERIAL); +} + +- (void)tearDown { + dispatch_sync(_mockQueue, ^{ + [_mocks makeObjectsPerformSelector:@selector(stopMocking)]; + }); + + _mocks = nil; + _mockQueue = nil; + + [super tearDown]; +} + +///-------------------------------------- +#pragma mark - Helpers +///-------------------------------------- + +- (XCTestExpectation *)currentSelectorTestExpectation { + NSInvocation *invocation = self.invocation; + NSString *selectorName = invocation ? NSStringFromSelector(invocation.selector) : @"testExpectation"; + return [self expectationWithDescription:selectorName]; +} + +- (void)waitForTestExpectations { + [self waitForExpectationsWithTimeout:10.0 handler:nil]; +} + +///-------------------------------------- +#pragma mark - Mock Registration +///-------------------------------------- + +- (void)registerMockObject:(id)mockObject { + dispatch_sync(_mockQueue, ^{ + [_mocks addObject:mockObject]; + }); +} + +@end diff --git a/ParseTwitterUtils/Tests/Resources/Info.plist b/ParseTwitterUtils/Tests/Resources/Info.plist new file mode 100644 index 000000000..ba72822e8 --- /dev/null +++ b/ParseTwitterUtils/Tests/Resources/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/ParseTwitterUtils/Tests/TestApplication/Classes/main.m b/ParseTwitterUtils/Tests/TestApplication/Classes/main.m new file mode 100644 index 000000000..453fc1227 --- /dev/null +++ b/ParseTwitterUtils/Tests/TestApplication/Classes/main.m @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface AppDelegate : NSObject + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].applicationFrame]; + window.rootViewController = [[UIViewController alloc] init]; + [window makeKeyAndVisible]; + return YES; +} + +@end + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/ParseTwitterUtils/Tests/TestApplication/Resources/Info.plist b/ParseTwitterUtils/Tests/TestApplication/Resources/Info.plist new file mode 100644 index 000000000..6dcc65160 --- /dev/null +++ b/ParseTwitterUtils/Tests/TestApplication/Resources/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ParseTwitterUtils/Tests/Unit/OAuth1FlowDialogTests.m b/ParseTwitterUtils/Tests/Unit/OAuth1FlowDialogTests.m new file mode 100644 index 000000000..d7c70943d --- /dev/null +++ b/ParseTwitterUtils/Tests/Unit/OAuth1FlowDialogTests.m @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFOAuth1FlowDialog.h" +#import "PFTwitterTestCase.h" + +@interface UIActivityIndicatorView (Private) + +- (void)_generateImages; + +@end + +@interface OAuth1FlowDialogTests : PFTwitterTestCase +@end + +@interface UIDevice (Yolo) + +- (void)setOrientation:(UIDeviceOrientation)orientation animated:(BOOL)animated; + +@end + +@implementation OAuth1FlowDialogTests + +///-------------------------------------- +#pragma mark - Tests +///-------------------------------------- + +- (void)testConstructors { + NSURL *exampleURL = [NSURL URLWithString:@"http://foo.bar"]; + NSDictionary *parameters = @{ @"a" : @"b" }; + + PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:exampleURL + queryParameters:parameters]; + XCTAssertNotNil(flowDialog); + XCTAssertEqualObjects(flowDialog.queryParameters, parameters); + XCTAssertEqualObjects(flowDialog->_baseURL, exampleURL); + + flowDialog = [PFOAuth1FlowDialog dialogWithURL:exampleURL queryParameters:parameters]; + XCTAssertNotNil(flowDialog); + XCTAssertEqualObjects(flowDialog.queryParameters, parameters); + XCTAssertEqualObjects(flowDialog->_baseURL, exampleURL); +} + +- (void)testTitle { + PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; + XCTAssertEqualObjects(flowDialog.title, @"Connect to Service"); + flowDialog.title = @"Bleh"; + XCTAssertEqualObjects(flowDialog.title, @"Bleh"); +} + +- (void)testShow { + PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; + + [flowDialog showAnimated:NO]; + [flowDialog layoutSubviews]; + [flowDialog dismissAnimated:NO]; +} + +- (void)testKeyboard { + PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; + [flowDialog showAnimated:NO]; + + NSDictionary *notificationuserInfo = @{ UIKeyboardAnimationDurationUserInfoKey : @0, + UIKeyboardAnimationCurveUserInfoKey : @(UIViewAnimationCurveLinear) }; + [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification + object:nil + userInfo:notificationuserInfo]; + + [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillHideNotification + object:nil + userInfo:notificationuserInfo]; + + [flowDialog dismissAnimated:NO]; +} + +- (void)testRotation { + [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]; + [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO]; + + PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; + + [flowDialog showAnimated:NO]; + CGRect oldBounds = flowDialog.bounds; + + [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft]; + [[UIDevice currentDevice] setOrientation:UIDeviceOrientationLandscapeLeft animated:NO]; + [[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil]; + + CGRect newBounds = flowDialog.bounds; + XCTAssertFalse(CGRectEqualToRect(oldBounds, newBounds)); + + [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]; + [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO]; + [[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil]; + + newBounds = flowDialog.bounds; + XCTAssertTrue(CGRectEqualToRect(oldBounds, newBounds)); + + [flowDialog dismissAnimated:NO]; +} + +- (void)testWebViewDelegate { + NSURL *sampleURL = [NSURL URLWithString:@"http://foo.bar"]; + NSURL *successURL = [NSURL URLWithString:@"foo://success"]; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:sampleURL queryParameters:nil]; + flowDialog.redirectURLPrefix = @"foo://"; + flowDialog.completion = ^(BOOL succeeded, NSURL *url, NSError *error) { + XCTAssertTrue(succeeded); + XCTAssertNil(error); + XCTAssertEqualObjects(url, successURL); + + [expectation fulfill]; + }; + + [flowDialog showAnimated:NO]; + + id webView = PFStrictClassMock([UIWebView class]); + + NSURLRequest *request = [NSURLRequest requestWithURL:sampleURL]; + XCTAssertTrue([flowDialog webView:webView + shouldStartLoadWithRequest:request + navigationType:UIWebViewNavigationTypeOther]); + + [flowDialog webViewDidStartLoad:webView]; + [flowDialog webViewDidFinishLoad:webView]; + + NSURLRequest *successRequest = [NSURLRequest requestWithURL:successURL]; + [flowDialog webView:webView shouldStartLoadWithRequest:successRequest navigationType:UIWebViewNavigationTypeOther]; + + [self waitForTestExpectations]; +} + +@end diff --git a/ParseTwitterUtils/Tests/Unit/OAuthCoreTests.m b/ParseTwitterUtils/Tests/Unit/OAuthCoreTests.m new file mode 100644 index 000000000..c4e20cb64 --- /dev/null +++ b/ParseTwitterUtils/Tests/Unit/OAuthCoreTests.m @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +@import ObjectiveC.runtime; + +#import "PFTwitterTestCase.h" +#import "PF_OAuthCore.h" + +@implementation NSURLComponents (OAuthCoreTests) + ++ (Class)class { + return [super class]; +} + ++ (Class)_nilClass { + return nil; +} + +@end + +@interface OAuthCoreTests : PFTwitterTestCase + +@property (nonatomic, strong) NSDate *authDate; +@property (nonatomic, copy) NSString *authNonce; + +@end + +@implementation OAuthCoreTests + +///-------------------------------------- +#pragma mark - Helpers +///-------------------------------------- + +- (NSURL *)sampleURL { + return [NSURL URLWithString:@"https://localhost/foo/bar"]; +} + +- (NSData *)sampleData { + return [@"sampe=@!value" dataUsingEncoding:NSUTF8StringEncoding]; +} + +- (void)assertAuthHeader:(NSString *)authHeader matchesExpectedSignature:(NSString *)signature { + XCTAssertTrue([authHeader hasPrefix:@"OAuth "]); + + PFAssertStringContains(authHeader, + ([NSString stringWithFormat:@"oauth_timestamp=\"%llu\"", + (unsigned long long)[_authDate timeIntervalSince1970]])); + + PFAssertStringContains(authHeader, ([NSString stringWithFormat:@"oauth_nonce=\"%@\"", _authNonce])); + PFAssertStringContains(authHeader, @"oauth_version=\"1.0\""); + PFAssertStringContains(authHeader, @"oauth_consumer_key=\"consumer_key\""); + PFAssertStringContains(authHeader, @"oauth_signature_method=\"HMAC-SHA1\""); + PFAssertStringContains(authHeader, ([NSString stringWithFormat:@"oauth_signature=\"%@\"", signature])); +} + +///-------------------------------------- +#pragma mark - XCTestCase +///-------------------------------------- + +- (void)setUp { + [super setUp]; + + _authDate = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0]; + _authNonce = @"UUID-STRING"; +} + +///-------------------------------------- +#pragma mark - Tests +///-------------------------------------- + +- (void)testBasic { + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] + method:@"POST" + body:[self sampleData] + additionalParameters:nil + consumerKey:@"consumer_key" + consumerSecret:@"consumer_secret" + token:nil + tokenSecret:nil]; + configuration.nonce = self.authNonce; + configuration.timestampDate = self.authDate; + NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + + [self assertAuthHeader:authHeader matchesExpectedSignature:@"3Nvy4O1Ok3qkeKcjvtv4wtyjc%2FY%3D"]; +} + +- (void)testNoBody { + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] + method:@"POST" + body:nil + additionalParameters:nil + consumerKey:@"consumer_key" + consumerSecret:@"consumer_secret" + token:nil + tokenSecret:nil]; + configuration.nonce = self.authNonce; + configuration.timestampDate = self.authDate; + NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + + [self assertAuthHeader:authHeader matchesExpectedSignature:@"rXtmqPIUmMbl4e1%2Bz4JgJUuVIz0%3D"]; +} + +- (void)testWithToken { + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] + method:@"POST" + body:nil + additionalParameters:nil + consumerKey:@"consumer_key" + consumerSecret:@"consumer_secret" + token:@"token" + tokenSecret:nil]; + configuration.nonce = self.authNonce; + configuration.timestampDate = self.authDate; + NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + + XCTAssertTrue([authHeader rangeOfString:@"oauth_token=\"token\""].location != NSNotFound); + [self assertAuthHeader:authHeader matchesExpectedSignature:@"iRsvN%2FUCXyzhf3o9tIL0DAX%2F4HY%3D"]; +} + +- (void)testNoNSURLComponents { + // Disable NSURLComponents for a single test. + Method originalMethod = class_getClassMethod([NSURLComponents class], @selector(class)); + Method replacementMethod = class_getClassMethod([NSURLComponents class], @selector(_nilClass)); + method_exchangeImplementations(originalMethod, replacementMethod); + + @try { + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] + method:@"POST" + body:[self sampleData] + additionalParameters:nil + consumerKey:@"consumer_key" + consumerSecret:@"consumer_secret" + token:nil + tokenSecret:nil]; + configuration.nonce = self.authNonce; + configuration.timestampDate = self.authDate; + NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + + [self assertAuthHeader:authHeader matchesExpectedSignature:@"3Nvy4O1Ok3qkeKcjvtv4wtyjc%2FY%3D"]; + } @finally { + method_exchangeImplementations(originalMethod, replacementMethod); + } +} + +- (void)testWithQuery { + NSURL *url = [NSURL URLWithString:[[[self sampleURL] absoluteString] stringByAppendingString:@"?key=value"]]; + + PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:url + method:@"GET" + body:nil + additionalParameters:nil + consumerKey:@"consumer_key" + consumerSecret:@"consumer_secret" + token:nil + tokenSecret:nil]; + configuration.nonce = self.authNonce; + configuration.timestampDate = self.authDate; + NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; + [self assertAuthHeader:authHeader matchesExpectedSignature:@"LecftA2NX%2FvSD4KakdTFjPZlmc0%3D"]; +} + +@end diff --git a/ParseTwitterUtils/Tests/Unit/TwitterAuthenticationProviderTests.m b/ParseTwitterUtils/Tests/Unit/TwitterAuthenticationProviderTests.m new file mode 100644 index 000000000..6660317b1 --- /dev/null +++ b/ParseTwitterUtils/Tests/Unit/TwitterAuthenticationProviderTests.m @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +@import Bolts.BFTask; + +#import "PFTwitterAuthenticationProvider.h" +#import "PFTwitterTestCase.h" +#import "PF_Twitter.h" + +@interface TwitterAuthenticationProviderTests : PFTwitterTestCase + +@end + +@implementation TwitterAuthenticationProviderTests + +///-------------------------------------- +#pragma mark - Helpers +///-------------------------------------- + +- (PF_Twitter *)mockedTwitter { + PF_Twitter *twitter = PFStrictClassMock([PF_Twitter class]); + + OCMStub(twitter.consumerKey).andReturn(@"yarr"); + OCMStub(twitter.consumerSecret).andReturn(@"yolo"); + + return twitter; +} + +- (void)assertValidAuthenticationData:(NSDictionary *)authData forTwitter:(PF_Twitter *)twitter { + XCTAssertEqualObjects(authData[@"id"], @"a"); + XCTAssertEqualObjects(authData[@"screen_name"], @"b"); + XCTAssertEqualObjects(authData[@"auth_token"], @"c"); + XCTAssertEqualObjects(authData[@"auth_token_secret"], @"d"); + XCTAssertEqualObjects(authData[@"consumer_key"], twitter.consumerKey); + XCTAssertEqualObjects(authData[@"consumer_secret"], twitter.consumerSecret); +} + +///-------------------------------------- +#pragma mark - Tests +///-------------------------------------- + +- (void)testConstructors { + PF_Twitter *twitter = [self mockedTwitter]; + PFTwitterAuthenticationProvider *provider = [[PFTwitterAuthenticationProvider alloc] initWithTwitter:twitter]; + XCTAssertNotNil(provider); + XCTAssertEqual(provider.twitter, twitter); + + provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; + XCTAssertNotNil(provider); + XCTAssertEqual(provider.twitter, twitter); + + PFAssertThrowsInconsistencyException([PFTwitterAuthenticationProvider new]); +} + +- (void)testAuthData { + PF_Twitter *twitter = [self mockedTwitter]; + PFTwitterAuthenticationProvider *provider = [[PFTwitterAuthenticationProvider alloc] initWithTwitter:twitter]; + + NSDictionary *authData = [provider authDataWithTwitterId:@"a" + screenName:@"b" + authToken:@"c" + secret:@"d"]; + [self assertValidAuthenticationData:authData forTwitter:twitter]; +} + +- (void)testAuthType { + XCTAssertEqualObjects(PFTwitterUserAuthenticationType, @"twitter"); +} + +- (void)testAuthenticateAsync { + PF_Twitter *twitter = [self mockedTwitter]; + + OCMStub(twitter.userId).andReturn(@"a"); + OCMStub(twitter.screenName).andReturn(@"b"); + OCMStub(twitter.authToken).andReturn(@"c"); + OCMStub(twitter.authTokenSecret).andReturn(@"d"); + + BFTask *task = [BFTask taskWithResult:nil]; + OCMStub([twitter authorizeInBackground]).andReturn(task); + + PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[provider authenticateAsync] continueWithBlock:^id(BFTask *t) { + NSDictionary *authData = t.result; + XCTAssertNotNil(authData); + [self assertValidAuthenticationData:authData forTwitter:twitter]; + + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testRestoreAuthentication { + PF_Twitter *twitter = [self mockedTwitter]; + OCMExpect(twitter.userId = @"a"); + OCMExpect(twitter.screenName = @"b"); + OCMExpect(twitter.authToken = @"c"); + OCMExpect(twitter.authTokenSecret = @"d"); + + PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; + + NSDictionary *authData = @{ @"id" : @"a", + @"screen_name" : @"b", + @"auth_token" : @"c", + @"auth_token_secret" : @"d" }; + XCTAssertTrue([provider restoreAuthenticationWithAuthData:authData]); + + OCMVerifyAll((id)twitter); +} + +- (void)testRestoreAuthenticationBadData { + PF_Twitter *twitter = [self mockedTwitter]; + PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; + + NSDictionary *authData = @{ @"bad" : @"data" }; + XCTAssertFalse([provider restoreAuthenticationWithAuthData:authData]); +} + +- (void)testRestoreAuthenticationWithNoData { + PF_Twitter *twitter = [self mockedTwitter]; + OCMExpect(twitter.userId = nil); + OCMExpect(twitter.authToken = nil); + OCMExpect(twitter.authTokenSecret = nil); + OCMExpect(twitter.screenName = nil); + + PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; + XCTAssertTrue([provider restoreAuthenticationWithAuthData:nil]); + + OCMVerifyAll((id)twitter); +} + +@end diff --git a/ParseTwitterUtils/Tests/Unit/TwitterTests.m b/ParseTwitterUtils/Tests/Unit/TwitterTests.m new file mode 100644 index 000000000..8e81b81c7 --- /dev/null +++ b/ParseTwitterUtils/Tests/Unit/TwitterTests.m @@ -0,0 +1,684 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +@import Accounts; +@import Bolts.BFTask; +@import Parse.PFConstants; +@import Social; + +#import "PFOAuth1FlowDialog.h" +#import "PFTwitterAlertView.h" +#import "PFTwitterTestCase.h" +#import "PFTwitterTestMacros.h" +#import "PF_Twitter_Private.h" + +typedef void (^NSURLSessionDataTaskCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error); + +@interface TwitterTests : PFTwitterTestCase +@end + +@implementation TwitterTests + +///-------------------------------------- +#pragma mark - Tests +///-------------------------------------- + +- (void)testConstructors { + PF_Twitter *twitter = [[PF_Twitter alloc] init]; + XCTAssertNotNil(twitter); + XCTAssertNotNil(twitter.accountStore); + + ACAccountStore *store = PFStrictClassMock([ACAccountStore class]); + NSURLSession *session = PFStrictClassMock([NSURLSession class]); + id dialogClass = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:dialogClass]; + XCTAssertNotNil(twitter); + XCTAssertEqual(twitter.accountStore, store); + XCTAssertEqual(twitter.urlSession, session); + XCTAssertEqual(twitter.oauthDialogClass, dialogClass); +} + +- (void)testProperties { + PF_Twitter *twitter = [[PF_Twitter alloc] init]; + + XCTAssertNil(twitter.consumerKey); + XCTAssertNil(twitter.consumerKey); + XCTAssertNil(twitter.consumerSecret); + XCTAssertNil(twitter.authToken); + XCTAssertNil(twitter.authTokenSecret); + XCTAssertNil(twitter.userId); + XCTAssertNil(twitter.screenName); + + twitter.consumerKey = @"a"; + XCTAssertEqualObjects(twitter.consumerKey, @"a"); + twitter.consumerSecret = @"b"; + XCTAssertEqualObjects(twitter.consumerSecret, @"b"); + twitter.authToken = @"c"; + XCTAssertEqualObjects(twitter.authToken, @"c"); + twitter.authTokenSecret = @"d"; + XCTAssertEqualObjects(twitter.authTokenSecret, @"d"); + twitter.userId = @"e"; + XCTAssertEqualObjects(twitter.userId, @"e"); + twitter.screenName = @"f"; + XCTAssertEqualObjects(twitter.screenName, @"f"); +} + +- (void)testAuthorizeWithoutRequiredKeys { + id store = PFStrictClassMock([ACAccountStore class]); + NSURLSession *session = PFStrictClassMock([NSURLSession class]); + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + NSError *error = task.error; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + //TODO: (nlutsenko) Add code verification when we have proper code reported. + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testSignRequest { + id store = PFStrictClassMock([ACAccountStore class]); + NSURLSession *session = PFStrictClassMock([NSURLSession class]); + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; + [twitter signRequest:request]; + + XCTAssertNotNil([request valueForHTTPHeaderField:@"Authorization"]); +} + +- (void)testAuthorizeWithCallbackBlocks { + id store = PFStrictClassMock([ACAccountStore class]); + NSURLSession *session = PFStrictClassMock([NSURLSession class]); + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [twitter authorizeWithSuccess:^{ + XCTFail(@"Did not expect success!"); + } failure:^(NSError *error) { + [expectation fulfill]; + } cancel:^{ + XCTFail(@"Did not expect cancellation!"); + }]; + + [self waitForTestExpectations]; +} + +- (void)testAuthorizeWithLocalAccountErrorAndNetworkError { + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedURLSession = PFStrictClassMock([NSURLSession class]); + id mockedOperationQueue = PFStrictClassMock([NSOperationQueue class]); + id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); + + NSError *expectedError = [NSError errorWithDomain:PFParseErrorDomain code:1337 userInfo:nil]; + + OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); + OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); + + OCMStub([mockedStore requestAccessToAccountsWithType:nil + options:nil + completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + handler(NO, expectedError); + }); + + [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"request_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + completionHandler(nil, nil, expectedError); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore + urlSession:mockedURLSession + dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.error, expectedError); + + [expectation fulfill]; + + return nil; + }]; + + [self waitForTestExpectations]; + OCMVerifyAll(mockedOperationQueue); +} + +- (void)testAuthorizeWithoutLocalAccountAndNetworkError { + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedURLSession = PFStrictClassMock([NSURLSession class]); + id mockedOperationQueue = PFStrictClassMock([NSOperationQueue class]); + + NSError *expectedError = [NSError errorWithDomain:PFParseErrorDomain code:1337 userInfo:nil]; + + [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"request_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + completionHandler(nil, nil, expectedError); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore + urlSession:mockedURLSession + dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.error, expectedError); + XCTAssertNil(task.result); + + [expectation fulfill]; + + return nil; + }]; + + [self waitForTestExpectations]; + OCMVerifyAll(mockedOperationQueue); +} + +- (void)testAuthorizeWithLocalAccountAndNetworkError { + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedURLSession = PFStrictClassMock([NSURLSession class]); + id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); + + OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); + OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); + + OCMStub([mockedStore requestAccessToAccountsWithType:nil + options:nil + completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + handler(YES, nil); + }); + + id mockedAccount = PFStrictClassMock([ACAccount class]); + + NSArray *twitterAccounts = @[ mockedAccount ]; + OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); + + NSError *expectedError = [NSError errorWithDomain:PFParseErrorDomain code:1337 userInfo:nil]; + + __block NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [OCMStub([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"request_token"]; + }] completionHandler:[OCMArg checkWithBlock:^BOOL(id obj) { + completionHandler = obj; + return (obj != nil); + }]]).andDo(^(NSInvocation *invocation) { + completionHandler(nil, nil, expectedError); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore + urlSession:mockedURLSession + dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + XCTAssertEqualObjects(task.error, expectedError); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testAuthorizeWithSingleLocalAccountAndNetworkSuccess { + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedURLSession = PFStrictClassMock([NSURLSession class]); + id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); + id mockedSLRequest = PFStrictClassMock([SLRequest class]); + + OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); + OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); + + OCMStub([mockedStore requestAccessToAccountsWithType:nil + options:nil + completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + handler(YES, nil); + }); + + id mockedAccount = PFStrictClassMock([ACAccount class]); + OCMStub([mockedAccount accountType]).andReturn(nil); + + NSArray *twitterAccounts = @[ mockedAccount ]; + OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); + + [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"request_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; + completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + __weak typeof(mockedSLRequest) weakSLRequest = mockedSLRequest; + OCMStub(ClassMethod([[mockedSLRequest ignoringNonObjectArgs] requestForServiceType:SLServiceTypeTwitter + requestMethod:0 + URL:OCMOCK_ANY + parameters:OCMOCK_ANY])) + .andDo(^(NSInvocation *invocation) { + __strong typeof(mockedSLRequest) slRequest = weakSLRequest; + [invocation setReturnValue:&slRequest]; + }); + + OCMStub([mockedSLRequest setAccount:OCMOCK_ANY]); + OCMStub([mockedSLRequest performRequestWithHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained SLRequestHandler requestHandler = nil; + [invocation getArgument:&requestHandler atIndex:2]; + + NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" + @"screen_name=test_name"; + requestHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }); + + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore + urlSession:mockedURLSession + dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + XCTAssertNil(task.error); + XCTAssertNil(task.result); + + XCTAssertEqualObjects(@"access_token", twitter.authToken); + XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); + XCTAssertEqualObjects(@"test_user", twitter.userId); + XCTAssertEqualObjects(@"test_name", twitter.screenName); + + [expectation fulfill]; + + return nil; + }]; + + [self waitForTestExpectations]; +} + +- (void)testAuthorizeWithMultipleLocalAccountsAndNetworkSuccess { + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedSession = PFStrictClassMock([NSURLSession class]); + id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); + id mockedSLRequest = PFStrictClassMock([SLRequest class]); + id mockedAlertView = PFStrictClassMock([PFTwitterAlertView class]); + + OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); + OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); + + OCMStub([mockedStore requestAccessToAccountsWithType:nil + options:nil + completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + handler(YES, nil); + }); + + id mockedAccount = PFStrictClassMock([ACAccount class]); + OCMStub([mockedAccount accountType]).andReturn(nil); + OCMStub([mockedAccount valueForKey:@"accountDescription"]).andReturn(@"An Account"); + + NSArray *twitterAccounts = @[ mockedAccount, mockedAccount ]; + OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); + + [OCMExpect([mockedSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"request_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + + NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; + completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + __weak typeof(mockedSLRequest) weakSLRequest = mockedSLRequest; + OCMStub(ClassMethod([[mockedSLRequest ignoringNonObjectArgs] requestForServiceType:SLServiceTypeTwitter + requestMethod:0 + URL:OCMOCK_ANY + parameters:OCMOCK_ANY])) + .andDo(^(NSInvocation *invocation) { + __strong typeof(mockedSLRequest) slRequest = weakSLRequest; + [invocation setReturnValue:&slRequest]; + }); + + OCMStub([mockedSLRequest setAccount:OCMOCK_ANY]); + OCMStub([mockedSLRequest performRequestWithHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained SLRequestHandler requestHandler = nil; + [invocation getArgument:&requestHandler atIndex:2]; + + NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" + @"screen_name=test_name"; + requestHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }); + + OCMStub(ClassMethod([mockedAlertView showAlertWithTitle:OCMOCK_ANY + message:nil + cancelButtonTitle:@"Cancel" + otherButtonTitles:[OCMArg checkWithBlock:^BOOL(id obj) { + return [obj count] == 2; + }] + completion:OCMOCK_ANY])).andDo(^(NSInvocation *invocation) { + __unsafe_unretained PFTwitterAlertViewCompletion completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:6]; + + completionHandler(0); + }); + + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore + urlSession:mockedSession + dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + XCTAssertNil(task.error); + XCTAssertNil(task.result); + + XCTAssertEqualObjects(@"access_token", twitter.authToken); + XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); + XCTAssertEqualObjects(@"test_user", twitter.userId); + XCTAssertEqualObjects(@"test_name", twitter.screenName); + + [expectation fulfill]; + + return nil; + }]; + + [self waitForTestExpectations]; +} + +- (void)testAuthorizeWithZeroLocalAccountsAndNetworkSuccess { + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedURLSession = PFStrictClassMock([NSURLSession class]); + id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); + id mockedSLRequest = PFStrictClassMock([SLRequest class]); + + OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); + OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); + + OCMStub([mockedStore requestAccessToAccountsWithType:nil + options:nil + completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; + [invocation getArgument:&handler atIndex:4]; + + handler(YES, nil); + }); + + id mockedAccount = PFStrictClassMock([ACAccount class]); + OCMStub([mockedAccount accountType]).andReturn(nil); + + NSArray *twitterAccounts = @[]; + OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); + + __weak typeof(mockedSLRequest) weakSLRequest = mockedSLRequest; + OCMStub(ClassMethod([[mockedSLRequest ignoringNonObjectArgs] requestForServiceType:SLServiceTypeTwitter + requestMethod:0 + URL:OCMOCK_ANY + parameters:OCMOCK_ANY])) + .andDo(^(NSInvocation *invocation) { + __strong typeof(mockedSLRequest) slRequest = weakSLRequest; + [invocation setReturnValue:&slRequest]; + }); + + [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"request_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + + NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; + completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"access_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + + NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" + @"screen_name=test_name"; + completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + OCMExpect([mockedDialog dialogWithURL:OCMOCK_ANY queryParameters:OCMOCK_ANY]).andReturnWeak(mockedDialog); + OCMExpect([mockedDialog setRedirectURLPrefix:@"http://twitter-oauth.callback"]); + + __block PFOAuth1FlowDialogCompletion completionHandler = nil; + OCMExpect([mockedDialog setCompletion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained PFOAuth1FlowDialogCompletion newHandler = nil; + [invocation getArgument:&newHandler atIndex:2]; + + completionHandler = [newHandler copy]; + }); + + OCMExpect([[mockedDialog ignoringNonObjectArgs] showAnimated:NO]).andDo(^(NSInvocation *invocation) { + completionHandler( + YES, + [NSURL URLWithString:@"http://twitter-oauth.callback/?oauth_token=sucess_token&oauth_token_secret=success_secret"], + nil + ); + }); + + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore + urlSession:mockedURLSession + dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + XCTAssertNil(task.error); + XCTAssertNil(task.result); + + XCTAssertEqualObjects(@"access_token", twitter.authToken); + XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); + XCTAssertEqualObjects(@"test_user", twitter.userId); + XCTAssertEqualObjects(@"test_name", twitter.screenName); + + [expectation fulfill]; + + return nil; + }]; + + [self waitForTestExpectations]; + + OCMVerifyAll(mockedDialog); + OCMVerifyAll(mockedURLSession); +} + +- (void)testAuthorizeWithoutLocalAccountAndNetworkSuccess { + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedURLSession = PFStrictClassMock([NSURLSession class]); + + [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"request_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + + NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; + completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"access_token"]; + }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [invocation getArgument:&completionHandler atIndex:3]; + + + NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" + @"screen_name=test_name"; + completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + OCMExpect([mockedDialog dialogWithURL:OCMOCK_ANY queryParameters:OCMOCK_ANY]).andReturnWeak(mockedDialog); + OCMExpect([mockedDialog setRedirectURLPrefix:@"http://twitter-oauth.callback"]); + + __block PFOAuth1FlowDialogCompletion completionHandler = nil; + OCMExpect([mockedDialog setCompletion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained PFOAuth1FlowDialogCompletion newHandler = nil; + [invocation getArgument:&newHandler atIndex:2]; + + completionHandler = [newHandler copy]; + }); + + OCMExpect([[mockedDialog ignoringNonObjectArgs] showAnimated:NO]).andDo(^(NSInvocation *invocation) { + completionHandler( + YES, + [NSURL URLWithString:@"http://twitter-oauth.callback/?oauth_token=sucess_token&oauth_token_secret=success_secret"], + nil + ); + }); + + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore + urlSession:mockedURLSession + dialogClass:mockedDialog]; + + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + XCTAssertNil(task.error); + XCTAssertNil(task.result); + + XCTAssertEqualObjects(@"access_token", twitter.authToken); + XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); + XCTAssertEqualObjects(@"test_user", twitter.userId); + XCTAssertEqualObjects(@"test_name", twitter.screenName); + + [expectation fulfill]; + + return nil; + }]; + + [self waitForTestExpectations]; + + OCMVerifyAll(mockedDialog); + OCMVerifyAll(mockedURLSession); +} + +- (void)testDeauthorizeLoggedOutAccount { + id store = PFStrictClassMock([ACAccountStore class]); + NSURLSession *session = PFStrictClassMock([NSURLSession class]); + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { + NSError *error = task.error; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqual(error.code, 1); + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +- (void)testDeauthorizeLoggedInAccount { + id mockedStore = PFStrictClassMock([ACAccountStore class]); + id mockedURLSession = PFStrictClassMock([NSURLSession class]); + + id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); + PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore urlSession:mockedURLSession dialogClass:mockedDialog]; + twitter.consumerKey = @"consumer_key"; + twitter.consumerSecret = @"consumer_secret"; + twitter.authToken = @"auth_token"; + twitter.authTokenSecret = @"auth_token_secret"; + twitter.userId = @"user_id"; + twitter.screenName = @"screen_name"; + + __block NSURLSessionDataTaskCompletionHandler completionHandler = nil; + [OCMStub([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { + NSURLRequest *request = obj; + return [request.URL.lastPathComponent isEqualToString:@"invalidate_token"]; + }] completionHandler:[OCMArg checkWithBlock:^BOOL(id obj) { + completionHandler = obj; + return (obj != nil); + }]]).andDo(^(NSInvocation *invocation) { + completionHandler([NSData data], nil, nil); + }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; + + XCTestExpectation *expectation = [self currentSelectorTestExpectation]; + [[twitter deauthorizeInBackground] continueWithBlock:^id(BFTask *task) { + NSError *error = task.error; + XCTAssertNil(error); + XCTAssertNotNil(task.result); + + XCTAssertNil(twitter.authToken); + XCTAssertNil(twitter.authTokenSecret); + XCTAssertNil(twitter.userId); + XCTAssertNil(twitter.screenName); + + [expectation fulfill]; + return nil; + }]; + [self waitForTestExpectations]; +} + +@end diff --git a/ParseTwitterUtils/Tests/Unit/TwitterUtilsTests.m b/ParseTwitterUtils/Tests/Unit/TwitterUtilsTests.m new file mode 100644 index 000000000..cdcba2eb9 --- /dev/null +++ b/ParseTwitterUtils/Tests/Unit/TwitterUtilsTests.m @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFTwitterTestCase.h" +#import "PFTwitterUtils_Private.h" +#import "PF_Twitter.h" + +@import Parse; + +///-------------------------------------- +#pragma mark - Tests +///-------------------------------------- + +@interface TwitterUtilsTests : PFTwitterTestCase + +@end + +@implementation TwitterUtilsTests + +///-------------------------------------- +#pragma mark - XCTestCase +///-------------------------------------- + +- (void)tearDown { + [PFTwitterUtils _setAuthenticationProvider:nil]; + + [super tearDown]; +} + +///-------------------------------------- +#pragma mark - Tests +///-------------------------------------- + +- (void)testInitialize { + id parseMock = PFStrictClassMock([Parse class]); + OCMStub([parseMock getApplicationId]).andReturn(@"yolo"); + OCMStub([parseMock getClientKey]).andReturn(@"yarr"); + + id userMock = PFStrictClassMock([PFUser class]); + OCMExpect(ClassMethod([userMock registerAuthenticationDelegate:[OCMArg checkWithBlock:^BOOL(id obj) { + return (obj != nil); + }] forAuthType:@"twitter"])); + + [PFTwitterUtils initializeWithConsumerKey:@"a" consumerSecret:@"b"]; + XCTAssertNotNil([PFTwitterUtils twitter]); + XCTAssertEqualObjects([PFTwitterUtils twitter].consumerKey, @"a"); + XCTAssertEqualObjects([PFTwitterUtils twitter].consumerSecret, @"b"); + + OCMVerifyAll(userMock); +} + +- (void)testInitializeTwice { + id parseMock = PFStrictClassMock([Parse class]); + OCMStub([parseMock getApplicationId]).andReturn(@"yolo"); + OCMStub([parseMock getClientKey]).andReturn(@"yarr"); + + id userMock = PFStrictClassMock([PFUser class]); + + [PFTwitterUtils initializeWithConsumerKey:@"a" consumerSecret:@"b"]; + XCTAssertNotNil([PFTwitterUtils twitter]); + XCTAssertEqualObjects([PFTwitterUtils twitter].consumerKey, @"a"); + XCTAssertEqualObjects([PFTwitterUtils twitter].consumerSecret, @"b"); + + [PFTwitterUtils initializeWithConsumerKey:@"b" consumerSecret:@"c"]; + XCTAssertNotNil([PFTwitterUtils twitter]); + XCTAssertEqualObjects([PFTwitterUtils twitter].consumerKey, @"a"); + XCTAssertEqualObjects([PFTwitterUtils twitter].consumerSecret, @"b"); + + OCMVerifyAll(userMock); +} + +- (void)testInitializeRequiresParseInitialize { + PFAssertThrowsInconsistencyException([PFTwitterUtils initializeWithConsumerKey:@"a" consumerSecret:@"b"]); +} + +@end diff --git a/ParseTwitterUtils/third_party_licenses.txt b/ParseTwitterUtils/third_party_licenses.txt new file mode 100644 index 000000000..492c44335 --- /dev/null +++ b/ParseTwitterUtils/third_party_licenses.txt @@ -0,0 +1,35 @@ +THE FOLLOWING SETS FORTH ATTRIBUTION NOTICES FOR THIRD PARTY SOFTWARE THAT MAY BE CONTAINED IN PORTIONS OF THE PARSE PRODUCT. + +----- + +The following software may be included in this product: OAuthCore. This software contains the following license and notice below: + +Copyright (C) 2012 Loren Brichter + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------- + +Copyright 2001-2004 Unicode, Inc. + +Disclaimer + +This source code is provided as is by Unicode, Inc. No claims are +made as to fitness for any particular purpose. No warranties of any +kind are expressed or implied. The recipient agrees to determine +applicability of information provided. If this file has been +purchased on magnetic or optical media from Unicode, Inc., the +sole remedy for any claim will be exchange of defective media +within 90 days of receipt. + +Limitations on Rights to Redistribute This Code + +Unicode, Inc. hereby grants the right to freely use the information +supplied in this file in the creation of products supporting the +Unicode Standard, and to make copies of this file in any form +for internal or external distribution as long as this notice +remains attached. diff --git a/ParseUI/Classes/Cells/PFCollectionViewCell.h b/ParseUI/Classes/Cells/PFCollectionViewCell.h new file mode 100644 index 000000000..ed0e262d9 --- /dev/null +++ b/ParseUI/Classes/Cells/PFCollectionViewCell.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#else +#import +#endif + + +NS_ASSUME_NONNULL_BEGIN + +@class PFImageView; +@class PFObject; + +/** + The `PFCollectionViewCell` class represents a collection view cell which can + download and display remote images stored on Parse as well as has a default simple text label. + */ +@interface PFCollectionViewCell : UICollectionViewCell + +/** + A simple lazy-loaded label for the collection view cell. + */ +@property (nonatomic, strong, readonly) UILabel *textLabel; + +/** + The lazy-loaded imageView of the collection view cell. + + @see PFImageView + */ +@property (nonatomic, strong, readonly) PFImageView *imageView; + +/** + This method should update all the relevant information inside a subclass of `PFCollectionViewCell`. + + This method is automatically called by `PFQueryCollectionViewController` whenever the cell + should display new information. By default this method does nothing. + + @param object An instance of `PFObject` to update from. + */ +- (void)updateFromObject:(nullable PFObject *)object; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/Cells/PFCollectionViewCell.m b/ParseUI/Classes/Cells/PFCollectionViewCell.m new file mode 100644 index 000000000..6fb1f466a --- /dev/null +++ b/ParseUI/Classes/Cells/PFCollectionViewCell.m @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFCollectionViewCell.h" + +#import "PFImageView.h" +#import "PFRect.h" + +@implementation PFCollectionViewCell + +@synthesize imageView = _imageView; +@synthesize textLabel = _textLabel; + +#pragma mark - +#pragma mark UIView + +- (void)layoutSubviews { + [super layoutSubviews]; + + const CGRect bounds = self.contentView.bounds; + + CGRect imageViewFrame = CGRectZero; + if (_imageView && _imageView.image){ + imageViewFrame = PFRectMakeWithSizeCenteredInRect(PFSizeMin(_imageView.image.size, bounds.size), + bounds); + } + CGRect textLabelFrame = CGRectZero; + if (_textLabel) { + CGSize maxImageViewSize = CGSizeMake(CGRectGetWidth(bounds), CGRectGetHeight(bounds) / 3.0f * 2.0f); + CGSize imageViewSize = PFSizeMin(imageViewFrame.size, maxImageViewSize); + + imageViewFrame = PFRectMakeWithSizeCenteredInRect(imageViewSize, PFRectMakeWithSize(maxImageViewSize)); + CGFloat textLabelTopInset = (CGRectIsEmpty(imageViewFrame) ? 0.0f : CGRectGetMaxY(imageViewFrame)); + + textLabelFrame = PFRectMakeWithOriginSize(CGPointMake(0.0f, textLabelTopInset), + CGSizeMake(CGRectGetWidth(bounds), CGRectGetHeight(bounds) - textLabelTopInset)); + } + + // Adapt content mode of _imageView to fit the image in bounds if the layout frame is smaller or center if it's bigger. + if (!CGRectIsEmpty(imageViewFrame)) { + if (CGRectContainsRect(PFRectMakeWithSize(_imageView.image.size), PFRectMakeWithSize(imageViewFrame.size))) { + _imageView.contentMode = UIViewContentModeScaleAspectFit; + } else { + _imageView.contentMode = UIViewContentModeCenter; + } + } + + _imageView.frame = CGRectIntegral(imageViewFrame); + _textLabel.frame = CGRectIntegral(textLabelFrame); +} + +#pragma mark - +#pragma mark Update + +- (void)updateFromObject:(PFObject *)object { + // Do nothing +} + +#pragma mark - +#pragma mark Accessors + +- (PFImageView *)imageView { + if (!_imageView) { + _imageView = [[PFImageView alloc] initWithFrame:CGRectZero]; + [self.contentView addSubview:_imageView]; + } + return _imageView; +} + +- (UILabel *)textLabel { + if (!_textLabel) { + _textLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + _textLabel.numberOfLines = 0; + [self.contentView addSubview:_textLabel]; + } + return _textLabel; +} + +@end diff --git a/ParseUI/Classes/Cells/PFPurchaseTableViewCell.h b/ParseUI/Classes/Cells/PFPurchaseTableViewCell.h new file mode 100644 index 000000000..3e3ffa002 --- /dev/null +++ b/ParseUI/Classes/Cells/PFPurchaseTableViewCell.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#import "PFTableViewCell.h" +#else +#import +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + An enum that represents states of the `PFPurchaseTableViewCell`. + @see `PFPurchaseTableViewCell` + */ +typedef NS_ENUM(uint8_t, PFPurchaseTableViewCellState) { + /** Normal state of the cell. */ + PFPurchaseTableViewCellStateNormal = 0, + /** Downloading state of the cell. */ + PFPurchaseTableViewCellStateDownloading, + /** State of the cell, when the product was downloaded. */ + PFPurchaseTableViewCellStateDownloaded +}; + +/** + `PFPurchaseTableViewCell` is a subclass `PFTableViewCell` that is used to show + products in a `PFProductTableViewController`. + + @see `PFProductTableViewController` + */ +@interface PFPurchaseTableViewCell : PFTableViewCell + +/** + State of the cell. + @see `PFPurchaseTableViewCellState` + */ +@property (nonatomic, assign) PFPurchaseTableViewCellState state; + +/** + Label where price of the product is displayed. + */ +@property (nullable, nonatomic, strong, readonly) UILabel *priceLabel; + +/** + Progress view that is shown, when the product is downloading. + */ +@property (nullable, nonatomic, strong, readonly) UIProgressView *progressView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/Cells/PFPurchaseTableViewCell.m b/ParseUI/Classes/Cells/PFPurchaseTableViewCell.m new file mode 100644 index 000000000..6ff164cf7 --- /dev/null +++ b/ParseUI/Classes/Cells/PFPurchaseTableViewCell.m @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFPurchaseTableViewCell.h" + +#import "PFLocalization.h" +#import "PFRect.h" + +@interface PFPurchaseTableViewCell () + +@property (nonatomic, strong) UILabel *priceLabel; +@property (nonatomic, strong) UIProgressView *progressView; + +@end + +@implementation PFPurchaseTableViewCell + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + self.backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; + + self.imageView.layer.shadowColor = [UIColor blackColor].CGColor; + self.imageView.layer.shadowOffset = CGSizeMake(0.0f, 1.0f); + self.imageView.layer.shadowRadius = 1.0f; + self.imageView.layer.shadowOpacity = 1.0f; + + self.textLabel.backgroundColor = [UIColor clearColor]; + self.detailTextLabel.backgroundColor = [UIColor clearColor]; + self.detailTextLabel.numberOfLines = 2; + self.detailTextLabel.font = [UIFont systemFontOfSize:12.0f]; + + self.priceLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + self.priceLabel.backgroundColor = [UIColor colorWithWhite:242.0f/255.0f alpha:1.0f]; + self.priceLabel.textColor = [UIColor grayColor]; + self.priceLabel.shadowColor = [UIColor whiteColor]; + self.priceLabel.shadowOffset = CGSizeMake(0.0f, -1.0f); + self.priceLabel.font = [UIFont boldSystemFontOfSize:12.0f]; + self.priceLabel.layer.borderColor = [UIColor grayColor].CGColor; + self.priceLabel.layer.borderWidth = 1.0f; + self.priceLabel.layer.cornerRadius = 3.0f; + self.priceLabel.lineBreakMode = NSLineBreakByWordWrapping; + self.priceLabel.numberOfLines = 0; + [self.contentView addSubview:self.priceLabel]; + + self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + self.state = PFPurchaseTableViewCellStateNormal; + } + return self; +} +#pragma mark - +#pragma mark UIView + +- (void)layoutSubviews { + [super layoutSubviews]; + + const CGRect bounds = self.contentView.bounds; + + CGFloat iconWidth = floorf(0.8f * CGRectGetHeight(bounds)); + CGFloat iconMarginY = floorf((CGRectGetHeight(bounds) - iconWidth)/2.0f); + CGFloat iconMarginX = iconMarginY; + CGFloat x = iconMarginX; + CGFloat y = iconMarginY; + self.imageView.frame = CGRectMake(x, y, iconWidth, iconWidth); + x += self.imageView.frame.size.width + iconMarginX; + + self.priceLabel.frame = CGRectZero; // this is necessary for sizeToFit to work correctly + [self.priceLabel sizeToFit]; + CGFloat priceLabelRightInset = 10.0f; + CGFloat priceLabelX = CGRectGetWidth(bounds) - CGRectGetWidth(self.priceLabel.frame) - priceLabelRightInset; + CGFloat priceLabelY = floorf((CGRectGetHeight(self.textLabel.frame) - CGRectGetHeight(self.priceLabel.frame))/2.0f) + iconMarginY; + + self.priceLabel.frame = PFRectMakeWithOriginSize(CGPointMake(priceLabelX, priceLabelY), self.priceLabel.frame.size); + + CGFloat titleWidth = self.contentView.frame.size.width - self.imageView.frame.size.width - iconMarginX - 100.0f; + CGFloat titleHeight = self.textLabel.frame.size.height; + self.textLabel.frame = CGRectMake(x, y, titleWidth, titleHeight); + + CGFloat textMarginBottom = 5.0f; + y += self.textLabel.frame.size.height + textMarginBottom; + + CGFloat detailTextLabelWidth = CGRectGetWidth(bounds) - x - 50.0f; + self.detailTextLabel.frame = CGRectMake(x, y, detailTextLabelWidth, CGRectGetWidth(self.detailTextLabel.frame)); + self.progressView.frame = CGRectMake(x, CGRectGetHeight(bounds) - CGRectGetHeight(self.progressView.frame) - iconMarginY - 2.0f, + detailTextLabelWidth, CGRectGetHeight(self.progressView.frame)); +} + +#pragma mark - +#pragma mark PFPurchaseTableViewCell + +- (void)setState:(PFPurchaseTableViewCellState)state { + if (self.state == state) { + return; + } + + _state = state; + + switch (state) { + case PFPurchaseTableViewCellStateNormal: + { + self.detailTextLabel.numberOfLines = 2; + } + break; + case PFPurchaseTableViewCellStateDownloading: + { + self.detailTextLabel.numberOfLines = 1; + self.priceLabel.backgroundColor = [UIColor colorWithRed:132.0f/255.0f green:175.0f/255.0f blue:230.0f/255.0f alpha:1.0f]; + NSString *downloadingText = PFLocalizedString(@"DOWNLOADING", @"DOWNLOADING"); + self.priceLabel.text = [NSString stringWithFormat:@" %@ ", downloadingText]; + self.priceLabel.textColor = [UIColor whiteColor]; + self.priceLabel.shadowColor = [UIColor blackColor]; + self.priceLabel.shadowOffset = CGSizeMake(0.0f, -1.0f); + [self.contentView addSubview:self.progressView]; + } + break; + case PFPurchaseTableViewCellStateDownloaded: + { + self.detailTextLabel.numberOfLines = 2; + NSString *installedText = PFLocalizedString(@"INSTALLED", @"INSTALLED"); + self.priceLabel.text = [NSString stringWithFormat:@" %@ ", installedText]; + self.priceLabel.textColor = [UIColor whiteColor]; + self.priceLabel.shadowColor = [UIColor blackColor]; + self.priceLabel.shadowOffset = CGSizeMake(0.0f, -1.0f); + self.priceLabel.backgroundColor = [UIColor colorWithRed:160.0f/255.0f green:200.0f/255.0f blue:120.0f/255.0f alpha:1.0f]; + [self.progressView removeFromSuperview]; + } + break; + default: + break; + } + [self setNeedsLayout]; +} + +@end diff --git a/ParseUI/Classes/Cells/PFTableViewCell.h b/ParseUI/Classes/Cells/PFTableViewCell.h new file mode 100644 index 000000000..a4226dc19 --- /dev/null +++ b/ParseUI/Classes/Cells/PFTableViewCell.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#import "PFImageView.h" +#else +#import +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + The `PFTableViewCell` class represents a table view cell which can download and display remote images stored on Parse. + + When used in a `PFQueryTableViewController` - downloading and + displaying of the remote images are automatically managed by the controller. + */ +@interface PFTableViewCell : UITableViewCell + +/** + The imageView of the table view cell. + + @see `PFImageView` + */ +@property (nullable, nonatomic, strong, readonly) PFImageView *imageView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/Cells/PFTableViewCell.m b/ParseUI/Classes/Cells/PFTableViewCell.m new file mode 100644 index 000000000..65f67cddb --- /dev/null +++ b/ParseUI/Classes/Cells/PFTableViewCell.m @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFTableViewCell.h" + +#import "PFRect.h" + +@interface PFTableViewCell () + +@property (nonatomic, assign) UITableViewCellStyle style; +@property (nonatomic, strong) PFImageView *customImageView; + +@end + +@implementation PFTableViewCell + +#pragma mark - +#pragma mark NSObject + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + _style = style; + + _customImageView = [[PFImageView alloc] initWithFrame:CGRectZero]; + [self.contentView addSubview:_customImageView]; + } + return self; +} + +#pragma mark - +#pragma mark UIView + +- (void)layoutSubviews { + [super layoutSubviews]; + + // We cannot depend on the parent class to lay out things perfectly because + // UITableViewCell layoutSubviews use its internal imageView member rather than via + // its self.imageView property, so we need to lay out things manually + + // Don't relayout anything if there is no file/image + if (!self.imageView.file && !self.imageView.image) { + return; + } + + // Value2 ignores imageView entirely + if (self.style == UITableViewCellStyleValue2) { + return; + } + + const CGRect bounds = self.contentView.bounds; + + CGFloat imageHeight = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)); + CGFloat imageWidth = floorf(13.0f * imageHeight / 9.0f); // Default is 13/9 aspect ratio + _customImageView.frame = PFRectMakeWithSize(CGSizeMake(imageWidth, imageHeight)); + + CGFloat imageViewRightInset = 10.0f; + CGFloat textOrigin = CGRectGetMaxX(_customImageView.frame) + imageViewRightInset; + + CGRect textLabelFrame = self.textLabel.frame; + CGRect detailTextLabelFrame = self.detailTextLabel.frame; + + switch (self.style) { + case UITableViewCellStyleDefault: + case UITableViewCellStyleSubtitle: + { + CGFloat originalTextLabelInset = CGRectGetMinX(textLabelFrame); + CGFloat originalDetailTextLabelInset = CGRectGetMinX(detailTextLabelFrame); + + CGFloat maxTextLabelWidth = CGRectGetMaxX(bounds) - textOrigin - originalTextLabelInset; + CGFloat maxDetailTextLabelWidth = CGRectGetMaxX(bounds) - textOrigin - originalDetailTextLabelInset; + + textLabelFrame.origin.x = textOrigin; + textLabelFrame.size.width = MIN(maxTextLabelWidth, CGRectGetWidth(textLabelFrame)); + + detailTextLabelFrame.origin.x = textOrigin; + detailTextLabelFrame.size.width = MIN(maxDetailTextLabelWidth, CGRectGetWidth(detailTextLabelFrame)); + } + break; + case UITableViewCellStyleValue1: + { + CGFloat maxTextLabelWidth = CGRectGetMinX(detailTextLabelFrame) - textOrigin; + + textLabelFrame.origin.x = textOrigin; + textLabelFrame.size.width = MIN(maxTextLabelWidth, CGRectGetWidth(textLabelFrame)); + } + break; + default: + break; + } + self.textLabel.frame = textLabelFrame; + self.detailTextLabel.frame = detailTextLabelFrame; +} + +#pragma mark - +#pragma mark PFImageTableViewCell + +- (PFImageView *)imageView { + return _customImageView; +} + +@end diff --git a/ParseUI/Classes/Internal/Cells/PFActivityIndicatorCollectionReusableView.h b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorCollectionReusableView.h new file mode 100644 index 000000000..44004498b --- /dev/null +++ b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorCollectionReusableView.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +/** + The `PFActivityIndicatorCollectionReusableView` class represents a collection footer + that has a simple text label and displays UIActivityIndicatorView if property is set to `YES`. + An instance of this class is used as a default next page button inside . + */ +@interface PFActivityIndicatorCollectionReusableView : UICollectionReusableView + +@property (nonatomic, strong, readonly) UILabel *textLabel; + +@property (nonatomic, assign, getter=isAnimating) BOOL animating; + +- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents; +- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents; + +@end diff --git a/ParseUI/Classes/Internal/Cells/PFActivityIndicatorCollectionReusableView.m b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorCollectionReusableView.m new file mode 100644 index 000000000..3385edd81 --- /dev/null +++ b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorCollectionReusableView.m @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFActivityIndicatorCollectionReusableView.h" + +#import "PFRect.h" + +@interface PFActivityIndicatorCollectionReusableView () { + UIActivityIndicatorView *_activityIndicator; + UIButton *_actionButton; +} + +@end + +@implementation PFActivityIndicatorCollectionReusableView + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (!self) return nil; + + _actionButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _actionButton.backgroundColor = self.backgroundColor; + [self addSubview:_actionButton]; + + _textLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + _textLabel.numberOfLines = 0; + _textLabel.textAlignment = NSTextAlignmentCenter; + [self addSubview:_textLabel]; + + _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + _activityIndicator.hidesWhenStopped = YES; + [self addSubview:_activityIndicator]; + + return self; +} + +#pragma mark - +#pragma mark Dealloc + +- (void)dealloc { + [self removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents]; +} + +#pragma mark - +#pragma mark UIView + +- (void)setBackgroundColor:(UIColor *)backgroundColor { + [super setBackgroundColor:backgroundColor]; + _actionButton.backgroundColor = backgroundColor; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + const CGRect bounds = self.bounds; + + _actionButton.frame = bounds; + + _textLabel.frame = PFRectMakeWithSizeCenteredInRect([_textLabel sizeThatFits:bounds.size], bounds); + _activityIndicator.frame = PFRectMakeWithSizeCenteredInRect([_activityIndicator sizeThatFits:bounds.size], bounds); +} + +#pragma mark - +#pragma mark Accessors + +- (void)setAnimating:(BOOL)animating { + if (self.animating != animating) { + + if (animating) { + [_activityIndicator startAnimating]; + _textLabel.alpha = 0.0f; + } else { + [_activityIndicator stopAnimating]; + _textLabel.alpha = 1.0f; + } + } +} + +- (BOOL)isAnimating { + return [_activityIndicator isAnimating]; +} + +#pragma mark - +#pragma mark Actions + +- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents { + [_actionButton addTarget:target action:action forControlEvents:controlEvents]; +} + +- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents { + [_actionButton removeTarget:target action:action forControlEvents:controlEvents]; +} + +@end diff --git a/ParseUI/Classes/Internal/Cells/PFActivityIndicatorTableViewCell.h b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorTableViewCell.h new file mode 100644 index 000000000..1578f1b5b --- /dev/null +++ b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorTableViewCell.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "PFTableViewCell.h" +#else +#import +#endif + +/** + The `PFActivityIndicatorTableViewCell` class represents a table view cell + that displays UIActivityIndicatorView as the accessory view. + */ +@interface PFActivityIndicatorTableViewCell : PFTableViewCell + +@property (nonatomic, assign, getter=isAnimating) BOOL animating; + +@end diff --git a/ParseUI/Classes/Internal/Cells/PFActivityIndicatorTableViewCell.m b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorTableViewCell.m new file mode 100644 index 000000000..b825bd15c --- /dev/null +++ b/ParseUI/Classes/Internal/Cells/PFActivityIndicatorTableViewCell.m @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFActivityIndicatorTableViewCell.h" + +@interface PFActivityIndicatorTableViewCell () + +@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator; + +@end + +@implementation PFActivityIndicatorTableViewCell + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier +{ + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + _activityIndicator.hidesWhenStopped = YES; + self.accessoryView = _activityIndicator; + } + return self; +} + +#pragma mark - +#pragma mark Accessors + +- (void)setAnimating:(BOOL)animating { + if (self.animating != animating) { + + if (animating) { + [_activityIndicator startAnimating]; + } else { + [_activityIndicator stopAnimating]; + } + } +} + +- (BOOL)isAnimating { + return [_activityIndicator isAnimating]; +} + +@end diff --git a/ParseUI/Classes/Internal/Extensions/PFColor.h b/ParseUI/Classes/Internal/Extensions/PFColor.h new file mode 100644 index 000000000..bcf4f1067 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFColor.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface PFColor : UIColor + +///-------------------------------------- +/// @name Common +///-------------------------------------- + ++ (UIColor *)commonBackgroundColor; + +///-------------------------------------- +/// @name TextFields +///-------------------------------------- + ++ (UIColor *)textFieldBackgroundColor; ++ (UIColor *)textFieldTextColor; ++ (UIColor *)textFieldPlaceholderColor; ++ (UIColor *)textFieldSeparatorColor; + +///-------------------------------------- +/// @name Buttons +///-------------------------------------- + ++ (UIColor *)loginButtonBackgroundColor; ++ (UIColor *)signupButtonBackgroundColor; ++ (UIColor *)facebookButtonBackgroundColor; ++ (UIColor *)twitterButtonBackgroundColor; + +@end diff --git a/ParseUI/Classes/Internal/Extensions/PFColor.m b/ParseUI/Classes/Internal/Extensions/PFColor.m new file mode 100644 index 000000000..467d9eec2 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFColor.m @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFColor.h" + +@implementation PFColor + +#pragma mark - +#pragma mark Common + ++ (UIColor *)commonBackgroundColor { + return [UIColor colorWithRed:249/255.0f + green:251.0f/255.0f + blue:1.0f + alpha:1.0f]; +} + +#pragma mark - +#pragma mark TextField + ++ (UIColor *)textFieldBackgroundColor { + return [UIColor whiteColor]; +} + ++ (UIColor *)textFieldTextColor { + return [UIColor blackColor]; +} + ++ (UIColor *)textFieldPlaceholderColor { + return [UIColor colorWithWhite:194.0f/255.0f alpha:1.0f]; +} + ++ (UIColor *)textFieldSeparatorColor { + return [UIColor colorWithWhite:227.0f/255.0f alpha:1.0f]; +} + +#pragma mark - +#pragma mark Buttons + ++ (UIColor *)loginButtonBackgroundColor { + return [UIColor colorWithRed:97.0f/255.0f + green:106.f/255.0f + blue:116.0f/255.0f + alpha:1.0f]; +} + ++ (UIColor *)signupButtonBackgroundColor { + return [UIColor colorWithRed:108.0f/255.0f + green:150.0f/255.0f + blue:249.0f/255.0f + alpha:1.0f]; +} + ++ (UIColor *)facebookButtonBackgroundColor { + return [UIColor colorWithRed:58.0f/255.0f + green:89.0f/255.0f + blue:152.0f/255.0f + alpha:1.0f]; +} + ++ (UIColor *)twitterButtonBackgroundColor { + return [UIColor colorWithRed:45.0f/255.0f + green:170.0f/255.0f + blue:1.0f + alpha:1.0f]; +} + +@end diff --git a/ParseUI/Classes/Internal/Extensions/PFImage.h b/ParseUI/Classes/Internal/Extensions/PFImage.h new file mode 100644 index 000000000..b7d522253 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFImage.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface PFImage : UIImage + ++ (UIImage *)imageWithColor:(UIColor *)color; ++ (UIImage *)imageWithColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius; + ++ (UIImage *)imageNamed:(NSString *)name; + +@end diff --git a/ParseUI/Classes/Internal/Extensions/PFImage.m b/ParseUI/Classes/Internal/Extensions/PFImage.m new file mode 100644 index 000000000..ac01c1e85 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFImage.m @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFImage.h" + +#import "PFColor.h" +#import "PFRect.h" +#import "PFResources.h" + +@implementation PFImage + ++ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size +{ + UIGraphicsBeginImageContext(size); + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextSetFillColorWithColor(context, color.CGColor); + CGContextFillRect(context, (CGRect){.size = size}); + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + ++ (UIImage *)imageWithColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius { + CGSize size = CGSizeMake(cornerRadius * 2.0f + 1.0f, cornerRadius * 2.0f + 1.0f); + + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f); + + [color setFill]; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:PFRectMakeWithSize(size) cornerRadius:cornerRadius]; + [path fill]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(cornerRadius, + cornerRadius, + cornerRadius, + cornerRadius) + resizingMode:UIImageResizingModeStretch]; + + return image; +} + ++ (UIImage *)imageWithColor:(UIColor *)color { + return [self imageWithColor:color size:CGSizeMake(1.0f, 1.0f)]; +} + ++ (UIImage *)imageNamed:(NSString *)imageName { + UIImage *image = [UIImage imageNamed:imageName]; + if (image) { + // If there is an external override for the image at the given path, use it. + return image; + } + + NSString *fileExtension = [imageName pathExtension]; + NSMutableString *filenameWithoutExtension = [[imageName stringByDeletingPathExtension] mutableCopy]; + [filenameWithoutExtension replaceOccurrencesOfString:@"-\\." + withString:@"_" + options:NSRegularExpressionSearch + range:NSMakeRange(0, [filenameWithoutExtension length])]; + + NSData *data = nil; + + int imageScale = (int)ceilf([UIScreen mainScreen].scale); + while (data == nil && imageScale > 1) { + NSString *selectorName = [filenameWithoutExtension stringByAppendingFormat:@"%dx_%@", + imageScale, + fileExtension]; + SEL selector = NSSelectorFromString(selectorName); + if ([PFResources respondsToSelector:selector]) { + data = (NSData *)[PFResources performSelector:selector]; + } + if (data == nil) { + imageScale--; + } + } + if (!data) { + NSString *selectorName = [filenameWithoutExtension stringByAppendingFormat:@"_%@", fileExtension]; + SEL selector = NSSelectorFromString(selectorName); + data = (NSData *)[PFResources performSelector:selector]; + } + image = [[UIImage alloc] initWithData:data]; + + // we need to indicate to the framework that the data is already a 2x image, otherwise the framework + // stretches the image by 2x again. To do that, we drop down to CGImage layer to take advantage of + // +[UIImage imageWithCGImage:scale:orientation] + return [UIImage imageWithCGImage:image.CGImage scale:imageScale orientation:image.imageOrientation]; +} + +@end diff --git a/ParseUI/Classes/Internal/Extensions/PFRect.h b/ParseUI/Classes/Internal/Extensions/PFRect.h new file mode 100644 index 000000000..e6c1f0503 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFRect.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import +#import + +extern CGRect PFRectMakeWithOriginSize(CGPoint origin, CGSize size); +extern CGRect PFRectMakeWithOrigin(CGPoint origin); +extern CGRect PFRectMakeWithSize(CGSize size); + +extern CGRect PFRectMakeWithSizeCenteredInRect(CGSize size, CGRect rect); +extern CGSize PFSizeMin(CGSize size1, CGSize size2); diff --git a/ParseUI/Classes/Internal/Extensions/PFRect.m b/ParseUI/Classes/Internal/Extensions/PFRect.m new file mode 100644 index 000000000..0aeb75cd4 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFRect.m @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFRect.h" + +CGRect PFRectMakeWithOriginSize(CGPoint origin, CGSize size) { + return CGRectMake(origin.x, origin.y, size.width, size.height); +} + +CGRect PFRectMakeWithOrigin(CGPoint origin) { + return PFRectMakeWithOriginSize(origin, CGSizeZero); +} + +CGRect PFRectMakeWithSize(CGSize size) { + return PFRectMakeWithOriginSize(CGPointZero, size); +} + +CGRect PFRectMakeWithSizeCenteredInRect(CGSize size, CGRect rect) { + CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); + CGPoint origin = CGPointMake(floorf(center.x - size.width / 2.0f), + floorf(center.y - size.height / 2.0f)); + return PFRectMakeWithOriginSize(origin, size); +} + +CGSize PFSizeMin(CGSize size1, CGSize size2) { + CGSize size = CGSizeZero; + size.width = MIN(size1.width, size2.width); + size.height = MIN(size1.height, size2.height); + return size; +} diff --git a/ParseUI/Classes/Internal/Extensions/PFUIAlertView.h b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.h new file mode 100644 index 000000000..f6742b310 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^PFUIAlertViewCompletion)(NSUInteger selectedOtherButtonIndex); +typedef void(^PFUIAlertViewTextFieldCompletion)(UITextField *textField, NSUInteger selectedOtherButtonIndex); +typedef void(^PFUIAlertViewTextFieldCustomizationHandler)(UITextField *textField); + +@interface PFUIAlertView : NSObject + +///-------------------------------------- +#pragma mark - Present +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewCompletion)completion; + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + textFieldCustomizationHandler:(PFUIAlertViewTextFieldCustomizationHandler)textFieldCustomizationHandler + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewTextFieldCompletion)completion; + +///-------------------------------------- +#pragma mark - Convenience +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + error:(NSError *)error; ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/Internal/Extensions/PFUIAlertView.m b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.m new file mode 100644 index 000000000..155f70d62 --- /dev/null +++ b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.m @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFUIAlertView.h" + +#import "PFLocalization.h" + +@interface PFUIAlertView () + +@property (nonatomic, copy) PFUIAlertViewCompletion completion; + +@end + +@implementation PFUIAlertView + +///-------------------------------------- +#pragma mark - Present +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewCompletion)completion { + if ([UIAlertController class] != nil) { + __block UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + void (^alertActionHandler)(UIAlertAction *) = [^(UIAlertAction *action) { + if (completion) { + // This block intentionally retains alertController, and releases it afterwards. + if (action.style == UIAlertActionStyleCancel) { + completion(NSNotFound); + } else { + NSUInteger index = [alertController.actions indexOfObject:action]; + completion(index - 1); + } + } + alertController = nil; + } copy]; + + [alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle + style:UIAlertActionStyleCancel + handler:alertActionHandler]]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle + style:UIAlertActionStyleDefault + handler:alertActionHandler]]; + } + + [viewController presentViewController:alertController animated:YES completion:nil]; + } else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + __block PFUIAlertView *pfAlertView = [[self alloc] init]; + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:nil + cancelButtonTitle:cancelButtonTitle + otherButtonTitles:nil]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertView addButtonWithTitle:buttonTitle]; + } + + pfAlertView.completion = ^(NSUInteger index) { + if (completion) { + completion(index); + } + + pfAlertView = nil; + }; + + alertView.delegate = pfAlertView; + [alertView show]; +#endif + } +} + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + textFieldCustomizationHandler:(PFUIAlertViewTextFieldCustomizationHandler)textFieldCustomizationHandler + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewTextFieldCompletion)completion { + if ([UIAlertController class] != nil) { + __block UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addTextFieldWithConfigurationHandler:textFieldCustomizationHandler]; + void (^alertActionHandler)(UIAlertAction *) = [^(UIAlertAction *action) { + if (completion) { + UITextField *textField = alertController.textFields.firstObject; + // This block intentionally retains alertController, and releases it afterwards. + if (action.style == UIAlertActionStyleCancel) { + completion(textField, NSNotFound); + } else { + NSUInteger index = [alertController.actions indexOfObject:action]; + completion(textField, index - 1); + } + } + alertController = nil; + } copy]; + + [alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle + style:UIAlertActionStyleCancel + handler:alertActionHandler]]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle + style:UIAlertActionStyleDefault + handler:alertActionHandler]]; + } + + [viewController presentViewController:alertController animated:YES completion:nil]; + } else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + __block PFUIAlertView *pfAlertView = [[self alloc] init]; + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:nil + cancelButtonTitle:cancelButtonTitle + otherButtonTitles:nil]; + alertView.alertViewStyle = UIAlertViewStylePlainTextInput; + for (NSString *buttonTitle in otherButtonTitles) { + [alertView addButtonWithTitle:buttonTitle]; + } + textFieldCustomizationHandler([alertView textFieldAtIndex:0]); + + __weak UIAlertView *walertView = alertView; + pfAlertView.completion = ^(NSUInteger index) { + if (completion) { + UITextField *textField = [walertView textFieldAtIndex:0]; + completion(textField, index); + } + + pfAlertView = nil; + }; + + alertView.delegate = pfAlertView; + [alertView show]; +#endif + } +} + +///-------------------------------------- +#pragma mark - Convenience +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + error:(NSError *)error { + NSString *message = error.userInfo[@"error"]; + if (!message) { + message = [error.userInfo[@"originalError"] localizedDescription]; + } + if (!message) { + message = [error localizedDescription]; + } + [self presentAlertInViewController:viewController withTitle:title message:message]; +} + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message { + [self presentAlertInViewController:viewController + withTitle:title + message:message + cancelButtonTitle:PFLocalizedString(@"OK", @"OK") + otherButtonTitles:nil + completion:nil]; +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + +///-------------------------------------- +#pragma mark - UIAlertViewDelegate +///-------------------------------------- + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if (self.completion) { + if (buttonIndex == alertView.cancelButtonIndex) { + self.completion(NSNotFound); + } else { + self.completion(buttonIndex - 1); + } + } +} + +#endif + +@end diff --git a/ParseUI/Classes/Internal/PFImageCache.h b/ParseUI/Classes/Internal/PFImageCache.h new file mode 100644 index 000000000..38c184e28 --- /dev/null +++ b/ParseUI/Classes/Internal/PFImageCache.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +/** + A memory cache for UIImage, based on NSCache + */ +@interface PFImageCache : NSCache + ++ (instancetype)sharedCache; + +- (void)setImage:(UIImage *)image forURL:(NSURL *)url; +- (UIImage *)imageForURL:(NSURL *)url; + +@end diff --git a/ParseUI/Classes/Internal/PFImageCache.m b/ParseUI/Classes/Internal/PFImageCache.m new file mode 100644 index 000000000..260cd50e7 --- /dev/null +++ b/ParseUI/Classes/Internal/PFImageCache.m @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFImageCache.h" + +static NSString *PFImageCacheKeyFromURL(NSURL *url) { + return [url absoluteString]; +} + +@implementation PFImageCache + ++ (instancetype)sharedCache { + static dispatch_once_t onceToken; + static PFImageCache *sharedCache; + dispatch_once(&onceToken, ^{ + sharedCache = [[self alloc] init]; + }); + return sharedCache; +} + +- (void)setImage:(UIImage *)image forURL:(NSURL *)url { + [self setObject:image forKey:PFImageCacheKeyFromURL(url)]; +} + +- (UIImage *)imageForURL:(NSURL *)url { + return [self objectForKey:PFImageCacheKeyFromURL(url)]; +} + +@end diff --git a/ParseUI/Classes/Internal/PFLocalization.h b/ParseUI/Classes/Internal/PFLocalization.h new file mode 100644 index 000000000..a328dfb83 --- /dev/null +++ b/ParseUI/Classes/Internal/PFLocalization.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#define PFLocalizedString(key, comment) \ +[PFLocalization localizedStringForKey:key] + +/** + Used by the above macro to fetch a localized string + */ +@interface PFLocalization : NSObject + ++ (NSString *)localizedStringForKey:key; + +@end diff --git a/ParseUI/Classes/Internal/PFLocalization.m b/ParseUI/Classes/Internal/PFLocalization.m new file mode 100644 index 000000000..162b95819 --- /dev/null +++ b/ParseUI/Classes/Internal/PFLocalization.m @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFLocalization.h" + +@implementation PFLocalization + ++ (NSString *)localizedStringForKey:key { + return [[self resourcesBundle] localizedStringForKey:key value:nil table:@"ParseUI"]; +} + ++ (NSBundle *)resourcesBundle { + static NSBundle *bundle; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSBundle *classBundle = [NSBundle bundleForClass:[self class]]; + NSURL *bundleURL = [classBundle URLForResource:@"ParseUI" withExtension:@"bundle"]; + + if (bundleURL) { + bundle = [NSBundle bundleWithURL:bundleURL]; + } else { + bundle = [NSBundle mainBundle]; + } + }); + return bundle; +} + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFActionButton.h b/ParseUI/Classes/Internal/Views/Buttons/PFActionButton.h new file mode 100644 index 000000000..c9772ef62 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFActionButton.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +typedef NS_ENUM(uint8_t, PFActionButtonStyle) +{ + PFActionButtonStyleNormal, + PFActionButtonStyleWide +}; + +@class PFActionButtonConfiguration; + +@interface PFActionButton : UIButton + +@property (nonatomic, assign, getter=isLoading) BOOL loading; + +@property (nonatomic, assign) PFActionButtonStyle buttonStyle; + +///-------------------------------------- +/// @name Class +///-------------------------------------- + ++ (NSString *)titleForButtonStyle:(PFActionButtonStyle)buttonStyle; + +///-------------------------------------- +/// @name Init +///-------------------------------------- + +- (instancetype)initWithConfiguration:(PFActionButtonConfiguration *)configuration + buttonStyle:(PFActionButtonStyle)buttonStyle NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; + +@end + +@interface PFActionButtonConfiguration : NSObject + +@property (nonatomic, strong, readonly) UIColor *backgroundImageColor; +@property (nonatomic, strong, readonly) UIImage *image; + +- (instancetype)initWithBackgroundImageColor:(UIColor *)backgroundImageColor + image:(UIImage *)image NS_DESIGNATED_INITIALIZER; + +- (void)setTitle:(NSString *)title forButtonStyle:(PFActionButtonStyle)style; +- (NSString *)titleForButtonStyle:(PFActionButtonStyle)style; + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFActionButton.m b/ParseUI/Classes/Internal/Views/Buttons/PFActionButton.m new file mode 100644 index 000000000..f741d350e --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFActionButton.m @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFActionButton.h" + +#import "PFImage.h" +#import "PFRect.h" + +static const UIEdgeInsets PFActionButtonContentEdgeInsets = { .top = 0.0f, .left = 12.0f, .bottom = 0.0f, .right = 0.0f }; + +@interface PFActionButton () +{ + UIActivityIndicatorView *_activityIndicatorView; +} + +@property (nonatomic, strong) PFActionButtonConfiguration *configuration; + +- (instancetype)initWithCoder:(nonnull NSCoder *)decoder NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER; + +@end + +@implementation PFActionButton + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithFrame:(CGRect)frame { + return [super initWithFrame:frame]; +} + +- (instancetype)initWithCoder:(nonnull NSCoder *)decoder { + return [super initWithCoder:decoder]; +} + +- (instancetype)initWithConfiguration:(PFActionButtonConfiguration *)configuration + buttonStyle:(PFActionButtonStyle)buttonStyle { + self = [super initWithFrame:CGRectZero]; + if (!self) return nil; + + _buttonStyle = buttonStyle; + _configuration = configuration; + + self.backgroundColor = [UIColor clearColor]; + self.titleLabel.font = [UIFont systemFontOfSize:16.0f]; + + self.contentEdgeInsets = UIEdgeInsetsZero; + self.imageEdgeInsets = UIEdgeInsetsZero; + + UIImage *backgroundImage = [PFImage imageWithColor:configuration.backgroundImageColor cornerRadius:4.0f]; + [self setBackgroundImage:backgroundImage forState:UIControlStateNormal]; + + [self setImage:configuration.image forState:UIControlStateNormal]; + + [self setTitle:[configuration titleForButtonStyle:buttonStyle] + forState:UIControlStateNormal]; + + return self; +} + +#pragma mark - +#pragma mark Layout + +- (void)layoutSubviews { + [super layoutSubviews]; + + _activityIndicatorView.center = self.imageView.center; + self.imageView.alpha = (self.loading ? 0.0f : 1.0f); +} + +- (CGSize)sizeThatFits:(CGSize)boundingSize { + CGSize size = CGSizeZero; + size.width = MAX([super sizeThatFits:boundingSize].width, boundingSize.width); + size.height = MIN(44.0f, boundingSize.height); + return size; +} + +- (CGRect)imageRectForContentRect:(CGRect)contentRect { + CGRect imageRect = PFRectMakeWithSize([self imageForState:UIControlStateNormal].size); + imageRect.origin.x = PFActionButtonContentEdgeInsets.left; + imageRect.origin.y = CGRectGetMidY(contentRect) - CGRectGetMidY(imageRect); + return imageRect; +} + +- (CGRect)titleRectForContentRect:(CGRect)contentRect { + contentRect.origin.x = CGRectGetMaxX([self imageRectForContentRect:contentRect]); + contentRect.size.width = CGRectGetWidth(self.bounds) - CGRectGetMaxX([self imageRectForContentRect:contentRect]); + + CGSize size = [super titleRectForContentRect:contentRect].size; + CGRect rect = PFRectMakeWithSizeCenteredInRect(size, contentRect); + return rect; +} + +#pragma mark - +#pragma mark Configuration + ++ (UIColor *)backgroundImageColor { + return [UIColor clearColor]; +} + ++ (NSString *)titleForButtonStyle:(PFActionButtonStyle)buttonStyle { + return nil; +} + +#pragma mark - +#pragma mark Accessors + +- (void)setLoading:(BOOL)loading { + if (self.loading != loading) { + if (loading) { + if (!_activityIndicatorView) { + _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + } + + [_activityIndicatorView startAnimating]; + [self addSubview:_activityIndicatorView]; + [self setNeedsLayout]; + } else { + [_activityIndicatorView stopAnimating]; + [_activityIndicatorView removeFromSuperview]; + } + + self.imageView.alpha = (loading ? 0.0f : 1.0f); + } +} + +- (BOOL)isLoading { + return [_activityIndicatorView isAnimating]; +} + +- (void)setButtonStyle:(PFActionButtonStyle)buttonStyle { + if (self.buttonStyle != buttonStyle) { + _buttonStyle = buttonStyle; + + [self setTitle:[self.configuration titleForButtonStyle:self.buttonStyle] forState:UIControlStateNormal]; + } +} + +@end + +@interface PFActionButtonConfiguration () { + NSMutableDictionary *_titlesDictionary; +} + +@property (nonatomic, strong, readwrite) UIColor *backgroundImageColor; +@property (nonatomic, strong, readwrite) UIImage *image; + +@end + +@implementation PFActionButtonConfiguration + +#pragma mark - +#pragma mark Init + +- (instancetype)init { + return [self initWithBackgroundImageColor:nil image:nil]; +} + +- (instancetype)initWithBackgroundImageColor:(UIColor *)backgroundImageColor + image:(UIImage *)image { + self = [super init]; + if (!self) return nil; + + _backgroundImageColor = backgroundImageColor; + _image = image; + + return self; +} + +#pragma mark - +#pragma mark Title + +- (void)setTitle:(NSString *)title forButtonStyle:(PFActionButtonStyle)style { + if (!_titlesDictionary) { + _titlesDictionary = [NSMutableDictionary dictionaryWithCapacity:style]; + } + _titlesDictionary[@(style)] = title; +} + +- (NSString *)titleForButtonStyle:(PFActionButtonStyle)style { + return _titlesDictionary[@(style)]; +} + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFDismissButton.h b/ParseUI/Classes/Internal/Views/Buttons/PFDismissButton.h new file mode 100644 index 000000000..c34e4db00 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFDismissButton.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface PFDismissButton : UIButton + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFDismissButton.m b/ParseUI/Classes/Internal/Views/Buttons/PFDismissButton.m new file mode 100644 index 000000000..ba40367d4 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFDismissButton.m @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFDismissButton.h" + +#import "PFRect.h" + +@implementation PFDismissButton + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (!self) return nil; + + [self setImage:[self _defaultImage] forState:UIControlStateNormal]; + + return self; +} + +#pragma mark - +#pragma mark Init + +- (UIImage *)_defaultImage { + CGRect imageRect = PFRectMakeWithSize(CGSizeMake(22.0f, 22.0f)); + + UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0f); + + [[UIColor colorWithRed:91.0f/255.0f green:107.0f/255.0f blue:118.0f/255.0f alpha:1.0f] setStroke]; + + UIBezierPath *path = [UIBezierPath bezierPath]; + + [path moveToPoint:CGPointZero]; + [path addLineToPoint:CGPointMake(CGRectGetMaxX(imageRect), CGRectGetMaxY(imageRect))]; + + [path moveToPoint:CGPointMake(CGRectGetMaxX(imageRect), CGRectGetMinY(imageRect))]; + [path addLineToPoint:CGPointMake(CGRectGetMinX(imageRect), CGRectGetMaxY(imageRect))]; + + path.lineWidth = 2.0f; + + [path stroke]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + +#pragma mark - +#pragma mark UIView + +- (CGSize)sizeThatFits:(CGSize)boundingSize { + CGSize size = CGSizeZero; + size.width = MIN(22.0f, boundingSize.width); + size.height = MIN(22.0f, boundingSize.height); + return size; +} + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + CGRect bigBounds = CGRectInset(self.bounds, -22.0f, -22.0f); + return CGRectContainsPoint(bigBounds, point); +} + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFPrimaryButton.h b/ParseUI/Classes/Internal/Views/Buttons/PFPrimaryButton.h new file mode 100644 index 000000000..85171806d --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFPrimaryButton.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface PFPrimaryButton : UIButton + +@property (nonatomic, assign, getter=isLoading) BOOL loading; + +- (instancetype)initWithBackgroundImageColor:(UIColor *)color NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFPrimaryButton.m b/ParseUI/Classes/Internal/Views/Buttons/PFPrimaryButton.m new file mode 100644 index 000000000..5c5a076a5 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFPrimaryButton.m @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFPrimaryButton.h" + +#import "PFImage.h" +#import "PFRect.h" + +@interface PFPrimaryButton () +{ + UIActivityIndicatorView *_activityIndicatorView; +} + +- (instancetype)initWithCoder:(nonnull NSCoder *)decoder NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER; + +@end + +@implementation PFPrimaryButton + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithFrame:(CGRect)frame { + return [super initWithFrame:frame]; +} + +- (instancetype)initWithCoder:(nonnull NSCoder *)decoder { + return [super initWithCoder:decoder]; +} + +- (instancetype)initWithBackgroundImageColor:(UIColor *)color { + self = [super initWithFrame:CGRectZero]; + if (!self) return nil; + + [self setBackgroundImage:[PFImage imageWithColor:color] forState:UIControlStateNormal]; + + self.titleLabel.font = [UIFont systemFontOfSize:20.0f]; + self.contentVerticalAlignment = UIControlContentHorizontalAlignmentCenter; + self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; + + return self; +} + +#pragma mark - +#pragma mark Layout + +- (void)layoutSubviews { + [super layoutSubviews]; + + CGFloat activityIndicatorRightInset = 12.0f; + + CGRect activityIndicatorFrame = PFRectMakeWithSizeCenteredInRect(_activityIndicatorView.bounds.size, self.bounds); + activityIndicatorFrame.origin.x = (CGRectGetMinX(self.titleLabel.frame) + - CGRectGetWidth(activityIndicatorFrame) + - activityIndicatorRightInset); + _activityIndicatorView.frame = activityIndicatorFrame; +} + +- (CGSize)sizeThatFits:(CGSize)boundingSize { + CGSize size = CGSizeZero; + size.width = boundingSize.width; + size.height = MIN(56.0f, boundingSize.height); + return size; +} + +#pragma mark - +#pragma mark Accessors + +- (void)setLoading:(BOOL)loading { + if (self.loading != loading) { + if (loading) { + if (!_activityIndicatorView) { + _activityIndicatorView = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + } + + [_activityIndicatorView startAnimating]; + [self addSubview:_activityIndicatorView]; + [self setNeedsLayout]; + } else { + [_activityIndicatorView stopAnimating]; + [_activityIndicatorView removeFromSuperview]; + } + } +} + +- (BOOL)isLoading { + return [_activityIndicatorView isAnimating]; +} + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFTextButton.h b/ParseUI/Classes/Internal/Views/Buttons/PFTextButton.h new file mode 100644 index 000000000..75db8f251 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFTextButton.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface PFTextButton : UIButton + +@end diff --git a/ParseUI/Classes/Internal/Views/Buttons/PFTextButton.m b/ParseUI/Classes/Internal/Views/Buttons/PFTextButton.m new file mode 100644 index 000000000..9ada72677 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/Buttons/PFTextButton.m @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFTextButton.h" + +@implementation PFTextButton + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (!self) return nil; + + self.titleLabel.font = [UIFont systemFontOfSize:16.0f]; + [self setTitleColor:[UIColor colorWithRed:82.0f/255.0f + green:152.0f/255.0f + blue:252.0f/255.0f + alpha:1.0f] + forState:UIControlStateNormal]; + + return self; +} + +#pragma mark - +#pragma mark UIView + +- (CGSize)sizeThatFits:(CGSize)boundingSize { + CGSize size = [super sizeThatFits:boundingSize]; + size.width = MAX(32.0f, boundingSize.width); + size.height = MIN(32.0f, boundingSize.height); + return size; +} + +@end diff --git a/ParseUI/Classes/Internal/Views/PFLoadingView.h b/ParseUI/Classes/Internal/Views/PFLoadingView.h new file mode 100644 index 000000000..2663181d1 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/PFLoadingView.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +/** + A loading view that is used to show users that data is being loaded before any data is available. + */ +@interface PFLoadingView : UIView + +@end diff --git a/ParseUI/Classes/Internal/Views/PFLoadingView.m b/ParseUI/Classes/Internal/Views/PFLoadingView.m new file mode 100644 index 000000000..0e511e346 --- /dev/null +++ b/ParseUI/Classes/Internal/Views/PFLoadingView.m @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFLoadingView.h" + +#import "PFRect.h" +#import "PFLocalization.h" + +@interface PFLoadingView () + +@property (nonatomic, strong) UILabel *loadingLabel; +@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator; + +@end + +@implementation PFLoadingView + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + [_activityIndicator startAnimating]; + [self addSubview:_activityIndicator]; + + _loadingLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + _loadingLabel.text = PFLocalizedString(@"Loading...", @"Loading message of PFQueryTableViewController"); + _loadingLabel.backgroundColor = [UIColor clearColor]; + _loadingLabel.shadowOffset = CGSizeMake(0.0f, 1.0f); + _loadingLabel.shadowColor = [UIColor whiteColor]; + [_loadingLabel sizeToFit]; + [self addSubview:_loadingLabel]; + } + return self; +} + +#pragma mark - +#pragma mark UIView + +- (void)layoutSubviews { + [super layoutSubviews]; + + const CGRect bounds = self.bounds; + + CGFloat viewsInset = 4.0f; + CGFloat startX = floorf((CGRectGetMaxX(bounds) + - CGRectGetWidth(_loadingLabel.frame) + - CGRectGetWidth(_activityIndicator.frame) + - viewsInset) + / 2.0f); + + CGRect activityIndicatorFrame = PFRectMakeWithSizeCenteredInRect(_activityIndicator.frame.size, bounds); + activityIndicatorFrame.origin.x = startX; + _activityIndicator.frame = activityIndicatorFrame; + + CGRect loadingLabelFrame = PFRectMakeWithSizeCenteredInRect(_loadingLabel.frame.size, bounds); + loadingLabelFrame.origin.x = CGRectGetMaxX(activityIndicatorFrame) + viewsInset; + _loadingLabel.frame = loadingLabelFrame; +} + +@end diff --git a/ParseUI/Classes/LogInViewController/PFLogInView.h b/ParseUI/Classes/LogInViewController/PFLogInView.h new file mode 100644 index 000000000..7600f4d28 --- /dev/null +++ b/ParseUI/Classes/LogInViewController/PFLogInView.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + `PFLogInFields` bitmask specifies the log in elements which are enabled in the view. + + @see PFLogInViewController + @see PFLogInView + */ +typedef NS_OPTIONS(NSInteger, PFLogInFields) { + /** No fields. */ + PFLogInFieldsNone = 0, + /** Username and password fields. */ + PFLogInFieldsUsernameAndPassword = 1 << 0, + /** Forgot password button. */ + PFLogInFieldsPasswordForgotten = 1 << 1, + /** Login button. */ + PFLogInFieldsLogInButton = 1 << 2, + /** Button to login with Facebook. */ + PFLogInFieldsFacebook = 1 << 3, + /** Button to login with Twitter. */ + PFLogInFieldsTwitter = 1 << 4, + /** Signup Button. */ + PFLogInFieldsSignUpButton = 1 << 5, + /** Dismiss Button. */ + PFLogInFieldsDismissButton = 1 << 6, + + /** Default value. Combines Username, Password, Login, Signup, Forgot Password and Dismiss buttons. */ + PFLogInFieldsDefault = (PFLogInFieldsUsernameAndPassword | + PFLogInFieldsLogInButton | + PFLogInFieldsSignUpButton | + PFLogInFieldsPasswordForgotten | + PFLogInFieldsDismissButton) +}; + +/** + `PFLoginFields`'s accessibility label identifiers + + @see PFLogInView + */ +extern NSString *const PFLogInViewUsernameFieldAccessibilityIdentifier; +extern NSString *const PFLogInViewPasswordFieldAccessibilityIdentifier; +extern NSString *const PFLogInViewLogInButtonAccessibilityIdentifier; +extern NSString *const PFLogInViewSignUpButtonAccessibilityIdentifier; +extern NSString *const PFLogInViewPasswordForgottenButtonAccessibilityIdentifier; +extern NSString *const PFLogInViewTwitterButtonAccessibilityIdentifier; +extern NSString *const PFLogInViewFacebookButtonAccessibilityIdentifier; +extern NSString *const PFLogInViewDismissButtonAccessibilityIdentifier; + +@class PFTextField; + +/** + The `PFLogInView` class provides a standard log in interface for authenticating a `PFUser`. + */ +@interface PFLogInView : UIScrollView + +///-------------------------------------- +/// @name Creating Log In View +///-------------------------------------- + +/** + Initializes the view with the specified log in elements. + + @param fields A bitmask specifying the log in elements which are enabled in the view + + @return An initialized `PFLogInView` object or `nil` if the object couldn't be created. + + @see PFLogInFields + */ +- (instancetype)initWithFields:(PFLogInFields)fields; + +/** + The view controller that will present this view. + + Used to lay out elements correctly when the presenting view controller has translucent elements. + */ +@property (nullable, nonatomic, weak) UIViewController *presentingViewController; + +///-------------------------------------- +/// @name Customizing the Logo +///-------------------------------------- + +/// The logo. By default, it is the Parse logo. +@property (nullable, nonatomic, strong) UIView *logo; + +///-------------------------------------- +/// @name Configure Username Behaviour +///-------------------------------------- + +/** + If email should be used to log in, instead of username + + By default, this is set to `NO`. + */ +@property (nonatomic, assign) BOOL emailAsUsername; + +///-------------------------------------- +/// @name Log In Elements +///-------------------------------------- + +/** + The bitmask which specifies the enabled log in elements in the view. + */ +@property (nonatomic, assign, readonly) PFLogInFields fields; + +/** + The username text field. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) PFTextField *usernameField; + +/** + The password text field. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) PFTextField *passwordField; + +/** + The password forgotten button. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *passwordForgottenButton; + +/** + The log in button. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *logInButton; + +/** + The Facebook button. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *facebookButton; + +/** + The Twitter button. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *twitterButton; + +/** + The sign up button. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *signUpButton; + +/** + It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *dismissButton; + +/** + The facebook/twitter login label. + + @deprecated This property is deprecated and will always be nil. + */ +@property (nullable, nonatomic, strong, readonly) UILabel *externalLogInLabel __attribute__(PARSE_UI_DEPRECATED("This property is deprecated and will always be nil.")); + +/** + The sign up label. + + @deprecated This property is deprecated and will always be nil. + */ +@property (nullable, nonatomic, strong, readonly) UILabel *signUpLabel __attribute__(PARSE_UI_DEPRECATED("This property is deprecated and will always be nil.")); + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/LogInViewController/PFLogInView.m b/ParseUI/Classes/LogInViewController/PFLogInView.m new file mode 100644 index 000000000..4dfbc5c96 --- /dev/null +++ b/ParseUI/Classes/LogInViewController/PFLogInView.m @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFLogInView.h" + +#import "PFActionButton.h" +#import "PFColor.h" +#import "PFDismissButton.h" +#import "PFImage.h" +#import "PFLocalization.h" +#import "PFPrimaryButton.h" +#import "PFRect.h" +#import "PFTextButton.h" +#import "PFTextField.h" + +static NSString *const PFLogInViewDefaultLogoImageName = @"parse_logo.png"; +static NSString *const PFLogInViewDefaultFacebookButtonImageName = @"facebook_icon.png"; +static NSString *const PFLogInViewDefaultTwitterButtonImageName = @"twitter_icon.png"; + +///-------------------------------------- +#pragma mark - Accessibility Identifiers +///-------------------------------------- + +NSString *const PFLogInViewUsernameFieldAccessibilityIdentifier = @"PFLogInViewUsernameFieldAccessibilityIdentifier"; +NSString *const PFLogInViewPasswordFieldAccessibilityIdentifier = @"PFLogInViewPasswordFieldAccessibilityIdentifier"; +NSString *const PFLogInViewLogInButtonAccessibilityIdentifier = @"PFLogInViewLogInButtonAccessibilityIdentifier"; +NSString *const PFLogInViewSignUpButtonAccessibilityIdentifier = @"PFLogInViewSignUpButtonAccessibilityIdentifier"; +NSString *const PFLogInViewPasswordForgottenButtonAccessibilityIdentifier = @"PFLogInViewPasswordForgottenButtonAccessibilityIdentifier"; +NSString *const PFLogInViewTwitterButtonAccessibilityIdentifier = @"PFLogInViewTwitterButtonAccessibilityIdentifier"; +NSString *const PFLogInViewFacebookButtonAccessibilityIdentifier = @"PFLogInViewFacebookButtonAccessibilityIdentifier"; +NSString *const PFLogInViewDismissButtonAccessibilityIdentifier = @"PFLogInViewDismissButtonAccessibilityIdentifier"; + +@implementation PFLogInView + +///-------------------------------------- +#pragma mark - Class +///-------------------------------------- + ++ (PFActionButtonConfiguration *)_defaultSignUpButtonConfiguration { + PFActionButtonConfiguration *configuration = [[PFActionButtonConfiguration alloc] initWithBackgroundImageColor:[PFColor signupButtonBackgroundColor] + image:nil]; + NSString *title = PFLocalizedString(@"Sign Up", @"Sign Up"); + [configuration setTitle:title forButtonStyle:PFActionButtonStyleNormal]; + [configuration setTitle:title forButtonStyle:PFActionButtonStyleWide]; + + return configuration; +} + ++ (PFActionButtonConfiguration *)_defaultFacebookButtonConfiguration { + PFActionButtonConfiguration *configuration = [[PFActionButtonConfiguration alloc] initWithBackgroundImageColor:[PFColor facebookButtonBackgroundColor] + image:[PFImage imageNamed:PFLogInViewDefaultFacebookButtonImageName]]; + + [configuration setTitle:PFLocalizedString(@"Facebook", @"Facebook") + forButtonStyle:PFActionButtonStyleNormal]; + [configuration setTitle:PFLocalizedString(@"Log In with Facebook", @"Log In with Facebook") + + forButtonStyle:PFActionButtonStyleWide]; + + return configuration; +} + ++ (PFActionButtonConfiguration *)_defaultTwitterButtonConfiguration { + PFActionButtonConfiguration *configuration = [[PFActionButtonConfiguration alloc] initWithBackgroundImageColor:[PFColor twitterButtonBackgroundColor] + image:[PFImage imageNamed:PFLogInViewDefaultTwitterButtonImageName]]; + + [configuration setTitle:PFLocalizedString(@"Twitter", @"Twitter") + forButtonStyle:PFActionButtonStyleNormal]; + [configuration setTitle:PFLocalizedString(@"Log In with Twitter", @"Log In with Twitter") + + forButtonStyle:PFActionButtonStyleWide]; + + return configuration; +} + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +- (instancetype)initWithFields:(PFLogInFields)otherFields { + self = [super initWithFrame:CGRectZero]; + if (!self) return nil; + + [PFLogInView _validateFields:otherFields]; + + self.opaque = YES; + self.backgroundColor = [PFColor commonBackgroundColor]; + + _fields = otherFields; + + _logo = [[UIImageView alloc] initWithImage:[PFImage imageNamed:PFLogInViewDefaultLogoImageName]]; + _logo.contentMode = UIViewContentModeScaleAspectFit; + [self addSubview:_logo]; + + [self _updateAllFields]; + + return self; +} + +///-------------------------------------- +#pragma mark - Fields +///-------------------------------------- + +- (void)_updateAllFields { + if (_fields & PFLogInFieldsDismissButton) { + if (!_dismissButton) { + _dismissButton = [[PFDismissButton alloc] initWithFrame:CGRectZero]; + _dismissButton.accessibilityIdentifier = PFLogInViewDismissButtonAccessibilityIdentifier; + [self addSubview:_dismissButton]; + } + } else { + [_dismissButton removeFromSuperview]; + _dismissButton = nil; + } + + if (_fields & PFLogInFieldsUsernameAndPassword) { + if (!_usernameField) { + _usernameField = [[PFTextField alloc] initWithFrame:CGRectZero + separatorStyle:(PFTextFieldSeparatorStyleTop | + PFTextFieldSeparatorStyleBottom)]; + _usernameField.accessibilityIdentifier = PFLogInViewUsernameFieldAccessibilityIdentifier; + _usernameField.autocorrectionType = UITextAutocorrectionTypeNo; + _usernameField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _usernameField.returnKeyType = UIReturnKeyNext; + [self addSubview:_usernameField]; + [self _updateUsernameFieldStyle]; + } + + if (!_passwordField) { + _passwordField = [[PFTextField alloc] initWithFrame:CGRectZero + separatorStyle:PFTextFieldSeparatorStyleBottom]; + _passwordField.accessibilityIdentifier = PFLogInViewPasswordFieldAccessibilityIdentifier; + _passwordField.placeholder = PFLocalizedString(@"Password", @"Password"); + _passwordField.secureTextEntry = YES; + _passwordField.autocorrectionType = UITextAutocorrectionTypeNo; + _passwordField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _passwordField.returnKeyType = UIReturnKeyDone; + [self addSubview:_passwordField]; + } + } else { + [_usernameField removeFromSuperview]; + _usernameField = nil; + + [_passwordField removeFromSuperview]; + _passwordField = nil; + } + + if (_fields & PFLogInFieldsSignUpButton) { + if (!_signUpButton) { + _signUpButton = [[PFActionButton alloc] initWithConfiguration:[[self class] _defaultSignUpButtonConfiguration] + buttonStyle:PFActionButtonStyleNormal]; + _signUpButton.accessibilityIdentifier = PFLogInViewSignUpButtonAccessibilityIdentifier; + [self addSubview:_signUpButton]; + } + } else { + [_signUpButton removeFromSuperview]; + _signUpButton = nil; + } + + if (_fields & PFLogInFieldsPasswordForgotten) { + if (!_passwordForgottenButton) { + _passwordForgottenButton = [[PFTextButton alloc] initWithFrame:CGRectZero]; + _passwordForgottenButton.accessibilityIdentifier = PFLogInViewPasswordForgottenButtonAccessibilityIdentifier; + [_passwordForgottenButton setTitle:PFLocalizedString(@"Forgot Password?", "Forgot Password?") + forState:UIControlStateNormal]; + [self addSubview:_passwordForgottenButton]; + } + } else { + [_passwordForgottenButton removeFromSuperview]; + _passwordForgottenButton = nil; + } + + if (_fields & PFLogInFieldsLogInButton) { + if (!_logInButton) { + _logInButton = [[PFPrimaryButton alloc] initWithBackgroundImageColor:[PFColor loginButtonBackgroundColor]]; + _logInButton.accessibilityIdentifier = PFLogInViewLogInButtonAccessibilityIdentifier; + [_logInButton setTitle:PFLocalizedString(@"Log In", @"Log In") forState:UIControlStateNormal]; + [self addSubview:_logInButton]; + } + } else { + [_logInButton removeFromSuperview]; + _logInButton = nil; + } + + if (_fields & PFLogInFieldsFacebook) { + if (!_facebookButton) { + _facebookButton = [[PFActionButton alloc] initWithConfiguration:[[self class] _defaultFacebookButtonConfiguration] + buttonStyle:PFActionButtonStyleNormal]; + _facebookButton.accessibilityIdentifier = PFLogInViewFacebookButtonAccessibilityIdentifier; + [self addSubview:_facebookButton]; + } + } else { + [_facebookButton removeFromSuperview]; + _facebookButton = nil; + } + + if (_fields & PFLogInFieldsTwitter) { + if (!_twitterButton) { + _twitterButton = [[PFActionButton alloc] initWithConfiguration:[[self class] _defaultTwitterButtonConfiguration] + buttonStyle:PFActionButtonStyleNormal]; + _twitterButton.accessibilityIdentifier = PFLogInViewTwitterButtonAccessibilityIdentifier; + [self addSubview:_twitterButton]; + } + } else { + [_twitterButton removeFromSuperview]; + _twitterButton = nil; + } +} + +///-------------------------------------- +#pragma mark - UIView +///-------------------------------------- + +- (void)layoutSubviews { + [super layoutSubviews]; + + const CGRect bounds = PFRectMakeWithOriginSize(CGPointZero, self.bounds.size); + + if (_dismissButton) { + CGPoint origin = CGPointMake(16.0f, 16.0f); + + // In iOS 7+, if this view is presented fullscreen, it's top edge will be behind the status bar. + // This lets us move down the dismiss button a bit so that it's not covered by the status bar. + if ([self.presentingViewController respondsToSelector:@selector(topLayoutGuide)]) { + origin.y += self.presentingViewController.topLayoutGuide.length; + } + + CGRect frame = PFRectMakeWithOriginSize(origin, [_dismissButton sizeThatFits:bounds.size]); + _dismissButton.frame = frame; + } + + CGRect contentRect = PFRectMakeWithSizeCenteredInRect(PFSizeMin(bounds.size, [self _maxContentSize]), + bounds); + const CGSize contentSizeScale = [self _contentSizeScaleForContentSize:contentRect.size]; + + CGFloat socialButtonsDefaultInset = 16.0f; + UIEdgeInsets socialButtonsRectInsets = UIEdgeInsetsZero; + if (CGRectGetMinX(contentRect) <= CGRectGetMinX(bounds)) { + socialButtonsRectInsets = UIEdgeInsetsMake(0.0f, + socialButtonsDefaultInset, + 0.0f, + socialButtonsDefaultInset); + } + CGRect socialButtonsRect = UIEdgeInsetsInsetRect(contentRect, socialButtonsRectInsets); + + if (_signUpButton) { + CGSize buttonSize = [_signUpButton sizeThatFits:socialButtonsRect.size]; + [(PFActionButton *)_signUpButton setButtonStyle:PFActionButtonStyleWide]; + + CGRect frame = PFRectMakeWithSizeCenteredInRect(buttonSize, socialButtonsRect); + frame.origin.y = CGRectGetMaxY(socialButtonsRect) - CGRectGetHeight(frame) - socialButtonsRectInsets.left; + _signUpButton.frame = frame; + + contentRect.size.height = CGRectGetMinY(frame) - CGRectGetMinY(contentRect); + socialButtonsRect = UIEdgeInsetsInsetRect(contentRect, socialButtonsRectInsets); + } + + if (_facebookButton && _twitterButton) { + CGSize buttonSize = [_facebookButton sizeThatFits:socialButtonsRect.size]; + buttonSize.width = (socialButtonsRect.size.width - socialButtonsDefaultInset) / 2.0f; + + CGRect frame = PFRectMakeWithOriginSize(socialButtonsRect.origin, buttonSize); + frame.origin.y = CGRectGetMaxY(socialButtonsRect) - buttonSize.height - socialButtonsDefaultInset; + [(PFActionButton *)_facebookButton setButtonStyle:PFActionButtonStyleNormal]; + _facebookButton.frame = frame; + + frame.origin.x = CGRectGetMaxX(frame) + socialButtonsDefaultInset; + [(PFActionButton *)_twitterButton setButtonStyle:PFActionButtonStyleNormal]; + _twitterButton.frame = frame; + + contentRect.size.height = CGRectGetMinY(frame) - CGRectGetMinY(contentRect); + socialButtonsRect = UIEdgeInsetsInsetRect(contentRect, socialButtonsRectInsets); + } else if (_facebookButton) { + CGSize buttonSize = [_facebookButton sizeThatFits:socialButtonsRect.size]; + CGRect frame = PFRectMakeWithOriginSize(socialButtonsRect.origin, buttonSize); + frame.origin.y = CGRectGetMaxY(socialButtonsRect) - buttonSize.height - socialButtonsDefaultInset; + _facebookButton.frame = frame; + + [(PFActionButton *)_facebookButton setButtonStyle:PFActionButtonStyleWide]; + + contentRect.size.height = CGRectGetMinY(frame) - CGRectGetMinY(contentRect); + socialButtonsRect = UIEdgeInsetsInsetRect(contentRect, socialButtonsRectInsets); + } else if (_twitterButton) { + CGSize buttonSize = [_twitterButton sizeThatFits:socialButtonsRect.size]; + CGRect frame = PFRectMakeWithOriginSize(socialButtonsRect.origin, buttonSize); + frame.origin.y = CGRectGetMaxY(socialButtonsRect) - buttonSize.height - socialButtonsDefaultInset; + _twitterButton.frame = frame; + + [(PFActionButton *)_twitterButton setButtonStyle:PFActionButtonStyleWide]; + + contentRect.size.height = CGRectGetMinY(frame) - CGRectGetMinY(contentRect); + socialButtonsRect = UIEdgeInsetsInsetRect(contentRect, socialButtonsRectInsets); + } + + if (_signUpButton || _facebookButton || _twitterButton) { + contentRect.size.height -= socialButtonsDefaultInset; + } + + const CGRect loginContentRect = PFRectMakeWithSizeCenteredInRect([self _loginContentSizeThatFits:contentRect.size + withContentSizeScale:contentSizeScale], + contentRect); + const CGSize loginContentSize = loginContentRect.size; + CGFloat currentY = CGRectGetMinY(loginContentRect); + + if (_logo) { + CGFloat logoTopInset = (CGRectGetMinX(contentRect) > 0.0f ? 36.0f : 88.0f) * contentSizeScale.height; + CGFloat logoBottomInset = floorf(36.0f * contentSizeScale.height); + + CGFloat logoAvailableHeight = floorf(68.0f * contentSizeScale.height); + + CGSize logoSize = [_logo sizeThatFits:CGSizeMake(loginContentSize.width, logoAvailableHeight)]; + logoSize.width = MIN(loginContentSize.width, logoSize.width); + logoSize.height = MIN(logoAvailableHeight, logoSize.height); + + CGRect frame = PFRectMakeWithSizeCenteredInRect(logoSize, loginContentRect); + frame.origin.y = CGRectGetMinY(loginContentRect) + logoTopInset; + _logo.frame = CGRectIntegral(frame); + + currentY = floorf(CGRectGetMaxY(frame) + logoBottomInset); + } + + if (_usernameField) { + CGRect frame = PFRectMakeWithSizeCenteredInRect([_usernameField sizeThatFits:loginContentSize], + loginContentRect); + frame.origin.y = currentY; + _usernameField.frame = frame; + + currentY = CGRectGetMaxY(frame); + } + + if (_passwordField) { + CGRect frame = PFRectMakeWithSizeCenteredInRect([_passwordField sizeThatFits:loginContentSize], + loginContentRect); + frame.origin.y = currentY; + _passwordField.frame = frame; + + currentY = CGRectGetMaxY(frame); + } + + if (_logInButton) { + CGFloat loginButtonTopInset = floorf(24.0f * contentSizeScale.height); + + CGRect frame = PFRectMakeWithSizeCenteredInRect([_logInButton sizeThatFits:loginContentSize], loginContentRect); + frame.origin.y = currentY + loginButtonTopInset; + _logInButton.frame = frame; + + currentY = CGRectGetMaxY(frame); + } + + if (_passwordForgottenButton) { + CGFloat forgotPasswordInset = floorf(12.0f * contentSizeScale.height); + + CGSize buttonSize = [_passwordForgottenButton sizeThatFits:loginContentSize]; + CGRect frame = PFRectMakeWithSizeCenteredInRect(buttonSize, loginContentRect); + frame.origin.y = currentY + forgotPasswordInset; + _passwordForgottenButton.frame = frame; + } +} + +- (CGSize)_loginContentSizeThatFits:(CGSize)boundingSize withContentSizeScale:(CGSize)contentSizeScale { + CGSize size = boundingSize; + size.height = 0.0f; + if (_logo) { + CGFloat logoTopInset = floorf(36.0f * contentSizeScale.height); + CGFloat logoBottomInset = floorf(36.0f * contentSizeScale.height); + + CGFloat logoAvailableHeight = floorf(68.0f * contentSizeScale.height); + + CGFloat scale = MAX(contentSizeScale.width, contentSizeScale.height); + + CGSize logoSize = [_logo sizeThatFits:CGSizeMake(boundingSize.width, logoAvailableHeight)]; + logoSize.height *= scale; + logoSize.width *= scale; + + size.height += logoSize.height + logoTopInset + logoBottomInset; + } + if (_usernameField) { + CGSize fieldSize = [_usernameField sizeThatFits:boundingSize]; + size.height += fieldSize.height; + } + if (_passwordField) { + CGSize fieldSize = [_passwordField sizeThatFits:boundingSize]; + size.height += fieldSize.height; + } + if (_logInButton) { + CGFloat loginButtonTopInset = 24.0f * contentSizeScale.height; + + CGSize buttonSize = [_logInButton sizeThatFits:boundingSize]; + + size.height += buttonSize.height + loginButtonTopInset; + } + if (_passwordForgottenButton) { + CGFloat forgotPasswordInset = 12.0f * contentSizeScale.height; + + UIView *button = _signUpButton ?: _passwordForgottenButton; + CGSize buttonSize = [button sizeThatFits:boundingSize]; + + size.height += buttonSize.height + forgotPasswordInset * 2.0f; + } + size.width = floorf(size.width); + size.height = floorf(size.height); + + return size; +} + +- (CGSize)_maxContentSize { + return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? CGSizeMake(420.0f, 550.0f) : CGSizeMake(500.0f, 800.0f)); +} + +- (CGSize)_contentSizeScaleForContentSize:(CGSize)contentSize { + CGSize maxContentSize = [self _maxContentSize]; + if (maxContentSize.width < contentSize.width && + maxContentSize.height < contentSize.height) { + return CGSizeMake(1.0f, 1.0f); + } + + CGSize contentSizeScale = CGSizeMake(contentSize.width / maxContentSize.width, + contentSize.height / maxContentSize.height); + return contentSizeScale; +} + +///-------------------------------------- +#pragma mark - Accessors +///-------------------------------------- + +- (void)setFields:(PFLogInFields)fields { + if (_fields != fields) { + _fields = fields; + [self _updateAllFields]; + [self setNeedsLayout]; + } +} + +- (void)setLogo:(UIView *)logo { + if (self.logo != logo) { + [_logo removeFromSuperview]; + _logo = logo; + [self addSubview:_logo]; + + [self setNeedsLayout]; + } +} + +- (void)setEmailAsUsername:(BOOL)otherEmailAsUsername { + if (_emailAsUsername != otherEmailAsUsername) { + _emailAsUsername = otherEmailAsUsername; + + [self _updateUsernameFieldStyle]; + } +} + +///-------------------------------------- +#pragma mark - Private +///-------------------------------------- + ++ (void)_validateFields:(PFLogInFields)fields { + if (fields == PFLogInFieldsNone) { + [NSException raise:NSInvalidArgumentException + format:@"Fields must be set before initializing PFLogInView."]; + } + + if (fields & PFLogInFieldsLogInButton) { + if (!(fields & PFLogInFieldsUsernameAndPassword)) { + [NSException raise:NSInvalidArgumentException + format:@"Username and password must be enabled if done button is enabled."]; + } + } + + if (fields & PFLogInFieldsPasswordForgotten) { + if (!(fields & PFLogInFieldsUsernameAndPassword)) { + [NSException raise:NSInvalidArgumentException + format:@"Username and password must be enabled if password forgotten button is enabled."]; + } + } +} + +- (void)_updateUsernameFieldStyle { + UIKeyboardType keyboardType = UIKeyboardTypeDefault; + NSString *usernamePlaceholder = nil; + if (!_emailAsUsername) { + keyboardType = UIKeyboardTypeDefault; + usernamePlaceholder = PFLocalizedString(@"Username", @"Username"); + } else { + keyboardType = UIKeyboardTypeEmailAddress; + usernamePlaceholder = PFLocalizedString(@"Email", @"Email"); + } + + _usernameField.placeholder = usernamePlaceholder; + _usernameField.keyboardType = keyboardType; +} + +@end diff --git a/ParseUI/Classes/LogInViewController/PFLogInViewController.h b/ParseUI/Classes/LogInViewController/PFLogInViewController.h new file mode 100644 index 000000000..231c46fc4 --- /dev/null +++ b/ParseUI/Classes/LogInViewController/PFLogInViewController.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#import "PFLogInView.h" +#else +#import +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +@class PFSignUpViewController; +@class PFUser; +@protocol PFLogInViewControllerDelegate; + +/** + The `PFLogInViewController` class presents and manages a standard authentication interface for logging in a `PFUser`. + */ +@interface PFLogInViewController : UIViewController + +///-------------------------------------- +/// @name Configuring Log In Elements +///-------------------------------------- + +/** + A bitmask specifying the log in elements which are enabled in the view. + + @see PFLogInFields + */ +@property (nonatomic, assign) PFLogInFields fields; + + +/** + The log in view. It contains all the enabled log in elements. + + @see PFLogInView + */ +@property (nullable, nonatomic, strong, readonly) PFLogInView *logInView; + +///-------------------------------------- +/// @name Configuring Log In Behaviors +///-------------------------------------- + +/** + The delegate that responds to the control events of `PFLogInViewController`. + + @see PFLogInViewControllerDelegate + */ +@property (nullable, nonatomic, weak) id delegate; + +/** + The facebook permissions that Facebook log in requests for. + + If unspecified, the default is basic facebook permissions. + */ +@property (nullable, nonatomic, copy) NSArray *facebookPermissions; + +/** + The sign up controller if sign up is enabled. + + Use this to configure the sign up view, and the transition animation to the sign up view. + The default is a sign up view with a username, a password, a dismiss button and a sign up button. + */ +@property (nullable, nonatomic, strong) PFSignUpViewController *signUpController; + +/** + Whether to prompt for the email as username on the login view. + + If set to `YES`, we'll prompt for the email in the username field. + This property value propagates to the attached `signUpController`. + By default, this is set to `NO`. + */ +@property (nonatomic, assign) BOOL emailAsUsername; + +@end + +///-------------------------------------- +/// @name Notifications +///-------------------------------------- + +/** + The notification is posted immediately after the log in succeeds. + */ +extern NSString *const PFLogInSuccessNotification; + +/** + The notification is posted immediately after the log in fails. + If the delegate prevents the log in from starting, the notification is not sent. + */ +extern NSString *const PFLogInFailureNotification; + +/** + The notification is posted immediately after the log in is cancelled. + */ +extern NSString *const PFLogInCancelNotification; + +///-------------------------------------- +/// @name PFLogInViewControllerDelegate +///-------------------------------------- + +/** + The `PFLogInViewControllerDelegate` protocol defines methods a delegate of a `PFLogInViewController` should implement. + All methods of this protocol are optional. + */ +@protocol PFLogInViewControllerDelegate + +@optional + +///-------------------------------------- +/// @name Customizing Behavior +///-------------------------------------- + +/** + Sent to the delegate to determine whether the log in request should be submitted to the server. + + @param logInController The login view controller that is requesting the data. + @param username the username the user tries to log in with. + @param password the password the user tries to log in with. + + @return A `BOOL` indicating whether the log in should proceed. + */ +- (BOOL)logInViewController:(PFLogInViewController *)logInController +shouldBeginLogInWithUsername:(NSString *)username + password:(NSString *)password; + +///-------------------------------------- +/// @name Responding to Actions +///-------------------------------------- + +/** + Sent to the delegate when a `PFUser` is logged in. + + @param logInController The login view controller where login finished. + @param user `PFUser` object that is a result of the login. + */ +- (void)logInViewController:(PFLogInViewController *)logInController didLogInUser:(PFUser *)user; + +/** + Sent to the delegate when the log in attempt fails. + + If you implement this method, PFLoginViewController will not automatically show its default + login failure alert view. Instead, you should show your custom alert view in your implementation. + + @param logInController The login view controller where login failed. + @param error `NSError` object representing the error that occured. + */ +- (void)logInViewController:(PFLogInViewController *)logInController didFailToLogInWithError:(nullable NSError *)error; + +/** + Sent to the delegate when the log in screen is cancelled. + + @param logInController The login view controller where login was cancelled. + */ +- (void)logInViewControllerDidCancelLogIn:(PFLogInViewController *)logInController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/LogInViewController/PFLogInViewController.m b/ParseUI/Classes/LogInViewController/PFLogInViewController.m new file mode 100644 index 000000000..7799968f8 --- /dev/null +++ b/ParseUI/Classes/LogInViewController/PFLogInViewController.m @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFLogInViewController.h" + +#import + +#import "PFActionButton.h" +#import "PFUIAlertView.h" +#import "PFLocalization.h" +#import "PFPrimaryButton.h" +#import "PFSignUpViewController.h" +#import "PFTextField.h" +#import "PFLogInView_Private.h" + +NSString *const PFLogInSuccessNotification = @"com.parse.ui.login.success"; +NSString *const PFLogInFailureNotification = @"com.parse.ui.login.failure"; +NSString *const PFLogInCancelNotification = @"com.parse.ui.login.cancel"; + +/** + This protocol exists so that we can weakly refer to messages to pass to PFFacebookUtils without + actually taking a dependency on the symbols. + */ +@protocol WeaklyReferencedFBUtils + +// FBSDKv3 ++ (void)logInWithPermissions:(NSArray *)permissions block:(PFUserResultBlock)block; +// FBSDKv4 ++ (void)logInInBackgroundWithReadPermissions:(NSArray *)permissions block:(PFUserResultBlock)block; ++ (void)logInInBackgroundWithPublishPermissions:(NSArray *)permissions block:(PFUserResultBlock)block; + +@end + +@protocol WeaklyReferenceTwitterUtils + ++ (void)logInWithBlock:(PFUserResultBlock)block; + +@end + +@interface PFLogInViewController () { + struct { + BOOL shouldBeginLogIn : YES; + BOOL didLogInUser : YES; + BOOL didFailToLogIn : YES; + BOOL didCancelLogIn : YES; + } _delegateExistingMethods; +} + +@property (nonatomic, strong, readwrite) PFLogInView *logInView; +@property (nonatomic, assign) BOOL loading; + +@property (nonatomic, assign) CGFloat visibleKeyboardHeight; + +@end + +@implementation PFLogInViewController + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +- (instancetype)init { + if (self = [super init]) { + [self _commonInit]; + } + return self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + [self _commonInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + [self _commonInit]; + } + return self; +} + +- (void)_commonInit { + self.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + self.modalPresentationStyle = UIModalPresentationFormSheet; + _fields = PFLogInFieldsDefault; + + _facebookPermissions = @[ @"public_profile" ]; + + if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) { + self.automaticallyAdjustsScrollViewInsets = NO; + } +} + +///-------------------------------------- +#pragma mark - Dealloc +///-------------------------------------- + +- (void)dealloc { + // Unregister from all notifications + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +///-------------------------------------- +#pragma mark - UIViewController +///-------------------------------------- + +- (void)loadView { + _logInView = [[PFLogInView alloc] initWithFields:_fields]; + [_logInView setPresentingViewController:self]; + + UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_dismissKeyboard)]; + [_logInView addGestureRecognizer:gestureRecognizer]; + gestureRecognizer.cancelsTouchesInView = NO; + + self.view = _logInView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self _setupLogInViewActions]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self _registerForKeyboardNotifications]; + + if (self.navigationController && + self.fields & PFLogInFieldsDismissButton) { + self.fields = self.fields & ~PFLogInFieldsDismissButton; + + [_logInView.dismissButton removeFromSuperview]; + } +} + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return UIInterfaceOrientationMaskAll; + } + + return UIInterfaceOrientationMaskPortrait; +} + +///-------------------------------------- +#pragma mark - PFLogInViewController +///-------------------------------------- + +- (PFLogInView *)logInView { + return (PFLogInView *)self.view; // self.view will call loadView if the view is nil +} + +- (void)setEmailAsUsername:(BOOL)otherEmailAsUsername { + self.logInView.emailAsUsername = otherEmailAsUsername; +} + +- (BOOL)emailAsUsername { + return self.logInView.emailAsUsername; +} + +- (void)setFields:(PFLogInFields)fields { + if (_fields != fields) { + _fields = fields; + + // Avoid force loading logInView + if (_logInView) { + _logInView.fields = fields; + [self _setupLogInViewActions]; + } + } +} + +- (void)setDelegate:(id)delegate { + if (self.delegate != delegate) { + _delegate = delegate; + + _delegateExistingMethods.shouldBeginLogIn = [delegate respondsToSelector:@selector(logInViewController: + shouldBeginLogInWithUsername: + password:)]; + _delegateExistingMethods.didLogInUser = [delegate respondsToSelector:@selector(logInViewController: + didLogInUser:)]; + _delegateExistingMethods.didFailToLogIn = [delegate respondsToSelector:@selector(logInViewController: + didFailToLogInWithError:)]; + _delegateExistingMethods.didCancelLogIn = [delegate + respondsToSelector:@selector(logInViewControllerDidCancelLogIn:)]; + } +} + +- (PFSignUpViewController *)signUpController { + if (!_signUpController) { + _signUpController = [[PFSignUpViewController alloc] init]; + _signUpController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + _signUpController.emailAsUsername = self.emailAsUsername; + } + return _signUpController; +} + +- (void)setLoading:(BOOL)loading { + if (self.loading != loading) { + _loading = loading; + + _logInView.usernameField.enabled = !self.loading; + _logInView.passwordField.enabled = !self.loading; + _logInView.passwordForgottenButton.enabled = !self.loading; + _logInView.dismissButton.enabled = !self.loading; + } +} + +///-------------------------------------- +#pragma mark - UITextFieldDelegate +///-------------------------------------- + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + if (textField == _logInView.usernameField) { + [_logInView.passwordField becomeFirstResponder]; + } + if (textField == _logInView.passwordField) { + [self _dismissKeyboard]; + [self _loginAction]; + } + + return YES; +} + +///-------------------------------------- +#pragma mark - Private +///-------------------------------------- + +- (void)_setupLogInViewActions { + [_logInView.dismissButton removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents]; + [_logInView.dismissButton addTarget:self action:@selector(_dismissAction) forControlEvents:UIControlEventTouchUpInside]; + + _logInView.usernameField.delegate = self; + _logInView.passwordField.delegate = self; + + [_logInView.logInButton removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents]; + [_logInView.logInButton addTarget:self action:@selector(_loginAction) forControlEvents:UIControlEventTouchUpInside]; + + [_logInView.passwordForgottenButton removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents]; + [_logInView.passwordForgottenButton addTarget:self action:@selector(_forgotPasswordAction) forControlEvents:UIControlEventTouchUpInside]; + + [_logInView.facebookButton removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents]; + [_logInView.facebookButton addTarget:self action:@selector(_loginWithFacebook) forControlEvents:UIControlEventTouchUpInside]; + + [_logInView.twitterButton removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents]; + [_logInView.twitterButton addTarget:self action:@selector(_loginWithTwitter) forControlEvents:UIControlEventTouchUpInside]; + + [_logInView.signUpButton removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents]; + [_logInView.signUpButton addTarget:self action:@selector(_signupAction) forControlEvents:UIControlEventTouchUpInside]; +} + +- (void)_dismissAction { + [self cancelLogIn]; + + // Normally the role of dismissing a modal controller lies on the presenting controller. + // Here we violate the principle so that the presenting modal log in controller is especially easy. + // Cons of this design is that it makes working with non-modally presented log in controller hard; + // but this concern is mitigated by the fact that navigationally presented controller should not have + // dismiss button. + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)_forgotPasswordAction PF_EXTENSION_UNAVAILABLE("") { + NSString *title = PFLocalizedString(@"Reset Password", @"Forgot password request title in PFLogInViewController"); + NSString *message = PFLocalizedString(@"Please enter the email address for your account.", + @"Email request message in PFLogInViewController"); + [PFUIAlertView presentAlertInViewController:self + withTitle:title + message:message + textFieldCustomizationHandler:^(UITextField * _Nonnull textField) { + textField.placeholder = PFLocalizedString(@"Email", @"Email"); + textField.keyboardType = UIKeyboardTypeEmailAddress; + textField.returnKeyType = UIReturnKeyDone; + } + cancelButtonTitle:PFLocalizedString(@"Cancel", @"Cancel") + otherButtonTitles:@[ PFLocalizedString(@"OK", @"OK")] + completion:^(UITextField * _Nonnull textField, NSUInteger selectedOtherButtonIndex) { + if (selectedOtherButtonIndex != NSNotFound) { + NSString *email = textField.text; + [self _requestPasswordResetWithEmail:email]; + } + }]; +} + +- (void)_requestPasswordResetWithEmail:(NSString *)email { + [PFUser requestPasswordResetForEmailInBackground:email block:^(BOOL success, NSError *error) { + if (success) { + NSString *title = PFLocalizedString(@"Password Reset", + @"Password reset success alert title in PFLogInViewController."); + NSString *message = [NSString stringWithFormat:PFLocalizedString(@"An email with reset instructions has been sent to '%@'.", + @"Password reset message in PFLogInViewController"), email]; + [PFUIAlertView presentAlertInViewController:self withTitle:title message:message]; + } else { + NSString *title = PFLocalizedString(@"Password Reset Failed", + @"Password reset error alert title in PFLogInViewController."); + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; + } + }]; +} + +#pragma mark Log In With Facebook + +- (void)_loginWithFacebook { + if (self.loading) { + return; + } + + self.loading = YES; + if ([_logInView.facebookButton isKindOfClass:[PFActionButton class]]) { + [(PFActionButton *)_logInView.facebookButton setLoading:YES]; + } + + __weak typeof(self) wself = self; + PFUserResultBlock resultBlock = ^(PFUser *user, NSError *error) { + __strong typeof(wself) sself = wself; + sself.loading = NO; + if ([_logInView.facebookButton isKindOfClass:[PFActionButton class]]) { + [(PFActionButton *)_logInView.facebookButton setLoading:NO]; + } + + if (user) { + [sself _loginDidSucceedWithUser:user]; + } else if (error) { + [sself _loginDidFailWithError:error]; + } else { + // User cancelled login. + } + }; + + Class fbUtils = NSClassFromString(@"PFFacebookUtils"); + if ([fbUtils respondsToSelector:@selector(logInWithPermissions:block:)]) { + // Facebook SDK v3 Login + [fbUtils logInWithPermissions:_facebookPermissions block:resultBlock]; + } else if ([fbUtils respondsToSelector:@selector(logInInBackgroundWithReadPermissions:block:)]) { + // Facebook SDK v4 Login + if ([self _permissionsContainsFacebookPublishPermission:_facebookPermissions]) { + [fbUtils logInInBackgroundWithPublishPermissions:_facebookPermissions block:resultBlock]; + } else { + [fbUtils logInInBackgroundWithReadPermissions:_facebookPermissions block:resultBlock]; + } + } else { + [NSException raise:NSInternalInconsistencyException + format:@"Can't find PFFacebookUtils. Please link with ParseFacebookUtils or ParseFacebookUtilsV4 to enable login with Facebook."]; + } +} + +- (BOOL)_permissionsContainsFacebookPublishPermission:(NSArray *)permissions { + for (NSString *permission in permissions) { + if ([permission hasPrefix:@"publish"] || + [permission hasPrefix:@"manage"] || + [permission isEqualToString:@"ads_management"] || + [permission isEqualToString:@"create_event"] || + [permission isEqualToString:@"rsvp_event"]) { + return YES; + } + } + return NO; +} + +#pragma mark Log In With Twitter + +- (void)_loginWithTwitter { + if (self.loading) { + return; + } + + if ([_logInView.facebookButton isKindOfClass:[PFActionButton class]]) { + [(PFActionButton *)_logInView.twitterButton setLoading:YES]; + } + self.loading = YES; + + Class twitterUtils = NSClassFromString(@"PFTwitterUtils"); + if (twitterUtils && [twitterUtils respondsToSelector:@selector(logInWithBlock:)]) { + [twitterUtils logInWithBlock:^(PFUser *user, NSError *error) { + self.loading = NO; + if ([_logInView.facebookButton isKindOfClass:[PFActionButton class]]) { + [(PFActionButton *)_logInView.twitterButton setLoading:NO]; + } + + if (user) { + [self _loginDidSucceedWithUser:user]; + } else if (error) { + [self _loginDidFailWithError:error]; + } else { + // User cancelled login. + } + }]; + } else { + [NSException raise:NSInternalInconsistencyException + format:@"Can't find PFTwitterUtils. Please link with ParseTwitterUtils to enable login with Twitter."]; + } +} + +#pragma mark Log In + +- (void)_loginAction { + if (self.loading) { + return; + } + + NSString *username = _logInView.usernameField.text ?: @""; + NSString *password = _logInView.passwordField.text ?: @""; + + if (_delegateExistingMethods.shouldBeginLogIn) { + if (![_delegate logInViewController:self shouldBeginLogInWithUsername:username password:password]) { + return; + } + } + + self.loading = YES; + if ([_logInView.logInButton isKindOfClass:[PFPrimaryButton class]]) { + [(PFActionButton *)_logInView.logInButton setLoading:YES]; + } + + [PFUser logInWithUsernameInBackground:username password:password block:^(PFUser *user, NSError *error) { + self.loading = NO; + if ([_logInView.logInButton isKindOfClass:[PFPrimaryButton class]]) { + [(PFPrimaryButton *)_logInView.logInButton setLoading:NO]; + } + + if (user) { + [self _loginDidSucceedWithUser:user]; + } else { + [self _loginDidFailWithError:error]; + } + }]; +} + +- (void)_signupAction { + if (self.loading) { + return; + } + [self presentViewController:self.signUpController animated:YES completion:nil]; +} + +- (void)_loginDidSucceedWithUser:(PFUser *)user { + if (_delegateExistingMethods.didLogInUser) { + [_delegate logInViewController:self didLogInUser:user]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:PFLogInSuccessNotification object:self]; +} + +- (void)_loginDidFailWithError:(NSError *)error { + if (_delegateExistingMethods.didFailToLogIn) { + [_delegate logInViewController:self didFailToLogInWithError:error]; + } else { + NSString *title = PFLocalizedString(@"Login Failed", @"Login failed alert title in PFLogInViewController"); + NSString *message = nil; + if (error.code == kPFErrorObjectNotFound) { + message = PFLocalizedString(@"The username and password you entered don't match", @"Invalid login credentials alert message in PFLogInViewController"); + } else { + message = PFLocalizedString(@"Please try again", @"Generic login failed alert message in PFLogInViewController"); + } + [PFUIAlertView presentAlertInViewController:self withTitle:title message:message]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:PFLogInFailureNotification object:self]; +} + +- (void)cancelLogIn { + if (_delegateExistingMethods.didCancelLogIn) { + [_delegate logInViewControllerDidCancelLogIn:self]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:PFLogInCancelNotification object:self]; +} + +///-------------------------------------- +#pragma mark - Keyboard +///-------------------------------------- + +- (UIView *)currentFirstResponder { + if ([_logInView.usernameField isFirstResponder]) { + return _logInView.usernameField; + } + if ([_logInView.passwordField isFirstResponder]) { + return _logInView.passwordField; + } + return nil; +} + +- (void)_dismissKeyboard { + [self.view endEditing:YES]; +} + +- (void)_registerForKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)_keyboardWillShow:(NSNotification *)notification { + NSDictionary *userInfo = [notification userInfo]; + CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + CGFloat duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + + CGRect keyboardFrame = [self.view convertRect:endFrame fromView:self.view.window]; + CGFloat visibleKeyboardHeight = CGRectGetMaxY(self.view.bounds) - CGRectGetMinY(keyboardFrame); + + [self setVisibleKeyboardHeight:visibleKeyboardHeight + animationDuration:duration + animationOptions:curve << 16]; +} + +- (void)_keyboardWillHide:(NSNotification *)notification { + NSDictionary *userInfo = [notification userInfo]; + CGFloat duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + [self setVisibleKeyboardHeight:0.0 + animationDuration:duration + animationOptions:curve << 16]; +} + +- (void)setVisibleKeyboardHeight:(CGFloat)visibleKeyboardHeight + animationDuration:(NSTimeInterval)animationDuration + animationOptions:(UIViewAnimationOptions)animationOptions { + + dispatch_block_t animationsBlock = ^{ + self.visibleKeyboardHeight = visibleKeyboardHeight; + }; + + if (animationDuration == 0.0) { + animationsBlock(); + } else { + [UIView animateWithDuration:animationDuration + delay:0.0 + options:animationOptions | UIViewAnimationOptionBeginFromCurrentState + animations:animationsBlock + completion:nil]; + } +} + +- (void)setVisibleKeyboardHeight:(CGFloat)visibleKeyboardHeight { + if (self.visibleKeyboardHeight != visibleKeyboardHeight) { + _visibleKeyboardHeight = visibleKeyboardHeight; + [self _updateViewContentOffsetAnimated:NO]; + } +} + +- (void)_updateViewContentOffsetAnimated:(BOOL)animated { + CGPoint contentOffset = CGPointZero; + if (self.visibleKeyboardHeight > 0.0f) { + // Scroll the view to keep fields visible + CGFloat offsetForScrollingTextFieldToTop = CGRectGetMinY([self currentFirstResponder].frame); + + UIView *lowestView; + if (_logInView.passwordForgottenButton) { + lowestView = _logInView.passwordForgottenButton; + } else if (_logInView.logInButton) { + lowestView = _logInView.logInButton; + } else { + lowestView = _logInView.passwordField; + } + + CGFloat offsetForScrollingLowestViewToBottom = 0.0f; + offsetForScrollingLowestViewToBottom += self.visibleKeyboardHeight; + offsetForScrollingLowestViewToBottom += CGRectGetMaxY(lowestView.frame); + offsetForScrollingLowestViewToBottom -= CGRectGetHeight(_logInView.bounds); + + if (offsetForScrollingLowestViewToBottom < 0) { + return; // No scrolling required + } + + contentOffset = CGPointMake(0.0f, MIN(offsetForScrollingTextFieldToTop, offsetForScrollingLowestViewToBottom)); + } + + [_logInView setContentOffset:contentOffset animated:animated]; +} + +@end diff --git a/ParseUI/Classes/LogInViewController/PFLogInView_Private.h b/ParseUI/Classes/LogInViewController/PFLogInView_Private.h new file mode 100644 index 000000000..7cae2dd6e --- /dev/null +++ b/ParseUI/Classes/LogInViewController/PFLogInView_Private.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifdef COCOAPODS +#import "PFLogInView.h" +#else +#import +#endif + + +@interface PFLogInView (Private) + +@property (nonatomic, assign, readwrite) PFLogInFields fields; + +@end diff --git a/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.h b/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.h new file mode 100644 index 000000000..3611f37e6 --- /dev/null +++ b/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#import "PFQueryTableViewController.h" +#else +#import +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + `PFProductTableViewController` displays in-app purchase products stored on Parse. + In addition to setting up in-app purchases in iTunes Connect, the app developer needs + to register product information on Parse, in the Product class. + */ +@interface PFProductTableViewController : PFQueryTableViewController + +/** + Initializes a product table view controller. + + @param style The UITableViewStyle for the table + + @return An initialized `PFProductTableViewController` object or `nil` if the object couldn't be created. + */ +- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.m b/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.m new file mode 100644 index 000000000..a542caedf --- /dev/null +++ b/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.m @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFProductTableViewController.h" + +#import +#import +#import + +#import "PFUIAlertView.h" +#import "PFLocalization.h" +#import "PFPurchaseTableViewCell.h" + +static NSString *const PFProductMetadataPriceKey = @"price"; +static NSString *const PFProductMetadataPriceLocaleKey = @"priceLocale"; + +@interface PFProductTableViewController () { + NSMutableDictionary *_productMetadataDictionary; + NSMutableDictionary *_productProgressDictionary; + + SKProductsRequest *_storeProductsRequest; +} + +@end + +@implementation PFProductTableViewController + +#pragma mark - +#pragma mark NSObject + +- (instancetype)initWithStyle:(UITableViewStyle)style { + if (self = [super initWithStyle:UITableViewStylePlain className:[PFProduct parseClassName]]) { + self.pullToRefreshEnabled = NO; + self.paginationEnabled = NO; + + _productMetadataDictionary = [NSMutableDictionary dictionary]; + _productProgressDictionary = [NSMutableDictionary dictionary]; + } + return self; +} + +- (instancetype)initWithStyle:(UITableViewStyle)style className:(NSString *)className { + return [self initWithStyle:style]; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.rowHeight = 84.0f; +} + +- (void)objectsDidLoad:(NSError *)error { + [super objectsDidLoad:error]; + if (error) { + return; + } + + [self.objects enumerateObjectsUsingBlock:^(PFProduct *product, NSUInteger idx, BOOL *stop) { + // No download for this product - just continue + if (!product.downloadName) { + return; + } + + [PFPurchase addObserverForProduct:product.productIdentifier block:^(SKPaymentTransaction *transaction) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0]; + PFPurchaseTableViewCell *cell = (PFPurchaseTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; + + cell.state = PFPurchaseTableViewCellStateDownloading; + [PFPurchase downloadAssetForTransaction:transaction + completion:^(NSString *filePath, NSError *downloadError) { + if (!downloadError) { + cell.state = PFPurchaseTableViewCellStateDownloaded; + } else { + cell.state = PFPurchaseTableViewCellStateNormal; + + NSString *title = PFLocalizedString(@"Download Error", + @"Download Error"); + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; + } + } + progress:^(int percentDone) { + _productProgressDictionary[product.productIdentifier] = @(percentDone); + [cell.progressView setProgress:percentDone/100.0f animated:YES]; + }]; + }]; + }]; +} + +#pragma mark - +#pragma mark UITableViewDataSource + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(PFProduct *)product { + static NSString *cellIdentifier = @"PFPurchaseTableViewCell"; + + PFPurchaseTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (!cell) { + cell = [[PFPurchaseTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:cellIdentifier]; + } + + if (indexPath.row % 2 == 0) { + cell.backgroundView.backgroundColor = [UIColor colorWithWhite:242.0f/255.0f alpha:1.0f]; + cell.textLabel.shadowOffset = CGSizeZero; + cell.textLabel.shadowColor = [UIColor whiteColor]; + cell.priceLabel.shadowOffset = CGSizeZero; + cell.priceLabel.shadowColor = [UIColor whiteColor]; + + } else { + cell.backgroundView.backgroundColor = [UIColor colorWithWhite:232.0f/255.0f alpha:1.0f]; + cell.textLabel.shadowOffset = CGSizeMake(0.0f, 1.0f); + cell.textLabel.shadowColor = [UIColor whiteColor]; + cell.priceLabel.shadowOffset = CGSizeMake(0.0f, 1.0f); + cell.priceLabel.shadowColor = [UIColor whiteColor]; + } + + cell.imageView.file = product.icon; + cell.textLabel.text = product.title; + cell.detailTextLabel.text = product.subtitle; + + NSString *price = [self _priceForProduct:product]; + if (price) { + cell.priceLabel.text = [NSString stringWithFormat:@" $%@ ", price]; + } + + if (product.downloadName) { + NSString *contentPath = [PFPurchase assetContentPathForProduct:product]; + if (contentPath) { + cell.state = PFPurchaseTableViewCellStateDownloaded; + } + } else { + int progress = [self _downloadProgressForProduct:product]; + if (progress == 0) { + cell.state = PFPurchaseTableViewCellStateNormal; + } else if (progress == 100) { + cell.state = PFPurchaseTableViewCellStateDownloaded; + } else { + cell.state = PFPurchaseTableViewCellStateDownloading; + } + } + + return cell; +} + +#pragma mark - +#pragma mark UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row < self.objects.count) { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + PFProduct *product = self.objects[indexPath.row]; + [PFPurchase buyProduct:product.productIdentifier block:^(NSError *error) { + if (error) { + NSString *title = PFLocalizedString(@"Purchase Error", @"Purchase Error"); + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; + } + }]; + } +} + +#pragma mark - +#pragma mark Data + +- (NSString *)_priceForProduct:(PFProduct *)product { + return _productMetadataDictionary[product.productIdentifier][PFProductMetadataPriceKey]; +} + +- (int)_downloadProgressForProduct:(PFProduct *)product { + return [_productProgressDictionary[product.productIdentifier] intValue]; +} + +#pragma mark - +#pragma mark PFQueryTableViewController + +- (PFQuery *)queryForTable { + PFQuery *query = [super queryForTable]; + [query orderByAscending:@"order"]; + return query; +} + +#pragma mark - +#pragma mark Querying Store + +- (void)_queryStoreForProductsWithIdentifiers:(NSSet *)identifiers { + _storeProductsRequest.delegate = nil; + _storeProductsRequest = nil; + + _storeProductsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers]; + _storeProductsRequest.delegate = self; + [_storeProductsRequest start]; +} + +#pragma mark - +#pragma mark SKProductsRequestDelegate + +- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { + if (request != _storeProductsRequest) { + return; + } + + NSArray *validProducts = response.products; + if ([validProducts count] == 0) { + return; + } + + [validProducts enumerateObjectsUsingBlock:^(SKProduct *product, NSUInteger idx, BOOL *stop) { + NSDictionary *metadata = @{ PFProductMetadataPriceKey : product.price, + PFProductMetadataPriceLocaleKey : product.priceLocale }; + _productMetadataDictionary[product.productIdentifier] = metadata; + }]; + [self.tableView reloadData]; + + _storeProductsRequest.delegate = nil; +} + +- (void)requestDidFinish:(SKRequest *)request { + _storeProductsRequest.delegate = nil; + _storeProductsRequest = nil; +} + +- (void)request:(SKRequest *)request didFailWithError:(NSError *)error { + _storeProductsRequest.delegate = nil; + _storeProductsRequest = nil; +} + +@end diff --git a/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.h b/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.h new file mode 100644 index 000000000..10b6f1e71 --- /dev/null +++ b/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#else +#import +#endif + + +NS_ASSUME_NONNULL_BEGIN + +@class BFTask<__covariant BFGenericType>; +@class PFCollectionViewCell; +@class PFObject; +@class PFQuery; + +/** + This class allows you to think about a one-to-one mapping between a `PFObject` and a `UICollectionViewCell`, + rather than having to juggle index paths. + + You also get the following features out of the box: + + - Pagination with a cell that can be tapped to load the next page. + - Pull-to-refresh collection view header. + - Automatic downloading and displaying of remote images in cells. + - Loading screen, shown before any data is loaded. + - Automatic loading and management of the objects array. + - Various methods that can be overridden to customize behavior at major events in the data cycle. + + @see `PFCollectionViewCell` + */ +@interface PFQueryCollectionViewController : UICollectionViewController + +/** + The class name of the `PFObject` this collection will use as a datasource. + */ +@property (nullable, nonatomic, copy) IBInspectable NSString *parseClassName; + +/** + Whether the collection should use the default loading view. Default - `YES`. + */ +@property (nonatomic, assign) IBInspectable BOOL loadingViewEnabled; + +/** + Whether the collection should use the built-in pull-to-refresh feature. Default - `YES`. + */ +@property (nonatomic, assign) IBInspectable BOOL pullToRefreshEnabled NS_AVAILABLE_IOS(10.0); + +/** + Whether the collection should use the built-in pagination feature. Default - `YES`. + */ +@property (nonatomic, assign) IBInspectable BOOL paginationEnabled; + +/** + The number of objects to show per page. Default - `25`. + */ +@property (nonatomic, assign) IBInspectable NSUInteger objectsPerPage; + +/** + Whether the collection is actively loading new data from the server. + */ +@property (nonatomic, assign, getter=isLoading) BOOL loading; + +///-------------------------------------- +/// @name Creating a PFQueryCollectionViewController +///-------------------------------------- + +/** + Initializes a view controller with a `UICollectionViewFlowLayout` and a class name + of `PFObject` that will be associated with this collection. + + @param className The class name of the instances of `PFObject` that this table will display. + + @return An initialized `PFQueryCollectionViewController` object or `nil` if the object couldn't be created. + */ +- (instancetype)initWithClassName:(nullable NSString *)className; + +/** + Initializes a view controller with a class name of `PFObject` that will be associated with this collection. + + @param layout Layout for collection view to use. + @param className The class name of the instances of `PFObject` that this table will display. + + @return An initialized `PFQueryCollectionViewController` object or `nil` if the object couldn't be created. + */ +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout + className:(nullable NSString *)className NS_DESIGNATED_INITIALIZER; + +///-------------------------------------- +/// @name Responding to Events +///-------------------------------------- + +/** + Called when objects will be loaded from Parse. If you override this method, you must + call [super objectsWillLoad] in your implementation. + */ +- (void)objectsWillLoad NS_REQUIRES_SUPER; + +/** + Called when objects have loaded from Parse. If you override this method, you must + call [super objectsDidLoad:] in your implementation. + @param error The Parse error from running the PFQuery, if there was any. + */ +- (void)objectsDidLoad:(nullable NSError *)error NS_REQUIRES_SUPER; + +///-------------------------------------- +/// @name Accessing Results +///-------------------------------------- + +/** + The array of instances of `PFObject` that is used as a data source. + */ +@property (nonatomic, copy, readonly) NSArray<__kindof PFObject *> *objects; + +/** + Returns an object at a particular indexPath. + + The default impementation returns the object at `indexPath.item`. + If you want to return objects in a different indexPath order, like for sections, override this method. + + @param indexPath An instance of `NSIndexPath`. + + @return The object at the specified indexPath. + */ +- (nullable PFObject *)objectAtIndexPath:(nullable NSIndexPath *)indexPath; + +/** + Removes an object at the specified index path, animated. + */ +- (void)removeObjectAtIndexPath:(nullable NSIndexPath *)indexPath; + +/** + Removes all objects at the specified index paths, animated. + */ +- (void)removeObjectsAtIndexPaths:(nullable NSArray *)indexes; + +///-------------------------------------- +/// @name Loading Data +///-------------------------------------- + +/** + Clears the collection view and loads the first page of objects. + + @return An awaitable task that completes when the reload succeeds + */ +- (BFTask *> *)loadObjects; + +/** + Loads the objects of the `PFObject.parseClassName` at the specified page and appends it to the + objects already loaded and refreshes the collection. + + @param page The page of objects to load. + @param clear Whether to clear the collection view after receiving the objects. + + @return An awaitable task that completes when the reload succeeds + */ +- (BFTask *> *)loadObjects:(NSInteger)page clear:(BOOL)clear; + +/** + Loads the next page of objects, appends to table, and refreshes. + */ +- (void)loadNextPage; + +/** + Clears the collection view of all objects. + */ +- (void)clear; + +///-------------------------------------- +/// @name Querying +///-------------------------------------- + +/** + Override to construct your own custom `PFQuery` to get the objects. + + @return An instance of `PFQuery` that `-loadObjects` method will use to the objects for this collection. + */ +- (PFQuery *)queryForCollection; + +///-------------------------------------- +/// @name Data Source Methods +///-------------------------------------- + +/** + Override this method to customize each cell given a `PFObject` that is loaded. + + @warning The cell should inherit from `PFCollectionViewCell` which is a subclass of `UICollectionViewCell`. + + @param collectionView The collection view object associated with this controller. + @param indexPath The indexPath of the cell. + @param object The `PFObject` that is associated with the cell. + + @return The cell that represents this object. + */ +- (nullable PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(nullable PFObject *)object; + +/** + Override this method to customize the view that allows the user to load the + next page when pagination is turned on. + + @param collectionView The collection view object associated with this controller. + + @return The view that allows the user to paginate. + */ +- (nullable UICollectionReusableView *)collectionViewReusableViewForNextPageAction:(UICollectionView *)collectionView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.m b/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.m new file mode 100644 index 000000000..0120461a9 --- /dev/null +++ b/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.m @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFQueryCollectionViewController.h" + +#import +#import + +#import + +#import "PFActivityIndicatorCollectionReusableView.h" +#import "PFCollectionViewCell.h" +#import "PFImageView.h" +#import "PFLoadingView.h" +#import "PFLocalization.h" +#import "PFUIAlertView.h" + +static NSString *const PFQueryCollectionViewCellIdentifier = @"cell"; +static NSString *const PFQueryCollectionViewNextPageReusableViewIdentifier = @"nextPageView"; + +@interface PFQueryCollectionViewController () { + NSMutableArray *_mutableObjects; + + BOOL _firstLoad; // Whether we have loaded the first set of objects + NSInteger _currentPage; // The last page that was loaded + NSInteger _lastLoadCount; // The count of objects from the last load. +} + +@property (nonatomic, strong) PFLoadingView *loadingView; + +@property (nonatomic, strong) PFActivityIndicatorCollectionReusableView *currentNextPageView; + +- (instancetype)initWithCoder:(NSCoder *)decoder NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; + +@end + +@implementation PFQueryCollectionViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithCoder:(NSCoder *)decoder { + // initWithCoder is usually a parallel designated initializer, as is the case here + // It's used by storyboard + self = [super initWithCoder:decoder]; + if (!self) return nil; + + [self _setupWithClassName:nil]; + + return self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + // This is used by interface builder + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (!self) return nil; + + [self _setupWithClassName:nil]; + + return self; +} + +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { + return[self initWithCollectionViewLayout:layout className:nil]; +} + +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout className:(NSString *)className { + self = [super initWithCollectionViewLayout:layout]; + if (!self) return nil; + + [self _setupWithClassName:className]; + + return self; +} + +- (instancetype)initWithClassName:(NSString *)className { + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + return [self initWithCollectionViewLayout:layout className:className]; +} + +- (void)_setupWithClassName:(NSString *)otherClassName { + _mutableObjects = [NSMutableArray array]; + _firstLoad = YES; + + // Set some reasonable defaults + _objectsPerPage = 25; + _loadingViewEnabled = YES; + _paginationEnabled = YES; + _pullToRefreshEnabled = YES; + _lastLoadCount = -1; + + _parseClassName = [otherClassName copy]; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)loadView { + [super loadView]; + + self.collectionView.backgroundColor = [UIColor whiteColor]; + + [self.collectionView registerClass:[PFCollectionViewCell class] + forCellWithReuseIdentifier:PFQueryCollectionViewCellIdentifier]; + [self.collectionView registerClass:[PFActivityIndicatorCollectionReusableView class] + forSupplementaryViewOfKind:UICollectionElementKindSectionFooter + withReuseIdentifier:PFQueryCollectionViewNextPageReusableViewIdentifier]; + + if (@available(iOS 10.0, *)) { + if (self.pullToRefreshEnabled) { + self.collectionView.refreshControl = [[UIRefreshControl alloc] init]; + [self.collectionView.refreshControl addTarget:self + action:@selector(_refreshControlValueChanged:) + forControlEvents:UIControlEventValueChanged]; + self.collectionView.alwaysBounceVertical = YES; + } + } +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self loadObjects]; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + self.loadingView.frame = self.collectionView.bounds; +} + +#pragma mark - +#pragma mark Responding to Events + +- (void)objectsWillLoad { + [self _refreshLoadingView]; +} + +- (void)objectsDidLoad:(NSError *)error { + [self _refreshLoadingView]; + _firstLoad = NO; +} + +#pragma mark - +#pragma mark Accessing Results + +- (NSArray *)objects { + return _mutableObjects; +} + +- (PFObject *)objectAtIndexPath:(NSIndexPath *)indexPath { + return self.objects[indexPath.row]; +} + +#pragma mark - +#pragma mark Removing Objects + +- (void)removeObjectAtIndexPath:(NSIndexPath *)indexPath { + [self removeObjectsAtIndexPaths:@[ indexPath ]]; +} + +- (void)removeObjectsAtIndexPaths:(NSArray *)indexPaths { + if (indexPaths.count == 0) { + return; + } + + // We need the contents as both an index set and a list of index paths. + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + + for (NSIndexPath *indexPath in indexPaths) { + if (indexPath.section != 0) { + [NSException raise:NSRangeException format:@"Index Path section %lu out of range!", (long)indexPath.section]; + } + + if (indexPath.row >= self.objects.count) { + [NSException raise:NSRangeException format:@"Index Path row %lu out of range!", (long)indexPath.row]; + } + + [indexes addIndex:indexPath.row]; + } + + BFContinuationBlock deletionHandlerBlock = ^id (BFTask *task) { + if (@available(iOS 10.0, *)) { + self.collectionView.refreshControl.enabled = YES; + } + + if (task.error) { + [self _handleDeletionError:task.error]; + } + + return nil; + }; + + NSMutableArray *allDeletionTasks = [NSMutableArray arrayWithCapacity:indexes.count]; + NSArray *objectsToRemove = [self.objects objectsAtIndexes:indexes]; + + // Remove the contents from our local cache so we can give the user immediate feedback. + [_mutableObjects removeObjectsInArray:objectsToRemove]; + [self.collectionView deleteItemsAtIndexPaths:indexPaths]; + + for (id obj in objectsToRemove) { + [allDeletionTasks addObject:[obj deleteInBackground]]; + } + + [[BFTask taskForCompletionOfAllTasks:allDeletionTasks] + continueWithBlock:deletionHandlerBlock]; +} + +#pragma mark - +#pragma mark Loading Data + +- (BFTask *> *)loadObjects { + return [self loadObjects:0 clear:YES]; +} + +- (BFTask *> *)loadObjects:(NSInteger)page clear:(BOOL)clear { + self.loading = YES; + [self objectsWillLoad]; + + PFQuery *query = [self queryForCollection]; + [self _alterQuery:query forLoadingPage:page]; + + BFTaskCompletionSource *> *source = [BFTaskCompletionSource taskCompletionSource]; + [query findObjectsInBackgroundWithBlock:^(NSArray *foundObjects, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (![Parse isLocalDatastoreEnabled] && + query.cachePolicy != kPFCachePolicyCacheOnly && + error.code == kPFErrorCacheMiss) { + // no-op on cache miss + return; + } + + self.loading = NO; + + if (error) { + _lastLoadCount = -1; + _currentNextPageView.animating = NO; + } else { + _currentPage = page; + _lastLoadCount = [foundObjects count]; + + if (clear) { + [_mutableObjects removeAllObjects]; + } + + [_mutableObjects addObjectsFromArray:foundObjects]; + [self.collectionView reloadData]; + } + + [self objectsDidLoad:error]; + if (@available(iOS 10.0, *)) { + [self.collectionView.refreshControl endRefreshing]; + } + + if (error) { + [source trySetError:error]; + } else { + [source trySetResult:foundObjects]; + } + }); + }]; + return source.task; +} + +- (void)loadNextPage { + if (!self.loading) { + [self loadObjects:(_currentPage + 1) clear:NO]; + _currentNextPageView.animating = YES; + } +} + +- (void)clear { + [_mutableObjects removeAllObjects]; + [self.collectionView reloadData]; + _currentPage = 0; +} + +#pragma mark - +#pragma mark Querying + +- (PFQuery *)queryForCollection { + if (!self.parseClassName) { + [NSException raise:NSInternalInconsistencyException + format:@"You need to specify a parseClassName for the PFQueryTableViewController.", nil]; + } + + PFQuery *query = [PFQuery queryWithClassName:self.parseClassName]; + + // If no objects are loaded in memory, we look to the cache first to fill the table + // and then subsequently do a query against the network. + if ([self.objects count] == 0 && ![Parse isLocalDatastoreEnabled]) { + query.cachePolicy = kPFCachePolicyCacheThenNetwork; + } + + [query orderByDescending:@"createdAt"]; + + return query; +} + +- (void)_alterQuery:(PFQuery *)query forLoadingPage:(NSInteger)page { + if (self.paginationEnabled && self.objectsPerPage) { + query.limit = self.objectsPerPage; + query.skip = page * self.objectsPerPage; + } +} + +#pragma mark - +#pragma mark Data Source Methods + +- (PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + PFCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:PFQueryCollectionViewCellIdentifier + forIndexPath:indexPath]; + [cell updateFromObject:object]; + return cell; +} + +- (UICollectionReusableView *)collectionViewReusableViewForNextPageAction:(UICollectionView *)collectionView { + _currentNextPageView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter + withReuseIdentifier:PFQueryCollectionViewNextPageReusableViewIdentifier + forIndexPath:[self _indexPathForPaginationReusableView]]; + _currentNextPageView.textLabel.text = PFLocalizedString(@"Load more...", @"Load more..."); + [_currentNextPageView addTarget:self action:@selector(loadNextPage) forControlEvents:UIControlEventTouchUpInside]; + _currentNextPageView.animating = self.loading; + return _currentNextPageView; +} + +#pragma mark - +#pragma mark UICollectionViewDataSource + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return 1; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return [self.objects count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + return [self collectionView:collectionView cellForItemAtIndexPath:indexPath object:[self objectAtIndexPath:indexPath]]; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView + viewForSupplementaryElementOfKind:(NSString *)kind + atIndexPath:(NSIndexPath *)indexPath { + return [self collectionViewReusableViewForNextPageAction:collectionView]; +} + +#pragma mark - +#pragma mark UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { + if ([self _shouldShowPaginationView]) { + return CGSizeMake(CGRectGetWidth(collectionView.bounds), 50.0f); + } + return CGSizeZero; +} + +#pragma mark - +#pragma mark Pagination + +- (BOOL)_shouldShowPaginationView { + return (self.paginationEnabled && + [self.objects count] != 0 && + (_lastLoadCount == -1 || _lastLoadCount >= (NSInteger)self.objectsPerPage)); +} + +- (NSIndexPath *)_indexPathForPaginationReusableView { + return [NSIndexPath indexPathForItem:0 inSection:[self numberOfSectionsInCollectionView:self.collectionView] - 1]; +} + +#pragma mark - +#pragma mark Error handling + +- (void)_handleDeletionError:(NSError *)error { + // Fully reload on error. + [self loadObjects]; + + NSString *message = [NSString stringWithFormat:@"%@: \"%@\"", + PFLocalizedString(@"Error occurred during deletion", @"Error occurred during deletion"), + error.localizedDescription]; + [PFUIAlertView presentAlertInViewController:self withTitle:PFLocalizedString(@"Delete Error", @"Delete Error") message:message]; +} + +#pragma mark - +#pragma mark Actions + +- (void)_refreshControlValueChanged:(UIRefreshControl *)refreshControl { + if (!self.loading) { + [self loadObjects]; + } +} + +#pragma mark - +#pragma mark Loading View + +- (void)_refreshLoadingView { + BOOL showLoadingView = self.loadingViewEnabled && self.loading && _firstLoad; + + if (showLoadingView) { + [self.collectionView addSubview:self.loadingView]; + [self.view setNeedsLayout]; + } else { + // Avoid loading `loadingView` - just use an ivar. + if (_loadingView) { + [self.loadingView removeFromSuperview]; + self.loadingView = nil; + } + } +} + +- (PFLoadingView *)loadingView { + if (!_loadingView) { + _loadingView = [[PFLoadingView alloc] initWithFrame:CGRectZero]; + } + return _loadingView; +} + +@end diff --git a/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.h b/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.h new file mode 100644 index 000000000..662a51b51 --- /dev/null +++ b/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.h @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#else +#import +#endif + + +NS_ASSUME_NONNULL_BEGIN + +@class BFTask<__covariant BFGenericType>; +@class PFObject; +@class PFQuery; +@class PFTableViewCell; + +/** + This class allows you to think about a one-to-one mapping between a `PFObject` and a `UITableViewCell`, + rather than having to juggle index paths. + + You also get the following features out of the box: + + - Pagination with a cell that can be tapped to load the next page. + - Pull-to-refresh table view header. + - Automatic downloading and displaying of remote images in cells. + - Loading screen, shown before any data is loaded. + - Automatic loading and management of the objects array. + - Various methods that can be overridden to customize behavior at major events in the data cycle. + */ +@interface PFQueryTableViewController : UITableViewController + +///-------------------------------------- +/// @name Creating a PFQueryTableViewController +///-------------------------------------- + +/** + Initializes with a class name of the `PFObject` that will be associated with this table. + + @param style The UITableViewStyle for the table + @param className The class name of the instances of `PFObject` that this table will display. + + @return An initialized `PFQueryTableViewController` object or `nil` if the object couldn't be created. + */ +- (instancetype)initWithStyle:(UITableViewStyle)style + className:(nullable NSString *)className NS_DESIGNATED_INITIALIZER; + +/** + Initializes with a class name of the PFObjects that will be associated with this table. + + @param className The class name of the instances of `PFObject` that this table will display. + + @return An initialized `PFQueryTableViewController` object or `nil` if the object couldn't be created. + */ +- (instancetype)initWithClassName:(nullable NSString *)className; + +///-------------------------------------- +/// @name Configuring Behavior +///-------------------------------------- + +/** + The class name of the `PFObject` this table will use as a datasource. + */ +@property (nullable, nonatomic, copy) IBInspectable NSString *parseClassName; + +/** + The key to use to display for the cell text label. + + This won't apply if you override `-tableView:cellForRowAtIndexPath:object:` + */ +@property (nullable, nonatomic, copy) IBInspectable NSString *textKey; + +/** + The key to use to display for the cell image view. + + This won't apply if you override `-tableView:cellForRowAtIndexPath:object:` + */ +@property (nullable, nonatomic, copy) IBInspectable NSString *imageKey; + +/** + The image to use as a placeholder for the cell images. + + This won't apply if you override `-tableView:cellForRowAtIndexPath:object:` + */ +@property (nullable, nonatomic, strong) IBInspectable UIImage *placeholderImage; + +/** + Whether the table should use the default loading view. Default - `YES`. + */ +@property (nonatomic, assign) IBInspectable BOOL loadingViewEnabled; + +/** + Whether the table should use the built-in pull-to-refresh feature. Default - `YES`. + */ +@property (nonatomic, assign) IBInspectable BOOL pullToRefreshEnabled; + +/** + Whether the table should use the built-in pagination feature. Default - `YES`. + */ +@property (nonatomic, assign) IBInspectable BOOL paginationEnabled; + +/** + The number of objects to show per page. Default - `25`. + */ +@property (nonatomic, assign) IBInspectable NSUInteger objectsPerPage; + +/** + Whether the table is actively loading new data from the server. + */ +@property (nonatomic, assign, getter=isLoading) BOOL loading; + +///-------------------------------------- +/// @name Responding to Events +///-------------------------------------- + +/** + Called when objects will loaded from Parse. If you override this method, you must + call [super objectsWillLoad] in your implementation. + */ +- (void)objectsWillLoad; + +/** + Called when objects have loaded from Parse. If you override this method, you must + call [super objectsDidLoad:] in your implementation. + @param error The Parse error from running the PFQuery, if there was any. + */ +- (void)objectsDidLoad:(nullable NSError *)error; + +///-------------------------------------- +/// @name Accessing Results +///-------------------------------------- + +/** + The array of instances of `PFObject` that is used as a data source. + */ +@property (nullable, nonatomic, copy, readonly) NSArray<__kindof PFObject *> *objects; + +/** + Returns an object at a particular indexPath. + + The default impementation returns the object at `indexPath.row`. + If you want to return objects in a different indexPath order, like for sections, override this method. + + @param indexPath The indexPath. + + @return The object at the specified index + */ +- (nullable PFObject *)objectAtIndexPath:(nullable NSIndexPath *)indexPath; + +/** + Removes an object at the specified index path, animated. + */ +- (void)removeObjectAtIndexPath:(nullable NSIndexPath *)indexPath; + +/** + Removes an object at the specified index path, with or without animation. + */ +- (void)removeObjectAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated; + +/** + Removes all objects at the specified index paths, animated. + */ +- (void)removeObjectsAtIndexPaths:(nullable NSArray *)indexPaths; + +/** + Removes all objects at the specified index paths, with or without animation. + */ +- (void)removeObjectsAtIndexPaths:(nullable NSArray *)indexPaths animated:(BOOL)animated; + +/** + Removes the cell at the specified indexPath, with or without animation. + Important: This method only removes the cell. It does not delete any PFObject on the backend. + */ +- (void)removeCellAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated; + +/** + Removes the cells at the specified indexPaths, with or without animation. + Important: This method only removes the cells. It does not delete any PFObject on the backend. + */ +- (void)removeCellsAtIndexPaths:(NSArray *)indexPaths animated:(BOOL)animated; + +/** + Clears the table of all objects. + */ +- (void)clear; + +/** + Clears the table and loads the first page of objects. + + @return An awaitable task that completes when the reload succeeds + */ +- (BFTask *> *)loadObjects; + +/** + Loads the objects of the className at the specified page and appends it to the + objects already loaded and refreshes the table. + + @param page The page of objects to load. + @param clear Whether to clear the table after receiving the objects + + @return An awaitable task that completes when the reload succeeds + */ +- (BFTask *> *)loadObjects:(NSInteger)page clear:(BOOL)clear; + +/** + Loads the next page of objects, appends to table, and refreshes. + */ +- (void)loadNextPage; + +///-------------------------------------- +/// @name Querying +///-------------------------------------- + +/** + Override to construct your own custom PFQuery to get the objects. + @result PFQuery that loadObjects will use to the objects for this table. + */ +- (PFQuery *)queryForTable; + +///-------------------------------------- +/// @name Data Source Methods +///-------------------------------------- + +/** + Override this method to customize each cell given a PFObject that is loaded. + + If you don't override this method, it will use a default style cell and display either + the first data key from the object, or it will display the key as specified with `textKey`, `imageKey`. + + @warning The cell should inherit from `PFTableViewCell` which is a subclass of `UITableViewCell`. + + @param tableView The table view object associated with this controller. + @param indexPath The indexPath of the cell. + @param object The PFObject that is associated with the cell. + + @return The cell that represents this object. + */ +- (nullable PFTableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(nullable PFObject *)object; + +/** + Override this method to customize the cell that allows the user to load the + next page when pagination is turned on. + + @param tableView The table view object associated with this controller. + @param indexPath The indexPath of the cell. + + @return The cell that allows the user to paginate. + */ +- (nullable PFTableViewCell *)tableView:(UITableView *)tableView cellForNextPageAtIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.m b/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.m new file mode 100644 index 000000000..60cde6267 --- /dev/null +++ b/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.m @@ -0,0 +1,580 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFQueryTableViewController.h" + +#import +#import +#import + +#import + +#import "PFActivityIndicatorTableViewCell.h" +#import "PFImageView.h" +#import "PFLoadingView.h" +#import "PFLocalization.h" +#import "PFTableViewCell.h" +#import "PFUIAlertView.h" + +// Add headers to kill any warnings. +// `initWithStyle:` is a UITableViewController method. +// `initWithCoder:`/`initWithNibName:bundle:` are UIViewController methods and are, for sure, available. +@interface UITableViewController () + +- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithCoder:(NSCoder *)decoder NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; + +@end + +@interface PFQueryTableViewController () { + NSMutableArray *_mutableObjects; + + BOOL _firstLoad; // Whether we have loaded the first set of objects + NSInteger _currentPage; // The last page that was loaded + NSInteger _lastLoadCount; // The count of objects from the last load. + // Set to -1 when objects haven't loaded, or there was an error. + UITableViewCellSeparatorStyle _savedSeparatorStyle; +} + +@property (nonatomic, strong) PFLoadingView *loadingView; + +- (instancetype)initWithCoder:(NSCoder *)decoder NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; + +@end + +@implementation PFQueryTableViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithCoder:(NSCoder *)decoder { + // initWithCoder is usually a parallel designated initializer, as is the case here + // It's used by storyboard + if (self = [super initWithCoder:decoder]) { + [self _setupWithClassName:nil]; + } + return self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + // This is used by interface builder + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + [self _setupWithClassName:nil]; + } + return self; +} + +- (instancetype)initWithStyle:(UITableViewStyle)style { + return [self initWithStyle:style className:nil]; +} + +- (instancetype)initWithClassName:(NSString *)className { + return [self initWithStyle:UITableViewStylePlain className:className]; +} + +- (instancetype)initWithStyle:(UITableViewStyle)style className:(NSString *)className { + if (self = [super initWithStyle:style]) { + [self _setupWithClassName:className]; + } + return self; +} + +- (void)_setupWithClassName:(NSString *)otherClassName { + _mutableObjects = [NSMutableArray array]; + _firstLoad = YES; + + // Set some reasonable defaults + _objectsPerPage = 25; + _loadingViewEnabled = YES; + _paginationEnabled = YES; + _pullToRefreshEnabled = YES; + _lastLoadCount = -1; + + _parseClassName = [otherClassName copy]; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)loadView { + [super loadView]; + + // Setup the Pull to Refresh UI if needed + if (self.pullToRefreshEnabled) { + UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; + [refreshControl addTarget:self + action:@selector(_refreshControlValueChanged:) + forControlEvents:UIControlEventValueChanged]; + self.refreshControl = refreshControl; + } +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self loadObjects]; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + self.loadingView.frame = self.tableView.bounds; +} + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated { + [self.tableView beginUpdates]; + + // If we're currently showing the pagination cell, we need to hide it during editing. + if ([self paginationEnabled] && [self _shouldShowPaginationCell]) { + [self.tableView deleteRowsAtIndexPaths:@[ [self _indexPathForPaginationCell] ] + withRowAnimation:UITableViewRowAnimationAutomatic]; + } + + [super setEditing:editing animated:animated]; + + // Ensure proper re-insertion of the pagination cell. + if ([self paginationEnabled] && [self _shouldShowPaginationCell]) { + [self.tableView insertRowsAtIndexPaths:@[ [self _indexPathForPaginationCell] ] + withRowAnimation:UITableViewRowAnimationAutomatic]; + } + + [self.tableView endUpdates]; +} + +#pragma mark - +#pragma mark Data + +- (void)objectsWillLoad { + if (_firstLoad) { + _savedSeparatorStyle = self.tableView.separatorStyle; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + } + [self _refreshLoadingView]; +} + +- (void)objectsDidLoad:(NSError *)error { + if (_firstLoad) { + _firstLoad = NO; + self.tableView.separatorStyle = _savedSeparatorStyle; + } + [self _refreshLoadingView]; +} + +- (PFQuery *)queryForTable { + if (!self.parseClassName) { + [NSException raise:NSInternalInconsistencyException + format:@"You need to specify a parseClassName for the PFQueryTableViewController.", nil]; + } + + PFQuery *query = [PFQuery queryWithClassName:self.parseClassName]; + + // If no objects are loaded in memory, we look to the cache first to fill the table + // and then subsequently do a query against the network. + if ([self.objects count] == 0 && ![Parse isLocalDatastoreEnabled]) { + query.cachePolicy = kPFCachePolicyCacheThenNetwork; + } + + [query orderByDescending:@"createdAt"]; + + return query; +} + +// Alters a query to add functionality like pagination +- (void)_alterQuery:(PFQuery *)query forLoadingPage:(NSInteger)page { + if (self.paginationEnabled && self.objectsPerPage) { + query.limit = self.objectsPerPage; + query.skip = page * self.objectsPerPage; + } +} + +- (void)clear { + dispatch_async(dispatch_get_main_queue(), ^{ + [_mutableObjects removeAllObjects]; + [self.tableView reloadData]; + _currentPage = 0; + }); +} + +- (BFTask *> *)loadObjects { + return [self loadObjects:0 clear:YES]; +} + +- (BFTask *> *)loadObjects:(NSInteger)page clear:(BOOL)clear { + self.loading = YES; + [self objectsWillLoad]; + + PFQuery *query = [self queryForTable]; + [self _alterQuery:query forLoadingPage:page]; + + BFTaskCompletionSource *> *source = [BFTaskCompletionSource taskCompletionSource]; + [query findObjectsInBackgroundWithBlock:^(NSArray *foundObjects, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (![Parse isLocalDatastoreEnabled] && + query.cachePolicy != kPFCachePolicyCacheOnly && + error.code == kPFErrorCacheMiss) { + // no-op on cache miss + return; + } + + self.loading = NO; + + if (error) { + _lastLoadCount = -1; + } else { + _currentPage = page; + _lastLoadCount = [foundObjects count]; + + if (clear) { + [_mutableObjects removeAllObjects]; + } + + [_mutableObjects addObjectsFromArray:foundObjects]; + } + [self.tableView reloadData]; + [self objectsDidLoad:error]; + [self.refreshControl endRefreshing]; + + if (error) { + [source trySetError:error]; + } else { + [source trySetResult:foundObjects]; + } + }); + }]; + + return source.task; +} + +- (void)loadNextPage { + if (!self.loading) { + [self loadObjects:(_currentPage + 1) clear:NO]; + } +} + +#pragma mark - +#pragma mark UIScrollViewDelegate + +// scrollViewDidEndDragging:willDecelerate: is called when a user stops dragging the table view. +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + // If the user lets go and the table view has no momentum, + // scrollViewDidEndDragging:willDecelerate: is called with willDecelerate:NO. + // In this case, we trigger a load for all the PFImageViews + // in our PFTableViewCells through _loadImagesForOnscreenRows. + if (!decelerate) { + [self _loadImagesForOnscreenRows]; + } +} + +// If the user lets go and the table view has momentum, +// scrollViewDidEndDragging:willDecelerate: is called with willDecelerate:YES. +// We will defer loading of images until scrollViewDidEndDecelerating: is called. +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + [self _loadImagesForOnscreenRows]; +} + +#pragma mark - +#pragma mark UITableViewDataSource + +// Return the number of rows in the section. +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSInteger count = [self.objects count]; + + if ([self _shouldShowPaginationCell]) { + count += 1; + } + + return count; +} + +// Default implementation that displays a default style cell +- (PFTableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + static NSString *cellIdentifier = @"PFTableViewCell"; + PFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (!cell) { + cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; + } + + [self tableView:tableView configureCell:cell atIndexPath:indexPath object:object]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView + configureCell:(PFTableViewCell *)cell + atIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + // Grab a key to display + NSString *key; + if (self.textKey) { + key = self.textKey; + } else if ([[object allKeys] count] > 0) { + key = [[object allKeys] objectAtIndex:0]; + } + + // Configure the cell + if (key) { + cell.textLabel.text = [NSString stringWithFormat:@"%@", [object objectForKey:key]]; + } + + if (self.placeholderImage) { + cell.imageView.image = self.placeholderImage; + } + + if (self.imageKey) { + cell.imageView.file = object[self.imageKey]; + } +} + +- (PFObject *)objectAtIndexPath:(NSIndexPath *)indexPath { + return self.objects[indexPath.row]; +} + +- (void)removeObjectAtIndexPath:(NSIndexPath *)indexPath { + [self removeObjectAtIndexPath:indexPath animated:YES]; +} + +- (void)removeObjectAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { + [self removeObjectsAtIndexPaths:@[ indexPath ] animated:animated]; +} + +- (void)removeObjectsAtIndexPaths:(NSArray *)indexPaths { + [self removeObjectsAtIndexPaths:indexPaths animated:YES]; +} + +- (void)removeCellAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated { + [self removeCellsAtIndexPaths:@[indexPath] animated: animated]; +} + +- (void)removeCellsAtIndexPaths:(NSArray *)indexPaths animated:(BOOL)animated { + NSMutableIndexSet *mutableIndexSet = [[NSMutableIndexSet alloc]init]; + + for (NSIndexPath *indexPath in indexPaths) { + [mutableIndexSet addIndex:indexPath.row]; + } + + [_mutableObjects removeObjectsAtIndexes:mutableIndexSet]; + + [self.tableView deleteRowsAtIndexPaths:indexPaths + withRowAnimation:animated ? UITableViewRowAnimationAutomatic : UITableViewRowAnimationNone]; +} + +- (void)removeObjectsAtIndexPaths:(NSArray *)indexPaths animated:(BOOL)animated { + if (indexPaths.count == 0) { + return; + } + + // We need the contents as both an index set and a list of index paths. + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + + for (NSIndexPath *indexPath in indexPaths) { + if (indexPath.section != 0) { + [NSException raise:NSRangeException format:@"Index Path section %lu out of range!", (long)indexPath.section]; + } + + if (indexPath.row >= self.objects.count) { + [NSException raise:NSRangeException format:@"Index Path row %lu out of range!", (long)indexPath.row]; + } + + [indexes addIndex:indexPath.row]; + } + + BFContinuationBlock deletionHandlerBlock = ^id (BFTask *task) { + self.refreshControl.enabled = YES; + if (task.error) { + [self _handleDeletionError:task.error]; + } + + return nil; + }; + + NSMutableArray *allDeletionTasks = [NSMutableArray arrayWithCapacity:indexes.count]; + NSArray *objectsToRemove = [self.objects objectsAtIndexes:indexes]; + + // Remove the contents from our local cache so we can give the user immediate feedback. + [_mutableObjects removeObjectsInArray:objectsToRemove]; + [self.tableView deleteRowsAtIndexPaths:indexPaths + withRowAnimation:animated ? UITableViewRowAnimationAutomatic : UITableViewRowAnimationNone]; + + for (id obj in objectsToRemove) { + [allDeletionTasks addObject:[obj deleteInBackground]]; + } + + [[BFTask taskForCompletionOfAllTasks:allDeletionTasks] continueWithExecutor:[BFExecutor mainThreadExecutor] + withBlock:deletionHandlerBlock]; +} + +- (PFTableViewCell *)tableView:(UITableView *)otherTableView cellForNextPageAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellIdentifier = @"PFTableViewCellNextPage"; + + PFActivityIndicatorTableViewCell *cell = [otherTableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[PFActivityIndicatorTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:cellIdentifier]; + cell.textLabel.text = PFLocalizedString(@"Load more...", @"Load more..."); + } + + cell.animating = self.loading; + + return cell; +} + +- (UITableViewCell *)tableView:(UITableView *)otherTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + PFTableViewCell *cell; + if ([self _shouldShowPaginationCell] && [indexPath isEqual:[self _indexPathForPaginationCell]]) { + // Return the pagination cell on the last cell + cell = [self tableView:otherTableView cellForNextPageAtIndexPath:indexPath]; + } else { + cell = [self tableView:otherTableView + cellForRowAtIndexPath:indexPath + object:[self objectAtIndexPath:indexPath]]; + } + + if ([cell isKindOfClass:[PFTableViewCell class]] && + !otherTableView.dragging && + !otherTableView.decelerating) { + // The reason we dispatch to the main queue is that we want to enable subclasses to override + // tableView:cellForRowAtIndexPath:object:, and we still do image loading after they assign + // the remote image file. + dispatch_async(dispatch_get_main_queue(), ^{ + [cell.imageView loadInBackground]; + }); + } + return cell; +} + +#pragma mark - +#pragma mark UITableViewDelegate + +- (void)tableView:(UITableView *)otherTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + // Handle selection of the next page row + if (!_firstLoad && + self.paginationEnabled && + [indexPath isEqual:[self _indexPathForPaginationCell]]) { + [self loadNextPage]; + } +} + +- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView + editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { + if ([indexPath isEqual:[self _indexPathForPaginationCell]]) { + return UITableViewCellEditingStyleNone; + } + + return UITableViewCellEditingStyleDelete; +} + +- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath { + if ([indexPath isEqual:[self _indexPathForPaginationCell]]) { + return NO; + } + + return YES; +} + +#pragma mark - +#pragma mark Private + +// Whether we need to show the pagination cell +- (BOOL)_shouldShowPaginationCell { + return (self.paginationEnabled && + !self.editing && + [self.objects count] != 0 && + (_lastLoadCount == -1 || _lastLoadCount >= (NSInteger)self.objectsPerPage)); +} + +// The row of the pagination cell +- (NSIndexPath *)_indexPathForPaginationCell { + return [NSIndexPath indexPathForRow:[self.objects count] inSection:0]; +} + +- (void)_loadImagesForOnscreenRows { + if (self.objects.count > 0) { + NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows]; + for (NSIndexPath *indexPath in visiblePaths) { + [self _loadImageForCellAtIndexPath:indexPath]; + } + } +} + +- (void)_loadImageForCellAtIndexPath:(NSIndexPath *)indexPath { + PFTableViewCell *cell = (PFTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; + if ([cell isKindOfClass:[PFTableViewCell class]]) { + [cell.imageView loadInBackground]; + } +} + +#pragma mark - +#pragma mark Error handling + +- (void)_handleDeletionError:(NSError *)error { + // Fully reload on error. + [self loadObjects]; + + NSString *message = [NSString stringWithFormat:@"%@: \"%@\"", + PFLocalizedString(@"Error occurred during deletion", @"Error occurred during deletion"), + error.localizedDescription]; + [PFUIAlertView presentAlertInViewController:self withTitle:PFLocalizedString(@"Delete Error", @"Delete Error") message:message]; +} + +#pragma mark - +#pragma mark Actions + +- (void)_refreshControlValueChanged:(UIRefreshControl *)refreshControl { + [self loadObjects]; +} + +#pragma mark - +#pragma mark Accessors + +- (NSArray<__kindof PFObject *> *)objects { + return _mutableObjects; +} + +#pragma mark - +#pragma mark Loading View + +- (void)_refreshLoadingView { + BOOL showLoadingView = self.loadingViewEnabled && self.loading && _firstLoad; + + if (showLoadingView) { + [self.tableView addSubview:self.loadingView]; + [self.view setNeedsLayout]; + } else { + // Avoid loading `loadingView` - just use an ivar. + if (_loadingView) { + [self.loadingView removeFromSuperview]; + self.loadingView = nil; + } + } +} + +- (PFLoadingView *)loadingView { + if (!_loadingView) { + _loadingView = [[PFLoadingView alloc] initWithFrame:CGRectZero]; + } + return _loadingView; +} + +@end diff --git a/ParseUI/Classes/SignUpViewController/PFSignUpView.h b/ParseUI/Classes/SignUpViewController/PFSignUpView.h new file mode 100644 index 000000000..b951a174b --- /dev/null +++ b/ParseUI/Classes/SignUpViewController/PFSignUpView.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#else +#import +#endif + + +NS_ASSUME_NONNULL_BEGIN + +/** + `PFSignUpFields` bitmask specifies the sign up elements which are enabled in the view. + + @see PFSignUpViewController + @see PFSignUpView + */ +typedef NS_OPTIONS(NSInteger, PFSignUpFields) { + /** Username and password fields. */ + PFSignUpFieldsUsernameAndPassword = 1 << 0, + /** Email field. */ + PFSignUpFieldsEmail = 1 << 1, + /** This field can be used for something else. */ + PFSignUpFieldsAdditional = 1 << 2, + /** Sign Up Button */ + PFSignUpFieldsSignUpButton = 1 << 3, + /** Dismiss Button */ + PFSignUpFieldsDismissButton = 1 << 4, + /** Default value. Combines Username, Password, Email, Sign Up and Dismiss Buttons. */ + PFSignUpFieldsDefault = (PFSignUpFieldsUsernameAndPassword | + PFSignUpFieldsEmail | + PFSignUpFieldsSignUpButton | + PFSignUpFieldsDismissButton) +}; + +/** + `PFSignUpFields`'s accessibity identifiers + + @see PFSignUpView + */ +extern NSString *const PFSignUpViewUsernameFieldAccessibilityIdentifier; +extern NSString *const PFSignUpViewEmailFieldAccessibilityIdentifier; +extern NSString *const PFSignUpViewPasswordFieldAccessibilityIdentifier; +extern NSString *const PFSignUpViewAdditionalFieldAccessibilityIdentifier; +extern NSString *const PFSignUpViewSignUpButtonAccessibilityIdentifier; +extern NSString *const PFSignUpViewDismissButtonAccessibilityIdentifier; + +@class PFTextField; + +/** + The `PFSignUpView` class provides a standard sign up interface for authenticating a `PFUser`. + */ +@interface PFSignUpView : UIScrollView + +///-------------------------------------- +/// @name Creating SignUp View +///-------------------------------------- + +/** + Initializes the view with the specified sign up elements. + + @param fields A bitmask specifying the sign up elements which are enabled in the view + + @return An initialized `PFSignUpView` object or `nil` if the object couldn't be created. + + @see PFSignUpFields + */ +- (instancetype)initWithFields:(PFSignUpFields)fields; + +/** + The view controller that will present this view. + + Used to lay out elements correctly when the presenting view controller has translucent elements. + */ +@property (nullable, nonatomic, weak) UIViewController *presentingViewController; + +///-------------------------------------- +/// @name Customizing the Logo +///-------------------------------------- + +/** + The logo. By default, it is the Parse logo. + */ +@property (nullable, nonatomic, strong) UIView *logo; + +///-------------------------------------- +/// @name Configure Username Behaviour +///-------------------------------------- + +/** + If email should be used to log in, instead of username + + By default, this is set to `NO`. + */ +@property (nonatomic, assign) BOOL emailAsUsername; + +///-------------------------------------- +/// @name Sign Up Elements +///-------------------------------------- + +/** + The bitmask which specifies the enabled sign up elements in the view + */ +@property (nonatomic, assign, readonly) PFSignUpFields fields; + +/** + The username text field. + */ +@property (nullable, nonatomic, strong, readonly) PFTextField *usernameField; + +/** + The password text field. + */ +@property (nullable, nonatomic, strong, readonly) PFTextField *passwordField; + +/** + The email text field. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) PFTextField *emailField; + +/** + The additional text field. It is `nil` if the element is not enabled. + + This field is intended to be customized. + */ +@property (nullable, nonatomic, strong, readonly) PFTextField *additionalField; + +/** + The sign up button. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *signUpButton; + +/** + The dismiss button. It is `nil` if the element is not enabled. + */ +@property (nullable, nonatomic, strong, readonly) UIButton *dismissButton; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/SignUpViewController/PFSignUpView.m b/ParseUI/Classes/SignUpViewController/PFSignUpView.m new file mode 100644 index 000000000..5a35639a2 --- /dev/null +++ b/ParseUI/Classes/SignUpViewController/PFSignUpView.m @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFSignUpView.h" + +#import "PFColor.h" +#import "PFDismissButton.h" +#import "PFImage.h" +#import "PFLocalization.h" +#import "PFPrimaryButton.h" +#import "PFRect.h" +#import "PFTextButton.h" +#import "PFTextField.h" + +static NSString *const PFSignUpViewDefaultLogoImageName = @"parse_logo.png"; + +///-------------------------------------- +#pragma mark - Accessibility Identifiers +///-------------------------------------- + +NSString *const PFSignUpViewUsernameFieldAccessibilityIdentifier = @"PFSignUpViewUsernameFieldAccessibilityIdentifier"; +NSString *const PFSignUpViewEmailFieldAccessibilityIdentifier = @"PFSignUpViewEmailFieldAccessibilityIdentifier"; +NSString *const PFSignUpViewPasswordFieldAccessibilityIdentifier = @"PFSignUpViewPasswordFieldAccessibilityIdentifier"; +NSString *const PFSignUpViewAdditionalFieldAccessibilityIdentifier = @"PFSignUpViewAdditionalFieldAccessibilityIdentifier"; +NSString *const PFSignUpViewSignUpButtonAccessibilityIdentifier = @"PFSignUpViewSignUpButtonAccessibilityIdentifier"; +NSString *const PFSignUpViewDismissButtonAccessibilityIdentifier = @"PFSignUpViewDismissButtonAccessibilityIdentifier"; + +@implementation PFSignUpView + +#pragma mark - +#pragma mark NSObject + +- (instancetype)initWithFields:(PFSignUpFields)otherFields { + self = [super init]; + if (!self) return nil; + + _fields = otherFields; + + self.opaque = YES; + self.backgroundColor = [PFColor commonBackgroundColor]; + + _logo = [[UIImageView alloc] initWithImage:[PFImage imageNamed:PFSignUpViewDefaultLogoImageName]]; + _logo.contentMode = UIViewContentModeScaleAspectFit; + [self addSubview:_logo]; + + if (_fields & PFSignUpFieldsDismissButton) { + _dismissButton = [[PFDismissButton alloc] initWithFrame:CGRectZero]; + _dismissButton.accessibilityIdentifier = PFSignUpViewDismissButtonAccessibilityIdentifier; + [self addSubview:_dismissButton]; + } + + _usernameField = [[PFTextField alloc] initWithFrame:CGRectZero + separatorStyle:(PFTextFieldSeparatorStyleTop | + PFTextFieldSeparatorStyleBottom)]; + _usernameField.accessibilityIdentifier = PFSignUpViewUsernameFieldAccessibilityIdentifier; + _usernameField.autocorrectionType = UITextAutocorrectionTypeNo; + _usernameField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _usernameField.returnKeyType = UIReturnKeyNext; + [self addSubview:_usernameField]; + [self _updateUsernameFieldStyle]; + + _passwordField = [[PFTextField alloc] initWithFrame:CGRectZero + separatorStyle:PFTextFieldSeparatorStyleBottom]; + _passwordField.accessibilityIdentifier = PFSignUpViewPasswordFieldAccessibilityIdentifier; + _passwordField.placeholder = PFLocalizedString(@"Password", @"Password"); + _passwordField.secureTextEntry = YES; + _passwordField.autocorrectionType = UITextAutocorrectionTypeNo; + _passwordField.autocapitalizationType = UITextAutocapitalizationTypeNone; + if (!(_fields & PFSignUpFieldsEmail) && !(_fields & PFSignUpFieldsAdditional)) { + _passwordField.returnKeyType = UIReturnKeyDone; + } else { + _passwordField.returnKeyType = UIReturnKeyNext; + } + [self addSubview:_passwordField]; + + if (_fields & PFSignUpFieldsEmail) { + _emailField = [[PFTextField alloc] initWithFrame:CGRectZero + separatorStyle:PFTextFieldSeparatorStyleBottom]; + _emailField.accessibilityIdentifier = PFSignUpViewEmailFieldAccessibilityIdentifier; + _emailField.autocorrectionType = UITextAutocorrectionTypeNo; + _emailField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _emailField.keyboardType = UIKeyboardTypeEmailAddress; + _emailField.placeholder = PFLocalizedString(@"Email", @"Email"); + if (!(_fields & PFSignUpFieldsAdditional)) { + _emailField.returnKeyType = UIReturnKeyDone; + } else { + _emailField.returnKeyType = UIReturnKeyNext; + } + [self addSubview:_emailField]; + } + + if (_fields & PFSignUpFieldsAdditional) { + _additionalField = [[PFTextField alloc] initWithFrame:CGRectZero + separatorStyle:PFTextFieldSeparatorStyleBottom]; + _additionalField.accessibilityIdentifier = PFSignUpViewAdditionalFieldAccessibilityIdentifier; + _additionalField.autocorrectionType = UITextAutocorrectionTypeNo; + _additionalField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _additionalField.placeholder = PFLocalizedString(@"Additional", @"Additional"); + _additionalField.returnKeyType = UIReturnKeyDone; + [self addSubview:_additionalField]; + } + + if (_fields & PFSignUpFieldsSignUpButton) { + _signUpButton = [[PFPrimaryButton alloc] initWithBackgroundImageColor:[PFColor signupButtonBackgroundColor]]; + _signUpButton.accessibilityIdentifier = PFSignUpViewSignUpButtonAccessibilityIdentifier; + [_signUpButton setTitle:PFLocalizedString(@"Sign Up", @"Sign Up") forState:UIControlStateNormal]; + [self addSubview:_signUpButton]; + } + + return self; +} + +#pragma mark - +#pragma mark UIView + +- (void)layoutSubviews { + [super layoutSubviews]; + + CGRect bounds = self.bounds; + + if (_dismissButton) { + CGPoint origin = CGPointMake(16.0f, 16.0f); + + // In iOS 7+, if the view controller that contains this view + // is presented modally, it's edges extend under the status bar. + // This lets us move down the dismiss button a bit so that it's not covered by the status bar. + if ([self.presentingViewController respondsToSelector:@selector(topLayoutGuide)]) { + origin.y += self.presentingViewController.topLayoutGuide.length; + } + + CGRect frame = PFRectMakeWithOriginSize(origin, [_dismissButton sizeThatFits:bounds.size]); + _dismissButton.frame = frame; + } + + const CGRect contentRect = PFRectMakeWithSizeCenteredInRect([self _contentSizeThatFits:bounds.size], + PFRectMakeWithSize(bounds.size)); + const CGSize contentSize = contentRect.size; + const CGSize contentSizeScale = [self _contentSizeScaleForContentSize:bounds.size]; + + CGFloat currentY = CGRectGetMinY(contentRect); + if (_logo) { + CGFloat logoTopInset = floorf(48.0f * contentSizeScale.height); + CGFloat logoBottomInset = floorf(36.0f * contentSizeScale.height); + + CGFloat logoAvailableHeight = floorf(68.0f * contentSizeScale.height); + + CGSize logoSize = [_logo sizeThatFits:CGSizeMake(contentSize.width, logoAvailableHeight)]; + logoSize.width = MIN(contentSize.width, logoSize.width); + logoSize.height = MIN(logoAvailableHeight, logoSize.height); + + CGRect frame = PFRectMakeWithSizeCenteredInRect(logoSize, contentRect); + frame.origin.y = CGRectGetMinY(contentRect) + logoTopInset; + _logo.frame = CGRectIntegral(frame); + + currentY = floorf(CGRectGetMaxY(frame) + logoBottomInset); + } + + if (_usernameField) { + CGRect frame = PFRectMakeWithSizeCenteredInRect([_usernameField sizeThatFits:contentSize], contentRect); + frame.origin.y = currentY; + _usernameField.frame = frame; + + currentY = CGRectGetMaxY(frame); + } + + if (_passwordField) { + CGRect frame = PFRectMakeWithSizeCenteredInRect([_passwordField sizeThatFits:contentSize], contentRect); + frame.origin.y = currentY; + _passwordField.frame = frame; + + currentY = CGRectGetMaxY(frame); + } + + if (_emailField && !_emailAsUsername) { + CGRect frame = PFRectMakeWithSizeCenteredInRect([_emailField sizeThatFits:contentSize], contentRect); + frame.origin.y = currentY; + _emailField.frame = frame; + + currentY = CGRectGetMaxY(frame); + } + + if (_additionalField) { + CGRect frame = PFRectMakeWithSizeCenteredInRect([_additionalField sizeThatFits:contentSize], contentRect); + frame.origin.y = currentY; + _additionalField.frame = frame; + + currentY = CGRectGetMaxY(frame); + } + + if (_signUpButton) { + CGFloat loginButtonTopInset = floorf(24.0f * contentSizeScale.height); + + CGRect frame = PFRectMakeWithSizeCenteredInRect([_signUpButton sizeThatFits:contentSize], contentRect);; + frame.origin.y = currentY + loginButtonTopInset; + _signUpButton.frame = frame; + + currentY = CGRectGetMaxY(frame); + } +} + +- (CGSize)_contentSizeThatFits:(CGSize)boundingSize { + CGSize maxContentSize = [self _maxContentSize]; + CGSize contentSizeScale = [self _contentSizeScaleForContentSize:boundingSize]; + + CGSize size = PFSizeMin(maxContentSize, boundingSize); + size.height = 0.0f; + if (_logo) { + CGFloat logoTopInset = floorf(36.0f * contentSizeScale.height); + CGFloat logoBottomInset = floorf(36.0f * contentSizeScale.height); + + CGFloat logoAvailableHeight = floorf(68.0f * contentSizeScale.height); + + CGFloat scale = MAX(contentSizeScale.width, contentSizeScale.height); + + CGSize logoSize = [_logo sizeThatFits:CGSizeMake(boundingSize.width, logoAvailableHeight)]; + logoSize.height *= scale; + logoSize.width *= scale; + + size.height += logoSize.height + logoTopInset + logoBottomInset; + } + if (_usernameField) { + CGSize fieldSize = [_usernameField sizeThatFits:boundingSize]; + size.height += fieldSize.height; + } + if (_passwordField) { + CGSize fieldSize = [_passwordField sizeThatFits:boundingSize]; + size.height += fieldSize.height; + } + if (_emailField && !_emailAsUsername) { + CGSize fieldSize = [_emailField sizeThatFits:boundingSize]; + size.height += fieldSize.height; + } + if (_additionalField) { + CGSize fieldSize = [_additionalField sizeThatFits:boundingSize]; + size.height += fieldSize.height; + } + if (_signUpButton) { + CGFloat buttonTopInset = floorf(24.0f * contentSizeScale.height); + + CGSize buttonSize = [_signUpButton sizeThatFits:boundingSize]; + + size.height += buttonSize.height + buttonTopInset; + } + + size.width = floorf(size.width); + size.height = floorf(size.height); + + return size; +} + +- (CGSize)_maxContentSize { + return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? + CGSizeMake(420.0f, 500.0f) : + CGSizeMake(500.0f, 800.0f)); +} + +- (CGSize)_contentSizeScaleForContentSize:(CGSize)contentSize { + CGSize maxContentSize = [self _maxContentSize]; + if (maxContentSize.width < contentSize.width && + maxContentSize.height < contentSize.height) { + return CGSizeMake(1.0f, 1.0f); + } + + CGSize contentSizeScale = CGSizeMake(contentSize.width / maxContentSize.width, + contentSize.height / maxContentSize.height); + return contentSizeScale; +} + +#pragma mark - +#pragma mark PFSignUpView + +- (void)setLogo:(UIView *)logo { + if (self.logo != logo) { + [_logo removeFromSuperview]; + _logo = logo; + [self addSubview:_logo]; + + [self setNeedsLayout]; + } +} + +- (void)setEmailAsUsername:(BOOL)otherEmailAsUsername { + if (_emailAsUsername != otherEmailAsUsername) { + _emailAsUsername = otherEmailAsUsername; + + if (_emailAsUsername) { + if (_emailField.superview) { + [_emailField removeFromSuperview]; + } + } else { + if (_emailField.superview == nil) { + [self addSubview:_emailField]; + } + [self setNeedsLayout]; + } + [self _updateUsernameFieldStyle]; + } +} + +#pragma mark - +#pragma mark Private + +- (void)_updateUsernameFieldStyle { + UIKeyboardType keyboardType = UIKeyboardTypeDefault; + NSString *placeholder = nil; + if (!_emailAsUsername) { + placeholder = PFLocalizedString(@"Username", @"Username"); + keyboardType = UIKeyboardTypeDefault; + } else { + placeholder = PFLocalizedString(@"Email", @"Email"); + keyboardType = UIKeyboardTypeEmailAddress; + } + + _usernameField.placeholder = placeholder; + _usernameField.keyboardType = keyboardType; +} + +@end diff --git a/ParseUI/Classes/SignUpViewController/PFSignUpViewController.h b/ParseUI/Classes/SignUpViewController/PFSignUpViewController.h new file mode 100644 index 000000000..24b2703a1 --- /dev/null +++ b/ParseUI/Classes/SignUpViewController/PFSignUpViewController.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#import "PFSignUpView.h" +#else +#import +#import +#endif + + +@class PFUser; +@protocol PFSignUpViewControllerDelegate; + +NS_ASSUME_NONNULL_BEGIN + +/** + The `PFSignUpViewController` class that presents and manages + a standard authentication interface for signing up a `PFUser`. + */ +@interface PFSignUpViewController : UIViewController + +///-------------------------------------- +/// @name Configuring Sign Up Elements +///-------------------------------------- + +/** + A bitmask specifying the log in elements which are enabled in the view. + + @see PFSignUpFields + */ +@property (nonatomic, assign) PFSignUpFields fields; + +/** + The sign up view. It contains all the enabled log in elements. + + @see PFSignUpView + */ +@property (nullable, nonatomic, strong, readonly) PFSignUpView *signUpView; + +///-------------------------------------- +/// @name Configuring Sign Up Behaviors +///-------------------------------------- + +/** + The delegate that responds to the control events of `PFSignUpViewController`. + + @see PFSignUpViewControllerDelegate + */ +@property (nullable, nonatomic, weak) id delegate; + +/** + Minimum required password length for user signups, defaults to `0`. + */ +@property (nonatomic, assign) NSUInteger minPasswordLength; + +/** + Whether to use the email as username on the attached `signUpView`. + + If set to `YES`, we'll hide the email field, prompt for the email in + the username field, and save the email into both username and email + fields on the new `PFUser` object. By default, this is set to `NO`. + */ +@property (nonatomic, assign) BOOL emailAsUsername; + +@end + +///-------------------------------------- +/// @name Notifications +///-------------------------------------- + +/** + The notification is posted immediately after the sign up succeeds. + */ +extern NSString *const PFSignUpSuccessNotification; + +/** + The notification is posted immediately after the sign up fails. + + If the delegate prevents the sign up to start, the notification is not sent. + */ +extern NSString *const PFSignUpFailureNotification; + +/** + The notification is posted immediately after the user cancels sign up. + */ +extern NSString *const PFSignUpCancelNotification; + +///-------------------------------------- +// @name Keys for info dictionary on `signUpViewController:shouldBeginSignUp` delegate method. +///-------------------------------------- + +/** + Username supplied during sign up. + */ +extern NSString *const PFSignUpViewControllerDelegateInfoUsernameKey; + +/** + Password supplied during sign up. + */ +extern NSString *const PFSignUpViewControllerDelegateInfoPasswordKey; + +/** + Email address supplied during sign up. + */ +extern NSString *const PFSignUpViewControllerDelegateInfoEmailKey; + +/** + Additional info supplied during sign up. + */ +extern NSString *const PFSignUpViewControllerDelegateInfoAdditionalKey; + +///-------------------------------------- +/// @name PFSignUpViewControllerDelegate +///-------------------------------------- + +/** + The `PFLogInViewControllerDelegate` protocol defines methods a delegate of a `PFSignUpViewController` should implement. + All methods of this protocol are optional. + */ +@protocol PFSignUpViewControllerDelegate + +@optional + +///-------------------------------------- +/// @name Customizing Behavior +///-------------------------------------- + +/** + Sent to the delegate to determine whether the sign up request should be submitted to the server. + + @param signUpController The signup view controller that is requesting the data. + @param info An `NSDictionary` instance which contains all sign up information that the user entered. + + @return A `BOOL` indicating whether the sign up should proceed. + */ +- (BOOL)signUpViewController:(PFSignUpViewController *)signUpController shouldBeginSignUp:(NSDictionary *)info; + +///-------------------------------------- +/// @name Responding to Actions +///-------------------------------------- + +/** + Sent to the delegate when a `PFUser` is signed up. + + @param signUpController The signup view controller where signup finished. + @param user `PFUser` object that is a result of the sign up. + */ +- (void)signUpViewController:(PFSignUpViewController *)signUpController didSignUpUser:(PFUser *)user; + +/** + Sent to the delegate when the sign up attempt fails. + + @param signUpController The signup view controller where signup failed. + @param error `NSError` object representing the error that occured. + */ +- (void)signUpViewController:(PFSignUpViewController *)signUpController didFailToSignUpWithError:(nullable NSError *)error; + +/** + Sent to the delegate when the sign up screen is cancelled. + + @param signUpController The signup view controller where signup was cancelled. + */ +- (void)signUpViewControllerDidCancelSignUp:(PFSignUpViewController *)signUpController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/SignUpViewController/PFSignUpViewController.m b/ParseUI/Classes/SignUpViewController/PFSignUpViewController.m new file mode 100644 index 000000000..52dae1465 --- /dev/null +++ b/ParseUI/Classes/SignUpViewController/PFSignUpViewController.m @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFSignUpViewController.h" + +#import +#import + +#import "PFUIAlertView.h" +#import "PFLocalization.h" +#import "PFPrimaryButton.h" +#import "PFTextField.h" + +NSString *const PFSignUpSuccessNotification = @"com.parse.ui.signup.success"; +NSString *const PFSignUpFailureNotification = @"com.parse.ui.signup.failure"; +NSString *const PFSignUpCancelNotification = @"com.parse.ui.signup.cancel"; + +// Keys that are used to pass information to the delegate on `signUpViewController:shouldBeginSignUp`. +NSString *const PFSignUpViewControllerDelegateInfoUsernameKey = @"username"; +NSString *const PFSignUpViewControllerDelegateInfoPasswordKey = @"password"; +NSString *const PFSignUpViewControllerDelegateInfoEmailKey = @"email"; +NSString *const PFSignUpViewControllerDelegateInfoAdditionalKey = @"additional"; + +@interface PFSignUpViewController () { + struct { + BOOL shouldSignUp : YES; + BOOL didSignUp : YES; + BOOL didFailToSignUp : YES; + BOOL didCancelSignUp : YES; + } _delegateExistingMethods; +} + +@property (nonatomic, strong, readwrite) PFSignUpView *signUpView; + +@property (nonatomic, assign) BOOL loading; + +@property (nonatomic, assign) CGFloat visibleKeyboardHeight; + +@end + +@implementation PFSignUpViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)init { + if (self = [super init]) { + [self _commonInit]; + } + return self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + [self _commonInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + [self _commonInit]; + } + return self; +} + +- (void)_commonInit { + self.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + self.modalPresentationStyle = UIModalPresentationFormSheet; + _fields = PFSignUpFieldsDefault; + + if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) { + self.automaticallyAdjustsScrollViewInsets = NO; + } +} + +#pragma mark - +#pragma mark Dealloc + +- (void)dealloc { + // Unregister from all notifications + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)loadView { + _signUpView = [[PFSignUpView alloc] initWithFields:_fields]; + _signUpView.presentingViewController = self; + self.view = _signUpView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self _setupHandlers]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self _registerForKeyboardNotifications]; + if (self.navigationController && + self.fields & PFSignUpFieldsDismissButton) { + self.fields = self.fields & ~PFSignUpFieldsDismissButton; + + [_signUpView.dismissButton removeFromSuperview]; + } +} + +#pragma mark - +#pragma mark Rotation + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return UIInterfaceOrientationMaskAll; + } + + return UIInterfaceOrientationMaskPortrait; +} + +#pragma mark - +#pragma mark PFSignUpViewController + +- (PFSignUpView *)signUpView { + return (PFSignUpView *)self.view; // self.view will call loadView if the view is nil +} + +- (void)setDelegate:(id)delegate { + if (self.delegate != delegate) { + _delegate = delegate; + + _delegateExistingMethods.shouldSignUp = [delegate respondsToSelector:@selector(signUpViewController: + shouldBeginSignUp:)]; + _delegateExistingMethods.didSignUp = [delegate respondsToSelector:@selector(signUpViewController: + didSignUpUser:)]; + _delegateExistingMethods.didFailToSignUp = [delegate respondsToSelector:@selector(signUpViewController: + didFailToSignUpWithError:)]; + _delegateExistingMethods.didCancelSignUp = [delegate + respondsToSelector:@selector(signUpViewControllerDidCancelSignUp:)]; + } +} + +- (void)setEmailAsUsername:(BOOL)otherEmailAsUsername { + self.signUpView.emailAsUsername = otherEmailAsUsername; +} + +- (BOOL)emailAsUsername { + return self.signUpView.emailAsUsername; +} + +#pragma mark - +#pragma mark UITextFieldDelegate + +- (void)textFieldDidBeginEditing:(UITextField *)textField { + [self _updateSignUpViewContentOffsetAnimated:YES]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + if (textField == _signUpView.usernameField) { + [_signUpView.passwordField becomeFirstResponder]; + return YES; + } + + if (textField == _signUpView.passwordField) { + if (_signUpView.emailField) { + [_signUpView.emailField becomeFirstResponder]; + return YES; + } else if (_signUpView.additionalField) { + [_signUpView.additionalField becomeFirstResponder]; + return YES; + } + } + + if (textField == _signUpView.emailField) { + if (_signUpView.additionalField) { + [_signUpView.additionalField becomeFirstResponder]; + return YES; + } + } + + [self _signUpAction]; + + return YES; +} + +#pragma mark - +#pragma mark Private + +- (void)_setupHandlers { + _signUpView.delegate = self; // UIScrollViewDelegate + [_signUpView.dismissButton addTarget:self + action:@selector(_dismissAction) + forControlEvents:UIControlEventTouchUpInside]; + _signUpView.usernameField.delegate = self; + _signUpView.passwordField.delegate = self; + _signUpView.emailField.delegate = self; + _signUpView.additionalField.delegate = self; + [_signUpView.signUpButton addTarget:self + action:@selector(_signUpAction) + forControlEvents:UIControlEventTouchUpInside]; + + UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] + initWithTarget:self + action:@selector(_dismissKeyboard)]; + gestureRecognizer.cancelsTouchesInView = NO; + [_signUpView addGestureRecognizer:gestureRecognizer]; +} + +- (void)_dismissAction { + [self _cancelSignUp]; + + // Normally the role of dismissing a modal controller lies on the presenting controller. + // Here we violate the principle so that the presenting modal log in controller is especially easy. + // Cons of this design is that it makes working with non-modally presented log in controller hard; + // but this concern is mitigated by the fact that navigationally presented controller should not have + // dismiss button. + + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)_signUpAction { + if (self.loading) { + return; + } + + [self _dismissKeyboard]; + + NSString *username = _signUpView.usernameField.text ?: @""; + NSString *password = _signUpView.passwordField.text ?: @""; + NSString *email = (self.emailAsUsername ? username : _signUpView.emailField.text); + email = [email stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + NSString *additional = _signUpView.additionalField.text; + + NSMutableDictionary *dictionary = [@{ PFSignUpViewControllerDelegateInfoUsernameKey : username, + PFSignUpViewControllerDelegateInfoPasswordKey : password } mutableCopy]; + + if (email) { + dictionary[PFSignUpViewControllerDelegateInfoEmailKey] = email; + } + if (additional) { + dictionary[PFSignUpViewControllerDelegateInfoAdditionalKey] = additional; + } + + if (_delegateExistingMethods.shouldSignUp) { + if (![_delegate signUpViewController:self shouldBeginSignUp:dictionary]) { + return; + } + } + + if ([password length] < _minPasswordLength) { + NSString *errorMessage = PFLocalizedString(@"Password must be at least %d characters.", + @"Password too short error message in PFSignUpViewController"); + errorMessage = [NSString stringWithFormat:errorMessage, (unsigned long)_minPasswordLength]; + NSError *error = [NSError errorWithDomain:PFParseErrorDomain + code:0 + userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; + [self _signUpDidFailWithError:error]; + [_signUpView.passwordField becomeFirstResponder]; + + return; + } + + PFUser *user = [PFUser user]; + user.username = username; + user.password = password; + + if (email) { + user.email = email; + } + if (additional) { + user[PFSignUpViewControllerDelegateInfoAdditionalKey] = additional; + } + + self.loading = YES; + if ([_signUpView.signUpButton isKindOfClass:[PFPrimaryButton class]]) { + [(PFPrimaryButton *)_signUpView.signUpButton setLoading:YES]; + } + [user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { + self.loading = NO; + if ([_signUpView.signUpButton isKindOfClass:[PFPrimaryButton class]]) { + [(PFPrimaryButton *)_signUpView.signUpButton setLoading:NO]; + } + + if (succeeded) { + [self _signUpDidSuceedWithUser:user]; + } + else { + [self _signUpDidFailWithError:error]; + } + }]; +} + +- (void)_signUpDidSuceedWithUser:(PFUser *)user { + if (_delegateExistingMethods.didSignUp) { + [_delegate signUpViewController:self didSignUpUser:user]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:PFSignUpSuccessNotification object:self]; +} + +- (void)_signUpDidFailWithError:(NSError *)error { + if (_delegateExistingMethods.didFailToSignUp) { + [_delegate signUpViewController:self didFailToSignUpWithError:error]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:PFSignUpFailureNotification object:self]; + + NSString *title = PFLocalizedString(@"Sign Up Error", @"Sign Up Error"); + + if ([[error domain] isEqualToString:PFParseErrorDomain]) { + NSInteger errorCode = [error code]; + NSString *message = nil; + UIResponder *responder = nil; + + if (errorCode == kPFErrorInvalidEmailAddress) { + message = PFLocalizedString(@"The email address is invalid. Please enter a valid email.", + @"Invalid email address error message in PFSignUpViewControllers"); + responder = _signUpView.emailField ?: _signUpView.usernameField; + } else if (errorCode == kPFErrorUsernameMissing) { + message = PFLocalizedString(@"Please enter a username.", + @"Username missing error message in PFSignUpViewController"); + responder = _signUpView.usernameField; + } else if (errorCode == kPFErrorUserPasswordMissing) { + message = PFLocalizedString(@"Please enter a password.", + @"Password missing error message in PFSignUpViewController"); + responder = _signUpView.passwordField; + } else if (errorCode == kPFErrorUsernameTaken) { + NSString *format = PFLocalizedString(@"The username '%@' is taken. Please try choosing a different username.", + @"Username taken error format in PFSignUpViewController"); + message = [NSString stringWithFormat:format, _signUpView.usernameField.text]; + responder = _signUpView.usernameField; + } else if (error.code == kPFErrorUserEmailTaken) { + NSString *format = PFLocalizedString(@"The email '%@' is taken. Please try using a different email.", + @"Email is taken error format in PFSignUpViewController."); + UITextField *textField = self.emailAsUsername ? _signUpView.usernameField : _signUpView.emailField; + + message = [NSString stringWithFormat:format, textField.text]; + responder = textField; + } else if (error.code == kPFErrorUserEmailMissing) { + message = PFLocalizedString(@"Please enter an email.", + @"Email missing error message in PFSignUpViewController"); + responder = _signUpView.emailField; + } + + if (message != nil) { + [PFUIAlertView presentAlertInViewController:self withTitle:title message:message]; + [responder becomeFirstResponder]; + + return; + } + } + + // Show the generic error alert, as no custom cases matched before + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; +} + +- (void)_cancelSignUp { + if (_delegateExistingMethods.didCancelSignUp) { + [_delegate signUpViewControllerDidCancelSignUp:self]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:PFSignUpCancelNotification object:self]; +} + +- (UIView *)_currentFirstResponder { + if ([_signUpView.usernameField isFirstResponder]) { + return _signUpView.usernameField; + } + if ([_signUpView.passwordField isFirstResponder]) { + return _signUpView.passwordField; + } + if ([_signUpView.emailField isFirstResponder]) { + return _signUpView.emailField; + } + if ([_signUpView.additionalField isFirstResponder]) { + return _signUpView.additionalField; + } + + return nil; +} + +#pragma mark Keyboard + +- (void)_dismissKeyboard { + [self.view endEditing:YES]; +} + +- (void)_registerForKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_keyboardWillHide:) + name:UIKeyboardWillHideNotification object:nil]; +} + +- (void)_keyboardWillShow:(NSNotification *)notification { + NSDictionary *userInfo = [notification userInfo]; + CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + CGFloat duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + + CGRect keyboardFrame = [self.view convertRect:endFrame fromView:self.view.window]; + CGFloat visibleKeyboardHeight = CGRectGetMaxY(self.view.bounds) - CGRectGetMinY(keyboardFrame); + + [self setVisibleKeyboardHeight:visibleKeyboardHeight + animationDuration:duration + animationOptions:curve << 16]; +} + +- (void)_keyboardWillHide:(NSNotification *)notification { + NSDictionary *userInfo = [notification userInfo]; + CGFloat duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + [self setVisibleKeyboardHeight:0.0 + animationDuration:duration + animationOptions:curve << 16]; +} + +- (void)setVisibleKeyboardHeight:(CGFloat)visibleKeyboardHeight + animationDuration:(NSTimeInterval)animationDuration + animationOptions:(UIViewAnimationOptions)animationOptions { + + dispatch_block_t animationsBlock = ^{ + self.visibleKeyboardHeight = visibleKeyboardHeight; + }; + + if (animationDuration == 0.0) { + animationsBlock(); + } else { + [UIView animateWithDuration:animationDuration + delay:0.0 + options:animationOptions | UIViewAnimationOptionBeginFromCurrentState + animations:animationsBlock + completion:nil]; + } +} + +- (void)setVisibleKeyboardHeight:(CGFloat)visibleKeyboardHeight { + if (self.visibleKeyboardHeight != visibleKeyboardHeight) { + _visibleKeyboardHeight = visibleKeyboardHeight; + [self _updateSignUpViewContentOffsetAnimated:NO]; + } +} + +- (void)_updateSignUpViewContentOffsetAnimated:(BOOL)animated { + CGPoint contentOffset = CGPointZero; + if (self.visibleKeyboardHeight > 0.0f) { + // Scroll the view to keep fields visible + CGFloat offsetForScrollingTextFieldToTop = CGRectGetMinY([self _currentFirstResponder].frame); + + UIView *lowestView; + if (_signUpView.signUpButton) { + lowestView = _signUpView.signUpButton; + } else if (_signUpView.additionalField) { + lowestView = _signUpView.additionalField; + } else if (_signUpView.emailField) { + lowestView = _signUpView.emailField; + } else { + lowestView = _signUpView.passwordField; + } + + CGFloat offsetForScrollingLowestViewToBottom = 0.0f; + offsetForScrollingLowestViewToBottom += self.visibleKeyboardHeight; + offsetForScrollingLowestViewToBottom += CGRectGetMaxY(lowestView.frame); + offsetForScrollingLowestViewToBottom -= CGRectGetHeight(_signUpView.bounds); + + if (offsetForScrollingLowestViewToBottom < 0) { + return; // No scrolling required + } + + contentOffset = CGPointMake(0.0f, MIN(offsetForScrollingTextFieldToTop, + offsetForScrollingLowestViewToBottom)); + } + + [_signUpView setContentOffset:contentOffset animated:animated]; +} + +#pragma mark - +#pragma mark Accessors + +- (void)setLoading:(BOOL)loading { + if (self.loading != loading) { + _loading = loading; + + _signUpView.usernameField.enabled = !self.loading; + _signUpView.passwordField.enabled = !self.loading; + _signUpView.emailField.enabled = !self.loading; + _signUpView.additionalField.enabled = !self.loading; + _signUpView.dismissButton.enabled = !self.loading; + } +} + +@end diff --git a/ParseUI/Classes/Views/PFImageView.h b/ParseUI/Classes/Views/PFImageView.h new file mode 100644 index 000000000..9ee03fdf2 --- /dev/null +++ b/ParseUI/Classes/Views/PFImageView.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#else +#import +#endif + + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^PFImageViewImageResultBlock)(UIImage *__nullable image, NSError *__nullable error); + +@class BFTask<__covariant BFGenericType>; +@class PFFile; + +/** + An image view that downloads and displays remote image stored on Parse's server. + */ +@interface PFImageView : UIImageView + +/** + The remote file on Parse's server that stores the image. + + @warning Note that the download does not start until `-loadInBackground:` is called. + */ +@property (nullable, nonatomic, strong) PFFile *file; + +/** + Initiate downloading of the remote image. + + Once the download completes, the remote image will be displayed. + + @return The task, that encapsulates the work being done. + */ +- (BFTask *)loadInBackground; + +/** + Initiate downloading of the remote image. + + Once the download completes, the remote image will be displayed. + + @param completion the completion block. + */ +- (void)loadInBackground:(nullable PFImageViewImageResultBlock)completion; + +/** + Initiate downloading of the remote image. + + Once the download completes, the remote image will be displayed. + + @param completion the completion block. + @param progressBlock called with the download progress as the image is being downloaded. + Will be called with a value of 100 before the completion block is called. + */ +- (void)loadInBackground:(nullable PFImageViewImageResultBlock)completion + progressBlock:(nullable void (^)(int percentDone))progressBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/Views/PFImageView.m b/ParseUI/Classes/Views/PFImageView.m new file mode 100644 index 000000000..f10a9aa12 --- /dev/null +++ b/ParseUI/Classes/Views/PFImageView.m @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFImageView.h" + +#import + +#import + +#import "PFImageCache.h" + +@implementation PFImageView + +#pragma mark - +#pragma mark Accessors + +- (void)setFile:(PFFile *)otherFile { + // Here we don't check (file != otherFile) + // because self.image needs to be updated regardless. + // setFile: could have altered self.image + _file = otherFile; + NSURL *url = [NSURL URLWithString:self.file.url]; + UIImage *cachedImage = [[PFImageCache sharedCache] imageForURL:url]; + if (cachedImage) { + self.image = cachedImage; + } +} + +#pragma mark - +#pragma mark Load + +- (BFTask *)loadInBackground { + BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; + [self loadInBackground:^(UIImage *image, NSError *error) { + if (error) { + [source trySetError:error]; + } else { + [source trySetResult:image]; + } + }]; + return source.task; +} + + +- (void)loadInBackground:(void (^)(UIImage *, NSError *))completion { + [self loadInBackground:completion progressBlock:nil]; +} + +- (void)loadInBackground:(void (^)(UIImage *, NSError *))completion progressBlock:(PFProgressBlock)progressBlock { + if (!self.file) { + // When there is nothing to load, the user just wants to display + // the placeholder image. I think the better design decision is + // to return with no error, to simplify caller logic. (arguable) + if (completion) { + completion(nil, nil); + } + return; + } + + if (!self.file.url) { + // The file has not been saved. + if (completion) { + NSError *error = [NSError errorWithDomain:PFParseErrorDomain code:kPFErrorUnsavedFile userInfo:nil]; + completion(nil, error); + } + return; + } + + NSURL *url = [NSURL URLWithString:self.file.url]; + if (url) { + UIImage *cachedImage = [[PFImageCache sharedCache] imageForURL:url]; + if (cachedImage) { + self.image = cachedImage; + + if (progressBlock) { + progressBlock(100); + } + if (completion) { + completion(cachedImage, nil); + } + return; + } + } + + + PFFile *file = _file; + [_file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) { + if (error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + } + return; + } + + // We dispatch to a background queue to offload the work to decode data into image + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + UIImage *image = [UIImage imageWithData:data]; + if (!image) { + if (completion) { + NSError *invalidDataError = [NSError errorWithDomain:PFParseErrorDomain + code:kPFErrorInvalidImageData + userInfo:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, invalidDataError); + }); + } + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + // check if a latter issued loadInBackground has not replaced the file being loaded + if (file == _file) { + self.image = image; + } + + if (completion) { + completion(image, nil); + } + }); + + if (url) { + // We always want to store the image in the cache. + // In previous checks we've verified neither key nor value is nil. + [[PFImageCache sharedCache] setImage:image forURL:url]; + } + }); + } progressBlock:progressBlock]; +} + +@end diff --git a/ParseUI/Classes/Views/PFTextField.h b/ParseUI/Classes/Views/PFTextField.h new file mode 100644 index 000000000..a587b5c12 --- /dev/null +++ b/ParseUI/Classes/Views/PFTextField.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#ifdef COCOAPODS +#import "ParseUIConstants.h" +#else +#import +#endif + + +NS_ASSUME_NONNULL_BEGIN + +/** + `PFTextFieldSeparatorStyle` bitmask specifies the style of the separators, + that should be used for a given `PFTextField`. + + @see PFTextField + */ +typedef NS_OPTIONS(uint8_t, PFTextFieldSeparatorStyle){ + /** No separators are visible. */ + PFTextFieldSeparatorStyleNone = 0, + /** Separator on top of the text field. */ + PFTextFieldSeparatorStyleTop = 1 << 0, + /** Separator at the bottom of the text field. */ + PFTextFieldSeparatorStyleBottom = 1 << 1 +}; + +/** + `PFTextField` class serves as a stylable subclass of `UITextField`. + It includes styles that are specific to `ParseUI` framework and allows advanced customization. + */ +@interface PFTextField : UITextField + +/** + Separator style bitmask that should be applied to this textfield. + + Default: `PFTextFieldSeparatorStyleNone` + + @see PFTextFieldSeparatorStyle + */ +@property (nonatomic, assign) PFTextFieldSeparatorStyle separatorStyle; + +/** + Color that should be used for the separators, if they are visible. + + Default: `227,227,227,1.0`. + */ +@property (nullable, nonatomic, strong) UIColor *separatorColor UI_APPEARANCE_SELECTOR; + +/** + This method is a convenience initializer that sets both `frame` and `separatorStyle` for an instance of `PFTextField.` + + @param frame The frame rectangle for the view, measured in points. + @param separatorStyle Initial separator style to use. + + @return An initialized instance of `PFTextField` or `nil` if it couldn't be created. + */ +- (instancetype)initWithFrame:(CGRect)frame separatorStyle:(PFTextFieldSeparatorStyle)separatorStyle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/Views/PFTextField.m b/ParseUI/Classes/Views/PFTextField.m new file mode 100644 index 000000000..7372cd5ba --- /dev/null +++ b/ParseUI/Classes/Views/PFTextField.m @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFTextField.h" + +#import "PFColor.h" + +@implementation PFTextField + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (!self) return nil; + + self.backgroundColor = [PFColor textFieldBackgroundColor]; + self.textColor = [PFColor textFieldTextColor]; + + self.font = [UIFont systemFontOfSize:17.0f]; + + self.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; + + _separatorColor = [PFColor textFieldSeparatorColor]; + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame separatorStyle:(PFTextFieldSeparatorStyle)separatorStyle { + self = [self initWithFrame:frame]; + if (!self) return nil; + + _separatorStyle = separatorStyle; + + return self; +} + +#pragma mark - +#pragma mark Accessors + +- (void)setPlaceholder:(NSString *)placeholder { + NSDictionary *attributes = @{ NSForegroundColorAttributeName : [PFColor textFieldPlaceholderColor] }; + self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:placeholder attributes:attributes]; +} + +- (void)setSeparatorStyle:(PFTextFieldSeparatorStyle)separatorStyle { + if (self.separatorStyle != separatorStyle) { + _separatorStyle = separatorStyle; + [self setNeedsDisplay]; + } +} + +#pragma mark - +#pragma mark Drawing + +- (void)drawRect:(CGRect)rect { + [super drawRect:rect]; + + const CGRect bounds = self.bounds; + CGContextRef context = UIGraphicsGetCurrentContext(); + + if (self.separatorStyle != PFTextFieldSeparatorStyleNone) { + [self.separatorColor setFill]; + } + + if (self.separatorStyle & PFTextFieldSeparatorStyleTop) { + CGRect borderRect = CGRectMake(0.0f, 0.0f, CGRectGetWidth(bounds), 1.0f); + CGContextFillRect(context, borderRect); + } + + if (self.separatorStyle & PFTextFieldSeparatorStyleBottom) { + CGRect borderRect = CGRectMake(0.0f, CGRectGetMaxY(bounds) - 1.0f, CGRectGetWidth(bounds), 1.0f); + CGContextFillRect(context, borderRect); + } +} + +#pragma mark - +#pragma mark Frame + +- (CGRect)textRectForBounds:(CGRect)bounds { + return CGRectMake(20.0f, 0.0f, CGRectGetWidth(bounds) - 30.0f, CGRectGetHeight(bounds)); +} + +- (CGRect)placeholderRectForBounds:(CGRect)bounds { + return [self textRectForBounds:bounds]; +} + +- (CGRect)editingRectForBounds:(CGRect)bounds { + return [self textRectForBounds:bounds]; +} + +#pragma mark - +#pragma mark Sizing + +- (CGSize)sizeThatFits:(CGSize)boundingSize { + CGSize size = CGSizeZero; + size.width = boundingSize.width; + size.height = MIN(44.0f, boundingSize.height); + return size; +} + +@end diff --git a/ParseUI/Configurations/ParseUI-iOS-Dynamic.xcconfig b/ParseUI/Configurations/ParseUI-iOS-Dynamic.xcconfig new file mode 100644 index 000000000..16f3af8ba --- /dev/null +++ b/ParseUI/Configurations/ParseUI-iOS-Dynamic.xcconfig @@ -0,0 +1,18 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/DynamicFramework.xcconfig" + +PRODUCT_NAME = ParseUI +PRODUCT_BUNDLE_IDENTIFIER = com.parse.parseui + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info.plist + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(SRCROOT)/Vendor diff --git a/ParseUI/Configurations/ParseUI-iOS.xcconfig b/ParseUI/Configurations/ParseUI-iOS.xcconfig new file mode 100644 index 000000000..bd8de1283 --- /dev/null +++ b/ParseUI/Configurations/ParseUI-iOS.xcconfig @@ -0,0 +1,18 @@ +// +// Copyright (c) 2015-present, Parse, LLC. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/StaticFramework.xcconfig" + +PRODUCT_NAME = ParseUI +PRODUCT_BUNDLE_IDENTIFIER = com.parse.parseui + +INFOPLIST_FILE = $(SRCROOT)/Resources/Info.plist + +FRAMEWORK_SEARCH_PATHS = $(inherited) $(SRCROOT)/Vendor diff --git a/ParseUI/Configurations/Shared b/ParseUI/Configurations/Shared new file mode 120000 index 000000000..657caabf0 --- /dev/null +++ b/ParseUI/Configurations/Shared @@ -0,0 +1 @@ +../../Vendor/xctoolchain/Configurations \ No newline at end of file diff --git a/ParseUI/Generated/PFResources.h b/ParseUI/Generated/PFResources.h new file mode 100644 index 000000000..41eb39a9a --- /dev/null +++ b/ParseUI/Generated/PFResources.h @@ -0,0 +1,13 @@ +// This is an auto-generated file. +#import +@interface PFResources : NSObject ++ (NSData *)twitter_icon3x_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)facebook_icon2x_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)parse_logo_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)facebook_icon3x_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)twitter_icon2x_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)parse_logo2x_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)parse_logo3x_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)facebook_icon_png;//modified:2017-11-15 22:08:43 -0500 ++ (NSData *)twitter_icon_png;//modified:2017-11-15 22:08:43 -0500 +@end diff --git a/ParseUI/Generated/PFResources.m b/ParseUI/Generated/PFResources.m new file mode 100644 index 000000000..5f49e5051 --- /dev/null +++ b/ParseUI/Generated/PFResources.m @@ -0,0 +1,76 @@ +// This is an auto-generated file. +#import "PFResources.h" +@implementation PFResources + const unsigned char twitter_icon3x_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0, 0x36, 0x8, 0x6, 0x0, 0x0, 0x0, 0x73, 0xe7, 0x4f, 0x3f, 0x0, 0x0, 0x0, 0x1, 0x73, 0x52, 0x47, 0x42, 0x0, 0xae, 0xce, 0x1c, 0xe9, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0x2e, 0x23, 0x0, 0x0, 0x2e, 0x23, 0x1, 0x78, 0xa5, 0x3f, 0x76, 0x0, 0x0, 0x1, 0xcb, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x3c, 0x2f, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0xa, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0xa, 0x29, 0x2e, 0xcd, 0x3d, 0x0, 0x0, 0xb, 0x9, 0x49, 0x44, 0x41, 0x54, 0x68, 0x5, 0xd5, 0x9a, 0x7b, 0xa8, 0xe5, 0x55, 0x15, 0xc7, 0xe7, 0xce, 0x1d, 0xc7, 0xd1, 0x44, 0xcd, 0xde, 0x33, 0x15, 0xa9, 0x94, 0x56, 0x14, 0x64, 0x30, 0x44, 0xa1, 0x5d, 0x1a, 0x2c, 0x6d, 0xe8, 0x61, 0x94, 0x45, 0x30, 0x4a, 0x49, 0x52, 0x50, 0x4a, 0x18, 0x18, 0x64, 0xd0, 0x3, 0x26, 0x8, 0x89, 0xe8, 0x41, 0xfd, 0xa3, 0x21, 0x15, 0xa5, 0xff, 0x68, 0x62, 0x89, 0x77, 0xd4, 0x92, 0xc9, 0xc7, 0x58, 0x8, 0x9a, 0x64, 0xce, 0x4c, 0x83, 0x36, 0x39, 0x5a, 0x96, 0x8f, 0xd1, 0xd2, 0x79, 0xdc, 0xb9, 0x7d, 0x3e, 0xfb, 0xec, 0xf5, 0x73, 0xff, 0x7e, 0xe7, 0xf7, 0x3b, 0xf7, 0x9e, 0x39, 0xe7, 0xdc, 0x99, 0x16, 0x7c, 0xcf, 0x5a, 0x7b, 0xed, 0xb5, 0xd7, 0xde, 0x7b, 0xed, 0xb5, 0xf7, 0x6f, 0x9f, 0x73, 0x7e, 0x53, 0xcb, 0x1a, 0x34, 0x3f, 0x3f, 0x3f, 0x8d, 0x6a, 0x7e, 0x6a, 0x6a, 0xea, 0x40, 0xa3, 0x6a, 0x49, 0x8a, 0xf4, 0xbf, 0xdc, 0x8e, 0xa2, 0x7f, 0xca, 0xab, 0x29, 0xce, 0x80, 0x33, 0xc0, 0xa9, 0xe0, 0x64, 0xf0, 0x6a, 0xb0, 0x1b, 0x6c, 0x7, 0x3b, 0xc0, 0xdd, 0xe0, 0x16, 0xda, 0xdc, 0x3, 0x4f, 0x44, 0xbb, 0x15, 0x94, 0xf7, 0x17, 0xe5, 0x29, 0x64, 0xe7, 0xa6, 0xef, 0x52, 0x7f, 0x24, 0xe5, 0x3d, 0x61, 0xb7, 0x8c, 0x86, 0x1a, 0x56, 0x44, 0x39, 0x35, 0xaa, 0x14, 0x4b, 0x20, 0x94, 0x7d, 0x22, 0xbf, 0x1c, 0x5c, 0x6, 0xb6, 0x82, 0x1a, 0x1d, 0x80, 0x6a, 0x8a, 0x5e, 0xe1, 0x19, 0xd8, 0x2f, 0xc0, 0xbb, 0x62, 0xa8, 0xc8, 0x2b, 0xc1, 0x11, 0x22, 0x74, 0xc1, 0xd1, 0xad, 0x5, 0x5f, 0x7, 0x6b, 0x42, 0x67, 0x10, 0xd2, 0x2a, 0xc0, 0xa7, 0xc1, 0x9b, 0xc0, 0x31, 0x56, 0xc2, 0xfb, 0x1c, 0x54, 0x8d, 0xc6, 0x2c, 0xd8, 0x77, 0xb8, 0x44, 0x7e, 0x1f, 0xb8, 0x17, 0x24, 0xca, 0xf3, 0xde, 0x47, 0x61, 0x2f, 0xd8, 0xf, 0xc, 0xc4, 0x1c, 0x28, 0x75, 0x14, 0x13, 0x3d, 0xc7, 0xa7, 0x13, 0x5c, 0x15, 0xfe, 0x82, 0xab, 0x3, 0xa7, 0x83, 0xef, 0x2, 0xdb, 0x5f, 0x13, 0x75, 0x89, 0xa3, 0x48, 0x13, 0x86, 0xaf, 0x1, 0xf7, 0x81, 0x8d, 0x61, 0x80, 0xbc, 0x22, 0xe4, 0x49, 0x71, 0xfa, 0x48, 0xb, 0xa1, 0x7f, 0xe4, 0xb, 0xc1, 0x1e, 0x20, 0xc5, 0x44, 0x1d, 0xb4, 0x93, 0x6f, 0x45, 0xce, 0x10, 0x6d, 0x9f, 0x7, 0x41, 0xd7, 0x20, 0xbc, 0x12, 0x1c, 0xb, 0xce, 0x0, 0x97, 0x80, 0x9b, 0xc0, 0x6e, 0x20, 0x19, 0xd4, 0x37, 0xe7, 0x3e, 0xa7, 0xd3, 0x96, 0x40, 0x91, 0xb6, 0x5, 0x7c, 0x3, 0x8, 0xba, 0x20, 0x26, 0x8e, 0x62, 0x62, 0xc1, 0xc0, 0xf7, 0x94, 0xc8, 0x3, 0xba, 0x20, 0x3a, 0x87, 0xbb, 0xb2, 0x73, 0x39, 0x1b, 0x5a, 0x3, 0x40, 0x7d, 0x4d, 0x9f, 0x6d, 0x23, 0x88, 0xba, 0xba, 0x7, 0x6c, 0x6, 0xcf, 0x5a, 0x68, 0xd0, 0xa5, 0xb9, 0x4f, 0xb7, 0x4e, 0xa, 0x44, 0x35, 0x49, 0x14, 0xee, 0xb1, 0x20, 0x23, 0x76, 0xde, 0xa4, 0x83, 0x41, 0x1f, 0xa9, 0x7f, 0xf8, 0xbb, 0x41, 0xc, 0xf8, 0xf9, 0x61, 0x2, 0x40, 0xbb, 0x32, 0x20, 0x14, 0xd3, 0xf6, 0x71, 0xb, 0x95, 0xe4, 0x7c, 0xc, 0xae, 0x74, 0x3d, 0x38, 0x3a, 0xe6, 0x96, 0x38, 0x8a, 0x18, 0x88, 0x69, 0xf4, 0x0, 0x90, 0xfe, 0xd3, 0x63, 0x29, 0x7d, 0x2e, 0x8c, 0x6, 0xe8, 0xc6, 0x7a, 0x66, 0xe0, 0x2f, 0xfa, 0x3e, 0x6, 0xf9, 0x8e, 0xdc, 0xa7, 0x2b, 0x5a, 0x4e, 0x6c, 0x68, 0xb9, 0x8, 0xa2, 0xbe, 0xdc, 0x2e, 0xe5, 0x96, 0xf9, 0x15, 0xe5, 0xf2, 0x3c, 0x7a, 0xb, 0xe5, 0xf5, 0x6e, 0x8d, 0xa4, 0x84, 0x9f, 0x6, 0x9e, 0x6, 0x92, 0xd1, 0x2c, 0x53, 0xec, 0xb2, 0x32, 0x18, 0xd4, 0xd5, 0x9e, 0x30, 0x51, 0x37, 0x2c, 0xc7, 0x4f, 0xf4, 0x7d, 0x11, 0x72, 0x90, 0x7b, 0x5d, 0x1a, 0x3a, 0x0, 0x2d, 0x6d, 0xc2, 0x8f, 0xdc, 0x6c, 0xf8, 0x11, 0x58, 0xd, 0x5e, 0x7, 0xce, 0x7, 0xee, 0x80, 0xad, 0xe0, 0x23, 0x6, 0x22, 0x9e, 0x18, 0xeb, 0x90, 0x3d, 0x94, 0xa4, 0x48, 0xab, 0x2a, 0x18, 0x73, 0x73, 0x73, 0x3f, 0x41, 0x7f, 0x9c, 0x93, 0x85, 0x2f, 0x7, 0x55, 0x54, 0x87, 0xd, 0x40, 0xf6, 0x11, 0xd9, 0xe0, 0x61, 0x76, 0x27, 0x90, 0xa2, 0xbf, 0x71, 0x4, 0x21, 0x7c, 0xc4, 0x9c, 0x1e, 0xc6, 0xff, 0xcf, 0xc0, 0x2c, 0x78, 0x14, 0x4, 0xf5, 0x1e, 0xc, 0x94, 0x22, 0x10, 0x67, 0x45, 0xd, 0x3c, 0x1e, 0x51, 0xaa, 0xf6, 0x16, 0xfa, 0x2d, 0xc8, 0xef, 0x8c, 0x89, 0x23, 0xaf, 0x0, 0x6, 0x65, 0xe8, 0xc, 0xa1, 0xcd, 0xca, 0x1c, 0x90, 0xb3, 0x91, 0x63, 0xef, 0x46, 0x5f, 0x31, 0x89, 0x81, 0xbc, 0xd8, 0x2, 0x3, 0xed, 0xf0, 0x6f, 0x7d, 0x2c, 0x2e, 0x62, 0x45, 0xdf, 0x8f, 0xb9, 0x18, 0x84, 0x98, 0xc4, 0x73, 0xc8, 0xfb, 0xa2, 0x22, 0xf3, 0x79, 0xb8, 0xe7, 0x82, 0x37, 0x31, 0x6f, 0x9a, 0x6b, 0xc1, 0x8d, 0xb8, 0xf9, 0xa, 0x58, 0xe5, 0xd, 0xd, 0xa8, 0x37, 0x18, 0x8b, 0xe, 0x8, 0xb6, 0xf6, 0x19, 0xb7, 0x3b, 0x7d, 0xfa, 0xcc, 0xb7, 0x2f, 0xb3, 0x4c, 0x5e, 0xa3, 0x6c, 0x5f, 0xd3, 0x75, 0x15, 0x6, 0xd8, 0xea, 0x7b, 0x2e, 0xc3, 0xe6, 0x57, 0x80, 0x8b, 0x15, 0x68, 0xb3, 0xa2, 0xf7, 0xd1, 0x2b, 0x78, 0x91, 0x7a, 0x1c, 0x48, 0xee, 0xd3, 0x32, 0xca, 0xea, 0x8c, 0x68, 0xec, 0x5f, 0xcb, 0xb7, 0x83, 0xf7, 0xeb, 0x28, 0x88, 0x72, 0xda, 0x32, 0xf2, 0xd0, 0xb5, 0x71, 0xea, 0xab, 0x6d, 0x85, 0xfc, 0x73, 0x20, 0x99, 0xd, 0x65, 0x9f, 0x63, 0x93, 0x73, 0xe6, 0x94, 0x63, 0xff, 0x31, 0x7d, 0xc5, 0xd6, 0x4c, 0xbc, 0xc, 0xc4, 0xf1, 0x54, 0xfe, 0x1, 0x48, 0xee, 0xd5, 0xda, 0x40, 0x8a, 0x34, 0x2c, 0x1d, 0xba, 0xff, 0xae, 0x6, 0xd5, 0xb5, 0x36, 0x26, 0x8e, 0xce, 0x5b, 0xaa, 0x5b, 0x47, 0x1e, 0x59, 0x97, 0xaa, 0xd5, 0x2b, 0xc0, 0x8f, 0x2, 0x9b, 0x80, 0x34, 0x89, 0xf3, 0x21, 0xe6, 0x10, 0xe7, 0x84, 0xc1, 0xfe, 0x6a, 0x31, 0xc6, 0x5e, 0x10, 0x50, 0xb8, 0x72, 0x29, 0x15, 0x49, 0xf1, 0xa7, 0x90, 0xef, 0xca, 0x46, 0xb5, 0x81, 0xab, 0xa3, 0x3e, 0x52, 0x36, 0x52, 0xcc, 0xd4, 0xb6, 0xfd, 0xb9, 0xe0, 0x16, 0x3a, 0xf0, 0x26, 0xb7, 0x1e, 0xbc, 0x88, 0xb2, 0xf6, 0x73, 0xc0, 0xad, 0x63, 0x3a, 0x7a, 0x69, 0x32, 0x28, 0xbd, 0xcb, 0x4b, 0xfe, 0xf2, 0x83, 0xde, 0x73, 0xa2, 0xef, 0x2a, 0x8c, 0x6e, 0x9c, 0xe4, 0xb8, 0x23, 0x43, 0xbf, 0xcc, 0x78, 0xbe, 0xa9, 0x73, 0xc7, 0xe3, 0xf8, 0xa2, 0xa3, 0xe5, 0xe, 0x14, 0x65, 0xa4, 0xea, 0x6f, 0x72, 0x85, 0xe7, 0x82, 0x7b, 0xbf, 0x95, 0xb4, 0xcf, 0x6d, 0x62, 0xcf, 0x1d, 0x89, 0xe1, 0xc7, 0xc0, 0xd, 0xe0, 0x66, 0xea, 0x2e, 0x5, 0x6f, 0x7, 0x47, 0xe9, 0x80, 0x3e, 0xe, 0xd8, 0x29, 0xd8, 0x67, 0x7f, 0xa8, 0xe2, 0x2c, 0xb2, 0xf, 0xcb, 0x93, 0xa4, 0x58, 0x40, 0xfb, 0x78, 0xc0, 0xf, 0xc6, 0x75, 0x84, 0xe3, 0x51, 0xe, 0x8a, 0xd4, 0x88, 0xc, 0xb8, 0x95, 0x8a, 0x59, 0xf0, 0x5e, 0xa0, 0xa1, 0x13, 0x2c, 0x1d, 0x51, 0xac, 0x65, 0x87, 0x67, 0x42, 0x5a, 0xfd, 0x6c, 0xa7, 0xbf, 0x77, 0x64, 0x3c, 0xb, 0xff, 0x23, 0xf5, 0xbf, 0x97, 0x83, 0x7, 0xc1, 0x2e, 0x6, 0xb0, 0x1b, 0xa4, 0x20, 0xc3, 0xfd, 0xc6, 0xf8, 0x77, 0xf4, 0x92, 0x63, 0xe8, 0xeb, 0x2b, 0xd5, 0x8c, 0xef, 0x23, 0x7d, 0x99, 0xc4, 0x5d, 0x5f, 0xf0, 0x53, 0x20, 0x8c, 0x4e, 0x8e, 0x92, 0xfb, 0xf4, 0x3b, 0x18, 0xae, 0x3, 0x6, 0x61, 0x2f, 0x30, 0x7d, 0x3b, 0x7, 0x48, 0x5b, 0xaa, 0xab, 0xd4, 0x73, 0x82, 0x42, 0xa5, 0x9d, 0xce, 0x64, 0xc0, 0x96, 0x3d, 0x6, 0x76, 0xe0, 0x7f, 0x7, 0x7c, 0x27, 0x78, 0x34, 0xe3, 0x78, 0xb8, 0x14, 0x59, 0x99, 0xa, 0xd8, 0xe1, 0xba, 0xb7, 0x1d, 0x4b, 0x39, 0x55, 0xe6, 0x8f, 0x36, 0x7d, 0xa9, 0xcb, 0x72, 0xd9, 0xc4, 0xf9, 0x48, 0xb1, 0xf0, 0xbd, 0x52, 0x9b, 0xc2, 0x1a, 0x1c, 0x5c, 0xe, 0xbb, 0x44, 0x11, 0x98, 0x19, 0x6e, 0x95, 0xce, 0x60, 0x50, 0xd7, 0x24, 0x6d, 0x45, 0x4, 0x25, 0x5, 0xbc, 0x69, 0x94, 0xcb, 0x74, 0x97, 0xb2, 0xaa, 0xa3, 0x7a, 0x24, 0xb5, 0x13, 0x76, 0xf5, 0xd, 0xb2, 0xf3, 0x58, 0x4f, 0x70, 0x67, 0xe9, 0xaf, 0x76, 0x3e, 0xa0, 0xaf, 0x56, 0xd2, 0xc9, 0x9b, 0x1, 0x89, 0x30, 0xfe, 0x12, 0x82, 0xdf, 0xd5, 0x75, 0x64, 0x10, 0x3c, 0x47, 0xfa, 0xa2, 0x88, 0xbe, 0x8b, 0xb4, 0xf5, 0x80, 0x32, 0x0, 0xe, 0xc2, 0x80, 0x78, 0x2e, 0xb8, 0x22, 0xc2, 0x41, 0xa9, 0x93, 0x5c, 0xf9, 0x9e, 0x54, 0x7c, 0x96, 0xfd, 0x95, 0x72, 0x61, 0xe2, 0x98, 0xfb, 0x1a, 0x96, 0x3a, 0x64, 0xcd, 0x63, 0x1, 0xbd, 0x27, 0x3d, 0x53, 0xb6, 0x2f, 0xe5, 0x38, 0x4d, 0xd5, 0x9d, 0x49, 0xc3, 0x2b, 0xc0, 0xba, 0x6c, 0xf0, 0x49, 0xf8, 0xf, 0x80, 0x13, 0x98, 0x66, 0xb0, 0x31, 0xf0, 0x5c, 0x3d, 0x14, 0xf3, 0xa9, 0x61, 0x40, 0xdd, 0x66, 0xf2, 0xd8, 0x6, 0xfa, 0x6c, 0xf5, 0x4b, 0x7f, 0x31, 0x1, 0x23, 0x55, 0xc9, 0xd8, 0x57, 0xd4, 0xa6, 0x2f, 0x75, 0xc8, 0xda, 0x46, 0x5b, 0x7f, 0xda, 0x7b, 0x32, 0x37, 0xee, 0xb, 0x60, 0xba, 0x22, 0xd3, 0xc0, 0xc1, 0xb8, 0xa7, 0x3f, 0xd, 0xce, 0x65, 0xd0, 0xf7, 0xc2, 0x37, 0x83, 0x47, 0xc0, 0x76, 0xf0, 0x46, 0x30, 0x12, 0x95, 0x3, 0xcc, 0x8e, 0xd2, 0xef, 0x10, 0x2d, 0xfa, 0x91, 0xfa, 0x69, 0x69, 0x1c, 0x81, 0x78, 0x9c, 0xba, 0x27, 0x5a, 0xea, 0x93, 0xca, 0xd4, 0x8d, 0xe8, 0xfc, 0x15, 0xd9, 0x93, 0xde, 0x80, 0x78, 0x41, 0x12, 0x92, 0x29, 0x45, 0x6c, 0xe6, 0x7d, 0xd4, 0x86, 0xd3, 0x94, 0x96, 0x51, 0xa6, 0xe, 0xf1, 0x85, 0x83, 0xcd, 0x46, 0x51, 0x56, 0xee, 0xa2, 0x41, 0x36, 0x4d, 0x9f, 0x6d, 0xb6, 0xa5, 0x4d, 0x57, 0x1f, 0x85, 0x7e, 0x27, 0x72, 0x64, 0x44, 0x35, 0x8f, 0xa8, 0x77, 0x6b, 0x44, 0x20, 0x34, 0x14, 0xd2, 0x7f, 0x41, 0x3c, 0x62, 0xe2, 0x2e, 0x50, 0x6b, 0x5c, 0xe, 0xac, 0x29, 0x97, 0xe5, 0xe4, 0xed, 0x20, 0x3e, 0x4a, 0x1f, 0xa5, 0x5c, 0xba, 0xea, 0xd2, 0x17, 0x36, 0x8e, 0x39, 0xb6, 0xff, 0x36, 0xec, 0xdd, 0xe6, 0x52, 0xdf, 0x76, 0xd4, 0x28, 0x4d, 0x10, 0x23, 0x1f, 0x6f, 0x77, 0x6a, 0x5, 0xb9, 0x8f, 0xad, 0xf3, 0x50, 0x8b, 0x80, 0x20, 0xfe, 0xdf, 0x91, 0x13, 0x8e, 0x27, 0xd6, 0x9f, 0x1d, 0xbd, 0x99, 0x2d, 0x53, 0x2e, 0x49, 0xa5, 0xf7, 0x71, 0xb9, 0x74, 0x7d, 0x8f, 0xa5, 0x40, 0x84, 0x93, 0xa8, 0xcb, 0x55, 0x3d, 0x66, 0x5a, 0x86, 0x62, 0x31, 0x72, 0xd8, 0x6, 0xb7, 0x4d, 0xd9, 0xae, 0xd4, 0x2b, 0x97, 0x75, 0xa5, 0x1c, 0x76, 0x4d, 0x9b, 0x52, 0x9f, 0x65, 0xc7, 0x17, 0x2b, 0xef, 0x96, 0xb8, 0x3f, 0xeb, 0x3d, 0xf8, 0xfb, 0x3, 0x91, 0x95, 0x31, 0xd9, 0x59, 0x8c, 0x6f, 0xce, 0xd, 0x22, 0x8d, 0x72, 0xb1, 0xce, 0x4a, 0x67, 0x8b, 0x91, 0xeb, 0xad, 0x7b, 0x67, 0x48, 0xd9, 0x2e, 0xea, 0x43, 0x17, 0x5c, 0x7d, 0x29, 0x87, 0xdd, 0x20, 0x7d, 0x61, 0x13, 0x81, 0xf8, 0xb, 0xba, 0xad, 0x85, 0xbe, 0x4f, 0x4c, 0x1, 0xa0, 0x23, 0x6f, 0x96, 0x46, 0xca, 0x83, 0xd1, 0xcb, 0x94, 0x5b, 0xc2, 0x2f, 0x43, 0x3e, 0xf3, 0xab, 0x95, 0x47, 0xee, 0xa3, 0xae, 0xd5, 0xea, 0x33, 0x5c, 0x7a, 0x85, 0xab, 0x3e, 0x9d, 0xbb, 0xdd, 0xc2, 0xdc, 0xe2, 0xa0, 0x6c, 0xdd, 0xea, 0x91, 0x9, 0xda, 0xc7, 0x59, 0x71, 0x13, 0xf2, 0xc6, 0xec, 0xc0, 0xe7, 0x7e, 0x4, 0xa3, 0x35, 0x20, 0x5d, 0xab, 0x95, 0xdb, 0x1f, 0x2a, 0xe6, 0x58, 0x9d, 0xb0, 0xe7, 0x83, 0xfc, 0x36, 0xe0, 0x76, 0xf3, 0xcb, 0x56, 0x64, 0x89, 0xaa, 0x8a, 0xaa, 0x40, 0x68, 0x80, 0x61, 0x1c, 0x2c, 0xdf, 0xc0, 0xe2, 0xca, 0x6c, 0x65, 0x30, 0xfc, 0x6f, 0x50, 0x7, 0x76, 0xd0, 0x1a, 0x10, 0xf4, 0x87, 0x1b, 0xc5, 0xca, 0xdf, 0xc7, 0xc0, 0xee, 0x58, 0x68, 0x70, 0x55, 0x20, 0x34, 0x24, 0x18, 0x6e, 0x11, 0xef, 0xe1, 0x3a, 0xf9, 0x2c, 0xf8, 0xa1, 0x7a, 0x28, 0xae, 0xdf, 0x6, 0x64, 0xe0, 0xd9, 0x91, 0xac, 0xf, 0xfd, 0x87, 0x8b, 0xe6, 0x93, 0x4f, 0x9a, 0x65, 0x3e, 0xff, 0xec, 0x89, 0x69, 0xcb, 0x67, 0xb1, 0xce, 0x6a, 0x81, 0xb0, 0xaa, 0x8, 0x86, 0xbf, 0x1d, 0x7c, 0x1e, 0xd5, 0x45, 0x60, 0x17, 0xd0, 0xd6, 0x80, 0xd8, 0x41, 0x67, 0x56, 0x94, 0x67, 0x46, 0x29, 0xd3, 0xa6, 0x46, 0xd6, 0xb5, 0xd5, 0x87, 0x2e, 0xb8, 0x8d, 0x4a, 0xb9, 0x74, 0xd2, 0xa1, 0x77, 0x6c, 0x9e, 0x71, 0x8e, 0xd7, 0x73, 0xe1, 0x3a, 0xa0, 0x8f, 0x95, 0xcc, 0xa7, 0xef, 0x69, 0x61, 0x9d, 0x34, 0x68, 0x42, 0xee, 0xa7, 0xb4, 0xfa, 0x38, 0x79, 0x29, 0xb6, 0x9f, 0x1, 0xeb, 0xc1, 0x29, 0xe0, 0xc5, 0xa0, 0x2f, 0x88, 0xe8, 0xe, 0x17, 0x8a, 0xf3, 0xe1, 0x6a, 0xe6, 0xf0, 0x9, 0x7, 0xc5, 0x1c, 0xbc, 0x19, 0xb7, 0x9e, 0xf, 0xd6, 0x77, 0x6, 0x22, 0x37, 0xf6, 0x3b, 0x86, 0x93, 0xde, 0x9e, 0x6d, 0xd7, 0xc2, 0x75, 0xfc, 0x41, 0xe0, 0x55, 0x3c, 0xce, 0xd, 0xc4, 0xc3, 0x82, 0x9c, 0x8f, 0x8b, 0x67, 0xd6, 0xba, 0x8d, 0xcf, 0x61, 0xf2, 0xfe, 0xea, 0x5e, 0x2d, 0x2a, 0xba, 0x56, 0x8a, 0xc3, 0xb1, 0x56, 0x49, 0xc3, 0xf8, 0xbe, 0xee, 0xc4, 0xfd, 0xa1, 0x46, 0xa7, 0x5e, 0xbb, 0x25, 0x3, 0xe0, 0xa3, 0x55, 0x1a, 0x18, 0xc8, 0x9e, 0xc9, 0xd2, 0x7d, 0x32, 0xee, 0xf4, 0xa5, 0x27, 0xf7, 0x78, 0xa3, 0x41, 0xc8, 0x72, 0x1c, 0x9c, 0x9d, 0x83, 0x69, 0xd, 0x44, 0x61, 0xbd, 0x9, 0xf9, 0x69, 0x70, 0x62, 0xa1, 0xb, 0xb1, 0x75, 0xbf, 0xe5, 0xb1, 0xb4, 0xd6, 0x45, 0xc3, 0x9, 0x71, 0xe6, 0x9d, 0xb6, 0xb2, 0xd9, 0xe0, 0x7d, 0xe8, 0x7b, 0xf6, 0xc3, 0x78, 0x16, 0xcc, 0x6, 0xed, 0x5a, 0xf7, 0x39, 0xe, 0xe3, 0xe9, 0xe1, 0x21, 0xb9, 0x51, 0x43, 0xc8, 0xa8, 0x9a, 0x15, 0xd6, 0x75, 0x4e, 0x94, 0xb6, 0x9d, 0x75, 0x3a, 0x99, 0x4, 0x19, 0x7c, 0xfc, 0xba, 0x4d, 0xe3, 0x2, 0xf5, 0x53, 0xc6, 0xf1, 0xdb, 0xdc, 0x97, 0x7, 0xe7, 0x82, 0xd4, 0x99, 0xda, 0x38, 0xf7, 0xa6, 0x19, 0xbf, 0x4c, 0x5d, 0x8b, 0xa7, 0xf, 0x81, 0x48, 0x31, 0x3b, 0x5c, 0xf2, 0x9, 0x2f, 0x30, 0x1b, 0x27, 0x6c, 0x36, 0x3c, 0x4, 0x66, 0x18, 0xfb, 0xc3, 0xcc, 0x21, 0xb6, 0x38, 0xaa, 0xc1, 0xd4, 0x9a, 0x11, 0x36, 0xc9, 0x41, 0xd0, 0x91, 0x13, 0xfe, 0x2, 0xf8, 0x13, 0x30, 0x0, 0x46, 0x3e, 0x45, 0x39, 0xaf, 0x4, 0xc5, 0x43, 0x4a, 0x2e, 0xa6, 0xb, 0x64, 0x10, 0xa4, 0xaf, 0xe5, 0x20, 0xb8, 0x90, 0x8b, 0xca, 0x6, 0x1b, 0x75, 0x6, 0xc2, 0x4a, 0x1d, 0xe5, 0xa8, 0xee, 0xa4, 0xf8, 0x71, 0x60, 0x30, 0xec, 0x30, 0x5d, 0x5d, 0xa9, 0x77, 0x0, 0x91, 0x19, 0x9d, 0xd9, 0x85, 0xcd, 0x24, 0xa9, 0x7c, 0x72, 0x5d, 0xc9, 0x98, 0xae, 0xca, 0x9d, 0xc5, 0xb8, 0xc6, 0xd3, 0xb7, 0xab, 0xe, 0xd2, 0xde, 0x83, 0xbf, 0x6, 0x5c, 0xb, 0x4a, 0x72, 0xfb, 0xf8, 0x37, 0xa0, 0x7f, 0xa7, 0x19, 0xb8, 0xf8, 0x9b, 0x6d, 0xa9, 0xb8, 0xfd, 0x4a, 0xfe, 0x87, 0xf2, 0x12, 0x67, 0xd, 0x5f, 0xe8, 0x21, 0x70, 0x70, 0xc1, 0xc1, 0x71, 0x19, 0xc, 0xff, 0xd4, 0xf1, 0x85, 0xaf, 0xfb, 0x41, 0x1b, 0x2d, 0x55, 0x0, 0xec, 0x27, 0xfe, 0x2f, 0xfd, 0x7, 0xf2, 0x69, 0xa3, 0x4, 0x61, 0xa8, 0x74, 0xa6, 0xb3, 0x74, 0x80, 0xe6, 0xe, 0xbd, 0x68, 0x7d, 0x20, 0xe3, 0x6d, 0x70, 0xef, 0x17, 0x2b, 0xc1, 0x71, 0xc0, 0x2d, 0x57, 0x4b, 0x4d, 0xda, 0x92, 0xb5, 0x2f, 0x3c, 0x51, 0x2c, 0x63, 0xe3, 0xf6, 0x1b, 0x68, 0xa7, 0x4d, 0x7, 0x79, 0x71, 0xb2, 0x3f, 0xef, 0x38, 0x1f, 0xc5, 0xcf, 0xd, 0xf8, 0x4c, 0xfd, 0x36, 0x7d, 0x76, 0xb4, 0x1f, 0x4d, 0x6d, 0x67, 0x31, 0x89, 0xf0, 0x44, 0xd9, 0x3f, 0x77, 0x7d, 0x17, 0xe9, 0x97, 0x20, 0xc8, 0x2d, 0x33, 0xd6, 0xec, 0x28, 0xfe, 0x91, 0x8f, 0x4c, 0xd0, 0xff, 0x86, 0x62, 0x1c, 0x3, 0xcf, 0xbc, 0xb0, 0x1b, 0xb, 0xa7, 0x63, 0x3, 0xe1, 0x1b, 0xad, 0xd5, 0x3e, 0x44, 0x7e, 0x15, 0xf0, 0x25, 0xce, 0x78, 0x2b, 0xae, 0x2f, 0x8, 0xc5, 0x24, 0x52, 0x70, 0x2c, 0x37, 0x75, 0xb4, 0x1f, 0x14, 0x38, 0xaa, 0xd3, 0xab, 0x4d, 0xf1, 0x62, 0x98, 0xe7, 0xd1, 0xa7, 0x62, 0x52, 0xc8, 0xe9, 0x1c, 0x8b, 0xf2, 0xd8, 0x39, 0x1d, 0xa4, 0xf3, 0x1, 0xee, 0xaa, 0xc7, 0x23, 0x2a, 0xf5, 0x43, 0xf9, 0x4, 0xe0, 0x8b, 0x60, 0x7f, 0x3, 0x41, 0xfb, 0x87, 0x9c, 0xe0, 0xa0, 0xc9, 0x47, 0x9d, 0xbe, 0x9d, 0x78, 0x1c, 0x8c, 0xbe, 0xf4, 0xe6, 0x53, 0x2c, 0xc6, 0x31, 0x7a, 0x10, 0x70, 0x18, 0x2b, 0x9c, 0x26, 0x4a, 0x59, 0xee, 0x8a, 0xb, 0xe5, 0xbe, 0x74, 0x43, 0xf7, 0x5a, 0xf0, 0x39, 0xb0, 0x5, 0x4, 0xf9, 0xe4, 0x18, 0x6b, 0x10, 0x72, 0x40, 0xf5, 0xbf, 0xa7, 0x90, 0xb7, 0x51, 0x7e, 0xcf, 0x58, 0x83, 0x80, 0xb3, 0x45, 0x1f, 0x96, 0x74, 0xbe, 0x6, 0x7b, 0xf, 0xc5, 0x75, 0xe0, 0x2c, 0x70, 0x2a, 0x90, 0x3c, 0xec, 0xe2, 0x1b, 0x5f, 0x52, 0x8c, 0xe9, 0xc3, 0xb1, 0x79, 0x4f, 0xf1, 0x52, 0x14, 0x3f, 0xc, 0xfd, 0x1a, 0xf9, 0x8b, 0x1c, 0x86, 0x6, 0x23, 0x2d, 0x10, 0xb2, 0xf7, 0x88, 0x91, 0xc9, 0xb4, 0x7f, 0x3d, 0x5e, 0x7c, 0xfe, 0x3e, 0x6, 0xfc, 0x2e, 0xe1, 0x1f, 0x3a, 0x3e, 0x1, 0x7c, 0x2a, 0xac, 0x6, 0x27, 0x3, 0xdf, 0x59, 0x7e, 0x2b, 0x70, 0xf2, 0xe5, 0xf6, 0xf0, 0xf7, 0x4c, 0xcf, 0xa, 0x7, 0x55, 0x3b, 0xfd, 0x29, 0x1f, 0x14, 0x31, 0x1e, 0xe6, 0x96, 0x26, 0x67, 0x70, 0x23, 0x0, 0xbb, 0x91, 0xbf, 0xd, 0x2e, 0xa7, 0xce, 0x83, 0x32, 0x2e, 0x74, 0x63, 0xe9, 0xd3, 0x81, 0xea, 0xd0, 0xce, 0xce, 0x3, 0x67, 0x83, 0x55, 0xc0, 0x4e, 0xd5, 0xfb, 0xde, 0x82, 0x68, 0x6e, 0xd, 0x57, 0xc8, 0x55, 0xd0, 0xc6, 0xc7, 0x97, 0x83, 0x19, 0xd7, 0x80, 0x7c, 0xb3, 0x26, 0xae, 0xcb, 0x11, 0x4, 0xb3, 0xe0, 0x5b, 0xe8, 0x6f, 0x87, 0x2f, 0xfa, 0xdb, 0xa4, 0xb6, 0x43, 0x13, 0x11, 0x7e, 0x5, 0xb8, 0x18, 0x3c, 0x8, 0x9a, 0xe4, 0x21, 0xe5, 0x2a, 0x78, 0x5a, 0x7b, 0x58, 0x79, 0x80, 0x55, 0x27, 0xfe, 0x28, 0x7, 0x23, 0x6d, 0xf5, 0x55, 0xde, 0x4c, 0x75, 0x1d, 0x74, 0x17, 0xc2, 0x6, 0x60, 0xb0, 0x13, 0x21, 0x1b, 0xfc, 0x89, 0x50, 0xed, 0x8c, 0xa0, 0x23, 0x5f, 0x4, 0xfb, 0x30, 0x38, 0x1f, 0xcc, 0x80, 0x72, 0x1b, 0xb8, 0x52, 0x41, 0x55, 0x3b, 0xda, 0xb0, 0x58, 0x55, 0x31, 0xea, 0x9b, 0xbc, 0x99, 0x31, 0x65, 0x16, 0x35, 0x27, 0xf7, 0x3b, 0x1a, 0x5f, 0x5, 0xae, 0xc3, 0xef, 0x53, 0x3a, 0xa2, 0xf, 0xc7, 0xe1, 0xcb, 0x69, 0x63, 0x39, 0xf, 0xf4, 0xd9, 0x24, 0xcf, 0x8, 0x67, 0xe1, 0x60, 0xaa, 0x8e, 0xd0, 0x1d, 0x4d, 0xf9, 0x74, 0x70, 0xe, 0x38, 0x13, 0x9c, 0x4, 0x9a, 0xe4, 0xa0, 0x9a, 0x13, 0xd4, 0x97, 0x3e, 0xbd, 0x31, 0xb6, 0xd, 0xda, 0x6d, 0xd6, 0x16, 0xb5, 0x1d, 0xe8, 0x37, 0x1, 0xbf, 0xee, 0x6f, 0xa6, 0x6d, 0xfa, 0x35, 0xc, 0x3f, 0xda, 0xfb, 0x5b, 0xe3, 0xa2, 0xbf, 0x45, 0x62, 0x7f, 0x50, 0x54, 0xd, 0x2a, 0x7, 0xc4, 0xe7, 0xb1, 0x1, 0xa9, 0x26, 0x88, 0xde, 0xa7, 0x85, 0x2f, 0x88, 0xcd, 0x80, 0xb5, 0xe0, 0xd, 0xc0, 0xb3, 0x63, 0x14, 0xfa, 0x37, 0x8d, 0xb7, 0x81, 0xbb, 0xc1, 0x6d, 0xc0, 0x7f, 0xa2, 0x1e, 0x81, 0x27, 0xea, 0x1a, 0x4b, 0xd4, 0x4f, 0x82, 0x57, 0x81, 0x8, 0xe7, 0xc5, 0x20, 0x3c, 0xb8, 0x6a, 0xab, 0x4a, 0x9d, 0x5b, 0xe7, 0x44, 0x70, 0x4a, 0x86, 0x99, 0xe2, 0x93, 0xe5, 0x4, 0x70, 0x2c, 0x78, 0x19, 0xd0, 0xc6, 0x76, 0x4f, 0x82, 0x27, 0x80, 0xef, 0x5c, 0xfc, 0xb, 0xec, 0x2, 0xae, 0xbc, 0x1, 0xd8, 0xa, 0x1e, 0xc2, 0x7f, 0x5a, 0x79, 0xe4, 0x44, 0xf8, 0x4f, 0x19, 0x40, 0xa1, 0xb6, 0x18, 0xb9, 0x7a, 0xa2, 0xec, 0x7f, 0x27, 0x77, 0xca, 0xf8, 0x2b, 0x28, 0xe6, 0x63, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)twitter_icon3x_png { + return [NSData dataWithBytes:twitter_icon3x_png length:sizeof(twitter_icon3x_png)]; + } + + const unsigned char facebook_icon2x_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x2c, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1e, 0x84, 0x5a, 0x1, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x0, 0x0, 0x3, 0x23, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2d, 0x63, 0x30, 0x31, 0x34, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x31, 0x34, 0x38, 0x31, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2f, 0x30, 0x33, 0x2f, 0x31, 0x33, 0x2d, 0x31, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x31, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x4d, 0x61, 0x63, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x29, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x44, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x45, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x3e, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x42, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x43, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0x64, 0xec, 0x39, 0x7f, 0x0, 0x0, 0x2, 0x9a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x97, 0xcf, 0x8f, 0xd2, 0x40, 0x14, 0xc7, 0x99, 0x96, 0xd2, 0xf2, 0x63, 0xbb, 0x18, 0x10, 0x83, 0x42, 0x14, 0x12, 0x4f, 0x12, 0xaf, 0x60, 0xbc, 0x60, 0xf8, 0x2f, 0x8c, 0x81, 0x4, 0x88, 0x5c, 0xbc, 0x7a, 0xf2, 0x60, 0xb8, 0xb0, 0x7f, 0x0, 0xc6, 0x13, 0x7, 0xc2, 0xdd, 0x83, 0x37, 0xc2, 0xca, 0x9, 0x3, 0x44, 0x8d, 0x82, 0x31, 0x21, 0x1c, 0x48, 0x5c, 0x12, 0x48, 0x38, 0xd8, 0x8, 0xba, 0x14, 0x97, 0xa5, 0xf8, 0x6a, 0x62, 0xb2, 0x36, 0x9d, 0x2e, 0x34, 0xed, 0xb2, 0x8d, 0x7d, 0xc9, 0x24, 0x4d, 0xdf, 0xcc, 0x9b, 0x4f, 0xa7, 0x6f, 0xde, 0x7c, 0x7, 0x5, 0x2, 0x1, 0x5f, 0xa9, 0x54, 0x7a, 0x19, 0x8f, 0xc7, 0x1f, 0xd0, 0x34, 0x4d, 0x5a, 0x2e, 0xa1, 0x2d, 0x97, 0x4b, 0xe1, 0x1d, 0x58, 0x36, 0x9b, 0xcd, 0x59, 0xaa, 0xd5, 0xea, 0xab, 0xb5, 0x41, 0xac, 0xdd, 0x6e, 0x1f, 0x22, 0x41, 0x10, 0x7e, 0x20, 0x84, 0x5c, 0x16, 0x63, 0xd8, 0x9, 0x2, 0xf0, 0x29, 0x3c, 0xb0, 0x6, 0x1, 0x5e, 0x88, 0xc0, 0xdf, 0xe1, 0x61, 0xdf, 0x20, 0xc0, 0xbc, 0x9, 0xbc, 0xd1, 0x7f, 0x5d, 0x2c, 0x66, 0xc3, 0xe1, 0xf0, 0x2b, 0xc7, 0x71, 0xdf, 0x4e, 0xc1, 0xec, 0x76, 0x3b, 0x45, 0x92, 0x24, 0xe3, 0x72, 0xb9, 0x9c, 0x2c, 0xcb, 0xee, 0x7b, 0xbd, 0xde, 0xeb, 0x4, 0x41, 0x58, 0xe5, 0x80, 0x2d, 0x22, 0xf0, 0x45, 0xed, 0xf2, 0x6e, 0xb7, 0xdb, 0xcc, 0xe5, 0x72, 0x8f, 0xc2, 0xe1, 0xf0, 0xd, 0x8a, 0xa2, 0x90, 0x94, 0xc6, 0x66, 0xb3, 0x21, 0x8f, 0xc7, 0xe3, 0x18, 0xc, 0x6, 0x9f, 0x31, 0x21, 0xe6, 0x17, 0x6, 0x5c, 0x28, 0x14, 0x9e, 0x31, 0xc, 0x43, 0x6c, 0xf2, 0x7, 0xfa, 0xfd, 0xfe, 0xa7, 0x9d, 0x2, 0xe7, 0xf3, 0xf9, 0xa7, 0x9b, 0xa6, 0x8b, 0xd5, 0x6a, 0xdd, 0x2d, 0x30, 0x14, 0xfb, 0x37, 0xdb, 0xe4, 0xf7, 0x79, 0xc0, 0x84, 0xde, 0x1b, 0xec, 0x0, 0x6c, 0x9b, 0xfe, 0xb0, 0x7, 0xc5, 0x45, 0x5c, 0x63, 0x3f, 0x48, 0x4f, 0xd8, 0xc9, 0x64, 0x72, 0x54, 0xaf, 0xd7, 0xdf, 0x2a, 0xf5, 0xf1, 0xf9, 0x7c, 0x62, 0x55, 0x70, 0x43, 0x95, 0x40, 0x7f, 0x57, 0x18, 0x72, 0x9d, 0xc6, 0xe, 0xd0, 0x33, 0x25, 0x6a, 0xb5, 0xda, 0x6b, 0xdc, 0xbc, 0x4e, 0xa7, 0x93, 0x2e, 0x97, 0xcb, 0x45, 0x28, 0x6d, 0xe3, 0xd5, 0x6a, 0x35, 0x87, 0xee, 0xfc, 0x99, 0x26, 0xe0, 0x52, 0x42, 0xd7, 0x15, 0x1e, 0x8d, 0x46, 0x43, 0x9c, 0x2f, 0x93, 0xc9, 0x3c, 0x4c, 0xa5, 0x52, 0x4f, 0xb6, 0x8d, 0xa9, 0x6b, 0xe, 0x43, 0x3e, 0x2e, 0x70, 0xbe, 0x58, 0x2c, 0x76, 0x4f, 0x4d, 0x4c, 0x5d, 0x81, 0x41, 0x5, 0x62, 0x7d, 0xa0, 0xbd, 0x6d, 0x97, 0xe, 0x58, 0xc9, 0x40, 0xd6, 0xae, 0xd, 0x5, 0xac, 0xd6, 0x74, 0x5, 0x6, 0x51, 0xc3, 0xe0, 0x7c, 0xe, 0x87, 0xc3, 0xae, 0x2a, 0xcd, 0xb4, 0x50, 0x6b, 0xe3, 0xf1, 0xf8, 0xe8, 0x39, 0x98, 0x34, 0x36, 0x9c, 0x58, 0x5f, 0x1a, 0x8d, 0xc6, 0x7, 0xb9, 0x31, 0x89, 0x44, 0xe2, 0x7e, 0x28, 0x14, 0xba, 0x2d, 0x56, 0xd6, 0xb3, 0xef, 0xe1, 0x8e, 0x79, 0xd, 0x42, 0x1d, 0x60, 0x16, 0x53, 0x1b, 0xb5, 0x6, 0x2a, 0xec, 0xbd, 0x56, 0x7f, 0x25, 0x1a, 0x8d, 0xde, 0x55, 0x98, 0x4a, 0x9b, 0xa3, 0x19, 0xb4, 0x2b, 0xa1, 0x54, 0x11, 0xb6, 0xb1, 0x48, 0x24, 0x72, 0xc7, 0x50, 0x9b, 0xe, 0xd2, 0xe4, 0x96, 0x9, 0x2c, 0xa3, 0x47, 0x94, 0x4, 0xd6, 0x56, 0x16, 0xc, 0x6, 0x6f, 0x2a, 0xca, 0x4f, 0x2d, 0x26, 0x81, 0xab, 0xd, 0xe5, 0xf7, 0xfb, 0xaf, 0x4a, 0xa1, 0x79, 0x9e, 0x9f, 0x4f, 0xa7, 0xd3, 0x63, 0xb9, 0x31, 0x6e, 0xb7, 0x7b, 0xf, 0x54, 0xd9, 0x3f, 0x65, 0x4f, 0xbc, 0x36, 0x9d, 0x7, 0xac, 0x49, 0x95, 0x0, 0xb5, 0x75, 0xa, 0x60, 0x9c, 0xb4, 0x15, 0x8b, 0xc5, 0x3c, 0x6e, 0xde, 0x4a, 0xa5, 0xf2, 0x42, 0xda, 0x7f, 0x36, 0x9b, 0x71, 0xa, 0x4a, 0x4d, 0x3b, 0xb5, 0x6, 0x45, 0x82, 0x84, 0xdb, 0xee, 0x15, 0xe9, 0x7b, 0xb8, 0x5, 0xef, 0xe1, 0xc6, 0x80, 0x8f, 0x95, 0x1b, 0xb3, 0xd3, 0x93, 0xe, 0x56, 0x7e, 0xa5, 0xc6, 0x67, 0x6a, 0x9, 0x13, 0xd8, 0x4, 0x36, 0x81, 0x4d, 0x60, 0x13, 0xf8, 0xff, 0x6, 0x46, 0x6, 0xe2, 0x45, 0x22, 0x30, 0x69, 0x20, 0x60, 0x92, 0x68, 0xb5, 0x5a, 0x4d, 0x3d, 0x57, 0x44, 0xa5, 0x4f, 0xd6, 0x7a, 0xbd, 0xde, 0x47, 0x22, 0x9d, 0x4e, 0x3f, 0x6, 0xe8, 0x43, 0x10, 0x4f, 0x3f, 0xff, 0x5c, 0xa3, 0xb5, 0x6d, 0x27, 0xa, 0xf3, 0xff, 0xda, 0x34, 0x8e, 0x20, 0x8, 0xc7, 0x9d, 0x4e, 0xa7, 0x99, 0x4c, 0x26, 0xd3, 0xbf, 0x5, 0x18, 0x0, 0x98, 0xef, 0x3, 0xa7, 0xc, 0x73, 0xce, 0xd3, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)facebook_icon2x_png { + return [NSData dataWithBytes:facebook_icon2x_png length:sizeof(facebook_icon2x_png)]; + } + + const unsigned char parse_logo_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xde, 0x0, 0x0, 0x0, 0x44, 0x8, 0x6, 0x0, 0x0, 0x0, 0xee, 0x9a, 0x8b, 0x92, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x0, 0x0, 0x3, 0x23, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2d, 0x63, 0x30, 0x31, 0x34, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x31, 0x34, 0x38, 0x31, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2f, 0x30, 0x33, 0x2f, 0x31, 0x33, 0x2d, 0x31, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x31, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x4d, 0x61, 0x63, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x29, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x37, 0x44, 0x43, 0x37, 0x45, 0x43, 0x42, 0x35, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x44, 0x43, 0x37, 0x45, 0x43, 0x42, 0x36, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x3e, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x31, 0x30, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x44, 0x43, 0x37, 0x45, 0x43, 0x42, 0x34, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0xf6, 0xc4, 0x10, 0x9d, 0x0, 0x0, 0x11, 0x8e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x5d, 0x9, 0x98, 0x54, 0xc5, 0x11, 0xae, 0xdd, 0x9d, 0x5, 0x59, 0x90, 0x9b, 0x5, 0xb9, 0x5, 0x11, 0xf0, 0x2, 0x14, 0x54, 0x54, 0x22, 0x6e, 0x44, 0x3c, 0x50, 0xbc, 0x95, 0xc3, 0x88, 0x9, 0x41, 0x3c, 0x21, 0x1e, 0x68, 0x30, 0x11, 0x8f, 0x18, 0x3c, 0x51, 0x14, 0x13, 0x94, 0x28, 0x26, 0x9e, 0x78, 0x44, 0x12, 0x15, 0x25, 0xe0, 0x89, 0x8a, 0x28, 0x88, 0xa0, 0xe2, 0xe2, 0x2, 0x72, 0xca, 0xd, 0xcb, 0xd, 0x7b, 0xb0, 0xe9, 0xdf, 0xf7, 0xf7, 0x37, 0x9d, 0x9, 0xc8, 0xcc, 0xce, 0x3b, 0xe6, 0x3d, 0xba, 0xbe, 0xaf, 0xbe, 0xe9, 0xd9, 0x9d, 0x79, 0xd3, 0xaf, 0xbb, 0xff, 0xea, 0xaa, 0xea, 0xaa, 0x7a, 0x59, 0xfd, 0xae, 0xbd, 0xe5, 0x55, 0x11, 0x69, 0xa3, 0xb8, 0x5c, 0xfc, 0xa1, 0x2c, 0xc5, 0x6b, 0x15, 0x17, 0x2a, 0xfe, 0x54, 0xf1, 0xc7, 0x8a, 0x97, 0x8b, 0x25, 0x4b, 0x11, 0xa5, 0x8a, 0x8a, 0xa, 0xc5, 0x22, 0xf7, 0xe, 0x1f, 0x2a, 0xcd, 0x9b, 0x1c, 0xf4, 0xd3, 0xdf, 0x62, 0x8a, 0x4f, 0x52, 0xdc, 0x30, 0x80, 0xfe, 0xf4, 0x50, 0x7c, 0x9d, 0xe2, 0x95, 0x8a, 0xa7, 0x29, 0xfe, 0x87, 0xe2, 0xc9, 0x8a, 0xcb, 0xec, 0x54, 0x59, 0x8a, 0x3a, 0x1, 0x78, 0xbb, 0xd9, 0xde, 0xaa, 0x78, 0x1, 0x77, 0x24, 0xcf, 0xc0, 0xaf, 0xb8, 0x8a, 0xe2, 0x3a, 0x8a, 0x1b, 0xf0, 0xf7, 0x21, 0x2, 0x2e, 0x26, 0xcf, 0x54, 0x7c, 0xa7, 0xe2, 0x37, 0xed, 0xd4, 0x58, 0x8a, 0x3a, 0xf0, 0x34, 0xcd, 0x50, 0x7c, 0xaa, 0x4f, 0xbf, 0x59, 0x53, 0x71, 0x63, 0xc5, 0xc7, 0x2a, 0x3e, 0x9b, 0xbb, 0x5f, 0x75, 0xc5, 0x9d, 0x15, 0xbf, 0xa1, 0x78, 0x82, 0xe2, 0x6b, 0x15, 0xaf, 0xb3, 0x53, 0x64, 0x29, 0x8a, 0x94, 0x9d, 0x60, 0x7b, 0xf9, 0x41, 0x50, 0x25, 0x37, 0x28, 0xfe, 0x46, 0xf1, 0xd3, 0x8a, 0xcf, 0x53, 0xdc, 0x41, 0xf1, 0x63, 0x8a, 0x77, 0xf2, 0x33, 0x97, 0xd0, 0xfe, 0xeb, 0x64, 0xa7, 0xc8, 0x52, 0xd4, 0x81, 0x17, 0x24, 0x2d, 0x54, 0x7c, 0xbd, 0xe2, 0xe3, 0x15, 0x4f, 0xe7, 0xdf, 0xe0, 0xf0, 0x79, 0x57, 0x71, 0x81, 0x9d, 0x26, 0x4b, 0x16, 0x78, 0xde, 0xd2, 0x1c, 0xc5, 0xa7, 0x28, 0xfe, 0x2b, 0xdf, 0xc3, 0x16, 0x7c, 0x4d, 0xf1, 0x71, 0x76, 0xaa, 0x2c, 0x59, 0xe0, 0x79, 0x4b, 0xbb, 0x14, 0x5f, 0xad, 0xf8, 0x76, 0xbe, 0xaf, 0xad, 0xf8, 0x45, 0xc5, 0xcd, 0xec, 0x74, 0x59, 0xb2, 0xc0, 0xf3, 0x9e, 0xee, 0x56, 0xfc, 0x0, 0xdb, 0x7, 0x2b, 0xfe, 0x8b, 0x8f, 0x76, 0xa8, 0x25, 0x4b, 0xfb, 0x2d, 0xf0, 0x40, 0xb7, 0x88, 0xe3, 0xe5, 0x4, 0xf5, 0x52, 0x7c, 0xa5, 0x9d, 0x32, 0x4b, 0x16, 0x78, 0xde, 0x13, 0xce, 0xfd, 0xe0, 0x74, 0x59, 0xc5, 0xf7, 0xb7, 0x29, 0xce, 0xb7, 0xd3, 0x66, 0xc9, 0x2, 0xcf, 0x7b, 0x5a, 0xac, 0x78, 0x4, 0xdb, 0x4d, 0xc5, 0x89, 0x76, 0xb1, 0x64, 0xc9, 0x2, 0xcf, 0x7, 0x7a, 0x46, 0xf1, 0x6c, 0xb6, 0x2f, 0x57, 0x5c, 0xd7, 0x4e, 0x9d, 0x25, 0xb, 0x3c, 0xef, 0xa9, 0x44, 0xf1, 0xe3, 0x6c, 0xc3, 0xbb, 0xd9, 0xdb, 0x4e, 0x9d, 0x25, 0xb, 0x3c, 0x7f, 0x68, 0xa2, 0x38, 0x1, 0xd5, 0xa0, 0xf3, 0xec, 0xd4, 0x59, 0xb2, 0xc0, 0xf3, 0x87, 0xd6, 0x2b, 0x7e, 0x8f, 0x6d, 0x44, 0xb8, 0x34, 0xb0, 0xd3, 0x67, 0xc9, 0x2, 0xcf, 0x1f, 0xd2, 0xc0, 0x3, 0xe8, 0xe, 0xb7, 0xd3, 0x67, 0x29, 0xac, 0x14, 0xb, 0x59, 0x7f, 0xbf, 0x34, 0xda, 0x0, 0xde, 0x7, 0x2e, 0x5f, 0x1f, 0x7, 0xf4, 0x4d, 0x79, 0xed, 0x76, 0x8a, 0x9b, 0x28, 0xce, 0x13, 0xe7, 0x58, 0x3, 0xb4, 0x51, 0xf1, 0xf, 0x8a, 0xe7, 0x8b, 0x93, 0xc8, 0xbb, 0xde, 0xa7, 0xfb, 0x46, 0xbf, 0xaa, 0x89, 0x93, 0xac, 0xbc, 0xcb, 0xa5, 0x6b, 0xe6, 0x48, 0xfa, 0xc9, 0xcf, 0xb8, 0x46, 0x73, 0xc5, 0x6d, 0xc5, 0x9, 0x72, 0x40, 0xc6, 0x49, 0x9d, 0x84, 0xcf, 0x14, 0x8b, 0x73, 0x1c, 0xb4, 0x48, 0x9c, 0xb4, 0xb3, 0x25, 0x2e, 0xde, 0x83, 0x9b, 0x73, 0xde, 0x8e, 0xf7, 0x1, 0x1f, 0x42, 0xd, 0xce, 0x39, 0xfe, 0xb7, 0x59, 0x9c, 0x44, 0xed, 0x22, 0xc5, 0xf3, 0x14, 0xaf, 0x30, 0xd6, 0xc3, 0x7e, 0x3, 0xbc, 0x95, 0x5c, 0xec, 0xf5, 0x14, 0xb7, 0x76, 0x71, 0xd7, 0xef, 0x48, 0xbb, 0xb1, 0xa7, 0xe2, 0xf6, 0x1c, 0xf8, 0x7d, 0x11, 0xb2, 0xe8, 0x91, 0x41, 0xf1, 0x8a, 0x38, 0x87, 0xfc, 0x9b, 0x3d, 0xbc, 0xef, 0x2b, 0x14, 0xdf, 0x27, 0xce, 0xd1, 0xca, 0x99, 0xfc, 0xed, 0x64, 0xa9, 0x85, 0xe2, 0x13, 0x15, 0x1f, 0xa3, 0xb8, 0x15, 0xb5, 0x85, 0xfa, 0x4, 0xc1, 0xf9, 0xe2, 0x38, 0xae, 0x52, 0x5, 0xdb, 0x9, 0x8a, 0x2f, 0x54, 0xdc, 0x5d, 0x9c, 0x60, 0xf6, 0x6a, 0x49, 0x7e, 0xb7, 0x84, 0xc0, 0xfb, 0x94, 0x36, 0xfb, 0x54, 0x71, 0xf2, 0x40, 0x83, 0xa0, 0xc3, 0x14, 0x5f, 0xa4, 0xf8, 0x74, 0xc5, 0x47, 0x24, 0x39, 0xe7, 0x5b, 0x8, 0xbe, 0x77, 0x14, 0xa3, 0x72, 0xc3, 0x37, 0xfb, 0xb, 0xf0, 0x70, 0xe3, 0x1b, 0x8, 0xbc, 0xfa, 0x69, 0x5e, 0xb, 0x3, 0x8d, 0xe4, 0xdb, 0xdf, 0x8a, 0x13, 0x84, 0x9d, 0x18, 0x8e, 0xb6, 0x9b, 0xd2, 0x7a, 0x13, 0x77, 0x6, 0xec, 0x7c, 0xb5, 0xf9, 0xaa, 0xd5, 0xdd, 0xde, 0x64, 0x2c, 0xa6, 0x87, 0x14, 0x8f, 0x93, 0x78, 0x6a, 0x93, 0x9b, 0xd4, 0x8a, 0xf7, 0x8b, 0x9c, 0xc5, 0x9a, 0x49, 0x0, 0xf, 0xf3, 0x8a, 0x3c, 0xc7, 0x41, 0x8a, 0xbb, 0xf1, 0x7b, 0x89, 0x54, 0x93, 0x80, 0x29, 0x49, 0x41, 0x40, 0x61, 0xbc, 0x6e, 0x56, 0x7c, 0xf4, 0x1e, 0xfe, 0x5f, 0xc6, 0xb1, 0xda, 0xc6, 0x6b, 0x66, 0xf1, 0x77, 0x6b, 0x19, 0xc0, 0xac, 0x42, 0xa0, 0x82, 0x2f, 0xe7, 0xb8, 0x3d, 0x25, 0x4e, 0x38, 0xa0, 0x5f, 0xda, 0x43, 0x77, 0xde, 0xc3, 0xa9, 0xec, 0x8f, 0x49, 0x15, 0x9c, 0x73, 0x8, 0xd1, 0x52, 0xc5, 0xb9, 0x8a, 0xf, 0xe4, 0x3d, 0xe4, 0xb0, 0x7d, 0x1c, 0xf9, 0x36, 0x9a, 0x3e, 0xf, 0x13, 0x88, 0x91, 0x6, 0xde, 0xe, 0x4e, 0xac, 0x96, 0xbc, 0x95, 0x5, 0xdc, 0x60, 0x71, 0x12, 0x6d, 0x5b, 0x24, 0x0, 0xed, 0x3b, 0xc5, 0x1f, 0x2a, 0xfe, 0x84, 0xd2, 0x6c, 0xd, 0x25, 0x72, 0x39, 0x17, 0x4f, 0x4d, 0xaa, 0x56, 0x5d, 0xc4, 0x49, 0xde, 0xfd, 0x5, 0xff, 0x8e, 0xeb, 0x3c, 0xca, 0x9d, 0x69, 0x60, 0x82, 0x4a, 0xec, 0x6, 0x95, 0x1b, 0xb, 0x63, 0x5f, 0x6a, 0xe, 0x42, 0xeb, 0xee, 0x92, 0x3d, 0xe7, 0x32, 0x6e, 0xa5, 0xd6, 0xa0, 0xf3, 0x21, 0x93, 0x15, 0x12, 0x87, 0x2a, 0x1e, 0x2b, 0x4e, 0xe6, 0x88, 0x79, 0xad, 0x4f, 0xa9, 0xee, 0xcf, 0xe6, 0x6e, 0xac, 0x81, 0x57, 0x4a, 0xe0, 0xe5, 0x71, 0xb1, 0xe6, 0x73, 0x87, 0x41, 0xa2, 0xf3, 0x49, 0x6c, 0xc7, 0x38, 0x6e, 0xe8, 0x2b, 0x42, 0x1, 0x6f, 0x55, 0xfc, 0x9c, 0x87, 0x6b, 0x7, 0xf7, 0x70, 0xbf, 0xfc, 0xff, 0x51, 0xd4, 0x7c, 0x2, 0x8, 0x73, 0xfe, 0xad, 0x31, 0xe7, 0xa5, 0xec, 0x63, 0xd, 0xa, 0x7a, 0x7c, 0xbf, 0xab, 0x38, 0x69, 0x6a, 0x1d, 0xf8, 0xbf, 0xd3, 0xc8, 0x28, 0x59, 0x82, 0xf0, 0xc6, 0x39, 0x51, 0x5, 0x5e, 0x2e, 0x59, 0x24, 0xf5, 0x80, 0x69, 0x48, 0xec, 0x3e, 0xe2, 0x94, 0x96, 0x30, 0xd5, 0xd4, 0xd5, 0x8a, 0x5f, 0xe6, 0xa4, 0x7f, 0x29, 0x7b, 0xaf, 0xf9, 0xb2, 0x83, 0xb, 0x16, 0xb, 0xec, 0x23, 0xee, 0x70, 0x87, 0x28, 0xbe, 0x8a, 0xbb, 0xe6, 0x81, 0x5c, 0xec, 0xd3, 0x8, 0xbe, 0x17, 0x7d, 0x1e, 0x9b, 0x7c, 0x4a, 0xdf, 0xbe, 0x9, 0x7f, 0x2f, 0xa2, 0x2a, 0x3c, 0x85, 0x6a, 0xd2, 0xaa, 0x14, 0xd5, 0xcb, 0xd3, 0x39, 0x36, 0xf5, 0xf8, 0x7e, 0xa9, 0xe2, 0x31, 0x8a, 0x5f, 0x52, 0xbc, 0x6c, 0x1f, 0xdf, 0xdd, 0x2e, 0x4e, 0x15, 0x1, 0xd8, 0xc5, 0xa8, 0x70, 0x30, 0x9e, 0x2, 0xf3, 0x8, 0xf6, 0x73, 0x0, 0xfb, 0xd, 0x5b, 0xfa, 0x59, 0xaa, 0xb0, 0x43, 0xb8, 0xe8, 0xdd, 0xa4, 0x6b, 0x14, 0x8f, 0xe4, 0x1c, 0x9, 0x6d, 0xcc, 0x7f, 0x29, 0x7e, 0x42, 0x9c, 0x62, 0x5b, 0x25, 0xfb, 0xd0, 0xb2, 0x56, 0x52, 0x50, 0xfd, 0x93, 0xeb, 0xa8, 0x13, 0x85, 0x6c, 0x7f, 0xee, 0x86, 0x3d, 0xb9, 0x93, 0xde, 0x45, 0x93, 0xa0, 0x3c, 0x99, 0xc5, 0x18, 0x26, 0xca, 0xe3, 0xae, 0x23, 0x29, 0xda, 0x6, 0x47, 0x71, 0xe1, 0x3d, 0x67, 0x80, 0xe, 0x8b, 0x61, 0x28, 0xa5, 0x2f, 0xe2, 0x41, 0x3f, 0x97, 0xd4, 0xb, 0x2d, 0xc1, 0x4e, 0xba, 0x91, 0x92, 0x7c, 0xaa, 0xd1, 0x47, 0x2c, 0xa2, 0x5f, 0xf9, 0x38, 0x2e, 0x28, 0xa1, 0x31, 0x3d, 0x1, 0x74, 0xef, 0x2b, 0x3e, 0x47, 0xf1, 0x91, 0xec, 0xe3, 0x3b, 0x4, 0x4d, 0x2a, 0xa0, 0xc3, 0xae, 0xfe, 0xba, 0x1, 0xba, 0xd1, 0x1c, 0xcb, 0x7, 0x92, 0x0, 0xdd, 0xcf, 0xed, 0xde, 0x73, 0xb8, 0x43, 0x1c, 0xce, 0x5d, 0x48, 0xef, 0xbc, 0x57, 0x11, 0x9c, 0x6e, 0xd1, 0x1, 0xbc, 0xde, 0x18, 0x3, 0x74, 0x93, 0x38, 0x5e, 0x97, 0x70, 0xa7, 0x4b, 0xd5, 0xc6, 0x85, 0x66, 0x34, 0x8b, 0x1a, 0x13, 0xc6, 0xe2, 0x6f, 0xbc, 0xa7, 0xaa, 0x8a, 0xef, 0x21, 0xa0, 0xeb, 0x45, 0xd, 0x78, 0xb5, 0xc, 0xdb, 0x6e, 0x65, 0x92, 0xdf, 0x19, 0x46, 0x69, 0x5b, 0x60, 0x78, 0x26, 0x87, 0x53, 0x5d, 0x18, 0xcd, 0x5d, 0x2c, 0x5d, 0xfa, 0x9e, 0x52, 0xef, 0x21, 0x43, 0xd, 0x1e, 0xc7, 0x85, 0xeb, 0x35, 0x9d, 0x45, 0xd0, 0xb7, 0xe2, 0xfb, 0xc5, 0x74, 0x1a, 0x14, 0x70, 0xa7, 0xab, 0xac, 0x7, 0xb1, 0xa5, 0x38, 0xa1, 0x7a, 0x7, 0x70, 0x61, 0x5d, 0x41, 0x41, 0xb5, 0xc9, 0xc5, 0xbe, 0xaf, 0x23, 0x0, 0x7b, 0x48, 0xbc, 0xc4, 0x63, 0x3f, 0x89, 0xe7, 0x62, 0xa6, 0x43, 0x75, 0x79, 0xff, 0x3, 0xf8, 0xbe, 0x98, 0xf7, 0x80, 0xf1, 0x9a, 0xeb, 0x52, 0xff, 0x97, 0x52, 0xdb, 0x39, 0x8d, 0x42, 0x58, 0xcf, 0x7, 0x2a, 0x27, 0x34, 0x8b, 0x12, 0xf0, 0xe, 0x31, 0xc, 0xf5, 0xc2, 0x24, 0xbf, 0x53, 0xc0, 0xc5, 0x23, 0x54, 0x8f, 0x3a, 0x50, 0xed, 0xd8, 0xe2, 0x72, 0xdf, 0x20, 0x9, 0x6f, 0x12, 0x27, 0x8f, 0x50, 0x3b, 0x12, 0x9e, 0x10, 0x6f, 0xb3, 0x29, 0xe0, 0xe1, 0x7c, 0xcd, 0x90, 0xe6, 0x13, 0xe8, 0xbd, 0x7c, 0xd5, 0x85, 0x6b, 0x63, 0x8c, 0x1a, 0x1b, 0x3b, 0xd1, 0x33, 0x1e, 0xde, 0xc7, 0xc7, 0x54, 0x69, 0x7f, 0xe4, 0xfb, 0x6b, 0xd3, 0x1c, 0xb7, 0xea, 0x1c, 0x17, 0x5d, 0xbc, 0xeb, 0x3b, 0xda, 0xe3, 0x5e, 0xdd, 0x3, 0x76, 0xce, 0xe3, 0xd, 0x27, 0x4b, 0x7, 0xee, 0xac, 0xcd, 0xa2, 0x2, 0xbc, 0x2e, 0x86, 0x93, 0x21, 0x59, 0xe0, 0x5d, 0x47, 0xd5, 0xa0, 0x2f, 0x6d, 0xbc, 0x65, 0x1e, 0xf7, 0x11, 0xd2, 0xfa, 0x79, 0xb6, 0xf, 0xa6, 0xde, 0xef, 0x5, 0x75, 0xa5, 0x1d, 0x59, 0x95, 0xef, 0x1, 0xf8, 0x4b, 0x5d, 0xda, 0xc1, 0xbb, 0xf2, 0x5a, 0xa0, 0x17, 0xb8, 0x7b, 0x7b, 0x4d, 0xdf, 0xd2, 0x16, 0x9b, 0xc4, 0xdf, 0x5e, 0x53, 0xc9, 0xeb, 0xc0, 0xf6, 0xff, 0x3b, 0x6d, 0x2e, 0xe1, 0xdc, 0x43, 0xf8, 0x7e, 0xed, 0x71, 0xff, 0xd7, 0xd3, 0x71, 0xf3, 0x2c, 0xdf, 0x1f, 0x41, 0xb5, 0xb3, 0x6e, 0x14, 0x80, 0x57, 0x60, 0xa8, 0x53, 0xf3, 0x93, 0xfc, 0x4e, 0x11, 0x8d, 0x76, 0x3f, 0x9d, 0x1d, 0x37, 0xb0, 0x8f, 0x42, 0xf5, 0xe6, 0x28, 0x17, 0xaf, 0xd, 0x9b, 0xa4, 0xe, 0x17, 0x97, 0xb6, 0x77, 0x87, 0xbb, 0xa4, 0x9e, 0x69, 0xea, 0x6f, 0x38, 0x21, 0xee, 0xf5, 0x71, 0xdc, 0x26, 0x52, 0x55, 0x7b, 0x2f, 0x8d, 0x6b, 0xdc, 0xa1, 0xf8, 0x2, 0xb6, 0xe1, 0x10, 0xe9, 0x25, 0xf1, 0x7c, 0x4e, 0xaf, 0x9, 0x73, 0xf3, 0x6b, 0xee, 0xb6, 0x42, 0x27, 0xcc, 0x38, 0xd9, 0x83, 0x23, 0x30, 0x4c, 0xc0, 0x6b, 0xc5, 0xed, 0x5c, 0xab, 0x26, 0xdb, 0x53, 0x1c, 0x10, 0x3f, 0x69, 0x4d, 0x82, 0xca, 0x39, 0xd8, 0x45, 0x75, 0x16, 0x93, 0x88, 0xa3, 0x8b, 0x36, 0xfc, 0xdb, 0xa3, 0x54, 0xb, 0xdd, 0xa2, 0xaa, 0x86, 0x8a, 0xf6, 0xae, 0xf, 0x3b, 0x85, 0x9b, 0x84, 0x7e, 0xff, 0x91, 0xed, 0xb5, 0xdc, 0x39, 0x57, 0xf9, 0xdc, 0x87, 0x32, 0xa, 0xdb, 0xcf, 0xf9, 0xfe, 0x7c, 0x9a, 0x20, 0xa1, 0x5, 0xde, 0x25, 0x86, 0x2d, 0xf3, 0x6a, 0x8, 0xfa, 0xfb, 0x3c, 0xd5, 0x27, 0x10, 0xa2, 0x62, 0xdc, 0x8, 0xea, 0xde, 0x4e, 0x67, 0x41, 0x7f, 0x3, 0x18, 0x37, 0xb9, 0xdc, 0xef, 0x36, 0x74, 0xac, 0x80, 0xa6, 0x84, 0x68, 0x7d, 0xc0, 0xae, 0x7b, 0xd0, 0xd8, 0x5d, 0xae, 0x37, 0xc6, 0xdf, 0x6f, 0xda, 0x42, 0xa7, 0x4b, 0x31, 0xdf, 0x23, 0x91, 0xfb, 0xc8, 0x30, 0x2, 0xaf, 0x16, 0x6f, 0x44, 0x1b, 0xca, 0x53, 0x43, 0xd0, 0x67, 0xa8, 0x69, 0xfa, 0x40, 0xb8, 0x11, 0x8d, 0xfb, 0x74, 0xa9, 0xb6, 0x1, 0xb4, 0x62, 0xda, 0xaf, 0x6e, 0x9f, 0x79, 0x35, 0x93, 0x78, 0x44, 0x47, 0x61, 0x88, 0x80, 0x77, 0x3d, 0x9d, 0x1a, 0x42, 0x35, 0xfc, 0xa5, 0x80, 0xfb, 0x33, 0x57, 0xe2, 0x95, 0x13, 0xaa, 0x1b, 0x1a, 0x50, 0xa8, 0x80, 0x37, 0x88, 0x8e, 0xa, 0xa1, 0xce, 0xbc, 0x3d, 0x24, 0xfd, 0x7e, 0xc3, 0x0, 0x86, 0x1b, 0xe5, 0xf1, 0xab, 0x18, 0x76, 0xdd, 0x83, 0x14, 0x42, 0x6e, 0x53, 0xbe, 0xa1, 0xd6, 0xae, 0x9, 0xc9, 0x38, 0xe7, 0xd3, 0x31, 0x3, 0xc2, 0x11, 0xc5, 0x9d, 0x19, 0xd2, 0x2f, 0xd4, 0x87, 0x9d, 0x9e, 0x95, 0x95, 0x25, 0x25, 0xa5, 0x25, 0xbd, 0x67, 0xcd, 0x9d, 0xd7, 0x23, 0x4c, 0xc0, 0x83, 0xea, 0x73, 0x2b, 0xdb, 0x70, 0xa8, 0x3c, 0x1d, 0x22, 0x29, 0x5c, 0x28, 0xf1, 0x40, 0x5a, 0x37, 0xcb, 0xd1, 0xc3, 0x61, 0x34, 0xc6, 0xa3, 0x3e, 0xe7, 0x26, 0xd8, 0x94, 0x61, 0x20, 0xa8, 0xdf, 0x4d, 0xd8, 0x7e, 0x52, 0x9c, 0xe0, 0x88, 0x4c, 0x20, 0x8, 0xdd, 0x3f, 0xa1, 0x11, 0x8b, 0xc5, 0x64, 0xf2, 0x87, 0x9f, 0xdc, 0xb0, 0x79, 0xeb, 0xb6, 0x50, 0x0, 0x2f, 0xc6, 0x5, 0xa6, 0x5d, 0xb2, 0x77, 0x88, 0xbb, 0x7, 0xb8, 0x5e, 0x53, 0xb9, 0x1, 0x3c, 0x44, 0xcc, 0xd4, 0x71, 0xe9, 0xba, 0xe3, 0x3c, 0x1c, 0x87, 0x5d, 0x86, 0x50, 0xae, 0x1f, 0x82, 0x31, 0xc6, 0x19, 0xad, 0x8e, 0x12, 0x82, 0x4b, 0x7f, 0x6c, 0x86, 0xf5, 0xef, 0x6d, 0xc5, 0x1f, 0xc7, 0x72, 0x72, 0x64, 0xed, 0xfa, 0x8d, 0xa7, 0x7d, 0x53, 0x58, 0x74, 0x6c, 0x18, 0x80, 0x37, 0x4a, 0x9c, 0xa8, 0x0, 0xd0, 0xb, 0x19, 0xa0, 0xb7, 0x57, 0x86, 0xb4, 0x81, 0xf, 0xc7, 0x90, 0x1b, 0xcf, 0x21, 0x5c, 0xc7, 0xb1, 0xf0, 0x8a, 0x4c, 0x2f, 0x60, 0xdb, 0x10, 0x8c, 0x2f, 0x82, 0xae, 0x75, 0x52, 0x34, 0xdc, 0xf8, 0xcb, 0x32, 0xac, 0x7f, 0x38, 0x73, 0x46, 0x58, 0x99, 0x28, 0x95, 0x33, 0x7b, 0xea, 0xb4, 0xe9, 0x7d, 0x32, 0x1d, 0x78, 0xa3, 0x24, 0x5e, 0xca, 0xf, 0xbb, 0xc6, 0x10, 0x9, 0x27, 0xad, 0xe0, 0x2b, 0xdc, 0xf4, 0xf5, 0x5c, 0xb8, 0xde, 0x5b, 0xc6, 0x35, 0xbd, 0x20, 0x84, 0xbf, 0xe9, 0x38, 0xd8, 0x30, 0x14, 0x95, 0x3a, 0xc7, 0x68, 0x4f, 0xc8, 0xd0, 0x3e, 0x22, 0x28, 0x60, 0x75, 0x95, 0xdc, 0x5c, 0x59, 0xb8, 0x64, 0xf9, 0x99, 0xab, 0xd6, 0xac, 0xab, 0x96, 0x89, 0xc0, 0x83, 0xf3, 0x0, 0xae, 0xf8, 0xdf, 0xf1, 0x3d, 0x62, 0x32, 0x2f, 0x95, 0xf0, 0x3e, 0x2b, 0xaf, 0x38, 0xc1, 0x39, 0x92, 0x2e, 0xbd, 0xee, 0x71, 0x7f, 0x91, 0x23, 0x37, 0x83, 0x6d, 0x38, 0x3, 0x2e, 0xc8, 0xe0, 0xb1, 0xc5, 0xfa, 0x3d, 0x99, 0x6d, 0xc4, 0x4a, 0x7e, 0x96, 0xa1, 0xfd, 0xc4, 0x99, 0xe2, 0xfb, 0xd9, 0xd9, 0x59, 0xb2, 0x7d, 0xc7, 0xce, 0x43, 0x7f, 0x58, 0xbe, 0xa2, 0x63, 0xa6, 0x1, 0xf, 0x51, 0xb, 0x38, 0x78, 0xec, 0x6b, 0x2c, 0x82, 0xb3, 0x24, 0xb8, 0xf3, 0x18, 0xb7, 0x54, 0xd, 0x31, 0xd4, 0xcd, 0x74, 0x68, 0x87, 0xa4, 0x91, 0xf5, 0x9c, 0x2, 0x99, 0x8e, 0x1b, 0xa8, 0x49, 0xa7, 0x66, 0xe8, 0xd8, 0x1e, 0x4a, 0x6, 0x21, 0x9f, 0x2e, 0x93, 0xbd, 0xdd, 0x3f, 0x1d, 0x81, 0xed, 0x2e, 0x2f, 0x97, 0xa2, 0x45, 0x4b, 0x4e, 0x34, 0x81, 0x17, 0x94, 0x7, 0xb, 0x6, 0x3c, 0xe, 0xc7, 0x91, 0xc6, 0xf2, 0xa6, 0x61, 0x57, 0x20, 0xaf, 0xd, 0x67, 0x5f, 0xb3, 0x25, 0xdc, 0x94, 0xe5, 0x32, 0x88, 0xfd, 0x98, 0xa7, 0x89, 0xda, 0x2e, 0x11, 0xe7, 0xec, 0x10, 0xf3, 0x32, 0x52, 0x32, 0xcf, 0xd9, 0xd2, 0x56, 0xe2, 0x1, 0xf0, 0x9f, 0x64, 0xf8, 0x3a, 0xc0, 0x6e, 0x5c, 0x1e, 0xcb, 0x8d, 0xc9, 0xf7, 0xb, 0x17, 0x77, 0x36, 0x13, 0x61, 0x71, 0x70, 0x8a, 0x4c, 0xe0, 0x1c, 0xf1, 0xfe, 0x39, 0xe8, 0x31, 0xfe, 0x1e, 0xe, 0x3c, 0x3b, 0x25, 0x4c, 0x28, 0xec, 0x8b, 0xfb, 0xc9, 0x41, 0x14, 0xc5, 0xc1, 0xfd, 0xd7, 0xa2, 0xca, 0x8b, 0x7e, 0x9a, 0x45, 0x86, 0xf0, 0xa, 0x7f, 0x30, 0x22, 0x13, 0x76, 0x46, 0x0, 0xc8, 0x3f, 0x47, 0xd7, 0xf1, 0xde, 0xfb, 0xd1, 0x3e, 0xc5, 0x91, 0xe, 0x12, 0x7c, 0x91, 0x34, 0x8c, 0xb8, 0xd7, 0x2f, 0x24, 0xf8, 0xa2, 0x45, 0x87, 0x19, 0xed, 0x4c, 0xf, 0x6d, 0x5b, 0xac, 0x78, 0x69, 0x4e, 0x76, 0xf6, 0xc1, 0xc5, 0x9b, 0xb7, 0xb4, 0x8f, 0x25, 0x48, 0x8f, 0x20, 0x5d, 0xb1, 0xc8, 0x93, 0x7b, 0x89, 0x4e, 0x95, 0x5, 0x3e, 0xfe, 0x2e, 0x4a, 0x39, 0x20, 0x6, 0x14, 0xe9, 0x34, 0x8, 0xeb, 0x69, 0x41, 0x29, 0xaf, 0x81, 0x57, 0x95, 0xbb, 0x4c, 0x49, 0x2, 0xf0, 0x60, 0xbb, 0x21, 0x87, 0xc, 0x67, 0x46, 0x85, 0x64, 0x9c, 0xaf, 0xad, 0x12, 0x17, 0xaa, 0x50, 0x65, 0x0, 0x41, 0xb0, 0xf4, 0xa7, 0x26, 0x82, 0xb3, 0xa8, 0x46, 0x14, 0x90, 0x57, 0x93, 0x8b, 0xf8, 0xbf, 0xa9, 0x4, 0xe1, 0xd2, 0x0, 0xb4, 0xa6, 0xa6, 0x7c, 0x45, 0x8d, 0x94, 0x65, 0x19, 0x3e, 0x9e, 0x58, 0x37, 0x4b, 0xb3, 0x15, 0xf0, 0x36, 0x14, 0x6f, 0x6a, 0x10, 0x54, 0xe9, 0x87, 0x32, 0xe, 0x16, 0xca, 0x2e, 0x7c, 0x25, 0x4e, 0x1e, 0xd3, 0x64, 0xbe, 0xf7, 0x83, 0xb0, 0x88, 0x2e, 0xa4, 0xe3, 0x0, 0xe7, 0x2a, 0x79, 0x49, 0xec, 0x82, 0x3a, 0xf, 0x10, 0x35, 0x38, 0xf4, 0xb1, 0x40, 0xe7, 0x84, 0xcf, 0x6d, 0xe2, 0x82, 0x9c, 0xce, 0x45, 0xf9, 0x1f, 0x9, 0xcf, 0x21, 0xf4, 0xde, 0xe8, 0x29, 0xaa, 0x9e, 0x38, 0x2b, 0x1b, 0x40, 0xe1, 0x84, 0x5d, 0x57, 0x17, 0x2d, 0x1a, 0x44, 0xdb, 0xea, 0x3b, 0xaa, 0x53, 0xa8, 0xc3, 0x32, 0x93, 0x2, 0xa9, 0xd4, 0xe3, 0xbe, 0x69, 0x2f, 0xb1, 0xae, 0x3e, 0x97, 0xe9, 0x84, 0x0, 0x90, 0x93, 0x15, 0xf8, 0x6a, 0x99, 0xc0, 0x83, 0x53, 0xe3, 0xa, 0x4a, 0x79, 0x2f, 0xd5, 0x99, 0x2c, 0x4a, 0x53, 0xec, 0x18, 0xeb, 0x24, 0xf5, 0x72, 0xb, 0xe9, 0x4a, 0xc8, 0x61, 0x94, 0xe4, 0x7b, 0xaa, 0xff, 0x8, 0x75, 0x60, 0x11, 0xa5, 0xe7, 0x7a, 0xee, 0xc2, 0x15, 0x46, 0xbf, 0xb5, 0xa, 0xa, 0xc9, 0x7f, 0x10, 0xd5, 0xe5, 0x46, 0x5c, 0x0, 0xfa, 0xff, 0x9d, 0xc9, 0x50, 0xd5, 0x10, 0x72, 0xf5, 0x63, 0x4, 0x76, 0x3f, 0x8c, 0x5, 0xea, 0xb9, 0x3c, 0x46, 0xcd, 0x0, 0xa9, 0x36, 0x3d, 0x8, 0xc2, 0x3c, 0xf2, 0x31, 0xe4, 0x6b, 0xa8, 0x1d, 0x2c, 0x22, 0x0, 0x3f, 0xa2, 0x20, 0x2a, 0xf2, 0x40, 0x35, 0xad, 0xcd, 0x57, 0x8c, 0xfb, 0xef, 0x25, 0x9e, 0xbd, 0x91, 0xa9, 0x1a, 0x84, 0x3e, 0x6f, 0xcc, 0x33, 0x81, 0xa7, 0x6b, 0x6, 0x46, 0x91, 0x30, 0x19, 0x57, 0x51, 0x65, 0x32, 0x1, 0x57, 0x48, 0xc7, 0xc1, 0x14, 0xda, 0x8, 0xab, 0x53, 0xdc, 0xa1, 0x72, 0x8, 0xba, 0xe6, 0x5c, 0x84, 0xd8, 0x3d, 0x91, 0xfb, 0x87, 0xe2, 0xa8, 0x38, 0x3a, 0xc8, 0x97, 0x68, 0x3d, 0xcf, 0xf, 0x42, 0x72, 0x6, 0x79, 0x4, 0x5, 0xf, 0x92, 0x66, 0x4f, 0xa4, 0xba, 0xde, 0x8e, 0x1a, 0x41, 0x15, 0xb6, 0xdb, 0x51, 0xc8, 0xe1, 0x7b, 0xb, 0xe9, 0x0, 0x41, 0x24, 0x7, 0x2a, 0xb9, 0xad, 0x75, 0xb1, 0x5f, 0x10, 0x7e, 0xb7, 0x67, 0xfa, 0xe0, 0x55, 0x54, 0xc4, 0x2d, 0x90, 0x58, 0x0, 0x46, 0xbb, 0xdf, 0x4, 0xa9, 0x88, 0xf8, 0x4e, 0xf3, 0x41, 0x27, 0x38, 0xd0, 0x1c, 0x4d, 0x75, 0x30, 0x1d, 0x75, 0xa8, 0x9c, 0xbb, 0xda, 0x1a, 0x4a, 0x77, 0x5d, 0x45, 0xb, 0x2a, 0x18, 0x5c, 0xf0, 0xe7, 0x8a, 0x13, 0x59, 0x51, 0x35, 0x82, 0xe3, 0xa, 0x1, 0xb5, 0x84, 0xc, 0xdb, 0x1c, 0x1e, 0x72, 0xc4, 0x4b, 0x76, 0x24, 0x18, 0x1, 0xc4, 0xa3, 0x28, 0x98, 0x62, 0xf4, 0x21, 0x80, 0x91, 0x28, 0xba, 0x81, 0x63, 0xff, 0x22, 0x81, 0xb8, 0x3d, 0x8d, 0xf1, 0x17, 0xc3, 0xf9, 0x95, 0x15, 0x2, 0xe0, 0xa1, 0x8f, 0xa5, 0x31, 0x89, 0x36, 0x21, 0x7, 0xe, 0x19, 0x2, 0xc7, 0xf1, 0x3d, 0xd4, 0x9d, 0x21, 0x9c, 0x6c, 0xaf, 0xa8, 0xdc, 0x70, 0xb6, 0x8c, 0xa1, 0x5d, 0x34, 0x5e, 0xa2, 0x4f, 0xbb, 0xa9, 0xa2, 0x2f, 0x93, 0xf8, 0xe3, 0xb3, 0xeb, 0x13, 0x88, 0xa8, 0xc7, 0xd9, 0x9d, 0x6d, 0xa8, 0xa5, 0x75, 0x69, 0x5f, 0x5f, 0x40, 0x95, 0x14, 0x51, 0xfc, 0x4f, 0x48, 0xea, 0x75, 0x70, 0x36, 0xf2, 0x15, 0x26, 0x2, 0xa, 0xf8, 0xee, 0x90, 0xca, 0xd7, 0x5b, 0xf5, 0x7c, 0xa7, 0x1b, 0x3a, 0xf0, 0x32, 0x69, 0xd6, 0xb8, 0x51, 0x56, 0x69, 0x59, 0x59, 0xa4, 0x81, 0x57, 0x95, 0x12, 0x55, 0x83, 0xe, 0x35, 0x11, 0x7f, 0x23, 0xff, 0x1b, 0x49, 0xe2, 0x7, 0xad, 0x95, 0xfd, 0x97, 0x60, 0xc3, 0x4f, 0x95, 0x78, 0xfe, 0x24, 0x52, 0xbb, 0x7a, 0x12, 0x70, 0xdd, 0x38, 0x47, 0xa8, 0x2c, 0x80, 0x72, 0x81, 0x57, 0x52, 0x28, 0x4e, 0xaa, 0xc4, 0xd8, 0xd6, 0xe2, 0x2e, 0xba, 0x3a, 0x53, 0x7, 0x2, 0xd8, 0x6b, 0x94, 0x5f, 0x5f, 0xe, 0x6a, 0xe8, 0xe4, 0x43, 0x67, 0x47, 0x78, 0xd2, 0x61, 0x83, 0xfc, 0x92, 0x6d, 0xa8, 0x42, 0x17, 0x5, 0x0, 0xba, 0x28, 0xab, 0xf0, 0x95, 0x21, 0x78, 0x3a, 0xc7, 0xd2, 0x31, 0x83, 0x33, 0xdc, 0x51, 0xc6, 0x9c, 0xa0, 0x82, 0x1c, 0xe2, 0x50, 0xff, 0x90, 0xe2, 0xf5, 0xf4, 0xce, 0xda, 0x38, 0x4c, 0x3, 0x11, 0x55, 0xe0, 0xc1, 0xb6, 0x18, 0xca, 0x36, 0xce, 0x98, 0x6, 0x4a, 0xf8, 0xdd, 0xfa, 0x51, 0x23, 0xb8, 0xd6, 0x6f, 0xe4, 0x5c, 0x99, 0xa5, 0xdb, 0x91, 0xa9, 0x3d, 0x2c, 0xc9, 0x6b, 0x98, 0x89, 0xc0, 0x9d, 0x2c, 0xf0, 0x82, 0x27, 0xd4, 0x65, 0xc4, 0xb9, 0x5b, 0x5, 0x27, 0x71, 0x9b, 0x5d, 0xe7, 0x19, 0x4b, 0xb0, 0x9, 0x2f, 0x93, 0x78, 0x26, 0x8a, 0x6, 0xdf, 0xb1, 0x49, 0x7c, 0xb7, 0xd0, 0xd8, 0x31, 0x4f, 0xb2, 0xc0, 0xb, 0x96, 0x60, 0xb8, 0x9f, 0xc5, 0x36, 0xe, 0xe5, 0x3f, 0xb0, 0x6b, 0x3b, 0x14, 0x4, 0x47, 0x94, 0xae, 0x34, 0x80, 0xe3, 0x88, 0x5b, 0x92, 0x4, 0xed, 0x3c, 0x3, 0x78, 0x79, 0x16, 0x78, 0xc1, 0xaa, 0x99, 0x5a, 0xdf, 0x9f, 0x68, 0xd7, 0x73, 0xa8, 0x8, 0x25, 0xf0, 0x75, 0x59, 0x3c, 0xd8, 0x81, 0x2d, 0x93, 0xf8, 0xce, 0xbb, 0x7c, 0xc5, 0x11, 0x4e, 0x57, 0xb, 0xbc, 0xe0, 0xc8, 0xcc, 0x9a, 0x9e, 0x69, 0xd7, 0x72, 0xa8, 0x8, 0x7, 0xed, 0xcf, 0xb0, 0x8d, 0x14, 0xaa, 0x2e, 0x49, 0x7c, 0xe7, 0xd, 0x89, 0x47, 0x17, 0xf5, 0xb1, 0xc0, 0xb, 0x8e, 0x74, 0x1c, 0x25, 0xb2, 0x1c, 0x36, 0xda, 0xb5, 0x1c, 0x3a, 0x9a, 0x61, 0x0, 0xa9, 0x55, 0x12, 0x9f, 0x87, 0xf3, 0x4c, 0xa7, 0x4, 0x21, 0x48, 0xa2, 0xb9, 0x5, 0x5e, 0x30, 0x94, 0x69, 0xee, 0xfb, 0xa, 0x8b, 0xa5, 0x94, 0x68, 0xa5, 0x21, 0x30, 0x93, 0xb5, 0xd9, 0xc6, 0x1b, 0xf6, 0xfd, 0x95, 0x16, 0x78, 0xc1, 0x90, 0x2e, 0x11, 0x51, 0x4d, 0xd2, 0xcf, 0xf8, 0x76, 0x83, 0x1a, 0x5b, 0x2c, 0xa5, 0x44, 0x8, 0xe1, 0x2b, 0x4f, 0xf1, 0x3b, 0xc8, 0x11, 0xd4, 0x4e, 0x16, 0xc4, 0xe4, 0xb6, 0xb6, 0xc0, 0xf3, 0x9f, 0x16, 0xf3, 0x35, 0x27, 0x49, 0x55, 0xc5, 0x6b, 0xea, 0x65, 0xb1, 0x94, 0x12, 0x21, 0xb6, 0xb6, 0xba, 0x61, 0x2e, 0x24, 0x43, 0xf8, 0xdc, 0x68, 0xb6, 0x11, 0x4, 0x7f, 0x77, 0x86, 0xdc, 0x4b, 0x4d, 0x89, 0x3f, 0xc5, 0xa9, 0x6e, 0xd4, 0x81, 0x87, 0x43, 0xd5, 0x1d, 0x6c, 0x17, 0x4, 0xdc, 0x97, 0x6e, 0x16, 0x78, 0x29, 0x53, 0x2b, 0x43, 0xc5, 0x4c, 0x25, 0x21, 0x7a, 0xbc, 0x61, 0xeb, 0xc1, 0xc9, 0xd2, 0x2f, 0x3, 0xee, 0x5, 0xd1, 0x52, 0x88, 0xc4, 0x41, 0xe8, 0xe2, 0xc5, 0x51, 0x7, 0x1e, 0x82, 0x6e, 0x75, 0x9d, 0x96, 0x33, 0x2, 0x54, 0x37, 0x21, 0xb5, 0x1f, 0x92, 0x68, 0x87, 0xe5, 0x79, 0x41, 0xba, 0xb0, 0x12, 0x84, 0xe7, 0x9c, 0x14, 0x55, 0x54, 0x4, 0x4b, 0xe8, 0x27, 0x43, 0x3d, 0x22, 0xf1, 0xfc, 0xb7, 0xa0, 0x4c, 0xc, 0xfd, 0xe4, 0x22, 0xe4, 0x33, 0x4e, 0x8a, 0x3a, 0xf0, 0x40, 0xfa, 0xf9, 0x64, 0xad, 0x29, 0x75, 0x82, 0x20, 0x80, 0xae, 0x4b, 0xc8, 0xc6, 0xd, 0xc5, 0x83, 0x7, 0x5, 0xf8, 0xfb, 0x8, 0x76, 0xd6, 0xe5, 0x4, 0xbf, 0xa0, 0x10, 0x4d, 0x85, 0x90, 0xfd, 0xae, 0x1f, 0x14, 0x82, 0xf8, 0x4d, 0x14, 0xfe, 0x6d, 0x18, 0xd0, 0xbd, 0xe0, 0xf1, 0x69, 0x2d, 0xd8, 0xc6, 0x73, 0x2e, 0x96, 0xee, 0xf, 0xc0, 0xc3, 0x80, 0xeb, 0x8a, 0xc8, 0xb7, 0xca, 0x5e, 0x9e, 0xca, 0xe9, 0x21, 0x41, 0xaf, 0xd7, 0xde, 0x35, 0x84, 0xab, 0x85, 0x21, 0x4e, 0x14, 0xc5, 0x6b, 0x91, 0x14, 0x8c, 0xf4, 0x9c, 0x9b, 0x2, 0xea, 0xc3, 0x10, 0xc3, 0x2e, 0xaf, 0x6c, 0x2a, 0x15, 0x1e, 0xa4, 0xa9, 0x2b, 0x8e, 0x23, 0x98, 0x62, 0x2, 0x1, 0xed, 0xb7, 0xd0, 0xd5, 0x2, 0x4, 0xcf, 0x72, 0x1c, 0x95, 0xf8, 0x81, 0xa8, 0x2, 0xf, 0xa0, 0x7b, 0x98, 0xed, 0x36, 0x46, 0xdb, 0x6b, 0x42, 0x9a, 0xcb, 0x38, 0x89, 0x47, 0xd8, 0x7f, 0xc5, 0x76, 0x18, 0xc6, 0x19, 0x65, 0xf2, 0xf4, 0x3, 0x4b, 0x90, 0xa6, 0x83, 0x10, 0xae, 0x2a, 0x3e, 0xfe, 0x7e, 0x77, 0x71, 0xca, 0x37, 0x80, 0xbe, 0x94, 0xf4, 0x9e, 0xe0, 0x8b, 0x47, 0xba, 0x7d, 0xc8, 0x36, 0xa, 0xde, 0xa2, 0xc2, 0x80, 0x5f, 0xe7, 0x7b, 0x0, 0xd9, 0xd, 0x6c, 0xa3, 0xec, 0xc7, 0x40, 0xd9, 0xc3, 0x83, 0x51, 0xa3, 0x6c, 0x7f, 0x3c, 0x42, 0x69, 0x3, 0x42, 0xa1, 0x9e, 0xfb, 0x3c, 0xfe, 0x3d, 0x44, 0xc7, 0x4f, 0xe3, 0x40, 0x83, 0xe0, 0xe4, 0x39, 0x53, 0xc2, 0x53, 0x8c, 0x17, 0x3b, 0xc3, 0x0, 0x89, 0x67, 0xe4, 0xa3, 0x76, 0xa, 0xc2, 0xb1, 0xda, 0xfb, 0xf0, 0xdb, 0x28, 0x1d, 0xf1, 0xa, 0xc1, 0x5f, 0x46, 0x5b, 0x2d, 0x9d, 0xfa, 0x2c, 0x5b, 0xb9, 0xe3, 0xe8, 0xf9, 0xef, 0xc2, 0xb9, 0x39, 0xc5, 0xc3, 0x7b, 0x40, 0xa2, 0x1d, 0xaa, 0x7c, 0xeb, 0xa, 0xe8, 0xa8, 0x4a, 0x80, 0xa7, 0xc1, 0xee, 0xf1, 0x91, 0xe1, 0x51, 0x6, 0x1e, 0xa4, 0xc, 0x12, 0x5f, 0xf5, 0x73, 0x6, 0x30, 0x99, 0x8f, 0x8b, 0xfb, 0x65, 0x18, 0x1a, 0x11, 0xe4, 0xd3, 0xd, 0x9b, 0x6e, 0x32, 0x25, 0xf8, 0x4a, 0x89, 0x17, 0x5c, 0xd, 0x3, 0xe1, 0x81, 0x8e, 0x88, 0xfe, 0xd0, 0x7, 0xd8, 0x8, 0x3c, 0xfe, 0x8c, 0xea, 0x7a, 0x35, 0x8f, 0x7e, 0x13, 0xa5, 0x20, 0xde, 0x96, 0x78, 0x6d, 0xd5, 0x9b, 0x25, 0x1e, 0x7f, 0x99, 0xe, 0xc1, 0xa1, 0xd1, 0x8b, 0xea, 0xb3, 0x70, 0xc7, 0x9b, 0xc2, 0xdd, 0xdc, 0x6d, 0xd5, 0xf3, 0x22, 0xda, 0xa4, 0xe7, 0xf2, 0x3d, 0xd6, 0x1c, 0x32, 0xe2, 0x67, 0xec, 0xed, 0xb, 0x51, 0xf7, 0xb8, 0xe1, 0x1, 0x1c, 0x28, 0xe3, 0xa7, 0x33, 0x95, 0x51, 0xf, 0x12, 0x2e, 0xe7, 0x13, 0xd2, 0xbc, 0x2e, 0xa2, 0x63, 0x8e, 0x16, 0xa7, 0xea, 0xd6, 0x3c, 0xda, 0x26, 0x0, 0x34, 0x2a, 0x49, 0xd, 0xe7, 0x84, 0xeb, 0x87, 0x3a, 0x9a, 0x55, 0xd4, 0xaa, 0x84, 0x60, 0xcc, 0xde, 0x22, 0xe0, 0x74, 0xb0, 0x32, 0xce, 0xa2, 0x46, 0x52, 0x6d, 0x86, 0xdd, 0x5a, 0xdb, 0x85, 0xdf, 0xc0, 0xf8, 0xa1, 0x4a, 0x38, 0x32, 0xd3, 0x51, 0x3e, 0xf0, 0x40, 0x43, 0x38, 0x3e, 0xe2, 0xe2, 0xbd, 0x6c, 0x22, 0x18, 0xa0, 0xed, 0x20, 0x82, 0x28, 0x87, 0xf6, 0x2b, 0xee, 0x65, 0xb0, 0xc4, 0x1f, 0xf2, 0x59, 0x19, 0xc2, 0xb5, 0x90, 0x4d, 0x8f, 0xda, 0x31, 0x2f, 0x1b, 0x8e, 0x94, 0xf7, 0xb9, 0xbe, 0x3e, 0xff, 0xb9, 0x2f, 0xef, 0xf, 0xae, 0x6e, 0x48, 0x6c, 0x64, 0xa2, 0xeb, 0x67, 0xe, 0x1c, 0x43, 0xfd, 0x7f, 0x22, 0x55, 0xc1, 0x1a, 0x29, 0xa8, 0x12, 0x50, 0x55, 0xee, 0xa0, 0x24, 0xc3, 0xc0, 0x22, 0xef, 0x4f, 0x57, 0x2d, 0x7b, 0x93, 0x3b, 0xde, 0xc8, 0x4, 0xb0, 0xa1, 0xce, 0x8b, 0x3e, 0x57, 0xbc, 0x91, 0xbf, 0x9f, 0xe9, 0x34, 0x8f, 0xc0, 0x18, 0x21, 0xf1, 0x43, 0x6c, 0x3c, 0xa3, 0x60, 0x2c, 0x55, 0x68, 0xd4, 0x48, 0x41, 0xea, 0x55, 0x43, 0x49, 0x3e, 0x44, 0x2f, 0x8f, 0xea, 0xf8, 0x30, 0x8e, 0xdf, 0x7, 0x12, 0xaf, 0x10, 0xb0, 0x96, 0x2, 0xf2, 0x1, 0xf, 0xee, 0xa5, 0x9c, 0x3b, 0xf6, 0x19, 0x9c, 0xb, 0x50, 0x4b, 0xde, 0xc3, 0xb7, 0xb4, 0xff, 0xbb, 0x25, 0xb9, 0xe, 0xe, 0xe0, 0x3d, 0xc0, 0x16, 0x9d, 0x25, 0x4e, 0x3d, 0xd8, 0xee, 0xfc, 0xdf, 0x66, 0xee, 0xd6, 0x0, 0xe3, 0xd2, 0x7d, 0x5d, 0xc8, 0xeb, 0x1a, 0x9a, 0x99, 0x42, 0x5f, 0x53, 0x8a, 0xff, 0x59, 0x1c, 0x77, 0x39, 0xee, 0xbb, 0x37, 0x79, 0x5, 0x8d, 0xf9, 0x79, 0x34, 0x86, 0x35, 0x68, 0x70, 0xe, 0x87, 0xd2, 0x7c, 0x28, 0x61, 0xd7, 0x9a, 0x93, 0x95, 0xf8, 0xec, 0x80, 0x52, 0xaa, 0x95, 0xf, 0x1a, 0xc6, 0x7c, 0x22, 0x2d, 0x20, 0x28, 0xa1, 0x8e, 0xa0, 0xf2, 0xd6, 0x5d, 0x12, 0xcf, 0x17, 0x4c, 0x65, 0x87, 0xf0, 0x9b, 0x76, 0xb1, 0xaf, 0x78, 0x72, 0x13, 0x72, 0xe3, 0xfa, 0x72, 0x4c, 0x1a, 0x71, 0xb7, 0x18, 0x4c, 0x95, 0x74, 0x21, 0xed, 0x98, 0x65, 0x4, 0xd0, 0x4e, 0xf6, 0x17, 0x42, 0xbd, 0x2e, 0x3f, 0xdf, 0x9a, 0xc0, 0x6d, 0x96, 0x20, 0xec, 0xf1, 0x1b, 0xcf, 0x52, 0x98, 0xad, 0xf0, 0xf8, 0x7e, 0x26, 0x4b, 0xbc, 0xee, 0xe7, 0x50, 0xa, 0x8d, 0xa6, 0x6c, 0x83, 0x97, 0x13, 0x88, 0xf3, 0xd9, 0xd6, 0xc9, 0xd3, 0x50, 0xb1, 0x51, 0x3d, 0xd, 0x4e, 0xba, 0xc3, 0xb8, 0xb3, 0x99, 0x5, 0x95, 0xb6, 0x73, 0x8c, 0xee, 0x11, 0xa7, 0xe2, 0x5a, 0x52, 0x14, 0x33, 0x16, 0x5a, 0x79, 0xc4, 0xc1, 0xb7, 0x89, 0x83, 0x3e, 0x8e, 0x52, 0xf7, 0x1c, 0x2e, 0xa4, 0x26, 0xe4, 0xb3, 0x53, 0x58, 0x90, 0xd8, 0x3d, 0xff, 0x4d, 0x87, 0x40, 0x32, 0xcf, 0x21, 0x47, 0x76, 0x35, 0xca, 0xdc, 0x15, 0x48, 0xe5, 0x82, 0xa6, 0x4b, 0x8c, 0x39, 0x2a, 0xf3, 0x79, 0xdc, 0x16, 0x52, 0x58, 0x41, 0x68, 0xf5, 0xe1, 0xce, 0x84, 0x1a, 0xa2, 0xb9, 0xdc, 0xed, 0x75, 0x1, 0xdf, 0x54, 0x68, 0x1, 0x1d, 0x11, 0x4f, 0xed, 0xcd, 0xf9, 0xe0, 0x11, 0xa1, 0x8a, 0xd9, 0xbd, 0x5c, 0x3, 0xfd, 0xe8, 0x74, 0xeb, 0x48, 0x20, 0x35, 0x25, 0xf7, 0x4c, 0xe2, 0x3a, 0x15, 0x14, 0xd4, 0x13, 0x28, 0x38, 0x16, 0xa7, 0xda, 0x91, 0x18, 0x75, 0xe0, 0x6, 0xc9, 0x6c, 0x8f, 0x11, 0xa1, 0xaf, 0x28, 0xbd, 0x5b, 0x52, 0xfd, 0x0, 0x18, 0xe, 0xe7, 0xee, 0x56, 0x9d, 0xea, 0x44, 0x29, 0x25, 0xf7, 0x36, 0x2, 0x56, 0x67, 0x3a, 0xcf, 0xa4, 0x9a, 0x84, 0x85, 0x93, 0xca, 0xd9, 0xdc, 0x6a, 0xaa, 0xb5, 0x9d, 0xa5, 0x72, 0xa9, 0x4a, 0x4f, 0xd2, 0x78, 0xdf, 0x44, 0x69, 0x1c, 0x4, 0x2d, 0xa6, 0x1a, 0x7d, 0x3f, 0x77, 0xaf, 0x6e, 0xdc, 0x41, 0xda, 0x53, 0x70, 0xd5, 0xe4, 0xee, 0x50, 0x8d, 0xbb, 0xda, 0x4e, 0xa, 0x29, 0xf4, 0x79, 0x1d, 0xed, 0xed, 0xd9, 0xb4, 0xb1, 0x67, 0x49, 0x70, 0xf, 0x7d, 0xd1, 0x8e, 0x97, 0x47, 0xe9, 0x6c, 0x3b, 0x92, 0x26, 0x4, 0xec, 0xb2, 0xb6, 0xc4, 0x42, 0xd, 0xae, 0x85, 0xdd, 0xdc, 0xd1, 0xb6, 0x72, 0x37, 0x9f, 0xcf, 0xf9, 0x87, 0x76, 0x33, 0x57, 0xf6, 0x70, 0x4c, 0x90, 0x2c, 0xfd, 0x57, 0x80, 0x1, 0x0, 0xc5, 0xb1, 0xf0, 0x59, 0xcd, 0x2e, 0xe2, 0x4, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)parse_logo_png { + return [NSData dataWithBytes:parse_logo_png length:sizeof(parse_logo_png)]; + } + + const unsigned char facebook_icon3x_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0, 0x42, 0x8, 0x6, 0x0, 0x0, 0x0, 0xe3, 0x54, 0x0, 0xe8, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x0, 0x0, 0x3, 0x23, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2d, 0x63, 0x30, 0x31, 0x34, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x31, 0x34, 0x38, 0x31, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2f, 0x30, 0x33, 0x2f, 0x31, 0x33, 0x2d, 0x31, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x31, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x4d, 0x61, 0x63, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x29, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x39, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x41, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x3e, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x37, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x38, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0x90, 0x6f, 0x2b, 0x47, 0x0, 0x0, 0x3, 0x4d, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x9c, 0xdf, 0x4b, 0x53, 0x61, 0x18, 0xc7, 0xcf, 0x39, 0x9b, 0x4e, 0xdc, 0x28, 0x6d, 0xb3, 0xd4, 0xd9, 0x36, 0x1c, 0x4a, 0x2c, 0x49, 0xa2, 0x1b, 0x17, 0x24, 0xde, 0xc8, 0x48, 0x85, 0xa3, 0x5e, 0xf4, 0x17, 0x94, 0xd2, 0xa5, 0x5e, 0x8c, 0x6, 0x42, 0x41, 0x46, 0x30, 0x6, 0x61, 0x74, 0x57, 0xd7, 0x43, 0x2f, 0x92, 0xae, 0x84, 0x89, 0x45, 0x9b, 0x3f, 0x82, 0x5, 0xf9, 0xa3, 0x99, 0x76, 0xe3, 0x61, 0x3, 0x65, 0x84, 0xc2, 0xf4, 0xa0, 0xec, 0x6c, 0x3b, 0x3b, 0x3d, 0xcb, 0x6e, 0xba, 0x48, 0x5f, 0x73, 0x9c, 0xce, 0xf6, 0x3e, 0xf, 0x1c, 0x36, 0xb6, 0x67, 0xe7, 0x3d, 0xfb, 0xec, 0xf9, 0xf1, 0x7d, 0xde, 0x3, 0x63, 0x19, 0x30, 0x97, 0xcb, 0x65, 0xb, 0x4, 0x2, 0xcf, 0xdc, 0x6e, 0xf7, 0x9d, 0x9a, 0x9a, 0x1a, 0x3, 0xbc, 0xa4, 0x30, 0xe5, 0x6d, 0xec, 0xe1, 0xe1, 0x61, 0xf6, 0xb, 0xd8, 0xd8, 0xd8, 0xd8, 0xe3, 0x48, 0x24, 0xb2, 0x5a, 0x80, 0x60, 0xdf, 0xda, 0xda, 0x8a, 0x29, 0x94, 0x5a, 0x2a, 0x95, 0xda, 0xe9, 0xea, 0xea, 0xba, 0xc9, 0x84, 0x42, 0xa1, 0xa0, 0x42, 0xb9, 0xad, 0xac, 0xac, 0x7c, 0x60, 0x45, 0x51, 0xdc, 0x36, 0x99, 0x4c, 0x8d, 0xc, 0xc5, 0x96, 0xcf, 0xe7, 0xf, 0x38, 0x80, 0xc0, 0x31, 0x94, 0x1b, 0xc7, 0x71, 0x2c, 0x47, 0x41, 0x61, 0x24, 0x31, 0x85, 0x43, 0x6, 0xbf, 0xa3, 0x2, 0x11, 0x20, 0x88, 0xb2, 0x2, 0x91, 0x83, 0xee, 0x97, 0x85, 0x47, 0xf9, 0xbc, 0x27, 0xd2, 0x97, 0xd0, 0x97, 0xce, 0x80, 0x10, 0x5c, 0x9c, 0x9f, 0x9f, 0xff, 0xb8, 0xbc, 0xbc, 0xbc, 0x9a, 0x48, 0x24, 0xb6, 0x41, 0x1d, 0x8a, 0x70, 0xe4, 0xcd, 0x66, 0xb3, 0xe, 0xde, 0xaf, 0x80, 0xc3, 0x0, 0xcf, 0x8d, 0xd5, 0xd5, 0xd5, 0x17, 0xea, 0xea, 0xea, 0xcc, 0x9d, 0x9d, 0x9d, 0xb7, 0x7, 0x6, 0x6, 0x1e, 0x90, 0x95, 0x4b, 0x45, 0xd9, 0xd1, 0xb8, 0xde, 0x91, 0x82, 0xc1, 0xe0, 0xcb, 0x8e, 0x8e, 0xe, 0xd7, 0x59, 0xc9, 0xf5, 0xf5, 0xf5, 0xb9, 0x9, 0xd7, 0xd8, 0xd7, 0x74, 0x44, 0x8, 0x82, 0xf0, 0x75, 0x68, 0x68, 0xe8, 0xfe, 0xdc, 0xdc, 0x5c, 0xf4, 0x5f, 0x3e, 0xf, 0x91, 0x61, 0x2c, 0xf9, 0xd4, 0x88, 0xc5, 0x62, 0x8b, 0xf0, 0x8b, 0xf2, 0xf1, 0x78, 0x7c, 0x8f, 0xda, 0x62, 0x99, 0x4c, 0x26, 0x5, 0x9e, 0xe7, 0x7, 0xd5, 0x82, 0xa0, 0x59, 0x10, 0xa3, 0xa3, 0xa3, 0xf, 0x61, 0x22, 0xfe, 0x41, 0x75, 0xfb, 0x8c, 0x46, 0xa3, 0x33, 0x93, 0x93, 0x93, 0x21, 0xea, 0x75, 0xc4, 0xc4, 0xc4, 0xc4, 0x2b, 0xa8, 0xe2, 0x74, 0xb, 0xaa, 0x54, 0x2a, 0xb5, 0x3d, 0x3b, 0x3b, 0x1b, 0x2e, 0x9a, 0xf0, 0xc8, 0x64, 0x8e, 0x4a, 0xb2, 0x6b, 0x80, 0x60, 0xfa, 0xbc, 0xbb, 0xbb, 0x4b, 0x7c, 0xf1, 0x6d, 0x6d, 0x6d, 0xf6, 0xfe, 0xfe, 0xfe, 0xbb, 0x2d, 0x2d, 0x2d, 0x2e, 0x10, 0x50, 0x26, 0x96, 0x65, 0xff, 0x90, 0x48, 0xf5, 0xf5, 0xf5, 0xd6, 0x92, 0x4, 0xb1, 0xbe, 0xbe, 0xbe, 0x46, 0xea, 0x3b, 0x3c, 0x3c, 0x3c, 0x8, 0x69, 0xf4, 0xc6, 0x60, 0x30, 0xd4, 0x96, 0x5d, 0x6a, 0x80, 0x6c, 0x4e, 0x90, 0xf8, 0xd9, 0xed, 0x76, 0xb, 0x40, 0x78, 0x5d, 0x2c, 0x8, 0x9a, 0x3, 0x1, 0x73, 0xc3, 0x3e, 0x89, 0x1f, 0xcc, 0x10, 0x6e, 0x80, 0x70, 0xa9, 0x6c, 0x8b, 0x25, 0xe4, 0x38, 0xd1, 0x14, 0xd9, 0xdc, 0xdc, 0x6c, 0xc3, 0x31, 0xbc, 0x50, 0xd8, 0xf4, 0xfa, 0xa, 0x4, 0x71, 0x3c, 0x31, 0x2b, 0x8, 0x82, 0xa6, 0x59, 0x3, 0x41, 0x20, 0x88, 0x63, 0xab, 0xac, 0xac, 0x24, 0x2a, 0x82, 0x55, 0x55, 0x55, 0x86, 0xa2, 0x17, 0x60, 0x15, 0xf7, 0x18, 0xe2, 0xd3, 0xd3, 0xd3, 0x6f, 0x4f, 0xea, 0x9e, 0x20, 0xb1, 0x37, 0x48, 0xce, 0x15, 0x89, 0x44, 0x3e, 0x19, 0x8d, 0xc6, 0x17, 0xcc, 0x29, 0x37, 0xa7, 0x7a, 0x7a, 0x7a, 0x3c, 0xe, 0x87, 0xe3, 0x3a, 0x69, 0x5, 0x56, 0x65, 0xcf, 0x32, 0x1c, 0xe, 0xcf, 0xa8, 0x1d, 0x61, 0xb0, 0xe6, 0x3b, 0xd2, 0x3d, 0x4b, 0xd5, 0x52, 0x43, 0xa7, 0xd3, 0xa9, 0x3a, 0xd7, 0x80, 0xf2, 0x64, 0x6d, 0x36, 0x9b, 0x93, 0xfa, 0x62, 0x69, 0xb1, 0x58, 0x2e, 0xc2, 0x44, 0x7a, 0x85, 0x7a, 0x10, 0xd, 0xd, 0xd, 0x97, 0xa1, 0x8e, 0xd4, 0x52, 0xf, 0xc2, 0x6a, 0xb5, 0x36, 0x9e, 0xa5, 0x19, 0x94, 0x2d, 0x8, 0xe8, 0x16, 0x76, 0x14, 0x54, 0xc7, 0x13, 0xaa, 0x3, 0x41, 0x30, 0xbf, 0x36, 0x6f, 0xb4, 0x9, 0x42, 0x96, 0xe5, 0x9c, 0x9a, 0x20, 0x9a, 0x9a, 0x9a, 0xae, 0x6a, 0x52, 0x59, 0x3a, 0x9d, 0xce, 0x6b, 0x81, 0x40, 0xe0, 0xe9, 0x49, 0xca, 0x12, 0x94, 0x67, 0x70, 0x69, 0x69, 0xe9, 0xdb, 0x69, 0xe7, 0xf2, 0x78, 0x3c, 0xee, 0xee, 0xee, 0xee, 0xde, 0xbf, 0x29, 0x4b, 0xd0, 0x2c, 0x6c, 0x6b, 0x6b, 0xeb, 0x8d, 0xb3, 0xce, 0xf6, 0x9a, 0xb9, 0x1b, 0x3e, 0x32, 0x32, 0x72, 0x8f, 0xe4, 0x9a, 0xfd, 0x7e, 0xbf, 0xb7, 0xc8, 0x4b, 0xef, 0x6b, 0xaa, 0x46, 0x48, 0x92, 0x94, 0x25, 0xf1, 0x4b, 0xa7, 0xd3, 0x12, 0x8e, 0xe1, 0xb8, 0x1f, 0x81, 0x20, 0x10, 0x4, 0x82, 0x40, 0x10, 0x8, 0x2, 0x41, 0x20, 0x8, 0x4, 0x81, 0x20, 0x10, 0x4, 0x82, 0x40, 0x10, 0x8, 0x2, 0x41, 0x20, 0x8, 0x4, 0x51, 0x7e, 0x20, 0x58, 0xc4, 0xc0, 0xb0, 0x9c, 0x24, 0x49, 0x88, 0xa1, 0x10, 0x11, 0x85, 0xbf, 0xe, 0xa0, 0x1d, 0x82, 0x20, 0x8, 0xdf, 0x39, 0xaf, 0xd7, 0xeb, 0x13, 0x45, 0x31, 0xa9, 0x95, 0x10, 0x2d, 0xb2, 0xdf, 0xa9, 0x26, 0xcb, 0xf2, 0x91, 0xcf, 0xe7, 0x7b, 0xa4, 0x5f, 0x58, 0x58, 0x58, 0xe3, 0x79, 0xbe, 0xd7, 0xef, 0xf7, 0x3f, 0x6f, 0x6f, 0x6f, 0xbf, 0x55, 0x1, 0xf6, 0x1f, 0x41, 0x64, 0x9, 0xfd, 0xa, 0xf9, 0x7c, 0x70, 0x9e, 0x85, 0x72, 0xb9, 0x9c, 0xbc, 0xb9, 0xb9, 0xb9, 0x31, 0x3e, 0x3e, 0xfe, 0x64, 0x6a, 0x6a, 0xea, 0xfd, 0x4f, 0x1, 0x6, 0x0, 0xec, 0x28, 0x31, 0x86, 0x11, 0x87, 0xbb, 0xa9, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)facebook_icon3x_png { + return [NSData dataWithBytes:facebook_icon3x_png length:sizeof(facebook_icon3x_png)]; + } + + const unsigned char twitter_icon2x_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x24, 0x8, 0x6, 0x0, 0x0, 0x0, 0xf2, 0xd7, 0xd8, 0x6c, 0x0, 0x0, 0x0, 0x1, 0x73, 0x52, 0x47, 0x42, 0x0, 0xae, 0xce, 0x1c, 0xe9, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0x2e, 0x23, 0x0, 0x0, 0x2e, 0x23, 0x1, 0x78, 0xa5, 0x3f, 0x76, 0x0, 0x0, 0x1, 0xcb, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x3c, 0x2f, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0xa, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0xa, 0x29, 0x2e, 0xcd, 0x3d, 0x0, 0x0, 0x5, 0xbd, 0x49, 0x44, 0x41, 0x54, 0x58, 0x9, 0xc5, 0x98, 0x5b, 0xa8, 0x95, 0x45, 0x14, 0xc7, 0xdd, 0x9e, 0xa3, 0x42, 0x56, 0x50, 0x91, 0x5, 0x86, 0x9c, 0xae, 0x44, 0x50, 0x68, 0x46, 0xd1, 0x85, 0x12, 0x7a, 0x28, 0x22, 0x88, 0xa0, 0x22, 0x2, 0xe9, 0x25, 0x90, 0x82, 0xea, 0x21, 0xe8, 0xf6, 0x90, 0xd1, 0x4b, 0x29, 0xbd, 0x56, 0x4, 0x81, 0x51, 0x50, 0xd4, 0x93, 0x2f, 0x85, 0x74, 0x21, 0x3a, 0xa6, 0x60, 0x54, 0x90, 0x12, 0x89, 0x91, 0xfa, 0x90, 0x5d, 0xf5, 0xa1, 0x9b, 0x78, 0x3d, 0xbb, 0xdf, 0x6f, 0xed, 0x59, 0xdf, 0xf9, 0xbe, 0x7d, 0xf6, 0xf6, 0xec, 0xbd, 0xf, 0xda, 0x82, 0xff, 0x59, 0x6b, 0xd6, 0xcc, 0xac, 0xf9, 0xcf, 0xcc, 0x9a, 0xd9, 0xf3, 0x9d, 0x79, 0xf3, 0x90, 0x76, 0xbb, 0xdd, 0x52, 0x2b, 0xd8, 0xf3, 0x3b, 0xd6, 0xdc, 0xff, 0xd6, 0x63, 0x61, 0x4f, 0x80, 0xe7, 0xc1, 0x24, 0xf8, 0x19, 0x1c, 0x7, 0x3f, 0x81, 0xcf, 0xc0, 0x33, 0xe0, 0x82, 0x1c, 0x11, 0x7b, 0x1, 0x18, 0xcf, 0xb2, 0x9a, 0xf2, 0x32, 0xd0, 0xd2, 0x8, 0xb2, 0xe8, 0xd3, 0xc0, 0xe9, 0xa5, 0x72, 0xac, 0xde, 0x78, 0x14, 0x9b, 0x58, 0x55, 0xc, 0x6c, 0x89, 0x1e, 0x4, 0x75, 0x99, 0xaa, 0x17, 0xb0, 0xff, 0x6, 0x4f, 0x74, 0x8f, 0x85, 0x6f, 0x5, 0xf8, 0x10, 0x3c, 0x1b, 0x75, 0x18, 0x31, 0x13, 0xf4, 0x23, 0x60, 0x5b, 0x76, 0x48, 0x7f, 0x96, 0x87, 0xd1, 0xf4, 0x8d, 0x5d, 0x42, 0xb7, 0xc0, 0x46, 0x90, 0x72, 0x4, 0x43, 0x1c, 0x3, 0x12, 0x56, 0xa7, 0xf, 0x33, 0xe4, 0x2d, 0xfe, 0xde, 0x8, 0xd6, 0x82, 0xcd, 0x40, 0x39, 0x4, 0xce, 0x4b, 0xc2, 0xb9, 0xc2, 0x9b, 0xa2, 0xaa, 0xdd, 0x7e, 0x27, 0xc9, 0x51, 0x6e, 0x6c, 0x4b, 0xfa, 0x67, 0xd3, 0xf4, 0xcb, 0x98, 0x6f, 0x96, 0x98, 0xe, 0x98, 0x24, 0x25, 0xda, 0xb, 0xd6, 0x8b, 0x5e, 0x72, 0x7b, 0x63, 0x4c, 0x5a, 0x2c, 0x1, 0x7f, 0xd4, 0x5a, 0x7e, 0x80, 0xbd, 0xc0, 0x46, 0xe8, 0x85, 0x8d, 0xc6, 0xb3, 0x14, 0x68, 0x9f, 0x3b, 0x76, 0x7f, 0x89, 0xe7, 0xa, 0xf6, 0x22, 0xd8, 0xcf, 0x97, 0xc4, 0xd5, 0x7f, 0x82, 0x7b, 0xa, 0x8f, 0x55, 0xd8, 0xf, 0xe4, 0x1, 0x73, 0xb9, 0xcf, 0x2e, 0x5c, 0x8e, 0xa0, 0xef, 0x0, 0x5b, 0x69, 0xb0, 0xb4, 0xd5, 0x6a, 0x39, 0xe0, 0x98, 0x28, 0xf5, 0x7d, 0x15, 0x6d, 0x68, 0xde, 0x3a, 0x56, 0x1a, 0x3c, 0xd9, 0xb7, 0xe1, 0x9, 0x2a, 0x88, 0x21, 0x27, 0x77, 0xc8, 0xf1, 0x3e, 0x5, 0x57, 0xe2, 0xdb, 0x89, 0xfe, 0x8, 0xec, 0x5, 0xb1, 0x8a, 0x57, 0xe3, 0xc, 0x99, 0x42, 0x30, 0xe, 0x97, 0xe2, 0x7e, 0xf4, 0xbd, 0xd1, 0xa8, 0xd3, 0x6e, 0x9c, 0x72, 0x5f, 0xe2, 0x59, 0x87, 0xbe, 0x16, 0x28, 0xc6, 0xf2, 0x36, 0xe8, 0xb7, 0x9a, 0x3d, 0xfd, 0x1d, 0xa, 0x55, 0x3f, 0xe3, 0x28, 0x77, 0x26, 0xf, 0xb7, 0xfd, 0x22, 0xf0, 0xaf, 0x5e, 0xc4, 0x1, 0x14, 0x57, 0x36, 0xe5, 0x5d, 0x8c, 0x89, 0xec, 0x80, 0xed, 0x8a, 0x4b, 0x3e, 0x72, 0xb5, 0xe6, 0xcf, 0x74, 0x78, 0x94, 0x3a, 0xe5, 0x28, 0xe8, 0x49, 0xaa, 0xdb, 0x5f, 0x48, 0xd6, 0xdb, 0x66, 0x3e, 0xbb, 0x78, 0x91, 0xc3, 0xe8, 0xce, 0x99, 0xc2, 0xf0, 0xde, 0xfb, 0x1e, 0x28, 0x39, 0x48, 0xda, 0xd9, 0xd1, 0x6b, 0xe7, 0x45, 0xb0, 0x34, 0x9, 0xaa, 0x29, 0xcf, 0x7, 0xb1, 0xf2, 0xe8, 0xc8, 0x77, 0xf4, 0x3a, 0xa0, 0xc, 0x9c, 0xbf, 0x5d, 0x84, 0x73, 0xd1, 0xf6, 0x11, 0xe3, 0xa6, 0x32, 0x4e, 0x90, 0x75, 0xb0, 0x31, 0xf2, 0xee, 0x28, 0xce, 0x4d, 0x56, 0x20, 0x53, 0x1d, 0x25, 0x97, 0x98, 0x91, 0x39, 0x65, 0xbd, 0x77, 0xf4, 0xd3, 0xc0, 0x89, 0x79, 0xf5, 0xdc, 0x6, 0x16, 0xd3, 0x77, 0xca, 0xbc, 0x5, 0xc7, 0x81, 0xf9, 0xaf, 0xe4, 0xd9, 0x30, 0x48, 0x63, 0x17, 0x3a, 0xd5, 0x33, 0xff, 0xd2, 0xb7, 0x5d, 0xf3, 0x1e, 0x2f, 0xf6, 0x1b, 0xf8, 0xbf, 0x20, 0xc6, 0x22, 0x74, 0x9c, 0xd, 0x3, 0x67, 0xc3, 0xd7, 0xb1, 0x1d, 0xd0, 0x55, 0xa, 0xd2, 0x19, 0x84, 0xe, 0xde, 0x18, 0xfa, 0x24, 0x7e, 0x6, 0x58, 0xd, 0x9c, 0xe0, 0xe, 0xea, 0xde, 0x6, 0x6b, 0xc0, 0xd, 0x60, 0x2, 0x9f, 0xf2, 0x4b, 0x47, 0xcd, 0x23, 0x44, 0x83, 0x48, 0x71, 0xf, 0xac, 0xf2, 0x86, 0xca, 0x83, 0x1c, 0x5b, 0x5a, 0xad, 0x0, 0x3, 0x3e, 0x7, 0x94, 0x13, 0x1d, 0x14, 0xeb, 0xdc, 0xea, 0x4c, 0x15, 0xdb, 0xa7, 0x78, 0xd, 0xed, 0x2, 0x7b, 0x8b, 0xa3, 0x9e, 0x93, 0xc3, 0xd8, 0xa6, 0xa5, 0xb2, 0xd6, 0xa9, 0xa1, 0x3b, 0xb9, 0x8b, 0x3d, 0xee, 0xa, 0x20, 0x4b, 0x8a, 0xfd, 0x2, 0xb6, 0xd7, 0xdb, 0xe3, 0xc0, 0x6d, 0xa9, 0xb6, 0x16, 0x3b, 0xc5, 0xe, 0xb, 0xca, 0xca, 0xd9, 0xc6, 0x95, 0xd7, 0x67, 0xac, 0x33, 0xb1, 0x45, 0xe4, 0x13, 0x65, 0xcd, 0xb9, 0xc8, 0xfe, 0xee, 0xce, 0x49, 0xe8, 0xa, 0x2a, 0xbe, 0x63, 0xd0, 0x57, 0xd0, 0x5b, 0xc1, 0x16, 0xe0, 0xf5, 0x15, 0xa9, 0x81, 0xbf, 0x31, 0x72, 0x21, 0x4b, 0x75, 0x4c, 0xc8, 0xd9, 0x7b, 0xe, 0x6c, 0x2b, 0x9c, 0x84, 0xf9, 0x8c, 0x1a, 0x5c, 0xba, 0xc6, 0xc8, 0xce, 0xfb, 0xba, 0x23, 0x44, 0x5, 0x8d, 0x2f, 0xa4, 0x62, 0x17, 0xc8, 0xa5, 0x3f, 0x8c, 0x9d, 0xf9, 0xd3, 0xdd, 0xe7, 0x64, 0x97, 0x3d, 0x53, 0x2e, 0xa4, 0x79, 0xbb, 0x92, 0x89, 0x6f, 0x87, 0x9f, 0xb, 0xe2, 0x42, 0x74, 0xb6, 0x9c, 0xc2, 0x1e, 0xec, 0x6f, 0x74, 0x20, 0x1e, 0xac, 0x45, 0x61, 0xfd, 0x3f, 0x7f, 0xf2, 0x12, 0x70, 0x75, 0xe5, 0xa5, 0xc4, 0x4e, 0x6b, 0xc4, 0x1d, 0xaa, 0x81, 0xbc, 0xdf, 0x51, 0x71, 0x6b, 0x64, 0xa7, 0xe2, 0x3a, 0x35, 0xaa, 0xa4, 0x45, 0x92, 0xfb, 0x96, 0x85, 0xf4, 0xee, 0x9f, 0x8f, 0xae, 0xf8, 0xb8, 0xf4, 0xb1, 0xd4, 0x68, 0xf3, 0xd7, 0xb4, 0x30, 0x15, 0x5c, 0xe5, 0xcc, 0x23, 0xcc, 0x69, 0x29, 0x41, 0xc3, 0x51, 0xb7, 0xa7, 0x5b, 0x8c, 0x6e, 0xd5, 0x89, 0x11, 0xe5, 0x93, 0x12, 0x29, 0xcf, 0x59, 0x14, 0x83, 0x3d, 0x3, 0x7b, 0xc2, 0xf, 0xe1, 0x79, 0xa8, 0x34, 0x92, 0xb4, 0x79, 0x3c, 0x43, 0xba, 0x82, 0xce, 0xa8, 0x9f, 0xa3, 0xc3, 0x95, 0xf4, 0xce, 0x77, 0x11, 0x3f, 0x2e, 0xb1, 0xaa, 0xd5, 0xb5, 0x1c, 0xec, 0x21, 0xe1, 0x9d, 0x2a, 0xe9, 0xcd, 0xf8, 0xee, 0x6, 0x76, 0xc8, 0x3c, 0x6e, 0x74, 0xc0, 0x5f, 0xc9, 0x49, 0x20, 0x9f, 0xbb, 0xfd, 0x39, 0xb1, 0x77, 0x96, 0x1d, 0xcc, 0x14, 0x89, 0x71, 0xab, 0xe5, 0xa6, 0x41, 0x92, 0xde, 0x48, 0xcd, 0x25, 0xc0, 0x5f, 0x32, 0xd3, 0xc2, 0x3c, 0x47, 0x9d, 0x52, 0xd9, 0x50, 0x46, 0xf3, 0x76, 0x68, 0xc, 0x3e, 0x23, 0x4f, 0x21, 0xe7, 0x9b, 0x61, 0x31, 0x30, 0x45, 0xee, 0x2, 0x8f, 0x81, 0x95, 0xc0, 0x99, 0xfa, 0x3, 0x41, 0x8c, 0x4e, 0x90, 0xba, 0x4d, 0xdd, 0x5c, 0xc5, 0xd5, 0xf5, 0x5a, 0xfd, 0x1, 0x5c, 0xc5, 0x18, 0x7e, 0xa5, 0x54, 0x63, 0x65, 0xf0, 0xbc, 0x77, 0xe3, 0x91, 0x52, 0x88, 0x1c, 0xa4, 0xd2, 0xfc, 0x59, 0xe, 0x7e, 0x5, 0xe7, 0x0, 0x25, 0x26, 0x97, 0x64, 0xc3, 0xd1, 0x35, 0xfb, 0x68, 0x35, 0xf7, 0x3f, 0xeb, 0xa, 0x59, 0x53, 0x74, 0xfa, 0xd, 0xd1, 0x2b, 0x2e, 0x33, 0x8a, 0x9, 0xa0, 0x57, 0x83, 0xba, 0xf4, 0x7c, 0x7, 0x74, 0x3d, 0x9, 0x7b, 0xb6, 0x21, 0xc8, 0x6c, 0x7e, 0xc7, 0xf1, 0x6d, 0xa2, 0x7c, 0x9d, 0xbc, 0xb0, 0x67, 0xec, 0x7e, 0xd6, 0xd5, 0x75, 0x6c, 0xb9, 0xe, 0x3a, 0xbc, 0x67, 0x4, 0xc4, 0xcf, 0xf3, 0x7c, 0x9f, 0xce, 0x36, 0xf8, 0x28, 0xf5, 0xc6, 0xce, 0xf8, 0x37, 0x97, 0xb1, 0xab, 0x9d, 0xaf, 0x93, 0xeb, 0x69, 0xd3, 0x39, 0xe, 0x22, 0x7a, 0x11, 0x98, 0x4, 0x6d, 0x56, 0x52, 0x22, 0xbe, 0xfc, 0x3d, 0x98, 0x6, 0x1f, 0x85, 0x58, 0xbf, 0x3e, 0xf9, 0x39, 0xb6, 0x7e, 0x68, 0xb2, 0x39, 0x3, 0x8, 0xc5, 0x77, 0x1b, 0x7a, 0x21, 0x78, 0xd, 0xf4, 0x92, 0x7e, 0x4, 0x6, 0xf5, 0x1b, 0x33, 0xc9, 0x4e, 0xd6, 0xc6, 0x1e, 0x28, 0x15, 0xb2, 0x7d, 0xa5, 0x9, 0x56, 0x7d, 0x6c, 0x62, 0x5f, 0x7, 0x36, 0x80, 0xdd, 0xe0, 0x77, 0x90, 0x3, 0xd, 0x4a, 0xae, 0xbb, 0x1d, 0x21, 0xe2, 0x9f, 0x23, 0xea, 0x3d, 0x20, 0xbe, 0xd8, 0xd1, 0xd5, 0x98, 0x15, 0x91, 0x61, 0xc, 0x2, 0x78, 0x7, 0x37, 0x5e, 0x6d, 0x94, 0x1f, 0x6, 0xe6, 0xb5, 0x32, 0x6a, 0x7a, 0x78, 0x65, 0x29, 0x7e, 0x95, 0x5f, 0x26, 0x27, 0xb4, 0xbf, 0x70, 0xb3, 0xb, 0xd, 0x25, 0x55, 0x47, 0x7e, 0x11, 0x37, 0xbe, 0x8a, 0x69, 0x73, 0x3e, 0x78, 0x19, 0xe4, 0xea, 0xe, 0x4b, 0x96, 0xae, 0x31, 0xc1, 0xec, 0xbf, 0x87, 0xf2, 0xa5, 0x32, 0x44, 0xf, 0x46, 0x76, 0xf6, 0xe9, 0x44, 0xb0, 0xcb, 0x9, 0xf8, 0x12, 0x38, 0x0, 0x52, 0x86, 0x25, 0x6b, 0x4a, 0x78, 0x75, 0xa9, 0x95, 0xcd, 0x20, 0xee, 0x77, 0xf4, 0x50, 0x64, 0xbd, 0xc6, 0x96, 0x41, 0xdc, 0x44, 0x3f, 0x0, 0xdc, 0x7e, 0x3f, 0x97, 0x26, 0xc0, 0x35, 0xe0, 0x56, 0x70, 0x3d, 0xc8, 0x77, 0x85, 0xaf, 0x38, 0xf3, 0x6c, 0x90, 0x83, 0x61, 0x1b, 0x7f, 0x56, 0xf3, 0x7d, 0x90, 0x57, 0xd5, 0x7a, 0x7e, 0x10, 0x9e, 0xc2, 0x1f, 0x2b, 0x8b, 0x6d, 0xcc, 0xc1, 0x5, 0xc2, 0xcb, 0x81, 0xff, 0x61, 0xfc, 0x7, 0xb8, 0x5d, 0xb9, 0x65, 0x98, 0x95, 0xb8, 0x3a, 0xae, 0xaa, 0xd2, 0x38, 0x40, 0xde, 0x78, 0x89, 0x52, 0x67, 0x3b, 0xaf, 0x3f, 0xe3, 0x64, 0x1f, 0xcc, 0xf6, 0x16, 0xe0, 0xe4, 0x43, 0xb0, 0x47, 0x3f, 0x60, 0x74, 0x36, 0x5f, 0x1f, 0x4, 0x5f, 0x81, 0xba, 0xf8, 0xf5, 0x2a, 0x59, 0xa1, 0x2d, 0x24, 0xe3, 0xd5, 0x2c, 0x71, 0xed, 0xee, 0x36, 0xb8, 0x1a, 0xb2, 0x8d, 0xd2, 0x7d, 0x35, 0xa2, 0x8d, 0xb3, 0x91, 0xfe, 0x81, 0x35, 0xc1, 0xaa, 0x17, 0x9b, 0x9d, 0x28, 0xdf, 0x2, 0x5e, 0x5, 0x3f, 0x82, 0x51, 0xc5, 0xbe, 0xde, 0xdf, 0xab, 0xea, 0x44, 0x28, 0x67, 0x5a, 0xd4, 0xdd, 0x43, 0xd9, 0x91, 0x8b, 0x4, 0x52, 0xfb, 0x94, 0xab, 0x1e, 0x1b, 0xf8, 0xcc, 0xdb, 0x15, 0xc0, 0x5c, 0xf6, 0x21, 0x74, 0x31, 0x38, 0x17, 0x9c, 0x5, 0xbc, 0x37, 0x6d, 0x6b, 0xde, 0xff, 0x5, 0x7e, 0x3, 0xbb, 0xc1, 0x76, 0xf0, 0x25, 0xd8, 0x41, 0x2c, 0x5f, 0x7b, 0x21, 0x85, 0xa8, 0x5f, 0xd2, 0x8d, 0xa7, 0x62, 0xd6, 0xf, 0xa3, 0xff, 0x3, 0xf7, 0x93, 0x7f, 0xd4, 0xd5, 0x86, 0x9a, 0x4d, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)twitter_icon2x_png { + return [NSData dataWithBytes:twitter_icon2x_png length:sizeof(twitter_icon2x_png)]; + } + + const unsigned char parse_logo2x_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0xbb, 0x0, 0x0, 0x0, 0x88, 0x8, 0x6, 0x0, 0x0, 0x0, 0x7e, 0xf0, 0x45, 0xa9, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x0, 0x0, 0x3, 0x23, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2d, 0x63, 0x30, 0x31, 0x34, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x31, 0x34, 0x38, 0x31, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2f, 0x30, 0x33, 0x2f, 0x31, 0x33, 0x2d, 0x31, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x31, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x4d, 0x61, 0x63, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x29, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x45, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x46, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x3e, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x43, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x44, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0xe8, 0x51, 0xff, 0x90, 0x0, 0x0, 0x26, 0xd1, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x5d, 0x7, 0x98, 0x14, 0x55, 0xd6, 0xbd, 0xd, 0x33, 0xcc, 0x90, 0x54, 0x24, 0x88, 0xa2, 0x2, 0x8a, 0x28, 0x20, 0x62, 0x40, 0x51, 0x5c, 0x57, 0x10, 0x51, 0x44, 0xd7, 0x35, 0xff, 0xbb, 0x46, 0x5c, 0x31, 0xc3, 0x9a, 0x45, 0xd4, 0x55, 0x74, 0xd7, 0xac, 0xb0, 0x86, 0x5d, 0x73, 0x5e, 0x75, 0x4d, 0x6b, 0x56, 0x14, 0x11, 0xcc, 0x1, 0x41, 0xc9, 0x51, 0x82, 0x8, 0x8, 0x22, 0x82, 0x48, 0x9a, 0xf8, 0xbf, 0x63, 0x9d, 0x96, 0xa6, 0xe6, 0x55, 0x4f, 0xcf, 0x4c, 0x55, 0x77, 0x55, 0xf5, 0x3d, 0xdf, 0x77, 0x3f, 0xe0, 0x55, 0x33, 0x53, 0xfd, 0xea, 0xd5, 0x3b, 0x37, 0xbf, 0xc4, 0x49, 0x83, 0x86, 0xb4, 0x13, 0x91, 0x53, 0x8c, 0x34, 0x96, 0xf8, 0xa2, 0xbe, 0x91, 0xa9, 0x46, 0x66, 0x19, 0x99, 0x61, 0x64, 0xb5, 0x91, 0x52, 0x51, 0x28, 0x14, 0xa, 0x45, 0x6c, 0xb0, 0x66, 0xed, 0x3a, 0x19, 0x3a, 0x68, 0xa0, 0xec, 0xb3, 0x47, 0xd7, 0x2a, 0xd7, 0xa, 0x8c, 0xdc, 0x62, 0xa4, 0xb7, 0x91, 0xe2, 0x18, 0xcf, 0x41, 0x82, 0xe4, 0x56, 0x66, 0xa4, 0xc4, 0xc8, 0x14, 0x92, 0xdf, 0x24, 0xfe, 0x1d, 0xb2, 0x41, 0x97, 0x8a, 0x42, 0xa1, 0x50, 0xc4, 0x13, 0x20, 0xbb, 0xed, 0x8c, 0x34, 0xe3, 0xdf, 0xf3, 0x5, 0x6d, 0x8c, 0x1c, 0xca, 0xbf, 0xff, 0x42, 0xd2, 0xfb, 0xd0, 0xc8, 0x33, 0xfc, 0x7b, 0xa5, 0x2e, 0xd, 0x85, 0x42, 0xa1, 0x88, 0xf, 0xea, 0x19, 0x29, 0x37, 0x52, 0x91, 0xc7, 0x73, 0xd0, 0xc4, 0x48, 0x4f, 0x23, 0x43, 0x8c, 0x8c, 0x33, 0x32, 0xd2, 0xc8, 0x31, 0x46, 0xa, 0x75, 0x79, 0x28, 0x14, 0xa, 0x45, 0x7c, 0xc8, 0x4e, 0xb1, 0x11, 0x20, 0xb8, 0x43, 0x8c, 0xbc, 0x68, 0x64, 0xb4, 0x91, 0x63, 0x75, 0x8e, 0x14, 0xa, 0x85, 0x22, 0xfa, 0xf0, 0x72, 0x5d, 0x22, 0x89, 0x63, 0x6a, 0x8c, 0x8, 0xbd, 0x95, 0x91, 0x46, 0xfc, 0x73, 0xeb, 0xc, 0x9, 0xec, 0x0, 0xca, 0x28, 0x23, 0x7f, 0x17, 0xc7, 0xcd, 0xa9, 0x50, 0x28, 0x14, 0x8a, 0x18, 0x91, 0xdd, 0x93, 0x46, 0x6e, 0x8c, 0xd1, 0xf7, 0x6c, 0x40, 0xb2, 0xdb, 0xca, 0x48, 0x6b, 0x23, 0x9d, 0x53, 0xc8, 0xc, 0xf1, 0xbb, 0x44, 0x9a, 0xff, 0xdb, 0x97, 0x9f, 0xbb, 0xd7, 0xc8, 0x75, 0x46, 0x56, 0xe9, 0xb2, 0x51, 0x28, 0x14, 0x8a, 0xe8, 0x59, 0x3d, 0x36, 0x34, 0x8c, 0xd9, 0xf7, 0x44, 0x6, 0xe6, 0x4a, 0x23, 0x33, 0x8d, 0xbc, 0x4f, 0xe2, 0x3a, 0xd1, 0x48, 0x27, 0x23, 0xfd, 0x8d, 0x3c, 0x6a, 0xe4, 0xbb, 0x34, 0xff, 0x1f, 0x99, 0xaa, 0x17, 0x19, 0x79, 0xdb, 0x48, 0x77, 0x5d, 0x36, 0xa, 0x85, 0x42, 0x11, 0xf, 0xb2, 0xcb, 0x17, 0x20, 0x13, 0x13, 0x9, 0x29, 0x7f, 0x31, 0x72, 0x90, 0x91, 0x2b, 0x8d, 0xcc, 0x4d, 0xf3, 0xf9, 0x1e, 0x46, 0xde, 0x32, 0x32, 0x40, 0x97, 0x8e, 0x42, 0xa1, 0x50, 0x28, 0xd9, 0x45, 0x11, 0xb3, 0x8d, 0xdc, 0x64, 0x64, 0x2f, 0x71, 0xdc, 0x95, 0xcb, 0x3c, 0x3e, 0xd7, 0x82, 0x96, 0x20, 0xdc, 0xbc, 0x9, 0x9d, 0x36, 0x85, 0x42, 0xa1, 0x50, 0xb2, 0x8b, 0x22, 0xe0, 0xee, 0x1c, 0x66, 0xa4, 0x8f, 0x91, 0x17, 0xd2, 0x7c, 0x6e, 0xa8, 0x38, 0xee, 0xd0, 0x22, 0x9d, 0x32, 0x85, 0x42, 0xa1, 0x50, 0xb2, 0x8b, 0x2a, 0xd0, 0x55, 0xe5, 0x78, 0x23, 0x3, 0x8d, 0xac, 0xf0, 0xf8, 0xcc, 0xd9, 0x46, 0x1e, 0x96, 0xf8, 0xc5, 0x38, 0x15, 0xa, 0x85, 0x42, 0xc9, 0x2e, 0xcf, 0x0, 0x32, 0x43, 0xed, 0xdd, 0xd7, 0x1e, 0xd7, 0x4f, 0xa2, 0x85, 0xa7, 0x45, 0xe8, 0xa, 0x85, 0x42, 0xa1, 0x64, 0x17, 0x69, 0x8c, 0x17, 0xa7, 0x4, 0xe1, 0x4d, 0x8f, 0xeb, 0xa7, 0x89, 0xe3, 0xfa, 0x54, 0x28, 0x14, 0xa, 0x85, 0x92, 0x5d, 0xa4, 0xb1, 0xdc, 0xc8, 0x9f, 0xc4, 0x49, 0x4e, 0xb1, 0x1, 0x99, 0x9c, 0x67, 0xea, 0x34, 0x29, 0x14, 0xa, 0x85, 0x92, 0x5d, 0xd4, 0x81, 0xa3, 0x81, 0x10, 0xc3, 0x7b, 0xc8, 0xe3, 0xfa, 0x8, 0x23, 0xbd, 0x74, 0x9a, 0x14, 0xa, 0x85, 0x42, 0xc9, 0x2e, 0xea, 0x40, 0xd3, 0xec, 0xc1, 0xe2, 0xf4, 0xcf, 0x74, 0x3, 0x67, 0x2, 0x22, 0x7e, 0xd7, 0x42, 0xa7, 0x49, 0xa1, 0x50, 0x28, 0x94, 0xec, 0xa2, 0x8e, 0xf5, 0xb4, 0xf0, 0x3e, 0xb6, 0x5c, 0xdb, 0x45, 0x9c, 0x33, 0x2, 0xb, 0x74, 0x9a, 0x14, 0xa, 0x85, 0x42, 0xc9, 0x2e, 0xea, 0x40, 0x3d, 0xde, 0x0, 0x23, 0xdf, 0x5a, 0xae, 0x9d, 0x6a, 0xe4, 0x70, 0x9d, 0x22, 0x85, 0x42, 0xa1, 0x50, 0xb2, 0x8b, 0x3, 0xe6, 0x18, 0x19, 0x24, 0xce, 0x29, 0xe8, 0xa9, 0x48, 0x9e, 0x0, 0xbf, 0x85, 0x4e, 0x91, 0x42, 0xa1, 0x50, 0x28, 0xd9, 0xc5, 0x1, 0xaf, 0x19, 0xb9, 0xdb, 0x32, 0xbe, 0xb3, 0x91, 0x8b, 0x75, 0x7a, 0x14, 0xa, 0x85, 0x42, 0xc9, 0x2e, 0x2e, 0xb8, 0xd9, 0xc8, 0x44, 0xcb, 0xf8, 0x19, 0x46, 0x76, 0xd2, 0xe9, 0x51, 0x28, 0x14, 0xa, 0x25, 0xbb, 0x38, 0xe0, 0x7, 0x23, 0x97, 0x59, 0xc6, 0xb7, 0x11, 0xe7, 0x68, 0x20, 0x85, 0x42, 0xa1, 0x50, 0x28, 0xd9, 0xc5, 0x2, 0x38, 0xd1, 0xfc, 0x19, 0xcb, 0x38, 0x92, 0x55, 0xda, 0xeb, 0xf4, 0x28, 0x14, 0xa, 0x85, 0x92, 0x5d, 0x5c, 0x80, 0xa2, 0xf2, 0x9f, 0x5d, 0x63, 0xa8, 0xbd, 0xfb, 0x8b, 0x4e, 0x8d, 0x42, 0xa1, 0x50, 0x28, 0xd9, 0xc5, 0x5, 0xe3, 0x8c, 0xbc, 0x6a, 0x19, 0x3f, 0xc1, 0x48, 0x4b, 0x9d, 0x1e, 0x85, 0x42, 0xa1, 0x50, 0xb2, 0x8b, 0x93, 0x75, 0xe7, 0x46, 0x47, 0xd1, 0x36, 0x62, 0xa, 0x85, 0x42, 0xa1, 0x64, 0x17, 0x23, 0x7c, 0x65, 0xe4, 0xd, 0xcb, 0xf8, 0x19, 0x3a, 0x35, 0xa, 0x85, 0x42, 0xa1, 0x64, 0x17, 0x17, 0x54, 0x1a, 0x79, 0xce, 0x32, 0xde, 0xd3, 0x48, 0x3b, 0x9d, 0x1e, 0x85, 0x42, 0xa1, 0x50, 0xb2, 0x8b, 0xb, 0xde, 0x36, 0x32, 0xd7, 0x35, 0xd6, 0x54, 0x9c, 0x33, 0xf1, 0x14, 0xa, 0x85, 0x42, 0xa1, 0x64, 0x17, 0xb, 0x2c, 0x35, 0xf2, 0xa5, 0x65, 0xfc, 0x77, 0x3a, 0x35, 0xa, 0x85, 0x42, 0xa1, 0x64, 0x17, 0x27, 0xd8, 0xb2, 0x32, 0xf7, 0x36, 0xd2, 0x5a, 0xa7, 0x46, 0xa1, 0x50, 0x28, 0x94, 0xec, 0xe2, 0x82, 0xb1, 0x46, 0x4a, 0x5c, 0x63, 0x68, 0x1d, 0xb6, 0x9d, 0x4e, 0x8d, 0x42, 0xa1, 0x50, 0x28, 0xd9, 0xc5, 0x5, 0x3f, 0x1a, 0x99, 0xea, 0x1a, 0x2b, 0x10, 0xed, 0x95, 0xa9, 0x50, 0x28, 0x14, 0x59, 0x87, 0x1e, 0x30, 0x1a, 0x1c, 0x70, 0xc0, 0xeb, 0x27, 0x46, 0xf6, 0x70, 0x8d, 0xef, 0x66, 0xe4, 0xe9, 0x8, 0x7f, 0xaf, 0x42, 0x23, 0xd, 0x8c, 0x34, 0xf1, 0xb0, 0x52, 0x13, 0x46, 0xd6, 0x19, 0x59, 0x48, 0xcb, 0x76, 0x83, 0x38, 0xa7, 0xbb, 0x2b, 0x14, 0x99, 0xee, 0x49, 0xc9, 0x35, 0x86, 0xf5, 0xd5, 0x50, 0x9c, 0xc, 0x67, 0x9b, 0xa2, 0xe, 0x85, 0xf2, 0x7b, 0xae, 0x2f, 0xac, 0xb5, 0x72, 0x9d, 0x3e, 0x5f, 0x90, 0xe0, 0xfc, 0xe3, 0x59, 0x6c, 0x6d, 0x64, 0x4b, 0xcb, 0x3b, 0x8c, 0xf9, 0xc7, 0x99, 0x9e, 0x8b, 0x8c, 0x94, 0x89, 0x73, 0xcc, 0x59, 0x45, 0xd8, 0x17, 0x96, 0x22, 0x38, 0xcc, 0xb0, 0x8c, 0xed, 0x1c, 0xb1, 0x8d, 0x7, 0x27, 0xaf, 0x77, 0x30, 0xd2, 0xc9, 0xc8, 0x5e, 0x46, 0xda, 0xf2, 0xdf, 0x45, 0xdc, 0x94, 0x2a, 0x2d, 0x2f, 0x4a, 0x5, 0x17, 0xff, 0x4f, 0xe2, 0x9c, 0xf9, 0x87, 0x79, 0x98, 0x69, 0x64, 0x16, 0xff, 0xfc, 0x26, 0xa6, 0xcf, 0xbb, 0x1e, 0x37, 0x9, 0xe1, 0xc6, 0x5b, 0xaa, 0xaf, 0x40, 0x5a, 0x6c, 0x6b, 0x64, 0x47, 0xa, 0xd6, 0x19, 0x9a, 0x2f, 0xa0, 0x79, 0xfa, 0xf6, 0x54, 0xa6, 0xa, 0x39, 0xa7, 0x95, 0x1e, 0x1b, 0x72, 0x39, 0x37, 0x5a, 0x6c, 0xba, 0xf3, 0xc5, 0x39, 0x48, 0x79, 0x26, 0xff, 0x3e, 0xdb, 0xc8, 0x2, 0x6e, 0xc6, 0x8a, 0xf4, 0x68, 0xcd, 0xf7, 0xbb, 0x3, 0xf7, 0xa7, 0x2e, 0x46, 0x76, 0x30, 0xd2, 0x86, 0x7b, 0x40, 0x7d, 0x8f, 0xf7, 0x3c, 0x39, 0xff, 0xdf, 0x73, 0xee, 0x67, 0xf0, 0x1d, 0x9f, 0xc2, 0x77, 0x7c, 0x9e, 0x92, 0x5d, 0xfe, 0x60, 0x11, 0x17, 0x49, 0x22, 0x65, 0xac, 0x63, 0xc8, 0xef, 0x19, 0xbd, 0x3c, 0xf, 0x31, 0x72, 0xb0, 0x91, 0xae, 0x5c, 0xfc, 0xad, 0x6a, 0xf1, 0x73, 0x1a, 0xf0, 0x67, 0x61, 0x43, 0xeb, 0x95, 0x32, 0x8e, 0xd, 0x68, 0xba, 0x91, 0x31, 0x46, 0xfe, 0x27, 0x4e, 0x89, 0x46, 0x1c, 0x2c, 0x3f, 0x28, 0x2, 0xb7, 0x53, 0x9, 0x10, 0x6e, 0x2, 0x77, 0x18, 0x79, 0x25, 0x8b, 0xf7, 0xd0, 0x90, 0xd6, 0xd0, 0xce, 0x54, 0x4a, 0x5a, 0xf3, 0x7e, 0x92, 0x8d, 0xc8, 0x8b, 0x68, 0x1, 0x5d, 0x60, 0xe4, 0xbb, 0x1c, 0xcc, 0x11, 0x7e, 0x7f, 0x37, 0x23, 0x87, 0x89, 0x93, 0xac, 0xb5, 0x13, 0x89, 0xae, 0x7e, 0x1d, 0xf6, 0xaf, 0x22, 0xae, 0x33, 0x6c, 0xcc, 0xfb, 0xbb, 0x3c, 0x2b, 0xdf, 0x52, 0xd9, 0x1a, 0x2f, 0xce, 0xb9, 0x93, 0x93, 0xe8, 0x69, 0xc8, 0x77, 0xd4, 0xe7, 0x3e, 0x84, 0xf7, 0xbc, 0x37, 0x9f, 0x43, 0xe7, 0x3a, 0xcc, 0x7f, 0x52, 0x61, 0xe9, 0x9d, 0x72, 0x6d, 0x1e, 0x15, 0x8e, 0x64, 0xb, 0xc5, 0xaf, 0xa5, 0x6a, 0xe, 0x83, 0x92, 0x5d, 0x8c, 0xb0, 0xd8, 0xc8, 0x2f, 0xe2, 0xd4, 0xd8, 0x25, 0xd1, 0x82, 0xda, 0x6a, 0x98, 0x36, 0x78, 0xb8, 0x29, 0x50, 0xf4, 0x7e, 0x84, 0x91, 0xc3, 0x49, 0x50, 0x41, 0xa1, 0x2d, 0xa5, 0x9f, 0x91, 0xeb, 0x8d, 0x7c, 0x64, 0xe4, 0x29, 0x23, 0x6f, 0x8a, 0x53, 0xb2, 0x11, 0x55, 0x1c, 0x21, 0x55, 0x5b, 0xc2, 0x2d, 0xa, 0x98, 0xec, 0xb0, 0x69, 0x75, 0x37, 0xb2, 0x9f, 0x91, 0x3, 0x8c, 0xec, 0x23, 0x4e, 0xf, 0xd6, 0xa2, 0x6a, 0xfe, 0xdf, 0xf3, 0x46, 0xfe, 0x9b, 0xc5, 0xb9, 0x1, 0xb1, 0xf5, 0x31, 0x72, 0x1a, 0xad, 0x87, 0x6c, 0xec, 0x3b, 0xc5, 0xdc, 0xd0, 0x21, 0xfd, 0x8d, 0xc, 0xe5, 0x6, 0x8c, 0x86, 0xf, 0x23, 0xb9, 0xee, 0xf2, 0xd, 0xed, 0xa9, 0xc4, 0x9e, 0xc8, 0xf7, 0xbd, 0x41, 0xc0, 0xbf, 0xab, 0x3d, 0x9, 0x75, 0x8, 0x95, 0x8e, 0xff, 0x70, 0xee, 0xc7, 0x2b, 0xd9, 0xc5, 0xf, 0x38, 0x1, 0x61, 0x8d, 0x8b, 0xec, 0x8a, 0xa8, 0x7d, 0x2f, 0x8, 0xc1, 0xfd, 0x61, 0x23, 0x38, 0x95, 0x1b, 0x75, 0xb7, 0x1c, 0x69, 0xfa, 0x7d, 0x28, 0xd3, 0x8c, 0x3c, 0x6e, 0xe4, 0x3e, 0xa9, 0x7a, 0x72, 0x44, 0x14, 0x60, 0x73, 0xb5, 0x95, 0x7, 0xb8, 0x91, 0x9c, 0xcc, 0x79, 0xeb, 0x4e, 0xcb, 0xa6, 0x26, 0x48, 0x64, 0x61, 0x3e, 0x12, 0x24, 0x19, 0x9c, 0xf8, 0xd1, 0xd7, 0xf5, 0xe, 0xe4, 0x6a, 0xaf, 0x83, 0x5, 0x73, 0x95, 0x38, 0x67, 0x4f, 0xc2, 0xb3, 0x80, 0x23, 0xb9, 0x9e, 0xa5, 0x15, 0x18, 0x67, 0xec, 0x6e, 0xe4, 0x1c, 0x23, 0x87, 0x4a, 0x6e, 0xba, 0x38, 0x25, 0xc3, 0x21, 0xff, 0xe0, 0xdc, 0x7f, 0xc0, 0xf7, 0xfc, 0x1d, 0x7a, 0x40, 0x94, 0xec, 0x62, 0x80, 0xd5, 0x46, 0xd6, 0x5a, 0xb4, 0xf1, 0x46, 0x39, 0xbe, 0xaf, 0x83, 0xb8, 0x9, 0x1d, 0x23, 0x8e, 0xeb, 0xab, 0xa6, 0xc0, 0x26, 0xe, 0x3f, 0x3d, 0x12, 0x51, 0x56, 0x88, 0x13, 0x9b, 0x4a, 0x70, 0xc3, 0x87, 0x65, 0x81, 0x58, 0x4b, 0x73, 0x23, 0x9b, 0xd5, 0xe0, 0x67, 0xc2, 0x8d, 0x72, 0x8b, 0x91, 0xb3, 0xc4, 0x39, 0xf9, 0xfd, 0x71, 0x89, 0x56, 0xcc, 0xab, 0x32, 0xc3, 0xb1, 0xba, 0xa0, 0x27, 0x37, 0xad, 0xa3, 0x42, 0x40, 0x1e, 0xe9, 0x80, 0xe6, 0x9, 0x57, 0x53, 0xab, 0xaf, 0xd, 0xb1, 0xae, 0xa4, 0x60, 0xfe, 0x96, 0x71, 0x43, 0x4c, 0xfe, 0x1c, 0x78, 0x44, 0xb6, 0x20, 0xc1, 0x37, 0xa2, 0x57, 0xa2, 0xb0, 0x86, 0x3f, 0xbf, 0x1, 0x37, 0x7e, 0xc8, 0xa5, 0x46, 0x6e, 0x23, 0xe9, 0xc5, 0xcd, 0xc5, 0x9, 0x4b, 0xff, 0x5c, 0x5a, 0x72, 0xb5, 0xb1, 0xe2, 0xa0, 0x74, 0x22, 0xee, 0xe, 0xf7, 0xe3, 0xf2, 0x94, 0xf5, 0x5c, 0xc9, 0xf5, 0xd7, 0x94, 0xcf, 0x21, 0xe9, 0xad, 0xca, 0x4, 0x9b, 0x1b, 0xf9, 0x3, 0x15, 0x6c, 0x58, 0x78, 0x77, 0xd0, 0xd3, 0x10, 0x78, 0x72, 0x91, 0x92, 0x5d, 0xb0, 0xc0, 0x42, 0xf9, 0xc5, 0xb2, 0x1, 0xe6, 0xc2, 0x85, 0x59, 0x9f, 0x9b, 0xe4, 0x59, 0xb4, 0x8, 0x32, 0x8d, 0x93, 0x94, 0x93, 0xd0, 0x3e, 0x17, 0xa7, 0xc9, 0x35, 0x82, 0xcf, 0x70, 0x37, 0x2e, 0xa1, 0x46, 0x9c, 0x4a, 0x76, 0x15, 0x29, 0x64, 0x87, 0x17, 0xa0, 0x15, 0xad, 0x10, 0x4, 0xbc, 0x7f, 0x4f, 0xcd, 0xba, 0xba, 0x97, 0xe, 0xbe, 0xff, 0x7, 0x8d, 0xfc, 0xd9, 0xc8, 0xe5, 0xb9, 0x72, 0x79, 0x84, 0xc, 0x3d, 0x8c, 0x5c, 0xc8, 0xe7, 0x57, 0x1c, 0xe2, 0xfb, 0x44, 0x72, 0xc9, 0x35, 0x46, 0x4e, 0xcf, 0x70, 0x73, 0xc5, 0xba, 0x41, 0xec, 0x10, 0xb1, 0x34, 0xc4, 0x76, 0xe6, 0x51, 0x89, 0x5a, 0xc9, 0x77, 0x47, 0xaa, 0x21, 0xbb, 0x86, 0x54, 0xaa, 0x40, 0x78, 0xad, 0xb9, 0xce, 0xba, 0xd3, 0x8a, 0xd8, 0x3c, 0xc3, 0xd, 0x78, 0x57, 0x2a, 0x56, 0x3, 0x68, 0x79, 0xbc, 0x17, 0x83, 0xf5, 0x2, 0xeb, 0xed, 0x62, 0x2a, 0xb4, 0x99, 0x5a, 0xfd, 0x78, 0x97, 0x67, 0xf1, 0x3d, 0x1f, 0xc7, 0xe7, 0xb0, 0x84, 0xef, 0x77, 0x2a, 0xd9, 0x25, 0x95, 0xda, 0x26, 0x54, 0x66, 0x93, 0x64, 0xb7, 0xd, 0xdf, 0xef, 0xae, 0x54, 0x76, 0x9a, 0x57, 0xc3, 0x2f, 0x9, 0x3e, 0x2b, 0x58, 0xd7, 0x3, 0x8d, 0xdc, 0x4a, 0x4b, 0x4f, 0xc9, 0x2e, 0xa2, 0xa8, 0x97, 0x25, 0x97, 0x51, 0x75, 0xe8, 0x4f, 0xf7, 0x41, 0xaf, 0x1a, 0x68, 0xd6, 0xe3, 0xb9, 0xf0, 0xb1, 0x0, 0x27, 0xd0, 0x4a, 0xcd, 0x4, 0xc9, 0xec, 0xb7, 0xf9, 0x16, 0x6d, 0x1a, 0x19, 0x5e, 0x87, 0x51, 0xa3, 0x3e, 0xa0, 0x1a, 0xb, 0x17, 0xd6, 0xe7, 0x28, 0x5a, 0x8, 0xff, 0xce, 0xd3, 0xf5, 0x83, 0xac, 0xc4, 0x21, 0x54, 0x50, 0x6a, 0xfa, 0xae, 0x42, 0x2b, 0x5f, 0x98, 0x42, 0x1e, 0x73, 0xb9, 0x69, 0x81, 0x5c, 0x3e, 0xe, 0xe0, 0x5e, 0xf1, 0x5c, 0xef, 0x12, 0x27, 0x26, 0x97, 0xe, 0x3f, 0x70, 0x33, 0x7d, 0x5f, 0x9c, 0xf8, 0xcd, 0x2c, 0xa9, 0xb9, 0x1b, 0x71, 0x59, 0x35, 0x7b, 0x1a, 0x48, 0x10, 0x9, 0x43, 0x7, 0x8a, 0x93, 0xb4, 0xd2, 0x85, 0x9b, 0x71, 0x3a, 0xf4, 0xe6, 0x9a, 0xbc, 0xdb, 0xc8, 0x30, 0x89, 0xa6, 0x2b, 0x1d, 0xdf, 0xfd, 0x4c, 0x2a, 0x1c, 0x99, 0x74, 0x6a, 0x42, 0x1c, 0xed, 0xb, 0x71, 0x7a, 0xf9, 0x7e, 0xc8, 0x77, 0x37, 0xd3, 0x24, 0x12, 0x58, 0xc1, 0x3f, 0x7a, 0x5c, 0xc3, 0xfc, 0xef, 0xce, 0xf7, 0x1c, 0x4a, 0xee, 0xde, 0x24, 0x47, 0x2f, 0xf4, 0xe1, 0xfb, 0xfe, 0xb0, 0x91, 0x9b, 0xa4, 0x6a, 0x5f, 0x61, 0x25, 0x3b, 0x45, 0xb5, 0xc0, 0xb, 0x7f, 0xa5, 0x91, 0x23, 0x33, 0x78, 0xd6, 0x15, 0xd4, 0xb0, 0x9f, 0xe0, 0x46, 0x34, 0xc1, 0xe7, 0x7b, 0xc1, 0x4b, 0x34, 0x83, 0x82, 0x33, 0xff, 0xf6, 0x35, 0x72, 0x34, 0x37, 0xf2, 0x2d, 0x3c, 0xfe, 0x4f, 0x33, 0x23, 0xff, 0xa2, 0xa6, 0x8e, 0x4d, 0x7f, 0x5d, 0x1e, 0x3d, 0xbb, 0x53, 0xb8, 0x69, 0x75, 0xc8, 0xf0, 0xf3, 0x20, 0x8c, 0x89, 0xdc, 0xb8, 0xbe, 0x22, 0xa9, 0x2d, 0x94, 0xec, 0x24, 0xfd, 0xc0, 0xea, 0x84, 0xeb, 0x39, 0x5d, 0x62, 0xc, 0xb2, 0xf1, 0x1e, 0xa5, 0xe5, 0x34, 0x25, 0xc0, 0x7b, 0x29, 0xa3, 0x62, 0x36, 0x96, 0x22, 0x5c, 0x3f, 0x70, 0x1, 0xff, 0x89, 0xa, 0x5f, 0x61, 0x9a, 0xfd, 0xf0, 0x22, 0xbe, 0x37, 0x58, 0x97, 0x33, 0x23, 0xb4, 0x5e, 0x92, 0x61, 0x80, 0x23, 0x32, 0x98, 0x9f, 0xd1, 0xe2, 0x24, 0x8b, 0x20, 0x76, 0xf6, 0x6d, 0x0, 0xf7, 0x82, 0xf7, 0xf4, 0x53, 0x4a, 0x82, 0x64, 0x77, 0x38, 0xd7, 0x74, 0xfb, 0x34, 0x96, 0x1e, 0x2c, 0xbc, 0xbe, 0x5c, 0xf7, 0x4f, 0x28, 0xd9, 0x45, 0xb, 0x30, 0xe5, 0x9b, 0x7a, 0x3c, 0xd8, 0xa0, 0x2d, 0x2, 0xb8, 0x31, 0xce, 0x91, 0xea, 0x33, 0xf3, 0x16, 0xd3, 0x82, 0x7a, 0x82, 0xda, 0x5d, 0xb6, 0xe2, 0x64, 0x9f, 0x51, 0x86, 0xf3, 0x5e, 0xe1, 0xfa, 0xf2, 0x3a, 0xcd, 0x7d, 0x30, 0x5d, 0x24, 0x27, 0xd1, 0xad, 0x12, 0x67, 0x40, 0x23, 0xbf, 0x9d, 0xdf, 0x35, 0x13, 0x5, 0x2, 0x1b, 0xfa, 0x5b, 0x46, 0x5e, 0x26, 0xc1, 0x95, 0x65, 0xf1, 0x5e, 0xb, 0xf9, 0xfc, 0x6, 0xa5, 0xf9, 0xcc, 0x47, 0xb4, 0x96, 0x5e, 0x92, 0xdc, 0xc5, 0x60, 0x93, 0x4a, 0xd6, 0x63, 0xb4, 0x38, 0xce, 0x33, 0x72, 0x6c, 0x1a, 0x25, 0xb, 0xd6, 0x8, 0xd2, 0xe5, 0x7, 0x70, 0xc3, 0x8e, 0x82, 0x62, 0x84, 0xe7, 0xd0, 0x22, 0xcd, 0x67, 0xf0, 0xde, 0xa0, 0xd4, 0xe7, 0x11, 0xbe, 0x77, 0x95, 0x59, 0xba, 0xb7, 0x4a, 0x5a, 0x8f, 0x10, 0xb8, 0x2a, 0x8f, 0xe1, 0xbe, 0xd4, 0xd3, 0xe3, 0xf3, 0xc8, 0xd4, 0x7e, 0x9c, 0x4a, 0xc9, 0x10, 0x7a, 0x2, 0x7c, 0x81, 0xb6, 0xb, 0xb, 0x16, 0x70, 0xd3, 0x15, 0x5b, 0x2c, 0xa8, 0xa0, 0xb2, 0xbf, 0xea, 0x91, 0x34, 0xa0, 0x3d, 0x5f, 0x50, 0xd, 0xd1, 0x2d, 0xa4, 0xbb, 0xe6, 0x20, 0xbe, 0xd4, 0xef, 0xe5, 0x68, 0x33, 0x5a, 0xca, 0x45, 0xdd, 0x87, 0x1b, 0xa2, 0x17, 0xfa, 0x51, 0x1b, 0x6d, 0x16, 0xe3, 0xf5, 0x82, 0xd, 0xe0, 0x9d, 0xc, 0x88, 0x6e, 0x39, 0xad, 0x63, 0x3c, 0x3b, 0xb8, 0x8a, 0xfe, 0x29, 0x8e, 0xdb, 0xb8, 0x2c, 0xcb, 0xf7, 0x3b, 0x22, 0xd, 0xd1, 0x2d, 0xe2, 0xa6, 0x86, 0x24, 0x95, 0xe7, 0x24, 0x1c, 0xc9, 0x46, 0x15, 0xf4, 0x58, 0xc, 0xe4, 0x7a, 0x43, 0x46, 0xa0, 0x57, 0x62, 0x44, 0x47, 0x12, 0xde, 0x81, 0x21, 0x5e, 0x2f, 0x5, 0x24, 0x90, 0xc7, 0xd3, 0x10, 0x5d, 0x5, 0x49, 0x1e, 0xdf, 0xf7, 0x4c, 0x92, 0x77, 0x65, 0x8e, 0xee, 0x17, 0x99, 0xe9, 0x4f, 0xf2, 0x5e, 0xb0, 0xc6, 0xa7, 0xa6, 0xf9, 0xec, 0xe9, 0x54, 0xe2, 0x7a, 0x28, 0xd9, 0x45, 0x3, 0x4d, 0xa5, 0x6a, 0x5c, 0x6a, 0x43, 0x40, 0xae, 0x3, 0x68, 0xac, 0x23, 0xa9, 0xb9, 0xed, 0x98, 0xe6, 0x73, 0x88, 0x91, 0xc0, 0xb5, 0x89, 0xb6, 0x65, 0xd7, 0x85, 0xc8, 0x55, 0x33, 0x99, 0x5a, 0xdf, 0x5f, 0x65, 0x63, 0x72, 0x82, 0x1b, 0x88, 0xb, 0x3d, 0x95, 0x46, 0x23, 0x8f, 0x32, 0x4e, 0xe4, 0xf3, 0xeb, 0x9a, 0xe6, 0x33, 0x8b, 0xa8, 0xa0, 0xec, 0x46, 0x6b, 0xf8, 0xe3, 0x1c, 0xde, 0x2f, 0xea, 0xd6, 0xce, 0xf7, 0xb8, 0xf6, 0x16, 0x35, 0xf3, 0xfb, 0x25, 0xbc, 0xae, 0x67, 0x90, 0xde, 0xb9, 0x54, 0x16, 0x3e, 0xf4, 0xf8, 0x4c, 0xb, 0x2a, 0x58, 0x5d, 0x42, 0x78, 0xff, 0xf0, 0x82, 0x20, 0x83, 0x14, 0xb1, 0x78, 0x2f, 0x4f, 0xd1, 0xe7, 0xfc, 0x7e, 0x20, 0x8e, 0xaf, 0x43, 0x74, 0xef, 0x50, 0xf6, 0x9f, 0x26, 0x91, 0xd, 0x4d, 0x63, 0xbd, 0xc1, 0x9d, 0xfc, 0x6, 0xf7, 0x5, 0x25, 0xbb, 0x90, 0x3, 0xd9, 0x88, 0xee, 0xf4, 0xfb, 0x1f, 0xc4, 0xdf, 0x6c, 0x4c, 0x68, 0x77, 0x97, 0x88, 0xe3, 0x8a, 0xec, 0x5b, 0x8d, 0x1b, 0xe3, 0x6, 0x5a, 0x3, 0x8, 0x2, 0xaf, 0xc, 0xe9, 0x9c, 0xc1, 0xe5, 0x75, 0x38, 0x49, 0xd9, 0x8b, 0xf0, 0x86, 0x4b, 0x38, 0x12, 0x7f, 0xfc, 0xc2, 0x60, 0x6a, 0xdf, 0x4d, 0xd3, 0x68, 0xe7, 0xf, 0x50, 0x23, 0x86, 0x82, 0xb2, 0x24, 0xc7, 0xf7, 0x8b, 0x58, 0xeb, 0xdf, 0x3d, 0xae, 0xdd, 0xc1, 0xeb, 0x73, 0x22, 0x32, 0xf7, 0x88, 0x5f, 0xf5, 0xa7, 0x75, 0x6c, 0xc3, 0xb6, 0xb4, 0x9c, 0xb6, 0xc, 0xd1, 0x3d, 0xb7, 0xa5, 0x17, 0xc4, 0x8b, 0x4, 0xa0, 0x50, 0x27, 0x4b, 0x3f, 0xde, 0xd, 0xf1, 0xdc, 0xc3, 0xd2, 0xbb, 0x99, 0x7b, 0xd2, 0x48, 0x8f, 0xcf, 0x34, 0xe7, 0xfc, 0x5f, 0xa0, 0x64, 0x17, 0x6e, 0x6c, 0x6f, 0x19, 0x9b, 0xef, 0xb3, 0xe5, 0x8, 0x17, 0xd1, 0xed, 0x69, 0xdc, 0x18, 0x58, 0xf8, 0x88, 0xc7, 0xed, 0xc3, 0x17, 0x20, 0xa, 0xbd, 0x2, 0x3f, 0xe5, 0x6, 0xf4, 0x59, 0x1a, 0x17, 0xc7, 0xc5, 0x31, 0x58, 0x1f, 0x50, 0x54, 0xae, 0x15, 0x27, 0x8b, 0xd1, 0x2b, 0x69, 0x2, 0xc9, 0x26, 0x70, 0xe1, 0x9e, 0x1d, 0x12, 0x2b, 0x1c, 0xb1, 0xd3, 0x3b, 0xc5, 0x5e, 0xba, 0x72, 0xa3, 0x38, 0x75, 0x6b, 0x51, 0xab, 0x57, 0x43, 0x79, 0x10, 0x12, 0x53, 0x4e, 0x13, 0x7b, 0x88, 0x61, 0x2f, 0x3e, 0xa7, 0x30, 0x0, 0x56, 0x26, 0xba, 0xd, 0xed, 0xef, 0x71, 0x7d, 0x1a, 0xdf, 0x1d, 0x28, 0xb6, 0x51, 0xc9, 0x28, 0x9d, 0x42, 0x5, 0x17, 0x56, 0x9e, 0x2d, 0xeb, 0xbb, 0x9, 0x95, 0x91, 0xcb, 0x94, 0xec, 0xc2, 0xb, 0x5b, 0x26, 0x9d, 0x9f, 0x1a, 0x2f, 0x5c, 0x19, 0x7, 0xa4, 0xb9, 0xfe, 0x39, 0x17, 0x11, 0x5e, 0xe2, 0xa8, 0x35, 0x5f, 0xfe, 0x86, 0x16, 0xc2, 0x27, 0x1e, 0xd7, 0x61, 0x59, 0xfc, 0x3e, 0xe2, 0xeb, 0x3, 0x75, 0x5d, 0xc3, 0xaa, 0xb1, 0x72, 0xfb, 0xd2, 0x6a, 0xf, 0xb, 0xb0, 0xe9, 0xdb, 0x4e, 0xbb, 0xb8, 0x85, 0xca, 0x54, 0x94, 0x1, 0xa5, 0x10, 0xc9, 0x2b, 0xb6, 0xf8, 0xe2, 0xb9, 0x54, 0x3a, 0x72, 0x89, 0x4e, 0xb4, 0xe8, 0xbc, 0xfa, 0x58, 0xbe, 0x40, 0x6b, 0x2e, 0x8a, 0xb5, 0x82, 0x15, 0xb4, 0xf2, 0x8e, 0x13, 0xef, 0xee, 0x52, 0x37, 0x53, 0x29, 0x51, 0xb2, 0xb, 0x19, 0xa0, 0xa9, 0xef, 0x6a, 0x19, 0x9f, 0xec, 0xe3, 0xef, 0x98, 0x27, 0x76, 0x77, 0x12, 0x12, 0x18, 0xae, 0x20, 0x19, 0x8c, 0x8e, 0xf0, 0x1c, 0x7e, 0xcf, 0xc5, 0x6f, 0x4b, 0x55, 0x47, 0x2d, 0xcf, 0x8, 0x9, 0x77, 0x27, 0x91, 0x74, 0x80, 0x5b, 0x66, 0x88, 0xc7, 0x35, 0x3c, 0x3f, 0xb4, 0x3, 0x43, 0xfc, 0xf2, 0xc7, 0x10, 0xdd, 0x33, 0x14, 0xa7, 0x13, 0x2c, 0xe3, 0x2f, 0x92, 0xe8, 0x2a, 0x63, 0xf0, 0xde, 0xa2, 0x3c, 0xe2, 0x7c, 0xcb, 0x77, 0xc1, 0xfb, 0xdc, 0x26, 0x87, 0xf7, 0x5, 0xc5, 0xf6, 0x71, 0xf1, 0x3e, 0xf, 0x13, 0xef, 0x2, 0x4a, 0x2b, 0xa2, 0x7e, 0xca, 0xc3, 0x3b, 0x5c, 0x67, 0x5f, 0x7a, 0xf0, 0xd5, 0x70, 0x7a, 0xf, 0x94, 0xec, 0x42, 0x4, 0xc4, 0xea, 0xf6, 0xb3, 0x8c, 0xcf, 0xf2, 0xf1, 0x77, 0xe0, 0x85, 0xbc, 0x47, 0x36, 0x3d, 0x1f, 0x6f, 0x24, 0xad, 0x1, 0x68, 0xda, 0x25, 0x31, 0x98, 0xc7, 0x25, 0xb4, 0x4c, 0x6d, 0x41, 0xec, 0x3d, 0xeb, 0xea, 0xda, 0xc8, 0x11, 0x50, 0xc3, 0x75, 0x87, 0xc7, 0x35, 0x14, 0xd4, 0xa2, 0x9d, 0xd2, 0x53, 0x21, 0xbb, 0x67, 0x64, 0x15, 0x5f, 0x2c, 0x55, 0xdd, 0xad, 0x8b, 0x39, 0x5e, 0x16, 0xa3, 0x77, 0x17, 0x1d, 0x7c, 0x1e, 0x4a, 0xf9, 0x37, 0xdc, 0xe9, 0xe8, 0x21, 0xfb, 0x4c, 0x8e, 0xee, 0x7, 0xa, 0x1d, 0x5a, 0x6a, 0xed, 0xed, 0x71, 0xfd, 0x1a, 0x3e, 0x83, 0xb8, 0x9c, 0xe7, 0x87, 0x2c, 0xcd, 0x3f, 0xca, 0xc6, 0x3a, 0x49, 0x9b, 0x17, 0xe1, 0x7c, 0x25, 0xbb, 0xf0, 0x0, 0x8d, 0x95, 0xdd, 0xad, 0x7a, 0x16, 0x5, 0xa0, 0x79, 0x55, 0x70, 0xa1, 0x63, 0x61, 0xdc, 0xc4, 0x45, 0xf2, 0x75, 0xcc, 0xe6, 0x72, 0x2, 0xdd, 0x17, 0xb6, 0xc4, 0x1e, 0x8c, 0xef, 0x16, 0xa1, 0xef, 0x2, 0x57, 0x98, 0x57, 0xcc, 0xb, 0x5d, 0x6b, 0xe, 0x16, 0xef, 0x58, 0x65, 0x2e, 0x81, 0x98, 0x6f, 0x6f, 0xcb, 0xf8, 0xdf, 0x24, 0x98, 0xec, 0xe2, 0x5c, 0x3, 0x1e, 0x13, 0x64, 0x36, 0xa3, 0x1e, 0xf, 0xa1, 0x2, 0xa4, 0xcc, 0xaf, 0xcd, 0xc1, 0x7d, 0x60, 0x9d, 0x20, 0xa6, 0xeb, 0x55, 0x2, 0x71, 0x95, 0x78, 0x27, 0xb, 0x45, 0x19, 0x8b, 0xa9, 0xf4, 0xbd, 0xe2, 0xc1, 0x5b, 0xe8, 0x67, 0xda, 0x5f, 0xc9, 0x2e, 0x1c, 0x38, 0xd2, 0x43, 0x63, 0x59, 0x18, 0xc0, 0xef, 0x5a, 0xca, 0xdf, 0x77, 0x65, 0x4c, 0xac, 0x39, 0x1b, 0x60, 0xe9, 0x3c, 0x6a, 0x19, 0x47, 0xf0, 0x7a, 0x50, 0x44, 0xbe, 0x3, 0x6a, 0xb7, 0xee, 0x13, 0x7b, 0x7f, 0xcb, 0x2f, 0xf9, 0xc, 0xe7, 0x85, 0xf4, 0xde, 0x61, 0xd9, 0xb8, 0x33, 0x60, 0xe1, 0x92, 0x7f, 0x2e, 0xa6, 0xeb, 0xd, 0xef, 0xe9, 0x19, 0xe2, 0x14, 0x62, 0xe7, 0xd2, 0x6a, 0x1d, 0x4c, 0xcf, 0x86, 0x97, 0x45, 0x77, 0x63, 0x8c, 0xf7, 0x50, 0x24, 0xe, 0xd, 0xf0, 0x50, 0xfe, 0x10, 0xc6, 0x40, 0x86, 0x72, 0xa6, 0x1d, 0x86, 0x94, 0xec, 0x2, 0x2, 0xfa, 0x40, 0xda, 0x3a, 0x4, 0xa0, 0x8b, 0x40, 0x50, 0xc5, 0xb5, 0xab, 0xf3, 0x60, 0x5e, 0x87, 0x79, 0x90, 0x1, 0x62, 0x15, 0x5d, 0x43, 0x7e, 0xef, 0x28, 0xf0, 0x47, 0xdd, 0x59, 0x5b, 0xcb, 0xb5, 0xc9, 0xb4, 0x20, 0x16, 0x87, 0xf4, 0xde, 0xe1, 0x92, 0x3f, 0xd4, 0xe2, 0x51, 0xb8, 0x47, 0xaa, 0x36, 0x3a, 0x57, 0xf8, 0x7, 0xa4, 0xe4, 0xdf, 0x22, 0xf6, 0x32, 0x9b, 0xbb, 0x62, 0x6a, 0xd1, 0xb9, 0x81, 0x12, 0x29, 0xc4, 0xed, 0x6d, 0xde, 0xaa, 0x36, 0xb4, 0xb8, 0x33, 0x3a, 0x5d, 0x45, 0xc9, 0x2e, 0x18, 0xa0, 0xc0, 0x7b, 0x67, 0xd7, 0x18, 0xfc, 0xe9, 0x63, 0x74, 0x6a, 0xea, 0x84, 0xef, 0xf8, 0xf2, 0xbb, 0x81, 0x98, 0xc6, 0xd9, 0x21, 0xbf, 0x77, 0xd4, 0xc7, 0xf5, 0xf2, 0xb0, 0x20, 0x50, 0x50, 0x1e, 0x66, 0x57, 0x20, 0x36, 0xdd, 0xad, 0x5d, 0x63, 0x70, 0xc7, 0xbf, 0xac, 0x4b, 0x32, 0x30, 0xa0, 0x6d, 0xdc, 0xdd, 0x62, 0x3f, 0x3d, 0x2, 0x9d, 0x5d, 0x2e, 0xcb, 0xa3, 0xb9, 0xc0, 0x5a, 0x3b, 0xd9, 0xe3, 0x1d, 0x41, 0x8f, 0xdd, 0x8c, 0xca, 0x42, 0x94, 0xec, 0x82, 0xc1, 0x11, 0x16, 0x6d, 0x3, 0xe9, 0xb4, 0x1f, 0xeb, 0xd4, 0xd4, 0x19, 0xe8, 0x68, 0x31, 0xc9, 0x32, 0x7e, 0xbc, 0x64, 0xd6, 0xe9, 0x3d, 0x17, 0x40, 0x31, 0xf8, 0x85, 0x96, 0x71, 0xc4, 0x80, 0x90, 0xac, 0x32, 0x25, 0xe4, 0x73, 0xe, 0x2f, 0x85, 0x3b, 0xc6, 0x88, 0x92, 0x90, 0x65, 0xba, 0x1c, 0x3, 0x3, 0x36, 0x70, 0x5b, 0x89, 0x1, 0x9a, 0x7d, 0xa3, 0xc, 0xa2, 0x24, 0xcf, 0xe6, 0x63, 0x2a, 0xdf, 0x21, 0x9b, 0x67, 0xc, 0x39, 0xb, 0x47, 0x29, 0xd9, 0x65, 0x1f, 0x68, 0xf, 0xf6, 0x7f, 0x96, 0x71, 0xd4, 0xc7, 0x6c, 0xd0, 0xe9, 0xa9, 0x33, 0xd0, 0x75, 0xe1, 0x1e, 0xcb, 0x78, 0xcb, 0x4c, 0x16, 0x7c, 0x96, 0x51, 0x42, 0xab, 0x13, 0xd6, 0xa8, 0xad, 0x4f, 0x29, 0xd2, 0xf5, 0x47, 0x86, 0x7c, 0xbe, 0x11, 0x5f, 0xb4, 0x9d, 0x62, 0xff, 0xb6, 0x2e, 0xc5, 0xc0, 0xd0, 0x97, 0x4a, 0x90, 0x1b, 0xd8, 0x3f, 0xd0, 0x6f, 0x74, 0x71, 0x9e, 0xce, 0xb, 0xf6, 0x50, 0xaf, 0x2c, 0x66, 0xd4, 0xac, 0xb6, 0x50, 0xb2, 0xcb, 0x2e, 0x50, 0x8, 0xdd, 0xd1, 0x35, 0x6, 0x6d, 0xe4, 0x39, 0x9d, 0x1a, 0xdf, 0x80, 0x14, 0xf0, 0x19, 0xae, 0xb1, 0x4, 0xc9, 0xae, 0x28, 0x44, 0xf7, 0x9, 0xb2, 0x43, 0x3d, 0xdd, 0x5e, 0x96, 0x6b, 0x48, 0x7c, 0x18, 0x11, 0x81, 0xb9, 0x46, 0x1f, 0x52, 0xdb, 0xb1, 0x2c, 0x9f, 0xeb, 0x32, 0xc, 0x4, 0x50, 0x8e, 0x6e, 0xb7, 0xec, 0xcd, 0x28, 0x33, 0x42, 0xe6, 0xe5, 0x67, 0x79, 0x3e, 0x3f, 0xc8, 0x38, 0xff, 0xc0, 0x32, 0x8e, 0xce, 0x32, 0x43, 0x94, 0xec, 0xb2, 0x87, 0x22, 0xba, 0x18, 0xdc, 0x80, 0xcb, 0x67, 0x92, 0x4e, 0x8f, 0x6f, 0x40, 0x52, 0xc4, 0xf3, 0x96, 0x71, 0x9c, 0x90, 0xbc, 0x7d, 0x88, 0xee, 0x13, 0x75, 0x51, 0x36, 0xf7, 0xe5, 0x3c, 0x8f, 0xf1, 0xb0, 0x92, 0x9d, 0x7b, 0x4e, 0xe1, 0xbe, 0x5c, 0xa1, 0xcb, 0x30, 0x10, 0x60, 0xff, 0xb0, 0x95, 0xd2, 0x8c, 0x15, 0xe7, 0x6c, 0xc7, 0x7c, 0x7, 0x5a, 0xa0, 0x9d, 0x27, 0xf6, 0xc4, 0xa8, 0xf3, 0x3c, 0x14, 0x4b, 0x25, 0xbb, 0x0, 0x80, 0xd4, 0x71, 0x5b, 0xcf, 0x3a, 0xa4, 0xcd, 0xaf, 0xd7, 0xe9, 0xf1, 0x15, 0xb0, 0x94, 0xdd, 0x1d, 0xf5, 0x51, 0xd7, 0xd8, 0x33, 0x44, 0xf7, 0x88, 0x17, 0xaf, 0xb9, 0x6b, 0xc, 0x59, 0x8c, 0xc3, 0x24, 0x98, 0x12, 0x94, 0x20, 0xd0, 0xc4, 0x62, 0x2d, 0xa3, 0x31, 0xc2, 0x6a, 0x5d, 0x82, 0xbe, 0x3, 0xa7, 0xa9, 0xdb, 0xba, 0x83, 0x60, 0x9d, 0x5f, 0xa4, 0x7b, 0xc8, 0x6f, 0x98, 0x4a, 0xeb, 0xd7, 0x8d, 0x46, 0x95, 0x95, 0x95, 0xd7, 0x16, 0x35, 0x68, 0xd0, 0x40, 0xc9, 0x2e, 0x58, 0xa0, 0xb3, 0x84, 0xad, 0x33, 0xf7, 0x7c, 0x51, 0x17, 0x66, 0x10, 0x80, 0x1b, 0xd3, 0x76, 0x34, 0x4b, 0xbf, 0x90, 0xdf, 0x37, 0xda, 0xb7, 0x3d, 0x1d, 0xa1, 0x79, 0xb6, 0x95, 0x4a, 0x20, 0x76, 0x54, 0xa6, 0x4b, 0xd0, 0x77, 0xa0, 0x2b, 0x88, 0xed, 0x0, 0x63, 0xc4, 0xa8, 0x27, 0xea, 0xf4, 0x6c, 0x2, 0x34, 0x86, 0x9e, 0xe6, 0x1e, 0x34, 0x44, 0xd7, 0xef, 0x8d, 0xd1, 0x1f, 0xf4, 0x56, 0xb2, 0xb, 0x16, 0xa7, 0x79, 0x58, 0x75, 0x88, 0xcb, 0xac, 0xd2, 0xe9, 0xf1, 0x1d, 0x65, 0x1e, 0x64, 0xb7, 0xaf, 0x84, 0x2b, 0x6e, 0xe7, 0x26, 0x89, 0x2b, 0x23, 0x46, 0x14, 0x4d, 0x2c, 0x63, 0x68, 0xdd, 0x56, 0xaa, 0x4b, 0xd0, 0x57, 0x20, 0x2e, 0x3a, 0xc0, 0x32, 0x3e, 0x4f, 0xa2, 0x11, 0xdb, 0xcd, 0x36, 0xb0, 0xa7, 0x5e, 0xef, 0x1e, 0x2c, 0x28, 0x28, 0x28, 0x9c, 0x38, 0x6d, 0xe6, 0x25, 0xd3, 0x67, 0xcf, 0x4d, 0x28, 0xd9, 0x5, 0x3, 0xd4, 0x20, 0x5d, 0x61, 0x19, 0x9f, 0x2d, 0xb9, 0xeb, 0xa7, 0x97, 0xf, 0x40, 0x92, 0x84, 0xdb, 0xb5, 0x83, 0x73, 0xc7, 0x3a, 0x87, 0xf4, 0x7e, 0xd1, 0xc8, 0x77, 0x42, 0xc4, 0xe6, 0xd8, 0xd6, 0xa2, 0x6d, 0xb5, 0xf8, 0x7b, 0x26, 0xa3, 0xc2, 0x51, 0x96, 0xb7, 0xb1, 0x8c, 0xa3, 0x65, 0xd9, 0x12, 0x9d, 0x1e, 0x2b, 0x70, 0xca, 0xc3, 0x26, 0x8d, 0xee, 0x13, 0x86, 0xe2, 0x4a, 0x4a, 0x4b, 0xfb, 0xbe, 0xfc, 0xf6, 0x7b, 0x87, 0x54, 0x54, 0x54, 0x28, 0xd9, 0xf9, 0xc, 0x68, 0x10, 0xe8, 0x64, 0x60, 0x3b, 0x1d, 0xfc, 0x1f, 0xe2, 0x7d, 0xa, 0xaf, 0xa2, 0xee, 0x98, 0x60, 0xd9, 0x8, 0x90, 0xcd, 0xb6, 0x4b, 0x8, 0xef, 0x15, 0xa7, 0x17, 0xdc, 0x11, 0x13, 0x92, 0x80, 0x15, 0x52, 0xac, 0xcb, 0xcf, 0x37, 0x6c, 0xee, 0x61, 0xd5, 0xa1, 0x98, 0xfa, 0x5e, 0x9d, 0x1e, 0x4f, 0xa0, 0x51, 0xc7, 0xad, 0x6e, 0x2f, 0x43, 0xe3, 0x46, 0xd, 0x65, 0xfc, 0xa4, 0xa9, 0x83, 0xbf, 0x9e, 0x3a, 0xa3, 0xbe, 0x92, 0x9d, 0xbf, 0x40, 0xab, 0xaa, 0xd3, 0x2d, 0xe3, 0xef, 0x1b, 0x79, 0x56, 0xa7, 0x27, 0x70, 0x2, 0x59, 0x60, 0x51, 0x3e, 0xc2, 0x48, 0x76, 0x6f, 0x89, 0xbf, 0x27, 0x5e, 0x64, 0x53, 0x99, 0x73, 0xa3, 0xbe, 0x2e, 0x3d, 0x5f, 0x81, 0x63, 0x93, 0x6c, 0xb1, 0xd1, 0x7f, 0x4b, 0xb8, 0x8e, 0x78, 0xa, 0x23, 0x50, 0x86, 0x30, 0xae, 0xa, 0xb, 0x96, 0x57, 0xf4, 0x5a, 0xb7, 0x7e, 0x43, 0x77, 0x25, 0x3b, 0xff, 0x80, 0xb6, 0x60, 0x23, 0x2c, 0xf3, 0x88, 0xf4, 0xd8, 0x28, 0x9e, 0xd8, 0x1c, 0x45, 0x4c, 0xb3, 0x8c, 0x6d, 0x1b, 0xb2, 0x7b, 0x44, 0xbd, 0xdd, 0xdd, 0x11, 0x9d, 0x5f, 0x9b, 0x67, 0x2, 0x87, 0xb7, 0x36, 0xd6, 0xa5, 0xe7, 0xb, 0x10, 0x5f, 0x3e, 0xd6, 0x32, 0x8e, 0xc2, 0xf1, 0xa7, 0x74, 0x7a, 0xaa, 0x5, 0xc2, 0x18, 0x55, 0x4a, 0x32, 0x8a, 0x8b, 0x8b, 0x1a, 0xff, 0xe7, 0xc5, 0xd7, 0x8f, 0x57, 0xb2, 0xf3, 0x7, 0x88, 0xd, 0x3d, 0x6c, 0x64, 0x2b, 0xcb, 0x35, 0xb8, 0xab, 0xbe, 0xd4, 0x29, 0xca, 0xa, 0x6c, 0xf5, 0x8b, 0x88, 0xa1, 0x16, 0x86, 0xe8, 0x1e, 0xdf, 0x95, 0xe8, 0xc5, 0xea, 0x92, 0xb0, 0xc5, 0x8b, 0x60, 0x85, 0x34, 0xd5, 0xa5, 0xe7, 0xb, 0xe0, 0x85, 0xe8, 0x65, 0x19, 0x7f, 0x43, 0xbc, 0x4f, 0xec, 0x56, 0x6c, 0x8a, 0x57, 0xdc, 0x73, 0x55, 0x2f, 0x91, 0x90, 0x1f, 0x57, 0xae, 0x3a, 0xe9, 0xb5, 0x51, 0x63, 0x37, 0x53, 0xb2, 0xab, 0x1b, 0x50, 0x3b, 0x5, 0x17, 0xe5, 0x9e, 0x96, 0x6b, 0x1f, 0x89, 0x73, 0x7c, 0xbc, 0x22, 0x3b, 0xb0, 0x6d, 0x8, 0xcd, 0xc4, 0x69, 0xdb, 0x16, 0x6, 0x20, 0xae, 0xf0, 0x5f, 0x89, 0x6e, 0xaa, 0x3e, 0xba, 0xce, 0x2f, 0x75, 0x8d, 0x41, 0x91, 0xe8, 0xa9, 0x4b, 0xcf, 0x17, 0xf4, 0x11, 0x7b, 0xf6, 0xf0, 0x23, 0x3a, 0x35, 0x19, 0x3, 0x2d, 0x4, 0x1f, 0x4b, 0x1d, 0x48, 0x18, 0xb2, 0x2b, 0x29, 0x29, 0x6d, 0x3d, 0x67, 0xfe, 0xb7, 0x7d, 0x95, 0xec, 0x6a, 0x8f, 0x2d, 0xb8, 0x79, 0x1d, 0x6c, 0xb9, 0x86, 0xae, 0xfc, 0xe8, 0xbe, 0x5f, 0xa2, 0xd3, 0x94, 0x35, 0xd8, 0x3a, 0xa1, 0xb7, 0xe, 0x91, 0xe5, 0x1, 0xcb, 0xe8, 0xf5, 0x8, 0xcf, 0xef, 0x4f, 0x1e, 0x73, 0xfc, 0x47, 0x5d, 0x7a, 0xbe, 0xe0, 0x78, 0xcb, 0xd8, 0x78, 0x8a, 0x22, 0x73, 0xe0, 0x1d, 0xdb, 0xa4, 0xd1, 0x41, 0xc3, 0xe2, 0x6, 0x32, 0x7d, 0xf6, 0xdc, 0xa3, 0xd, 0xe1, 0x29, 0xd9, 0xd5, 0x2, 0xb0, 0x18, 0x9e, 0xf7, 0x20, 0x3a, 0x10, 0xdc, 0x99, 0x62, 0x8f, 0x21, 0x29, 0x82, 0xc3, 0x5a, 0x8b, 0x72, 0x1, 0xa2, 0x6b, 0x10, 0x92, 0xfb, 0x1b, 0x49, 0xc2, 0x88, 0x2a, 0x56, 0x79, 0x6c, 0xbc, 0xb0, 0x48, 0x3a, 0xea, 0xf2, 0xab, 0x13, 0x76, 0x12, 0xfb, 0x39, 0x8c, 0x50, 0xa6, 0xb5, 0x8e, 0xb1, 0x66, 0xc0, 0xc9, 0x21, 0x9b, 0x84, 0xa, 0xa, 0xa, 0xa, 0x64, 0xc9, 0xd2, 0xe5, 0xfb, 0x2d, 0x5f, 0xf1, 0x53, 0x1b, 0x25, 0xbb, 0x9a, 0x1, 0xbe, 0xf5, 0xb7, 0x3c, 0x88, 0xe, 0x18, 0x2c, 0xe1, 0xef, 0x60, 0x1f, 0x47, 0xa0, 0x95, 0x92, 0x3b, 0x89, 0x2, 0xae, 0xc3, 0xb0, 0xa4, 0xf8, 0xc7, 0x21, 0x23, 0x77, 0xb4, 0x65, 0x3e, 0xe1, 0xca, 0xbf, 0x5c, 0x97, 0x5f, 0x9d, 0x80, 0x73, 0x2, 0xdd, 0xee, 0x76, 0xf4, 0x7c, 0xd4, 0xa3, 0xc0, 0x6a, 0xe, 0x24, 0xaa, 0x8c, 0x4a, 0x1d, 0xa8, 0xac, 0xac, 0x34, 0x84, 0x57, 0xbf, 0x5d, 0x79, 0x45, 0xc5, 0xee, 0x4a, 0x76, 0x35, 0x73, 0x35, 0xe0, 0x85, 0xef, 0xe1, 0x71, 0x1d, 0x7, 0x73, 0x3e, 0xa0, 0xd3, 0x94, 0x33, 0x94, 0xbb, 0xfe, 0x8d, 0x1a, 0xb0, 0x30, 0x74, 0x51, 0x81, 0xff, 0x64, 0x72, 0xc, 0xe6, 0xf7, 0x1d, 0x23, 0x73, 0x2d, 0xe3, 0x67, 0x88, 0x93, 0x36, 0xaf, 0xa8, 0x1d, 0xd0, 0x71, 0xc9, 0x5d, 0xda, 0x31, 0x4f, 0xc2, 0x7f, 0xbe, 0x61, 0x58, 0xf1, 0x86, 0x5b, 0x29, 0x6b, 0x58, 0x5c, 0x54, 0xef, 0xc9, 0x17, 0x5e, 0xdb, 0x7f, 0x43, 0x49, 0x89, 0x92, 0x5d, 0x35, 0xc0, 0xf9, 0x48, 0x48, 0x19, 0x47, 0x2f, 0xc3, 0x6d, 0x3c, 0x3e, 0x73, 0x83, 0x38, 0x8d, 0x7d, 0x15, 0xb9, 0x83, 0x7b, 0xc3, 0x40, 0x6, 0x56, 0x93, 0x10, 0xdc, 0xd7, 0xa7, 0x12, 0x8f, 0x3, 0x4e, 0x51, 0x4a, 0x73, 0xbf, 0xc7, 0xb5, 0xfb, 0x8c, 0x1c, 0xa6, 0x4b, 0xb0, 0xc6, 0x40, 0x48, 0xa4, 0x8b, 0x65, 0x1c, 0x56, 0x9d, 0x36, 0xd9, 0xae, 0x1d, 0x10, 0x42, 0x9a, 0xed, 0x1e, 0x2c, 0x2d, 0x2b, 0x3b, 0xa0, 0x41, 0x61, 0x61, 0x42, 0xc9, 0xce, 0xe, 0x6c, 0x94, 0x28, 0x14, 0x47, 0x3b, 0xaa, 0x41, 0x46, 0xa, 0x2c, 0x9f, 0x41, 0x76, 0x1d, 0x8e, 0xe3, 0xb8, 0x5a, 0xa7, 0x2b, 0x94, 0xa8, 0xc, 0xc1, 0x3d, 0x6c, 0x8, 0xc9, 0x7d, 0xf8, 0x81, 0xc7, 0xc4, 0x1e, 0x8f, 0xc6, 0xa6, 0x8d, 0x18, 0xd3, 0xa9, 0xba, 0xe4, 0x6a, 0x84, 0x76, 0x46, 0x3a, 0x59, 0xc6, 0xc7, 0xe8, 0xd4, 0xd4, 0x1a, 0x25, 0xee, 0xf9, 0xfb, 0x35, 0x2b, 0xb3, 0xb4, 0xb4, 0xfb, 0xec, 0x79, 0xb, 0x5a, 0x28, 0xd9, 0x6d, 0x8a, 0x86, 0xe2, 0xb4, 0xed, 0x81, 0xcb, 0x12, 0xa9, 0xbf, 0x3b, 0x78, 0x7c, 0xe, 0xda, 0xfa, 0x49, 0xd4, 0x6a, 0x15, 0xa, 0x2f, 0xc4, 0xe9, 0xfd, 0x5a, 0x2e, 0xce, 0xe1, 0x98, 0xb6, 0x12, 0x8a, 0xcd, 0xf8, 0xbe, 0xa0, 0x13, 0x7d, 0x2b, 0x7d, 0xec, 0x19, 0x61, 0x3b, 0xee, 0x37, 0x6e, 0x5, 0x4d, 0xeb, 0x73, 0xeb, 0x86, 0x8f, 0xdd, 0x64, 0xf7, 0xcb, 0x2f, 0x6b, 0x8b, 0x47, 0x8e, 0xf9, 0x68, 0x8f, 0x7a, 0x21, 0xd6, 0x8a, 0xb3, 0x5, 0xbc, 0xa8, 0xbd, 0xc4, 0xe9, 0x63, 0x9, 0x5f, 0xf9, 0xa3, 0x46, 0xf6, 0x49, 0xf3, 0xf9, 0x2f, 0x8c, 0x1c, 0x22, 0x7a, 0x6c, 0x8f, 0x22, 0xff, 0x80, 0xf4, 0xee, 0x6b, 0x3d, 0xae, 0xa1, 0x85, 0xd8, 0x5, 0xdc, 0xac, 0x71, 0x54, 0x4d, 0x6b, 0x9d, 0xae, 0xb4, 0xb0, 0x35, 0x2b, 0x47, 0x7c, 0x57, 0x9b, 0x3e, 0xd7, 0xd, 0xd3, 0xc5, 0x75, 0xb0, 0x2b, 0x8, 0xaf, 0xb8, 0xa8, 0xa8, 0x73, 0x41, 0x9a, 0x85, 0x8b, 0x6b, 0x89, 0x18, 0x4d, 0x42, 0x25, 0xbf, 0x53, 0x63, 0xba, 0xf, 0xba, 0x89, 0x73, 0x1c, 0xc, 0xfe, 0xbe, 0x67, 0x6, 0xdf, 0x15, 0x49, 0x10, 0x77, 0x8a, 0xd3, 0xf4, 0x79, 0xa5, 0xae, 0x29, 0x45, 0x9e, 0x2, 0xd, 0x13, 0xd0, 0x35, 0xe8, 0xaf, 0x69, 0x2c, 0x96, 0x7b, 0x48, 0x78, 0xaf, 0x89, 0xe3, 0xfe, 0x9c, 0x91, 0x67, 0xa, 0x74, 0x26, 0xb0, 0x95, 0x1c, 0xe0, 0x50, 0x52, 0x3d, 0xa0, 0xb5, 0x6e, 0x40, 0xad, 0xf3, 0x42, 0x71, 0xb9, 0x88, 0xcb, 0xca, 0xcb, 0x3d, 0xc9, 0xe, 0xf1, 0x2a, 0xa4, 0xd8, 0xc7, 0xc9, 0xd, 0x53, 0xc9, 0x97, 0x14, 0x49, 0x27, 0x45, 0x52, 0xb3, 0x66, 0xb6, 0x63, 0xc5, 0xc9, 0xb8, 0x1c, 0x9b, 0xa7, 0xb, 0x8, 0xf3, 0x85, 0x62, 0xfa, 0x86, 0x29, 0xf3, 0x57, 0xc9, 0xf5, 0xd1, 0x82, 0x73, 0x89, 0xf8, 0xd4, 0xa, 0xd7, 0x7c, 0xe3, 0xdf, 0xf0, 0xa3, 0xaf, 0xa1, 0x82, 0xa0, 0xc5, 0xf6, 0xd1, 0x47, 0x5, 0x2d, 0xb8, 0xf9, 0x7c, 0x27, 0xbc, 0x8a, 0xf7, 0x3b, 0x51, 0x40, 0x8a, 0xef, 0x19, 0x79, 0xc9, 0xc8, 0x67, 0xa2, 0x99, 0x86, 0x49, 0xd8, 0x1a, 0x3f, 0xcf, 0x54, 0xa5, 0xa0, 0xce, 0x58, 0x4a, 0xc2, 0xfb, 0x8d, 0xec, 0x1a, 0x35, 0x2c, 0x96, 0xcf, 0x27, 0x4c, 0xda, 0xda, 0x8b, 0xec, 0xb6, 0xa6, 0xe4, 0x3b, 0x92, 0xc7, 0xbf, 0x3f, 0x9d, 0x27, 0x1b, 0x35, 0xda, 0x40, 0xc1, 0xad, 0xbb, 0x87, 0x38, 0x6e, 0x96, 0x5d, 0xb8, 0xe, 0x70, 0x7a, 0x72, 0x33, 0x17, 0xd9, 0x55, 0x90, 0xec, 0x52, 0x8b, 0xb7, 0x93, 0x5a, 0x69, 0xc2, 0x45, 0x76, 0x70, 0x2b, 0xa0, 0x38, 0x19, 0x59, 0x7d, 0xc8, 0x34, 0x43, 0x8b, 0xaf, 0x6f, 0x29, 0xd8, 0x34, 0xe7, 0xf0, 0x73, 0x7a, 0xfa, 0x75, 0x74, 0x80, 0x6, 0xe8, 0xe3, 0xf8, 0x7e, 0xf4, 0x48, 0xf3, 0x39, 0x94, 0x81, 0xf4, 0xa7, 0x2c, 0xe3, 0xff, 0x41, 0x5c, 0xe5, 0x4d, 0x5a, 0x7c, 0xf9, 0xda, 0x2c, 0x7d, 0x7b, 0xf, 0xb2, 0x53, 0xd4, 0x1d, 0xdf, 0x18, 0xe9, 0xeb, 0xb2, 0xec, 0x5a, 0x16, 0xe8, 0xbc, 0x54, 0x1, 0x36, 0x5d, 0xf4, 0xb7, 0x7c, 0x88, 0xda, 0x68, 0xdc, 0xdd, 0xa, 0x70, 0xa7, 0xec, 0x26, 0x4e, 0xaf, 0x43, 0xd4, 0xfd, 0x20, 0x29, 0xa7, 0x71, 0x2d, 0xad, 0x7a, 0xf7, 0x19, 0x67, 0xdb, 0x64, 0xf0, 0x7f, 0x4a, 0x49, 0x80, 0x58, 0xa0, 0x13, 0xc5, 0xa9, 0xe7, 0x9a, 0x4c, 0x57, 0xc4, 0x7c, 0xd1, 0x53, 0xde, 0xc3, 0xc, 0xbc, 0x27, 0xbd, 0x8d, 0x5c, 0x22, 0x4e, 0xf7, 0xa0, 0xed, 0xab, 0xf9, 0x3c, 0x92, 0x57, 0xe, 0xa7, 0x5c, 0xc7, 0xe7, 0x8b, 0x42, 0xe0, 0x4f, 0x48, 0x7c, 0x33, 0xe8, 0x5, 0x88, 0x3b, 0xa0, 0x30, 0x36, 0xb1, 0xec, 0x3b, 0x7a, 0xf6, 0xa5, 0x3f, 0xa8, 0x52, 0x13, 0x9a, 0x48, 0x24, 0x36, 0x57, 0xb2, 0xdb, 0xb8, 0xd0, 0x90, 0x56, 0x8d, 0x93, 0x6f, 0xdf, 0xa7, 0xe6, 0x19, 0x57, 0x77, 0x2, 0x48, 0xac, 0x83, 0x91, 0xa3, 0x8d, 0x1c, 0x6a, 0x64, 0x57, 0x5a, 0x6e, 0xb9, 0xb4, 0x26, 0xb7, 0xa4, 0xec, 0x9d, 0x32, 0xe, 0xab, 0x70, 0x1e, 0x5, 0xee, 0xaf, 0xb1, 0x7c, 0x46, 0xeb, 0x74, 0xb9, 0x86, 0xa, 0x78, 0x1e, 0x48, 0xee, 0x42, 0xf9, 0x1, 0xce, 0x76, 0x1c, 0x28, 0x76, 0x17, 0x9d, 0xed, 0xb9, 0xef, 0x44, 0x39, 0x8f, 0x4a, 0xcd, 0xc, 0x3e, 0x63, 0xbc, 0x7f, 0x1f, 0x50, 0xe1, 0x89, 0xa3, 0xb2, 0x89, 0xee, 0x33, 0xee, 0xbd, 0x17, 0xdf, 0x7f, 0xb9, 0x2e, 0x27, 0x5f, 0x30, 0xdf, 0x32, 0xb6, 0x59, 0xbe, 0x91, 0x1d, 0x5c, 0x26, 0x70, 0xa5, 0xe1, 0x40, 0xc4, 0xa5, 0xdc, 0x48, 0x91, 0x3d, 0x86, 0xda, 0x8c, 0xd9, 0x12, 0xef, 0x7e, 0x74, 0xdb, 0xd0, 0xb4, 0x3f, 0xc5, 0xc8, 0xef, 0x24, 0x1c, 0x1d, 0x46, 0xd2, 0x21, 0x49, 0x80, 0x7b, 0x19, 0x39, 0x8e, 0xcf, 0x6, 0x9a, 0xef, 0x48, 0x12, 0xdf, 0x24, 0x5a, 0x82, 0x3f, 0x8b, 0xc6, 0x39, 0xc2, 0x80, 0x39, 0x24, 0xbd, 0xbb, 0x8c, 0x1c, 0x43, 0xe2, 0x43, 0x56, 0x73, 0xb3, 0xc, 0xff, 0x3f, 0x4e, 0xeb, 0xee, 0x41, 0x39, 0x9d, 0xef, 0xea, 0x12, 0x3e, 0xeb, 0xf, 0x69, 0xf9, 0xcf, 0x90, 0xaa, 0x27, 0x30, 0x44, 0x11, 0xb0, 0xea, 0xdc, 0x39, 0x3, 0x3f, 0xc7, 0xe4, 0xbb, 0x85, 0x1, 0xf3, 0xb8, 0x5f, 0xa4, 0x1e, 0xf3, 0xd5, 0xc8, 0x8b, 0xec, 0x5e, 0xa0, 0xa6, 0x16, 0x27, 0xac, 0xe5, 0x82, 0x4a, 0x92, 0xdd, 0xf7, 0x12, 0x9e, 0xfe, 0x89, 0x41, 0x2, 0xae, 0xa5, 0xb3, 0xb9, 0x1, 0xd5, 0xe5, 0x4, 0xef, 0x52, 0x4a, 0x32, 0x16, 0xb7, 0x58, 0xec, 0x49, 0x3e, 0xb8, 0x8e, 0x7e, 0x7f, 0xdb, 0x52, 0x7b, 0xad, 0xc7, 0x3f, 0xeb, 0x4b, 0xdd, 0x12, 0x9e, 0xa, 0x49, 0xd8, 0x7f, 0xa1, 0xac, 0xa0, 0xc5, 0x87, 0x26, 0xc5, 0xd, 0xf5, 0xfd, 0xe, 0xd, 0xf0, 0x7e, 0x3d, 0x46, 0x41, 0xec, 0xb7, 0x27, 0xd7, 0xde, 0x7e, 0x35, 0x7c, 0x4e, 0x50, 0xc6, 0xda, 0x89, 0x53, 0xf7, 0xa, 0x41, 0x36, 0xf4, 0x4c, 0x92, 0xea, 0x44, 0x92, 0xe0, 0x4, 0x5a, 0x44, 0x51, 0x53, 0x76, 0x90, 0xb1, 0x5a, 0x6c, 0xb1, 0x90, 0xb1, 0x1f, 0xe9, 0xa1, 0xb8, 0x75, 0xc7, 0x1a, 0xae, 0x97, 0x54, 0xb2, 0xdb, 0xc2, 0x8b, 0xec, 0xbe, 0x36, 0xf2, 0xa2, 0xce, 0x59, 0xa4, 0x81, 0xc4, 0x12, 0xc4, 0x51, 0x2e, 0x12, 0x27, 0x93, 0x32, 0x53, 0x20, 0x6b, 0x72, 0x1, 0x5d, 0x1, 0xb, 0x68, 0xf1, 0x22, 0x91, 0x64, 0x91, 0x38, 0x6e, 0xa5, 0xe4, 0x42, 0x42, 0x32, 0x49, 0xc2, 0x83, 0xec, 0xea, 0xa5, 0x2c, 0xb4, 0x26, 0x24, 0x3e, 0x64, 0xed, 0xa1, 0xfb, 0xf8, 0x56, 0xdc, 0xc4, 0xf0, 0xc2, 0xa3, 0x16, 0xab, 0x25, 0xc7, 0x8b, 0x6b, 0x61, 0xf9, 0x25, 0x13, 0x1f, 0x14, 0xe1, 0xc4, 0x57, 0x94, 0x7b, 0xb9, 0x1e, 0xf1, 0xac, 0x10, 0xe3, 0xeb, 0x4a, 0xc5, 0xab, 0x26, 0x9e, 0x25, 0x28, 0x4b, 0x9d, 0x29, 0x47, 0x1a, 0xb9, 0x4a, 0x9c, 0xd3, 0x24, 0xd0, 0xe5, 0xe8, 0x7d, 0x12, 0xdf, 0x2c, 0xb1, 0x1f, 0x47, 0x14, 0x36, 0x94, 0x5b, 0x8, 0x1a, 0xdf, 0x6b, 0xae, 0x2e, 0x19, 0x5f, 0x50, 0x29, 0x16, 0xcf, 0x95, 0xd7, 0x62, 0x2b, 0xd6, 0xf9, 0x8a, 0x34, 0xe0, 0xaa, 0x1c, 0x2a, 0xf6, 0x76, 0x44, 0x36, 0x8b, 0xd, 0xee, 0x21, 0xb8, 0x72, 0x3f, 0xe0, 0xb, 0xb7, 0x40, 0x36, 0x2d, 0x23, 0xa8, 0xd, 0x4a, 0x52, 0xb4, 0x2c, 0x2f, 0xf7, 0x4c, 0xa3, 0x14, 0xb2, 0x83, 0xd5, 0x86, 0x44, 0x19, 0x74, 0x28, 0xef, 0x2e, 0x4e, 0x10, 0xbf, 0x50, 0x1f, 0x65, 0x2c, 0x50, 0x41, 0x65, 0xe9, 0x41, 0x4a, 0x4b, 0xae, 0x4d, 0x3c, 0x6f, 0x34, 0x68, 0x40, 0xbd, 0x6b, 0xb3, 0x1a, 0x92, 0x1f, 0x14, 0xaa, 0xe6, 0x2e, 0x85, 0x7, 0xae, 0x4e, 0x14, 0x15, 0x23, 0xe6, 0x87, 0xd3, 0xe1, 0x91, 0xe8, 0x14, 0x95, 0x6c, 0xcf, 0x7a, 0x6a, 0xd5, 0x5, 0xb, 0x4d, 0x50, 0x89, 0x17, 0x60, 0x2d, 0xdd, 0x40, 0xb2, 0xab, 0x6e, 0xf3, 0x41, 0x6, 0x1c, 0x62, 0x21, 0x38, 0x9f, 0x6f, 0xaa, 0xe4, 0xa6, 0xb4, 0x62, 0x2d, 0x89, 0x35, 0x79, 0xda, 0xf8, 0xb, 0xfc, 0x13, 0xee, 0x2e, 0x24, 0x2e, 0xf4, 0x12, 0xc7, 0xd, 0x6, 0xad, 0xb7, 0x8b, 0xe8, 0x29, 0x1d, 0x71, 0xc1, 0xf, 0x14, 0x28, 0x57, 0xf7, 0xd0, 0xea, 0x7, 0xf9, 0xfd, 0x5e, 0x1c, 0x77, 0x27, 0xce, 0xc9, 0xdb, 0x41, 0x6a, 0x7e, 0xda, 0xfc, 0x8e, 0x94, 0x23, 0x8c, 0x5c, 0x2f, 0x4e, 0xec, 0xe6, 0x15, 0x92, 0xdf, 0x67, 0xa2, 0x31, 0x31, 0x25, 0x3b, 0x45, 0x2c, 0x80, 0xce, 0xf3, 0x48, 0xe, 0xe8, 0x50, 0xd, 0xb9, 0x3c, 0x45, 0xf9, 0x44, 0xc2, 0x9b, 0x90, 0x83, 0xf8, 0xc5, 0x24, 0xca, 0x5d, 0xb4, 0x4, 0x40, 0x76, 0xfb, 0xf3, 0x7b, 0x22, 0x6b, 0xb3, 0x81, 0x3e, 0xf2, 0xd8, 0x0, 0xa5, 0x27, 0x5f, 0x50, 0x80, 0x56, 0x24, 0xbb, 0xce, 0xb4, 0xfa, 0x90, 0xe8, 0x2, 0xb7, 0x67, 0x4d, 0x92, 0xaa, 0xa, 0x49, 0x9a, 0x97, 0x51, 0x90, 0xe5, 0x39, 0x8e, 0xa, 0xd5, 0x58, 0x71, 0xb5, 0x94, 0xca, 0x81, 0x15, 0x97, 0xd0, 0xc7, 0xae, 0x64, 0xa7, 0xa8, 0x39, 0xd0, 0xa5, 0xe2, 0x16, 0xf1, 0x76, 0x3f, 0x23, 0xa5, 0x19, 0x8d, 0x7a, 0x11, 0x3b, 0x99, 0x1f, 0x51, 0x4b, 0x60, 0x2c, 0xe5, 0x66, 0x6e, 0x62, 0x28, 0x9d, 0xe8, 0xc7, 0xcd, 0x50, 0xdd, 0x9d, 0xf1, 0xc2, 0x32, 0xca, 0x67, 0x5c, 0xb7, 0x78, 0xbe, 0x6d, 0xa9, 0xe4, 0xe0, 0x79, 0xef, 0x46, 0x4b, 0x70, 0xab, 0x1a, 0xfc, 0xcc, 0x64, 0xbc, 0xef, 0x34, 0x7a, 0x12, 0x90, 0x80, 0xf7, 0x32, 0x7f, 0x47, 0x2e, 0x3c, 0x1a, 0xe5, 0x2e, 0x4f, 0x5, 0xe2, 0x4c, 0x1b, 0xf4, 0xd1, 0xd7, 0xd, 0xe8, 0x83, 0xb9, 0x6e, 0xfd, 0x6, 0x29, 0x2f, 0xdf, 0xe4, 0x88, 0x4b, 0xc4, 0x7b, 0x57, 0x2a, 0xd9, 0x45, 0x1f, 0x68, 0xcc, 0x3b, 0x2c, 0xcd, 0x75, 0x58, 0x71, 0xb7, 0xd2, 0x4a, 0x8a, 0x3, 0xb0, 0x8a, 0xa7, 0x53, 0xf0, 0xbd, 0x10, 0xaf, 0x79, 0x58, 0x9c, 0x18, 0x9f, 0x22, 0x9e, 0x80, 0x7, 0x62, 0xe, 0xe5, 0x19, 0x92, 0xc4, 0x2e, 0x54, 0x7a, 0x50, 0xaa, 0x70, 0x8, 0x2d, 0xff, 0x4c, 0x2d, 0x3f, 0x10, 0x27, 0x4e, 0x70, 0x18, 0x2c, 0x4e, 0x6c, 0xf, 0xeb, 0xe7, 0xd, 0xa9, 0x7a, 0x8, 0x70, 0x50, 0x40, 0xfc, 0x72, 0xbd, 0x4b, 0x49, 0x83, 0xcb, 0xf5, 0x6c, 0x12, 0xa1, 0xa2, 0x96, 0x58, 0x6f, 0x88, 0xee, 0x98, 0xc3, 0xfa, 0x48, 0x87, 0xf6, 0x6d, 0xdd, 0x96, 0xb4, 0x92, 0x5d, 0xc4, 0x81, 0xb8, 0xc4, 0xdf, 0x3c, 0xae, 0xc1, 0x82, 0xbb, 0x9a, 0x64, 0x17, 0x57, 0x20, 0x23, 0x14, 0x7d, 0x17, 0xd7, 0x28, 0xd9, 0xe5, 0x15, 0x10, 0x73, 0x9e, 0x46, 0x81, 0x75, 0x76, 0x8d, 0x38, 0x25, 0x36, 0x7, 0x18, 0xe9, 0x43, 0xcb, 0x6f, 0xb7, 0xc, 0x7e, 0xe, 0x62, 0x82, 0x47, 0x52, 0xe0, 0x35, 0x18, 0x2e, 0x4e, 0xf3, 0xea, 0xc0, 0xf7, 0x64, 0xa9, 0x9a, 0x8d, 0x9, 0xeb, 0x3, 0x9, 0x35, 0x1a, 0x57, 0xac, 0x8b, 0x56, 0x54, 0x56, 0x26, 0x5d, 0x3a, 0x76, 0x90, 0x6e, 0x5d, 0x76, 0xae, 0x72, 0x4d, 0x3, 0xfe, 0xd1, 0xc5, 0xe0, 0x34, 0x44, 0x7, 0x2d, 0xf5, 0xa0, 0x98, 0x13, 0x5d, 0x12, 0x8d, 0x75, 0x1d, 0xeb, 0x1e, 0x27, 0x4e, 0x26, 0xe6, 0x63, 0xe2, 0x24, 0x67, 0x61, 0xed, 0xa3, 0x81, 0x2, 0x8a, 0xdc, 0x67, 0x65, 0x68, 0xb1, 0xf5, 0x12, 0x27, 0x99, 0xe5, 0x79, 0x5a, 0x8c, 0x41, 0x2, 0x8d, 0x8a, 0xdd, 0x2e, 0x4b, 0x94, 0xd2, 0xb4, 0xd7, 0x47, 0x59, 0x37, 0xc0, 0x8d, 0xb9, 0xa1, 0xc4, 0x9e, 0x6b, 0xa7, 0x9b, 0x44, 0x34, 0x81, 0x6c, 0xb3, 0x5b, 0x3c, 0xae, 0xa1, 0xa7, 0xe7, 0x51, 0xe2, 0xb8, 0x45, 0x14, 0x8a, 0x7c, 0x4, 0x9a, 0x46, 0xbc, 0x4b, 0x65, 0x10, 0x47, 0x79, 0x1d, 0x28, 0x4e, 0xa2, 0xd3, 0xe4, 0xea, 0xf6, 0x4a, 0x71, 0xba, 0xf5, 0x20, 0x4b, 0xf9, 0x1c, 0x9, 0x2e, 0x89, 0x4, 0xbb, 0xf1, 0x32, 0xd7, 0x18, 0xea, 0x51, 0xf5, 0xc, 0xc0, 0x0, 0xa1, 0x64, 0x17, 0x3d, 0x20, 0x28, 0x8f, 0x74, 0x6d, 0x5b, 0x37, 0xa, 0x74, 0xa0, 0x3f, 0x57, 0xf4, 0xf4, 0x0, 0x85, 0x22, 0x9, 0xb8, 0xc, 0x51, 0x7a, 0x70, 0x1, 0x2d, 0x3e, 0xb4, 0x31, 0x7b, 0xb3, 0x1a, 0x6b, 0xf, 0xd9, 0xa0, 0xf7, 0x52, 0x82, 0x6a, 0xab, 0xb7, 0xc0, 0x42, 0xb4, 0x3b, 0xe8, 0xe3, 0x52, 0xb2, 0x53, 0x6c, 0x7c, 0x5e, 0xb0, 0xe8, 0x6c, 0x8d, 0x76, 0x9f, 0x14, 0x27, 0xc5, 0x5a, 0x89, 0x4e, 0xa1, 0xb0, 0x3, 0x59, 0xc9, 0xcf, 0x8a, 0xe3, 0x19, 0x41, 0x6c, 0xef, 0x25, 0x49, 0x5f, 0x5f, 0x7a, 0x36, 0x3f, 0x13, 0x44, 0xa3, 0xf4, 0x19, 0x96, 0xb1, 0x1d, 0xf5, 0x11, 0x29, 0xd9, 0x29, 0x1c, 0xa0, 0xd5, 0xd2, 0x9f, 0x2d, 0xe3, 0x68, 0x97, 0x74, 0x8e, 0x4e, 0x8f, 0x42, 0x91, 0x11, 0x2a, 0xf9, 0xce, 0xa0, 0x67, 0x27, 0xea, 0x36, 0xdf, 0x4b, 0xf3, 0x59, 0x5c, 0x7f, 0x24, 0x0, 0xb, 0xcf, 0x46, 0x76, 0x28, 0xa8, 0xd7, 0xee, 0x55, 0x4a, 0x76, 0x79, 0xf, 0xb8, 0x39, 0x2e, 0x97, 0xaa, 0xc5, 0xd4, 0x88, 0x4f, 0xe0, 0x88, 0x14, 0x4d, 0x59, 0x56, 0x28, 0x6a, 0xe, 0x10, 0x1d, 0x8e, 0xba, 0xba, 0x50, 0x9c, 0x5e, 0x9b, 0x36, 0xc0, 0x12, 0xbc, 0x55, 0xfc, 0x8d, 0xe1, 0x81, 0xec, 0xdc, 0xae, 0x54, 0x94, 0x4f, 0x68, 0x56, 0xb1, 0x92, 0x5d, 0xde, 0x3, 0xf1, 0x86, 0x43, 0x2c, 0xe3, 0xc8, 0x38, 0x9b, 0xa6, 0xd3, 0xa3, 0x50, 0xd4, 0x1a, 0x70, 0xfd, 0xdf, 0x69, 0xe4, 0xf, 0xe2, 0x7d, 0x5a, 0x38, 0x14, 0xca, 0xa3, 0x7c, 0xfc, 0x9d, 0x68, 0xaa, 0xbe, 0xd8, 0x35, 0x6, 0x45, 0xb6, 0x87, 0x3e, 0xe, 0x25, 0xbb, 0x7c, 0xc7, 0xf9, 0x96, 0x31, 0xf4, 0xb4, 0x7c, 0x5c, 0xa7, 0x46, 0xa1, 0xf0, 0x5, 0x48, 0x64, 0x39, 0xc6, 0x83, 0xf0, 0xa, 0xa8, 0x58, 0x36, 0xf5, 0xe9, 0x77, 0x2d, 0xf2, 0x50, 0x52, 0x7b, 0xeb, 0x63, 0x50, 0xb2, 0xcb, 0x67, 0xa0, 0x29, 0xf2, 0xfe, 0x96, 0x71, 0x94, 0x19, 0xfc, 0xa4, 0xd3, 0xa3, 0x50, 0xf8, 0x6, 0x10, 0x10, 0x8a, 0xcc, 0x97, 0x58, 0xae, 0xa1, 0xdd, 0xd8, 0xa9, 0x3e, 0xfd, 0x1e, 0xd4, 0x6, 0x7e, 0x65, 0x19, 0x47, 0x1f, 0x50, 0x8d, 0xdb, 0x29, 0xd9, 0xe5, 0x2d, 0x10, 0xb8, 0x6e, 0xe5, 0x1a, 0x43, 0xbf, 0xc8, 0x67, 0x74, 0x6a, 0x14, 0xa, 0xdf, 0x81, 0x42, 0xf4, 0x81, 0x62, 0x8f, 0x83, 0x23, 0x11, 0xcc, 0xaf, 0xc3, 0x82, 0xd1, 0x8c, 0xdd, 0x7d, 0x80, 0x34, 0x7a, 0x7e, 0xee, 0xae, 0x8f, 0x40, 0xc9, 0x2e, 0x5f, 0xb1, 0x97, 0x65, 0xc, 0xd9, 0x64, 0xda, 0x5a, 0x48, 0xa1, 0x8, 0x6, 0xa8, 0xc5, 0x7b, 0xc9, 0x32, 0x8e, 0xee, 0x2a, 0x7, 0xf8, 0xf4, 0x3b, 0xf0, 0xe, 0xbb, 0x3d, 0x33, 0x4d, 0x7c, 0xfc, 0xf9, 0xa, 0x25, 0xbb, 0x48, 0x1, 0x2e, 0x8d, 0xee, 0x96, 0xf1, 0x51, 0x3a, 0x35, 0xa, 0x45, 0xa0, 0xb8, 0x4d, 0x9c, 0xbe, 0xab, 0xa9, 0x40, 0x12, 0x49, 0x2f, 0x9f, 0x7e, 0xfe, 0xcf, 0x46, 0x46, 0x5b, 0xc6, 0x4f, 0x11, 0x3d, 0xc2, 0x4a, 0xc9, 0x2e, 0xf, 0x81, 0xde, 0x8f, 0xee, 0x33, 0xea, 0x90, 0xb2, 0xfc, 0xb5, 0x4e, 0x8d, 0x42, 0x11, 0x28, 0x26, 0x8a, 0xd3, 0x3a, 0xcc, 0x8d, 0x1e, 0x3e, 0x92, 0xd1, 0x2b, 0x96, 0xb1, 0xae, 0xe2, 0x1c, 0x5a, 0xac, 0x50, 0xb2, 0xcb, 0x3b, 0xcb, 0xce, 0x1d, 0xaf, 0x43, 0x5f, 0xbd, 0xe5, 0x3a, 0x35, 0xa, 0x45, 0xe0, 0x78, 0xce, 0x32, 0x86, 0x98, 0xda, 0x66, 0x3e, 0xfd, 0xfc, 0x31, 0x46, 0xe6, 0x5a, 0xc6, 0x4f, 0xd7, 0xa9, 0x57, 0xb2, 0xcb, 0x37, 0xd8, 0x5a, 0x83, 0x81, 0xec, 0x56, 0xeb, 0xd4, 0x28, 0x14, 0x81, 0x3, 0x19, 0x93, 0xa5, 0xae, 0x31, 0x9c, 0x50, 0xb0, 0xb9, 0x4f, 0x3f, 0x1f, 0x59, 0x9f, 0xef, 0x58, 0xc6, 0x51, 0xe8, 0xae, 0xed, 0xc3, 0x94, 0xec, 0xf2, 0xa, 0x8d, 0x3d, 0x9e, 0x5b, 0x42, 0xa7, 0x46, 0xa1, 0x8, 0x1c, 0xe8, 0x50, 0xb4, 0xd0, 0x32, 0xde, 0xce, 0xc7, 0xdf, 0x81, 0xa3, 0xb8, 0xdc, 0x59, 0x99, 0x68, 0xf8, 0xfe, 0x27, 0x9d, 0x7e, 0x25, 0xbb, 0x7c, 0x42, 0xa5, 0x4e, 0x81, 0x42, 0x91, 0x33, 0xac, 0x12, 0x7b, 0xd6, 0xb3, 0x9f, 0xca, 0x26, 0x4a, 0x10, 0x6c, 0xb1, 0x41, 0x9c, 0x60, 0xd2, 0x5c, 0x1f, 0x81, 0x92, 0x5d, 0xbe, 0xa0, 0xc4, 0x83, 0x0, 0x95, 0x4, 0x37, 0xce, 0x45, 0x85, 0x4e, 0x83, 0x22, 0x20, 0xfc, 0x62, 0x64, 0x45, 0xc0, 0xbf, 0x3, 0xeb, 0xf7, 0x7e, 0xcb, 0x78, 0x1b, 0x12, 0x9e, 0x42, 0xc9, 0x2e, 0x2f, 0xb0, 0xc0, 0x32, 0x6, 0xd7, 0x66, 0x91, 0x4e, 0xcd, 0xaf, 0x40, 0x81, 0x6f, 0x2b, 0x9d, 0x6, 0x45, 0xc0, 0xa, 0x55, 0xd0, 0x78, 0xd5, 0xc8, 0x78, 0xcb, 0xf8, 0x59, 0x46, 0xb6, 0xd7, 0x47, 0xa0, 0x64, 0x97, 0xf, 0x40, 0x17, 0x7, 0x77, 0xad, 0xcf, 0xd6, 0xe2, 0x4, 0xc9, 0x15, 0x4e, 0x9a, 0x76, 0x43, 0x9d, 0x6, 0x45, 0x40, 0x48, 0x64, 0x69, 0x9f, 0xc4, 0x3b, 0x7e, 0xa7, 0x65, 0x7c, 0x3b, 0x23, 0x97, 0xea, 0x63, 0x50, 0xb2, 0xcb, 0x7, 0xac, 0x33, 0xf2, 0x8d, 0x6b, 0xac, 0x91, 0x5a, 0x33, 0xbf, 0xe1, 0x8f, 0x3a, 0x5, 0x8a, 0x0, 0x81, 0xc6, 0xcf, 0xb6, 0xb8, 0x59, 0x10, 0xae, 0x73, 0x94, 0x39, 0x4c, 0xb4, 0x8c, 0xe3, 0x10, 0x59, 0x3d, 0xd, 0xa1, 0x2a, 0xba, 0x89, 0x93, 0xad, 0x9e, 0x11, 0x8f, 0x29, 0xd9, 0x45, 0x83, 0xec, 0xe6, 0x58, 0xc6, 0xf7, 0xd4, 0xa9, 0x91, 0xd6, 0xe2, 0x1c, 0xae, 0xa9, 0x50, 0x4, 0x85, 0x26, 0x16, 0xb2, 0x83, 0x5b, 0x73, 0x56, 0x0, 0xbf, 0x6b, 0x83, 0x91, 0x9b, 0xa5, 0xea, 0x39, 0x77, 0x28, 0x60, 0xbf, 0x4b, 0xfc, 0xab, 0xed, 0x8b, 0xcb, 0xbb, 0xff, 0x82, 0x38, 0xe7, 0x2, 0xbe, 0x6b, 0xe4, 0xa, 0xb1, 0x77, 0x9a, 0x52, 0xb2, 0x8b, 0x10, 0xb0, 0xf0, 0xa7, 0x5a, 0xc6, 0xf, 0xd5, 0xa9, 0xf9, 0x35, 0x9e, 0xd1, 0x46, 0xa7, 0x41, 0x11, 0xf0, 0xa6, 0xda, 0xde, 0x35, 0x86, 0xe3, 0x79, 0xd6, 0x4, 0xf4, 0xfb, 0x5e, 0x34, 0x32, 0xd2, 0x32, 0x8e, 0xd3, 0x10, 0x2e, 0xd6, 0xc7, 0xf1, 0x1b, 0xfe, 0x4f, 0x9c, 0xce, 0x52, 0x68, 0xba, 0x81, 0x63, 0x91, 0x6e, 0xe2, 0xbc, 0x15, 0x2b, 0xd9, 0x45, 0x1b, 0x68, 0xd, 0x56, 0x61, 0xb1, 0xec, 0xb6, 0xcd, 0xe3, 0x39, 0xc1, 0xa9, 0xce, 0x7f, 0xd5, 0xa5, 0xa1, 0x8, 0x18, 0xbb, 0x18, 0xa9, 0xef, 0x1a, 0x9b, 0x2c, 0x4e, 0x96, 0x66, 0x10, 0x40, 0x1, 0xfb, 0x95, 0x62, 0x6f, 0x1a, 0x31, 0xd4, 0xc8, 0xef, 0xf4, 0x91, 0xfc, 0x5a, 0x83, 0x78, 0xa5, 0x65, 0x1c, 0x46, 0x41, 0x99, 0x92, 0x5d, 0xb4, 0x31, 0x9e, 0xda, 0x64, 0x2a, 0xd0, 0xc1, 0xe1, 0xc8, 0x3c, 0x9d, 0xf, 0x24, 0xd, 0xdc, 0x2a, 0x5a, 0x83, 0xa4, 0x8, 0x1e, 0xfd, 0x3c, 0x94, 0xcf, 0xd2, 0x0, 0x7f, 0xe7, 0x24, 0x23, 0x77, 0x58, 0xc6, 0xe1, 0xce, 0xbc, 0xcf, 0xc8, 0x36, 0x79, 0xfe, 0x4c, 0xae, 0x15, 0x7b, 0xce, 0xc2, 0xbf, 0x94, 0xec, 0xa2, 0xf, 0x94, 0x1f, 0x7c, 0x62, 0xd9, 0xf0, 0x8f, 0x95, 0xfc, 0xec, 0x8e, 0x7e, 0x89, 0x91, 0xfe, 0xba, 0x2c, 0x14, 0x1, 0x63, 0x4b, 0xb, 0xd9, 0xc1, 0xc3, 0xf2, 0x65, 0x16, 0x7e, 0xf7, 0x70, 0xb1, 0x17, 0x9a, 0xc3, 0xa3, 0x71, 0xaf, 0xe4, 0x6f, 0xe9, 0x11, 0x14, 0xfc, 0xb3, 0x2c, 0xe3, 0x98, 0xab, 0x37, 0xd3, 0xfd, 0x47, 0x25, 0xbb, 0xe8, 0xc0, 0xd6, 0x1d, 0x1d, 0x2e, 0x8d, 0xdf, 0xe7, 0xd9, 0x3c, 0xc0, 0x3f, 0xff, 0x37, 0x5d, 0xe, 0x59, 0xc1, 0xbe, 0x92, 0xdf, 0x49, 0x11, 0xc7, 0x19, 0x69, 0xe6, 0x1a, 0x83, 0x87, 0x65, 0x42, 0x16, 0x7e, 0x37, 0xdc, 0x98, 0x70, 0xd3, 0xff, 0xe4, 0xb1, 0xe1, 0xff, 0x23, 0xf, 0x9f, 0x7, 0x2c, 0x5a, 0x94, 0x67, 0xd4, 0xb7, 0x5c, 0xbb, 0x41, 0xaa, 0x71, 0x2d, 0x2b, 0xd9, 0x45, 0x7, 0xaf, 0x4b, 0xd5, 0xee, 0xe8, 0xb0, 0xea, 0x2e, 0xcf, 0xa3, 0x39, 0xc0, 0x29, 0xce, 0xf, 0x8b, 0x66, 0xa5, 0x65, 0x3, 0x97, 0x51, 0x5b, 0x7e, 0x44, 0x9c, 0x8c, 0xc4, 0x7c, 0x3, 0x12, 0x1d, 0x4e, 0xb7, 0xec, 0x91, 0xef, 0x19, 0x99, 0x9f, 0xa5, 0x7b, 0x80, 0xbb, 0x14, 0x59, 0x86, 0x36, 0xd7, 0x1c, 0x6a, 0xef, 0x86, 0xe6, 0xd1, 0xf3, 0x68, 0xcc, 0xb5, 0xd8, 0xce, 0x72, 0xed, 0x49, 0x23, 0x6f, 0x57, 0xf7, 0x3, 0x94, 0xec, 0xa2, 0x83, 0xd5, 0xdc, 0xe8, 0xdd, 0x38, 0x58, 0x1c, 0x77, 0x66, 0x3e, 0x68, 0x75, 0xcf, 0x48, 0xd5, 0xcc, 0x38, 0x85, 0xbf, 0x80, 0x7b, 0xc, 0x87, 0x96, 0x22, 0x26, 0x5a, 0xc0, 0xb5, 0x95, 0x8f, 0x84, 0x77, 0x2a, 0x2d, 0x5b, 0x37, 0x1e, 0xcf, 0xf2, 0x7d, 0x3c, 0x64, 0xe4, 0x41, 0x8f, 0x6b, 0xd7, 0x1b, 0x39, 0x39, 0x4f, 0x9e, 0x7, 0xd6, 0xa4, 0x2d, 0x3, 0x1d, 0x8a, 0x47, 0x46, 0x9e, 0x1e, 0x25, 0xbb, 0x68, 0xe1, 0x9, 0xa9, 0xda, 0x81, 0x1d, 0xb1, 0xbb, 0x1b, 0xc5, 0x49, 0x91, 0x8e, 0x2b, 0x90, 0x62, 0x8c, 0x9a, 0x9a, 0x6e, 0x96, 0x6b, 0x3f, 0x48, 0xf0, 0xbd, 0xb, 0xf3, 0x9, 0x37, 0x4a, 0xd5, 0x8e, 0x1d, 0xc7, 0x8b, 0xe3, 0x46, 0xdf, 0x2a, 0x4f, 0xe6, 0xa0, 0x2d, 0x89, 0xc4, 0x8d, 0xb7, 0xc4, 0x1e, 0x47, 0xb, 0x12, 0x88, 0x11, 0x5e, 0xe8, 0x61, 0xb9, 0x14, 0x90, 0x8, 0x4f, 0x8d, 0xf1, 0xb3, 0xc0, 0x77, 0x1c, 0x21, 0xf6, 0x1e, 0xa1, 0x65, 0xf4, 0x40, 0x2c, 0x50, 0xb2, 0x8b, 0x1f, 0xbe, 0x13, 0x27, 0x1b, 0xcb, 0x8d, 0x8e, 0x62, 0xcf, 0xde, 0x8a, 0x3, 0xd0, 0x39, 0x2, 0x45, 0xa3, 0xfb, 0x59, 0xae, 0x7d, 0x6b, 0x64, 0xa0, 0xe8, 0x41, 0xb6, 0x7e, 0xc2, 0x6b, 0x4f, 0x38, 0x88, 0xcf, 0x61, 0xf7, 0x98, 0x7f, 0x7f, 0xc4, 0x83, 0x86, 0x5b, 0x88, 0x1d, 0xd9, 0x97, 0x77, 0x49, 0x9a, 0x6c, 0xbf, 0x0, 0x81, 0x66, 0xf0, 0x3, 0xc4, 0x39, 0x5b, 0xcf, 0x8d, 0x62, 0xee, 0x9, 0x43, 0x62, 0xfa, 0x3c, 0x6e, 0x25, 0xd9, 0xdb, 0x30, 0x82, 0x4a, 0x70, 0x9d, 0x16, 0xb6, 0x22, 0xbc, 0xf8, 0xa7, 0xd8, 0x5b, 0xa, 0x9d, 0x98, 0x66, 0x51, 0x44, 0x15, 0x28, 0x1c, 0x7d, 0x5d, 0xec, 0x7, 0xd8, 0x7e, 0x6f, 0xe4, 0x4, 0x23, 0xa3, 0x44, 0x7b, 0x63, 0xfa, 0x9, 0x58, 0x34, 0x5e, 0x59, 0x6d, 0xbb, 0xf2, 0x79, 0x1c, 0x17, 0xe3, 0xef, 0x7f, 0x9d, 0x91, 0x63, 0x2c, 0xe3, 0x2f, 0x89, 0xbd, 0xd8, 0x3b, 0x5b, 0xc0, 0x7a, 0x87, 0xcb, 0x72, 0xb6, 0xe5, 0x1a, 0xd6, 0x3f, 0x3a, 0xaf, 0xdc, 0x46, 0x4b, 0x28, 0xe, 0x80, 0xdb, 0xfc, 0x1, 0x23, 0x17, 0x79, 0x5c, 0x47, 0x48, 0xa3, 0x46, 0x31, 0x4b, 0x25, 0xbb, 0xe8, 0x61, 0x2d, 0x1f, 0xf2, 0x7a, 0xcb, 0x35, 0x64, 0x68, 0x9d, 0x12, 0x93, 0x85, 0xe, 0x52, 0xc7, 0xa1, 0x96, 0x2d, 0x2c, 0xd7, 0x91, 0x11, 0x7, 0xd7, 0xda, 0xe7, 0xa2, 0xc9, 0x2a, 0x7e, 0xe3, 0x27, 0x6e, 0xaa, 0x5e, 0x1b, 0x3b, 0x3a, 0xd6, 0xfc, 0x87, 0xa4, 0x10, 0xb7, 0xb2, 0x97, 0xab, 0x8d, 0x5c, 0xe5, 0x31, 0x27, 0x57, 0x87, 0xe0, 0xfe, 0xa6, 0x19, 0x39, 0xda, 0xc8, 0x4c, 0x8f, 0xeb, 0x97, 0xf2, 0xd9, 0xb4, 0x8c, 0xf8, 0x73, 0xc0, 0x29, 0xf, 0xe8, 0x13, 0x7a, 0xa6, 0xc7, 0x75, 0x9c, 0x10, 0x1, 0xb7, 0x66, 0xb9, 0x92, 0x5d, 0xfc, 0x81, 0xd8, 0xc1, 0x70, 0xcb, 0x38, 0x32, 0x96, 0x90, 0xc4, 0x12, 0x65, 0x1f, 0x3e, 0xdc, 0x65, 0x63, 0x8c, 0x5c, 0x20, 0xf6, 0x14, 0xe3, 0xe9, 0xd4, 0xbc, 0x3f, 0xd2, 0x65, 0x10, 0x28, 0xe1, 0xc1, 0x53, 0xe0, 0xe5, 0x22, 0x42, 0x12, 0xcb, 0x35, 0x46, 0xde, 0x31, 0xb2, 0x47, 0xc, 0xbe, 0x2f, 0xd6, 0xd9, 0x30, 0x23, 0x7f, 0xb7, 0x5c, 0xc3, 0x86, 0x3a, 0xd8, 0xc3, 0xa2, 0xca, 0x5, 0xa6, 0xd2, 0xb2, 0x9e, 0x93, 0xc6, 0x1b, 0x32, 0x8a, 0xef, 0x51, 0x14, 0x81, 0xfa, 0xd9, 0xb1, 0xe2, 0xdd, 0xf3, 0xf6, 0x25, 0x2a, 0x63, 0xab, 0x6a, 0xfa, 0x83, 0x95, 0xec, 0xa2, 0xb, 0xd4, 0x95, 0xd8, 0xdc, 0x4d, 0x85, 0xe2, 0x14, 0x9d, 0xe, 0x8c, 0xd8, 0xf7, 0xd9, 0x41, 0x9c, 0x60, 0x3b, 0x2c, 0xa, 0xaf, 0x86, 0xae, 0x78, 0x89, 0xf, 0x31, 0xf2, 0x85, 0x3e, 0xfe, 0xac, 0x59, 0x78, 0xf7, 0xa6, 0xf9, 0xcc, 0x81, 0xe2, 0x24, 0x4e, 0xa0, 0x75, 0x53, 0x71, 0x44, 0xbf, 0x27, 0xa, 0xc7, 0x91, 0x6d, 0x7a, 0xad, 0xc7, 0xf5, 0x11, 0xf4, 0x30, 0x84, 0x9, 0x53, 0x8c, 0x1c, 0x2e, 0x55, 0x1b, 0x4d, 0x24, 0xd1, 0x8d, 0xd6, 0xcf, 0xb5, 0x12, 0x9d, 0x2c, 0x5a, 0x3c, 0x87, 0xdb, 0xa9, 0x60, 0xb5, 0x4f, 0x63, 0xd1, 0xd, 0x10, 0x7b, 0x2b, 0x35, 0x25, 0xbb, 0x18, 0x63, 0x2d, 0x9, 0xcd, 0x16, 0xb4, 0x6e, 0x44, 0xe2, 0x80, 0xf5, 0xd7, 0x34, 0xe4, 0xdf, 0x63, 0x3b, 0x5a, 0x9, 0xe3, 0xf9, 0x7d, 0xa, 0x3d, 0x3e, 0x87, 0x56, 0x40, 0x28, 0xa6, 0xfd, 0x4e, 0x1f, 0x7d, 0xd6, 0x80, 0x2e, 0xfc, 0xe7, 0x89, 0x13, 0xb, 0x5e, 0xef, 0xf1, 0x99, 0x96, 0x54, 0xbc, 0x90, 0xa5, 0xf8, 0x87, 0x88, 0x7d, 0x3f, 0x28, 0x4e, 0xef, 0xa6, 0xf1, 0x84, 0x20, 0xfb, 0x39, 0xac, 0xb5, 0x6c, 0x38, 0x75, 0x1, 0x2e, 0xcd, 0xb7, 0x3c, 0xae, 0x37, 0xa6, 0xb5, 0x8a, 0xba, 0xc0, 0x7e, 0x21, 0x7e, 0x6, 0xc8, 0x26, 0x47, 0x48, 0x2, 0x9e, 0x1a, 0x74, 0x46, 0xf2, 0x8a, 0xbf, 0xa3, 0x4, 0x3, 0x21, 0x9a, 0x9f, 0x6b, 0xfb, 0x8b, 0x94, 0xec, 0xa2, 0x8d, 0x25, 0xd4, 0xbe, 0x67, 0x78, 0x5c, 0x47, 0x70, 0x17, 0xae, 0xa6, 0x7d, 0x43, 0x78, 0xef, 0xd0, 0xde, 0x90, 0x41, 0x3a, 0x5a, 0x9c, 0xf8, 0xcf, 0x16, 0x69, 0xbe, 0xe3, 0x69, 0x46, 0x6, 0xa5, 0xd9, 0x70, 0x13, 0x96, 0xb1, 0x72, 0x5d, 0x1e, 0xbe, 0x1, 0x5d, 0x2b, 0xe0, 0xd6, 0xfc, 0x36, 0xcd, 0x67, 0x60, 0x8d, 0xc3, 0xc5, 0xf4, 0x9a, 0x84, 0xdf, 0x85, 0xb6, 0x13, 0xad, 0x39, 0x24, 0xdb, 0x78, 0xb9, 0x61, 0x51, 0xa8, 0x7c, 0x8e, 0xe4, 0x26, 0xfb, 0x32, 0x53, 0x2c, 0x13, 0xa7, 0xe, 0xf2, 0x9f, 0x69, 0x3e, 0xb3, 0x37, 0xbf, 0xe7, 0xb3, 0x46, 0x76, 0xb, 0x19, 0xc9, 0xf5, 0x25, 0x59, 0x23, 0x3e, 0xd7, 0xc9, 0xe3, 0x73, 0xc8, 0x44, 0x45, 0x1c, 0xf5, 0xec, 0xba, 0x10, 0x9d, 0x92, 0x5d, 0x3c, 0x80, 0xa0, 0x35, 0x7c, 0xf8, 0x5e, 0x41, 0xeb, 0x7d, 0x49, 0x28, 0xc8, 0xd4, 0xca, 0xf5, 0x29, 0x9, 0x2d, 0x68, 0x9d, 0xe1, 0xc5, 0x43, 0xb3, 0xdb, 0x8b, 0xb9, 0xf1, 0x78, 0xe1, 0x79, 0x71, 0x5a, 0xa2, 0x3d, 0x91, 0xe6, 0x33, 0x78, 0x19, 0x56, 0x5a, 0xc6, 0x5b, 0xeb, 0xd2, 0xf0, 0x15, 0x20, 0x32, 0x34, 0x30, 0x78, 0x35, 0xcd, 0x67, 0x10, 0xfb, 0x3a, 0x42, 0x1c, 0x77, 0x33, 0x48, 0xef, 0xa8, 0x34, 0x4a, 0x4c, 0x2e, 0xb0, 0x97, 0x38, 0x47, 0xc1, 0x7c, 0x26, 0x4e, 0x77, 0x14, 0x2f, 0x2f, 0xc2, 0xfd, 0x46, 0xce, 0x10, 0xe7, 0x2c, 0xc9, 0xb0, 0x63, 0x1d, 0x95, 0x5a, 0x28, 0xbd, 0xdf, 0xa7, 0x79, 0x2e, 0x27, 0xf0, 0x7b, 0xa3, 0x4c, 0x21, 0x97, 0x2d, 0x6, 0x1b, 0x73, 0xf, 0x0, 0x1, 0x23, 0x64, 0x71, 0x68, 0x35, 0xd6, 0x2b, 0xbc, 0x5, 0xa8, 0xfd, 0xac, 0xf3, 0x61, 0xb9, 0x4a, 0x76, 0xf1, 0x0, 0x82, 0xd6, 0x8, 0xec, 0xbe, 0xeb, 0x71, 0x1d, 0x6e, 0xcd, 0x4b, 0xe9, 0xd2, 0x80, 0x6b, 0x63, 0xfb, 0x2c, 0xde, 0x5b, 0x23, 0x2e, 0xe8, 0xbb, 0x49, 0xba, 0xaf, 0xf0, 0xc5, 0x6b, 0x52, 0xcd, 0x22, 0x3f, 0x85, 0x9f, 0x9b, 0x5b, 0xcd, 0xcf, 0x5f, 0xe5, 0x61, 0xd9, 0x9e, 0x2f, 0xd1, 0x8d, 0x23, 0x85, 0x15, 0x48, 0xd2, 0x80, 0xcb, 0x9, 0x6e, 0xcd, 0xe5, 0xd5, 0xec, 0x2b, 0x47, 0x90, 0x20, 0xf1, 0xcc, 0x6f, 0xa1, 0xe6, 0x5e, 0x98, 0x83, 0x7b, 0xde, 0x92, 0x44, 0xf0, 0x3c, 0xd7, 0xff, 0x15, 0x1c, 0xb3, 0x1, 0xb5, 0x74, 0x70, 0xa5, 0x9d, 0x2b, 0xc1, 0x9e, 0x6a, 0x10, 0x4, 0x10, 0x57, 0xec, 0x43, 0x12, 0xf1, 0x42, 0x43, 0x5a, 0x48, 0xa3, 0x68, 0x51, 0xd, 0xc8, 0x92, 0x32, 0x2, 0x2b, 0x6e, 0x47, 0x71, 0xc2, 0x15, 0xc9, 0x3d, 0xa0, 0x7f, 0x35, 0xfc, 0xf3, 0x0, 0x3d, 0x4, 0xef, 0xf8, 0x75, 0x13, 0x5, 0xfa, 0xfe, 0xc6, 0x6, 0x20, 0x85, 0xa3, 0xb9, 0xb1, 0x9c, 0xe7, 0xf1, 0x19, 0x58, 0x51, 0x8, 0x5a, 0x23, 0xd3, 0xf1, 0x65, 0x71, 0x82, 0xc1, 0xe8, 0xbf, 0xb7, 0xc8, 0xc7, 0xfb, 0xd8, 0x8e, 0xb, 0xbb, 0x2b, 0x35, 0xc8, 0x9e, 0x46, 0xb6, 0x16, 0xbb, 0xab, 0xd1, 0xd, 0xb8, 0xc9, 0x1e, 0x13, 0xc7, 0xbd, 0x99, 0xa9, 0xcb, 0xa2, 0x82, 0x1a, 0xab, 0xbb, 0xf6, 0xb, 0x1a, 0xe1, 0x4, 0x6e, 0x2, 0xb7, 0xd1, 0x2, 0x54, 0xd4, 0x1d, 0x98, 0xc7, 0x3b, 0xb9, 0x9, 0x5d, 0x4d, 0x85, 0x24, 0xdd, 0x3e, 0xb2, 0x27, 0x5, 0x4d, 0x8d, 0x91, 0x58, 0x81, 0xa4, 0xaa, 0x4f, 0xc5, 0xc9, 0xaa, 0x5d, 0xe8, 0x87, 0xc6, 0xee, 0x2, 0x36, 0xef, 0x9d, 0x8d, 0x74, 0xe6, 0xe6, 0x7f, 0xa8, 0xd8, 0xcb, 0x57, 0xdc, 0x98, 0x44, 0x12, 0x1f, 0x13, 0x71, 0x2f, 0xcf, 0x91, 0x24, 0xeb, 0x2b, 0xf8, 0x2e, 0xda, 0x80, 0x92, 0x91, 0x7e, 0x94, 0xdb, 0x68, 0x85, 0xc3, 0xca, 0x9a, 0x4c, 0xc5, 0xb1, 0xd2, 0x87, 0x7b, 0x69, 0xce, 0xe7, 0x0, 0xcf, 0x12, 0x62, 0xa3, 0x7, 0x66, 0xa8, 0x7c, 0x8e, 0x13, 0x27, 0x6, 0xfc, 0x8a, 0xdf, 0x93, 0xa3, 0x64, 0x17, 0x2f, 0xfc, 0x42, 0x8b, 0x6, 0xc9, 0x1e, 0x28, 0x32, 0x6d, 0x99, 0x66, 0x43, 0x18, 0x40, 0x99, 0xc5, 0x17, 0x7d, 0xa, 0x17, 0xda, 0x54, 0x6a, 0xed, 0xa5, 0xdc, 0x88, 0x52, 0x49, 0xaa, 0x92, 0x6b, 0xa6, 0x3e, 0xb5, 0x63, 0xf4, 0xab, 0x44, 0xc1, 0x37, 0x32, 0x29, 0xd1, 0xc5, 0x5, 0x35, 0x58, 0xed, 0x49, 0x76, 0x35, 0xf1, 0x1a, 0xfc, 0x40, 0x4d, 0xe, 0x44, 0x37, 0xa7, 0x16, 0xdf, 0x1b, 0xc4, 0x3d, 0x54, 0xaa, 0x9e, 0x6f, 0x7, 0x6b, 0x62, 0x8, 0x7f, 0xee, 0x22, 0x5d, 0x1e, 0xbe, 0x2, 0x64, 0x75, 0x12, 0xe7, 0x16, 0x73, 0xdf, 0xbb, 0x9a, 0xcf, 0x63, 0xa3, 0xeb, 0x2e, 0x1b, 0x33, 0x6d, 0xe7, 0x92, 0xec, 0xe6, 0x71, 0xed, 0xc1, 0xd, 0xbf, 0x98, 0xe3, 0xa5, 0x24, 0xd5, 0x4a, 0xcb, 0xfa, 0x4b, 0x70, 0xd, 0xd6, 0xe3, 0xfa, 0x83, 0x22, 0xd5, 0x81, 0x8a, 0x5c, 0x27, 0x7a, 0x2d, 0x76, 0xa6, 0x47, 0x21, 0x13, 0xac, 0xa6, 0x72, 0xf5, 0x6f, 0xae, 0xc3, 0xa8, 0xa3, 0x92, 0xdf, 0x5, 0xd6, 0xdb, 0x20, 0x12, 0x5f, 0x3a, 0x8b, 0x1a, 0x8a, 0xc0, 0xe9, 0x94, 0xef, 0xf9, 0x5c, 0xe7, 0x90, 0xf8, 0xb0, 0x17, 0x7c, 0x93, 0x66, 0x3f, 0x10, 0xfe, 0xec, 0x62, 0xce, 0x3f, 0xf6, 0x82, 0x3d, 0x38, 0xff, 0x3b, 0x72, 0x4f, 0xc8, 0xd4, 0xbb, 0xf2, 0x1d, 0x15, 0xf5, 0x27, 0xa5, 0x16, 0x65, 0x5, 0x4a, 0x76, 0xf9, 0x8b, 0x47, 0xe8, 0xb2, 0xb9, 0x96, 0x1b, 0x52, 0xba, 0xc5, 0xde, 0x91, 0x72, 0x1c, 0x5f, 0x94, 0xd, 0xdc, 0x68, 0xd0, 0x6f, 0x6e, 0xbd, 0x6b, 0x71, 0x57, 0x90, 0x40, 0x5b, 0x91, 0xf0, 0xa, 0xea, 0xb8, 0x86, 0xd0, 0x9, 0xe6, 0x7f, 0xe2, 0xd4, 0x6, 0xd6, 0x85, 0x8c, 0xf0, 0x42, 0xc2, 0x4d, 0x3a, 0xcc, 0x72, 0x6d, 0xad, 0xd8, 0xeb, 0xf5, 0x14, 0xfe, 0x0, 0x9b, 0xea, 0x68, 0x5a, 0xd2, 0x83, 0xa8, 0xc1, 0x67, 0xe2, 0xae, 0xdc, 0x81, 0x72, 0x60, 0xca, 0x58, 0x69, 0x8a, 0x5, 0x3e, 0x87, 0x7f, 0xb7, 0x79, 0x4, 0x10, 0x7b, 0x6e, 0x9a, 0xb2, 0xfe, 0x6a, 0xf3, 0x7c, 0x57, 0x71, 0xed, 0xdd, 0x29, 0xf6, 0x8e, 0x44, 0x51, 0xc7, 0x6c, 0x7a, 0x70, 0x1e, 0x15, 0x27, 0x36, 0x7e, 0x94, 0x54, 0x9f, 0x99, 0xdd, 0x9a, 0xd2, 0x3b, 0xe5, 0x7d, 0xc7, 0x33, 0x28, 0xe7, 0x7e, 0xb0, 0xc6, 0xf2, 0x3c, 0xda, 0x51, 0xb1, 0x68, 0x50, 0xcb, 0xbd, 0x60, 0x3a, 0x9f, 0x3, 0xde, 0xdf, 0xa5, 0x41, 0x4e, 0x88, 0xd7, 0x42, 0xd1, 0x58, 0x5e, 0xf4, 0x31, 0x9f, 0xda, 0x1a, 0x88, 0xef, 0x12, 0x6e, 0x46, 0xd5, 0x3d, 0xd7, 0x4, 0x35, 0x31, 0x48, 0xd7, 0x80, 0xee, 0xb, 0x2f, 0xc, 0xe2, 0xa, 0x88, 0xa1, 0x8c, 0x35, 0xf2, 0xa3, 0x4f, 0x3f, 0x17, 0x41, 0xec, 0x5d, 0xa5, 0xaa, 0x3b, 0x33, 0x91, 0xa5, 0xf9, 0xae, 0x9f, 0xe1, 0x58, 0x1c, 0x81, 0x4d, 0x11, 0x6e, 0x27, 0x24, 0xaf, 0x1c, 0x60, 0xe4, 0xcf, 0xe2, 0xc4, 0xf6, 0x6a, 0x7a, 0x92, 0x7c, 0x61, 0xa, 0x51, 0x76, 0xb, 0xe8, 0x5e, 0x57, 0x90, 0x0, 0x9e, 0xa1, 0x7, 0x24, 0xee, 0x40, 0x98, 0x2, 0xa5, 0x15, 0x7b, 0xf3, 0xb9, 0x9c, 0x26, 0xde, 0x31, 0x4b, 0x1b, 0xf, 0x24, 0x2d, 0xb3, 0xce, 0x3e, 0xde, 0x13, 0xc8, 0xf3, 0xb, 0x7a, 0x5, 0x5e, 0x15, 0xef, 0xc4, 0x1a, 0xdf, 0xc9, 0x6e, 0x83, 0x54, 0x6d, 0xfb, 0x53, 0x2a, 0x8a, 0xb8, 0xe0, 0x43, 0xa, 0xb4, 0xb5, 0xf3, 0xb8, 0x19, 0x65, 0xbb, 0x7b, 0x3d, 0x5c, 0x53, 0x53, 0x48, 0x72, 0x6f, 0x4a, 0x30, 0x2e, 0x45, 0xac, 0xd9, 0x93, 0xa9, 0x29, 0x9e, 0x23, 0x1b, 0x5d, 0xb8, 0xd, 0x24, 0x3b, 0xf1, 0x3a, 0x5b, 0x46, 0xe8, 0xea, 0x3c, 0x5b, 0x6b, 0xf0, 0xc, 0x7c, 0x40, 0x41, 0x3c, 0xf, 0x9d, 0x6e, 0x8e, 0xa4, 0xe2, 0xd4, 0x36, 0x87, 0xf7, 0x5, 0x8b, 0xe1, 0x4b, 0xae, 0x3d, 0xa4, 0xb9, 0xe7, 0x63, 0xe3, 0xf0, 0x71, 0x94, 0x9b, 0xf9, 0x9e, 0x20, 0x81, 0xa8, 0xbb, 0x64, 0xb7, 0xe, 0x17, 0x7b, 0xc0, 0xc7, 0x46, 0x9e, 0x16, 0xa7, 0x20, 0x3e, 0xab, 0x65, 0x1d, 0x5, 0x34, 0x71, 0xfb, 0xa4, 0x30, 0x78, 0x5, 0x6f, 0x46, 0x11, 0x2f, 0x8c, 0xa1, 0x40, 0x43, 0xeb, 0xcb, 0x4d, 0x68, 0x1f, 0xf1, 0xbf, 0xc3, 0x42, 0x19, 0x37, 0xf9, 0x9, 0xb4, 0xdc, 0xf0, 0xe7, 0x34, 0xc9, 0xce, 0x81, 0x97, 0x50, 0xdc, 0xae, 0xe1, 0x86, 0x96, 0x4c, 0x8c, 0x81, 0x3b, 0x67, 0x59, 0x16, 0x7e, 0xf7, 0x53, 0xd4, 0x84, 0x1b, 0xf3, 0xdf, 0x20, 0xd8, 0x17, 0xf3, 0x78, 0xbd, 0xc1, 0x62, 0x7f, 0x90, 0x82, 0xf8, 0x4d, 0x17, 0x71, 0x12, 0x96, 0xb0, 0xd7, 0x20, 0xbe, 0xd3, 0x28, 0x40, 0xab, 0x7b, 0x3d, 0x15, 0x2a, 0x28, 0x79, 0xa3, 0x53, 0xd6, 0xa0, 0xc2, 0x79, 0x17, 0x86, 0x53, 0x60, 0xed, 0xe1, 0x34, 0x11, 0x94, 0x94, 0xa0, 0xc4, 0x67, 0x73, 0xf1, 0xd7, 0xab, 0xb7, 0x8e, 0x96, 0x25, 0xba, 0xec, 0x7c, 0xca, 0xbf, 0x2f, 0xcb, 0xd5, 0x17, 0x4f, 0x9c, 0x34, 0x68, 0x88, 0x3e, 0xfe, 0xfc, 0x44, 0x3d, 0x5a, 0x3f, 0xfb, 0x8b, 0x73, 0x8c, 0xe, 0x12, 0x4b, 0x5a, 0xd3, 0xc5, 0xd1, 0x9c, 0x1b, 0x11, 0x12, 0x59, 0x8a, 0x52, 0x88, 0xe4, 0x27, 0x8e, 0x63, 0x33, 0x41, 0xcc, 0x63, 0xd, 0xc7, 0x20, 0xf3, 0x68, 0xc1, 0x21, 0xb0, 0x3d, 0x93, 0xd7, 0x2a, 0x74, 0x9a, 0x15, 0x2e, 0x14, 0x72, 0x7d, 0x81, 0xfc, 0x76, 0x11, 0x27, 0x99, 0xa1, 0xd, 0xd7, 0x5d, 0x33, 0x71, 0xd2, 0xe3, 0x37, 0xe7, 0xda, 0x81, 0xe2, 0x90, 0xda, 0xe8, 0xbb, 0x84, 0x6b, 0x2d, 0xf9, 0xf7, 0x1f, 0xb9, 0xce, 0xf0, 0xe7, 0xf, 0x5c, 0x7f, 0xd3, 0x69, 0xc5, 0x2d, 0xe3, 0x9a, 0x55, 0x64, 0x6, 0xcc, 0x39, 0x92, 0x4b, 0xe0, 0x3e, 0xee, 0x44, 0x45, 0xb1, 0x15, 0xc7, 0x93, 0xe5, 0x9, 0x5b, 0xc8, 0xc6, 0xe, 0x27, 0x6b, 0x52, 0x3c, 0x17, 0x2b, 0xb9, 0x1f, 0x2c, 0xa3, 0x15, 0x8d, 0x78, 0x2b, 0xe2, 0xa0, 0x70, 0x13, 0xc3, 0x6d, 0x9c, 0xb5, 0x6, 0xf, 0x6b, 0xd6, 0xae, 0x93, 0xa1, 0x83, 0x6, 0xca, 0x3e, 0x7b, 0x54, 0x8d, 0xc2, 0xfc, 0xbf, 0x0, 0x3, 0x0, 0x73, 0x78, 0xf7, 0xf2, 0x7e, 0x74, 0x3a, 0x48, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)parse_logo2x_png { + return [NSData dataWithBytes:parse_logo2x_png length:sizeof(parse_logo2x_png)]; + } + + const unsigned char parse_logo3x_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x2, 0x99, 0x0, 0x0, 0x0, 0xcc, 0x8, 0x6, 0x0, 0x0, 0x0, 0x38, 0x73, 0xb8, 0x3f, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x0, 0x0, 0x3, 0x23, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2d, 0x63, 0x30, 0x31, 0x34, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x31, 0x34, 0x38, 0x31, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2f, 0x30, 0x33, 0x2f, 0x31, 0x33, 0x2d, 0x31, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x31, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x4d, 0x61, 0x63, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x29, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x41, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x42, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x3e, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x38, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x34, 0x31, 0x30, 0x31, 0x39, 0x34, 0x30, 0x39, 0x34, 0x30, 0x30, 0x43, 0x31, 0x31, 0x45, 0x34, 0x41, 0x33, 0x35, 0x31, 0x39, 0x31, 0x30, 0x33, 0x32, 0x36, 0x42, 0x31, 0x39, 0x32, 0x31, 0x42, 0x22, 0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0x66, 0x5f, 0x10, 0x71, 0x0, 0x0, 0x22, 0xe8, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x9d, 0x4d, 0x72, 0x1b, 0xbd, 0x11, 0x86, 0x3b, 0x29, 0xef, 0xa3, 0xef, 0x4, 0xdf, 0xf8, 0x4, 0xa6, 0x4f, 0xa0, 0xd1, 0x9, 0x4c, 0xaf, 0x93, 0x2a, 0x53, 0xfb, 0xa4, 0x2c, 0xad, 0x92, 0x9d, 0xc8, 0x5d, 0x76, 0x92, 0x2b, 0x7, 0x10, 0x5d, 0x95, 0xbd, 0xe8, 0x13, 0x68, 0x74, 0x2, 0x53, 0x27, 0xf0, 0xf8, 0x4, 0x56, 0x6e, 0x10, 0x21, 0x2, 0x22, 0x98, 0xa6, 0x8, 0x60, 0x7e, 0x31, 0xc0, 0xf3, 0x54, 0x4d, 0x49, 0x32, 0x29, 0x99, 0x83, 0x1, 0xba, 0xdf, 0x6e, 0x0, 0x8d, 0x3f, 0xfc, 0xf9, 0xaf, 0x7f, 0x3f, 0x12, 0x91, 0x8f, 0x2, 0x7d, 0x70, 0xa7, 0xbf, 0x6e, 0x1f, 0xaf, 0x7, 0x9a, 0x3, 0x0, 0x0, 0x0, 0x52, 0xe0, 0xdf, 0xff, 0xfa, 0xa7, 0xf3, 0x3d, 0xaf, 0x1e, 0xaf, 0xeb, 0xc7, 0x6b, 0x4e, 0x73, 0xd, 0x42, 0xa5, 0x5, 0xe7, 0xbd, 0xfe, 0xba, 0xa5, 0x49, 0x0, 0x0, 0x0, 0x20, 0x45, 0x94, 0xc8, 0x3c, 0xa2, 0x19, 0x6, 0xa3, 0xd4, 0x97, 0x41, 0x65, 0x37, 0x37, 0xf2, 0x94, 0xf1, 0xdc, 0x8, 0xd9, 0x4e, 0x0, 0x0, 0x0, 0x48, 0x84, 0x3f, 0xd2, 0x4, 0xa3, 0xa2, 0x4, 0xfe, 0x42, 0x9e, 0xb2, 0xc9, 0x3f, 0x1e, 0xaf, 0x1b, 0xfd, 0x33, 0x0, 0x0, 0x0, 0x0, 0x22, 0x13, 0x3a, 0x63, 0x6e, 0x9, 0xce, 0xcb, 0xc7, 0xab, 0xa0, 0x49, 0x0, 0x0, 0x0, 0x0, 0x91, 0x9, 0x5d, 0xa1, 0x32, 0x9c, 0x67, 0x8f, 0xd7, 0x37, 0x2d, 0x3a, 0x11, 0x9b, 0x0, 0x0, 0x0, 0x80, 0xc8, 0x84, 0x4e, 0x59, 0x20, 0x36, 0x1, 0x0, 0x0, 0x0, 0x91, 0x9, 0x7d, 0x8b, 0x4d, 0x35, 0x8d, 0xce, 0x66, 0x2d, 0x0, 0x0, 0x0, 0x88, 0x9a, 0x57, 0x8e, 0xd7, 0xab, 0xc7, 0xeb, 0x84, 0x66, 0xa, 0xa6, 0xd4, 0x5f, 0x8b, 0xc7, 0xeb, 0xf7, 0xc7, 0x6b, 0xa6, 0xbf, 0x9f, 0x75, 0xf0, 0xb7, 0xcf, 0xb4, 0xe0, 0x3c, 0x7f, 0xbc, 0xd6, 0x34, 0x35, 0x0, 0x0, 0x0, 0x4c, 0x51, 0x64, 0x42, 0x33, 0xaa, 0x17, 0xfe, 0xfd, 0x48, 0xb, 0xd0, 0x63, 0xfd, 0xb5, 0xa9, 0xe8, 0x54, 0x7f, 0x47, 0x4d, 0x9f, 0x7f, 0x78, 0xbc, 0x4e, 0x1f, 0xaf, 0x9a, 0x26, 0x7, 0x0, 0x0, 0x80, 0x98, 0x60, 0xba, 0x7c, 0x58, 0x4c, 0x5d, 0x4c, 0x95, 0x85, 0x7c, 0xfb, 0x78, 0xbd, 0xd6, 0xdf, 0x37, 0x2d, 0xca, 0xae, 0x84, 0xea, 0x57, 0xa1, 0x98, 0x3e, 0x0, 0x0, 0x0, 0x20, 0x32, 0xc1, 0xa2, 0x7e, 0xbc, 0xae, 0xb4, 0xe0, 0x54, 0xd7, 0xba, 0xc1, 0xdf, 0x50, 0x59, 0x4d, 0x55, 0x5f, 0xf3, 0x92, 0xe6, 0x4, 0x0, 0x0, 0x0, 0x44, 0x26, 0xec, 0xa2, 0xb2, 0x99, 0x6a, 0xea, 0x5b, 0x65, 0x37, 0x97, 0x12, 0x7e, 0xfa, 0x8f, 0x5a, 0xab, 0xf9, 0x55, 0xd8, 0x14, 0x4, 0x0, 0x0, 0x0, 0x88, 0x4c, 0xd8, 0x43, 0xfd, 0x78, 0xad, 0xb4, 0xd8, 0xbc, 0xa, 0xfc, 0x5d, 0xb5, 0xc6, 0xf3, 0x9b, 0x74, 0xb3, 0xc1, 0x8, 0x0, 0x0, 0x0, 0x0, 0x91, 0x99, 0x20, 0x2a, 0x93, 0x79, 0xae, 0xc5, 0x66, 0x15, 0xf0, 0x7b, 0x2a, 0x93, 0x79, 0x2b, 0x3f, 0x9f, 0x91, 0xe, 0x0, 0x0, 0x0, 0x80, 0xc8, 0x84, 0x9f, 0xa8, 0xe5, 0xa9, 0x8c, 0xd4, 0x7b, 0xf1, 0x9f, 0x42, 0x37, 0x42, 0x73, 0x41, 0xf3, 0x1, 0x0, 0x0, 0x0, 0x22, 0x13, 0xe, 0xa1, 0x76, 0xa5, 0xbf, 0xd6, 0x5f, 0x7d, 0xb9, 0x46, 0x68, 0x2, 0x0, 0x0, 0x0, 0x22, 0x13, 0x5c, 0xa8, 0x4c, 0xa6, 0xca, 0x68, 0x9e, 0x23, 0x34, 0x1, 0x0, 0x0, 0x0, 0x91, 0x9, 0x5d, 0x63, 0xca, 0x1e, 0xf9, 0x4e, 0x9f, 0x23, 0x34, 0x1, 0x0, 0x0, 0x0, 0x91, 0x9, 0x5e, 0xa8, 0x92, 0x47, 0xaf, 0xc5, 0xbf, 0x90, 0xbb, 0xaa, 0xa3, 0xc9, 0xae, 0x73, 0x0, 0x0, 0x0, 0x40, 0x64, 0x82, 0x13, 0x95, 0xc9, 0x54, 0x9b, 0x82, 0x7c, 0xd6, 0x69, 0x9a, 0xcd, 0x40, 0x5, 0xcd, 0x6, 0x0, 0x0, 0x0, 0x88, 0x4c, 0xf0, 0x11, 0x9a, 0x6a, 0x9d, 0xe6, 0xda, 0x53, 0x68, 0xde, 0x8, 0x5, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x91, 0x9, 0x9e, 0x9c, 0x7a, 0xa, 0x4d, 0x35, 0x65, 0x7e, 0x4d, 0x73, 0x1, 0x0, 0x0, 0x0, 0x22, 0x13, 0xba, 0x16, 0x9a, 0x73, 0x79, 0x3a, 0x86, 0x12, 0x0, 0x0, 0x0, 0x0, 0x91, 0x9, 0xde, 0x42, 0xb3, 0xf2, 0x78, 0x1f, 0x1b, 0x81, 0x0, 0x0, 0x0, 0x0, 0x91, 0x9, 0x41, 0xa8, 0x35, 0x9a, 0x3e, 0xbb, 0xce, 0xd5, 0xb4, 0x39, 0xeb, 0x33, 0x1, 0x0, 0x0, 0x0, 0x91, 0x9, 0x5e, 0x98, 0xcd, 0x40, 0xae, 0x3a, 0x9a, 0x2a, 0x93, 0x79, 0x41, 0x73, 0x1, 0x0, 0x0, 0x0, 0x22, 0x13, 0x7c, 0xa9, 0xb5, 0xd0, 0x74, 0x71, 0x26, 0x4c, 0x9b, 0x3, 0x0, 0x0, 0x0, 0x22, 0x13, 0x2, 0xa8, 0x1e, 0xaf, 0xa5, 0xc7, 0xfb, 0xd8, 0x6d, 0xe, 0x0, 0x0, 0x0, 0x88, 0x4c, 0x8, 0x62, 0x25, 0xee, 0x8d, 0x40, 0x2a, 0x93, 0xc9, 0x6e, 0x73, 0x0, 0x0, 0x0, 0x40, 0x64, 0x42, 0x10, 0x6a, 0xc7, 0xb9, 0x6b, 0x7d, 0xa6, 0x5a, 0x9b, 0xc9, 0x26, 0x20, 0x0, 0x0, 0x0, 0x40, 0x64, 0x82, 0x37, 0xb5, 0x3c, 0x65, 0x34, 0xf, 0xa1, 0x4, 0xe6, 0x47, 0x9a, 0xa, 0x0, 0x0, 0x0, 0x10, 0x99, 0x10, 0xc2, 0x95, 0xb8, 0xa7, 0xcd, 0x97, 0xc2, 0xd9, 0xe6, 0x0, 0x0, 0x0, 0x80, 0xc8, 0x84, 0x40, 0x4e, 0x3d, 0xde, 0x43, 0x49, 0x23, 0x0, 0x0, 0x0, 0x40, 0x64, 0x42, 0x10, 0xb5, 0x3c, 0x65, 0x34, 0xf, 0xb1, 0x10, 0xb2, 0x99, 0x0, 0x0, 0x0, 0x80, 0xc8, 0x84, 0x40, 0xd4, 0xda, 0x4c, 0xd7, 0x26, 0xa0, 0xf, 0x34, 0x13, 0x0, 0x0, 0x0, 0x20, 0x32, 0x21, 0x4, 0x25, 0x30, 0x5d, 0xd9, 0x4c, 0x55, 0xce, 0x88, 0x9d, 0xe6, 0x0, 0x0, 0x0, 0x80, 0xc8, 0x84, 0x20, 0x3e, 0xc9, 0xe1, 0x6c, 0xa6, 0x12, 0x98, 0x73, 0x9a, 0x9, 0x0, 0x0, 0x0, 0x10, 0x99, 0x10, 0x82, 0x4f, 0x36, 0x93, 0x72, 0x46, 0x0, 0x0, 0x0, 0x80, 0xc8, 0x84, 0x60, 0x3e, 0x39, 0x5e, 0x9f, 0x9, 0x67, 0x9a, 0x3, 0x0, 0x0, 0x0, 0x22, 0x13, 0x2, 0x51, 0xd9, 0xcc, 0xb5, 0xe3, 0x3d, 0x64, 0x33, 0x1, 0x0, 0x0, 0x0, 0x91, 0x9, 0xc1, 0xb8, 0xb2, 0x99, 0xac, 0xcb, 0x4, 0x0, 0x0, 0x0, 0x44, 0x26, 0x4, 0xb3, 0x95, 0xa7, 0xda, 0x99, 0x2f, 0xc1, 0x6, 0x20, 0x0, 0x0, 0x0, 0x40, 0x64, 0x42, 0x23, 0x5c, 0xd9, 0xcc, 0x63, 0x9a, 0x8, 0x0, 0x0, 0x0, 0x10, 0x99, 0x10, 0xca, 0xc6, 0xf1, 0x3a, 0x99, 0x4c, 0x0, 0x0, 0x0, 0x40, 0x64, 0x42, 0x30, 0xb5, 0x3c, 0x4d, 0x9b, 0xbf, 0x44, 0x21, 0x1c, 0x33, 0x9, 0x0, 0x0, 0x0, 0x88, 0x4c, 0x68, 0x0, 0xd9, 0x4c, 0x0, 0x0, 0x0, 0x40, 0x64, 0x42, 0xe7, 0x7c, 0x71, 0xbc, 0xce, 0xba, 0x4c, 0x0, 0x0, 0x0, 0x40, 0x64, 0x42, 0x30, 0x6a, 0xba, 0xfc, 0xd0, 0x31, 0x93, 0x14, 0x65, 0x7, 0x0, 0x0, 0x0, 0x44, 0x26, 0x34, 0x16, 0x9a, 0x2f, 0x51, 0x8, 0xeb, 0x32, 0x1, 0x0, 0x0, 0x0, 0x91, 0x9, 0xd, 0xa8, 0x1c, 0xaf, 0x23, 0x32, 0x1, 0x0, 0x0, 0xc0, 0x9b, 0x57, 0x34, 0x1, 0x68, 0xee, 0x1c, 0xaf, 0x1f, 0x7b, 0x8, 0x51, 0x78, 0x42, 0x15, 0xb1, 0xf7, 0x5d, 0x62, 0x50, 0xcb, 0xe1, 0x82, 0xf8, 0x0, 0xd0, 0x9e, 0xa2, 0x41, 0xa0, 0xec, 0x5a, 0x46, 0x4, 0xf4, 0x23, 0x6c, 0x3b, 0x22, 0x13, 0x2, 0xc, 0xaa, 0x6b, 0x70, 0xc1, 0xaf, 0x42, 0xf2, 0xd8, 0x32, 0x3c, 0x33, 0xfd, 0xef, 0x4d, 0xa9, 0xb4, 0x53, 0x53, 0xcf, 0xe2, 0x5e, 0xdc, 0xe5, 0xa5, 0x0, 0xe0, 0x89, 0x99, 0x1e, 0x83, 0x6f, 0xac, 0xf1, 0x18, 0x12, 0xec, 0xf9, 0xa, 0x4e, 0xf3, 0xf5, 0xce, 0xfa, 0x19, 0xd2, 0xed, 0x4b, 0xc6, 0xa6, 0x97, 0x2d, 0xff, 0x66, 0x65, 0x7d, 0xfd, 0xae, 0x6d, 0x7b, 0x85, 0xc8, 0x84, 0x9c, 0x70, 0x45, 0xec, 0xb9, 0x8b, 0xcc, 0x42, 0x1b, 0x9a, 0x63, 0xfd, 0xb5, 0x8f, 0xf6, 0x30, 0x86, 0x6c, 0xbe, 0xc7, 0x40, 0x55, 0xda, 0xb1, 0x55, 0x74, 0xd5, 0x5e, 0x32, 0x11, 0x29, 0x67, 0xad, 0x8c, 0xe0, 0x4a, 0x45, 0x10, 0x1d, 0x59, 0x63, 0x71, 0xd6, 0x81, 0x0, 0xf0, 0x15, 0x1e, 0xf2, 0xc2, 0xff, 0x55, 0x59, 0x81, 0x61, 0x25, 0x64, 0xaf, 0xa6, 0x86, 0x6d, 0xd7, 0xcb, 0x1e, 0xff, 0x8f, 0x7d, 0xfd, 0xc7, 0xf4, 0x9d, 0x3b, 0x2b, 0xc9, 0x80, 0xc8, 0x84, 0x64, 0xa9, 0xe, 0xc, 0xb2, 0x1c, 0x77, 0x98, 0x2b, 0xb1, 0xf7, 0xae, 0x47, 0x51, 0x19, 0x62, 0xa0, 0x4a, 0x2b, 0x18, 0x50, 0x75, 0x4d, 0xbf, 0x88, 0xbb, 0xbe, 0x29, 0xfc, 0xda, 0x87, 0x6f, 0x65, 0x7f, 0xb6, 0x59, 0x19, 0xfa, 0x93, 0x9, 0x19, 0x79, 0x3b, 0xdb, 0xb2, 0x9b, 0xb1, 0x7b, 0x69, 0xc, 0x9f, 0x3e, 0x5e, 0xeb, 0x9, 0xb, 0x1, 0x33, 0x16, 0x67, 0x11, 0x7e, 0x36, 0xbb, 0xcd, 0x6b, 0x6d, 0x4b, 0x19, 0xa3, 0xf1, 0x6, 0x5d, 0x73, 0x2d, 0x2c, 0xe7, 0x91, 0xf4, 0x9d, 0x33, 0xcb, 0xe, 0x19, 0xfb, 0x9e, 0x44, 0x50, 0x88, 0xc8, 0x4, 0x9b, 0x7, 0x47, 0xf6, 0x20, 0x27, 0x61, 0x39, 0x8f, 0xf4, 0x9e, 0xd5, 0x67, 0x5a, 0xe8, 0xcb, 0x8, 0xce, 0xcf, 0x42, 0x86, 0xd3, 0x87, 0x77, 0x7, 0x9e, 0xe9, 0x4c, 0x5f, 0x55, 0xa4, 0xcf, 0xbc, 0x94, 0xf6, 0x99, 0xbb, 0xf, 0x13, 0x13, 0x99, 0xea, 0x5e, 0x3f, 0x46, 0x3c, 0x16, 0xf, 0x89, 0x18, 0x33, 0x46, 0xc5, 0x12, 0xd, 0x6b, 0x86, 0xe0, 0xa8, 0x63, 0x68, 0xae, 0xfb, 0xd3, 0x2c, 0xf2, 0x3e, 0xaf, 0xae, 0xa5, 0xe, 0x56, 0x54, 0xdf, 0xf9, 0x24, 0x13, 0xce, 0x8e, 0xb3, 0xbb, 0x1c, 0x6c, 0xb6, 0x1e, 0x3, 0x35, 0x45, 0xd4, 0xa0, 0xbe, 0x7e, 0xbc, 0x7e, 0x3c, 0x5e, 0x37, 0xda, 0x39, 0x4c, 0xe1, 0x5e, 0x8d, 0xe0, 0x54, 0xd9, 0xb9, 0xaf, 0x96, 0x53, 0x83, 0x34, 0x9c, 0xe2, 0x42, 0xf7, 0x47, 0xd3, 0x2f, 0xcf, 0x64, 0x98, 0xa9, 0xe1, 0xb1, 0xef, 0x5b, 0xdd, 0xe7, 0x37, 0xab, 0x4f, 0x4f, 0xdd, 0xee, 0xcc, 0x2d, 0xfb, 0x72, 0x2d, 0xd4, 0x1d, 0x1e, 0x92, 0x72, 0xc2, 0x6d, 0x5f, 0x58, 0x63, 0xe1, 0x76, 0xaa, 0xf6, 0x1d, 0x91, 0x9, 0xa1, 0x62, 0x2c, 0x35, 0x27, 0xfe, 0x35, 0x11, 0x67, 0x66, 0x84, 0xf2, 0x37, 0xc4, 0xe6, 0xe4, 0x5, 0xc9, 0x8d, 0xe5, 0x14, 0x73, 0x39, 0xd2, 0xb5, 0xb0, 0xfa, 0xef, 0xa5, 0xa4, 0xb9, 0x6, 0xdc, 0xb6, 0x39, 0xb7, 0x8c, 0xd3, 0x5e, 0x49, 0xad, 0x9d, 0x6d, 0xb1, 0x7c, 0x31, 0x25, 0x5f, 0x85, 0xc8, 0x4, 0x9b, 0xff, 0x64, 0xe2, 0xcc, 0x2e, 0xb5, 0x33, 0x4b, 0x31, 0xab, 0x60, 0x3b, 0x6b, 0xce, 0x9c, 0x9f, 0x8e, 0xf8, 0xb8, 0xd0, 0xcf, 0xec, 0x26, 0xb3, 0xe7, 0x76, 0xb4, 0x13, 0x1c, 0xe5, 0xb2, 0x2c, 0xa7, 0x24, 0x28, 0xec, 0x4d, 0x5c, 0xa6, 0x6a, 0xdb, 0xcd, 0x78, 0x59, 0x5a, 0x41, 0x68, 0xf4, 0xc1, 0x18, 0x6b, 0x32, 0xc1, 0x26, 0xe5, 0x72, 0x1c, 0x66, 0x7d, 0xd7, 0xa2, 0xe7, 0xf6, 0x53, 0xeb, 0x24, 0x2b, 0x4b, 0xb4, 0xbf, 0xd4, 0xa6, 0xc7, 0x3b, 0xe, 0xa7, 0xcb, 0x72, 0x2b, 0x46, 0x6c, 0xde, 0xe8, 0xcf, 0xa2, 0x36, 0x7c, 0xd4, 0x74, 0xef, 0x28, 0x1d, 0x86, 0xea, 0x93, 0x67, 0x19, 0x89, 0x2b, 0x9b, 0x8b, 0x1e, 0xef, 0xdd, 0x8c, 0xc5, 0x7a, 0xa7, 0xef, 0xfb, 0xd4, 0x3, 0xb6, 0x6d, 0xc6, 0x91, 0xf4, 0x7b, 0xe2, 0x99, 0x9, 0xa, 0x55, 0x3f, 0x38, 0x17, 0xd6, 0x56, 0xb7, 0x11, 0x97, 0x17, 0x3d, 0x3e, 0xa7, 0x4a, 0x7e, 0x2e, 0x59, 0x75, 0xc8, 0xb6, 0xab, 0x3e, 0xf3, 0xc6, 0xfa, 0x7e, 0xd6, 0x83, 0x7d, 0x37, 0xf7, 0xac, 0xae, 0xab, 0xc7, 0x6b, 0x25, 0x91, 0x6e, 0x5a, 0x44, 0x64, 0x42, 0xe, 0x19, 0x83, 0xb, 0xe9, 0x7e, 0x2d, 0xdb, 0x56, 0x1b, 0x9e, 0x7b, 0xfd, 0x7d, 0xa8, 0x40, 0xb7, 0x9d, 0xc9, 0x6a, 0xc7, 0xb1, 0x15, 0xf2, 0x5c, 0x52, 0x63, 0xd6, 0xc1, 0xfd, 0x7f, 0xd3, 0xd1, 0xef, 0x8a, 0xee, 0x80, 0xb8, 0x8c, 0x64, 0x4c, 0x76, 0x95, 0x85, 0xa9, 0xad, 0xf1, 0x77, 0x27, 0xed, 0xb, 0x60, 0x57, 0xe, 0x41, 0x68, 0xc6, 0xe6, 0xcc, 0x1a, 0xab, 0x5d, 0x5, 0xc1, 0x6a, 0x6a, 0x77, 0xa3, 0xc5, 0x26, 0x41, 0xe1, 0x38, 0xf6, 0xdd, 0x24, 0x9, 0x4c, 0x7f, 0x6a, 0x5a, 0xda, 0x6c, 0x73, 0xe0, 0x39, 0x17, 0x5a, 0x84, 0x96, 0x1d, 0x7d, 0xee, 0xb3, 0x1d, 0xb1, 0x89, 0xc8, 0x4, 0x98, 0xa0, 0xf1, 0xa9, 0xb5, 0xe1, 0xe8, 0xbb, 0x9e, 0x99, 0x71, 0x98, 0x1b, 0x4b, 0x90, 0xd8, 0xe5, 0x36, 0x9a, 0x8a, 0x92, 0xa5, 0x6e, 0xb, 0xb2, 0x9a, 0xe3, 0xa2, 0x9c, 0xc1, 0xe5, 0x40, 0xe2, 0xb2, 0xda, 0xe9, 0x57, 0xf7, 0x11, 0xdc, 0xff, 0xa5, 0x3c, 0x97, 0x6b, 0x69, 0x23, 0x2, 0xbe, 0xc8, 0xf0, 0x35, 0x29, 0x8d, 0x80, 0xad, 0x76, 0x2, 0x86, 0xb2, 0xc3, 0xa0, 0x70, 0xae, 0xff, 0xce, 0x4a, 0x8b, 0x6, 0x78, 0x39, 0x50, 0xbb, 0x94, 0x6e, 0x66, 0xa6, 0x6c, 0xbb, 0xde, 0xf7, 0x6c, 0x9e, 0x6d, 0xdf, 0x57, 0x96, 0xaf, 0x32, 0xf6, 0x7d, 0xd6, 0xa2, 0x3d, 0x96, 0xba, 0x3d, 0x4e, 0x25, 0xa2, 0x8c, 0x38, 0x22, 0x13, 0x52, 0xa3, 0xd0, 0xe2, 0x72, 0xd1, 0x91, 0x41, 0xf8, 0xac, 0xd, 0xc2, 0x58, 0xc2, 0x4c, 0x39, 0xd5, 0xb5, 0xbe, 0x4e, 0xf5, 0x7d, 0x7d, 0x68, 0x28, 0x9e, 0xd5, 0xef, 0xa8, 0xc5, 0xf0, 0xef, 0x85, 0x69, 0xb9, 0xa1, 0x99, 0x69, 0xa7, 0x58, 0x76, 0xfc, 0x77, 0x6b, 0xcb, 0x71, 0xc5, 0x7c, 0x4a, 0x94, 0x1a, 0x97, 0x37, 0xd, 0x9d, 0x68, 0xcc, 0xb5, 0x61, 0xcd, 0x67, 0xdb, 0x58, 0xf7, 0xa9, 0x9e, 0xb1, 0x29, 0x83, 0xd6, 0x46, 0x40, 0x1d, 0xeb, 0x31, 0xcf, 0xd1, 0x96, 0xbf, 0xa, 0xf1, 0xeb, 0x96, 0x81, 0x9a, 0xdd, 0x9f, 0xc6, 0x6e, 0xdf, 0x4a, 0x5f, 0x2b, 0x79, 0xae, 0xe1, 0xf9, 0xa1, 0xe1, 0x58, 0x51, 0xbf, 0x6f, 0x32, 0xe2, 0x51, 0xf4, 0x1d, 0x36, 0xfe, 0x40, 0x6a, 0x91, 0x6d, 0xdb, 0x85, 0xf4, 0xb5, 0xce, 0x20, 0xbc, 0x7e, 0xbc, 0xde, 0xea, 0xef, 0xeb, 0x88, 0xee, 0x53, 0x89, 0xcd, 0x13, 0xfd, 0xf9, 0xd6, 0xd, 0xdb, 0x49, 0x19, 0xa1, 0x33, 0xba, 0xcc, 0x60, 0x5c, 0x68, 0x71, 0xdf, 0x85, 0xc0, 0x34, 0xfd, 0x53, 0x5, 0xa, 0xbf, 0xe9, 0x7e, 0xf0, 0x5e, 0x3b, 0xa8, 0x4d, 0xa4, 0x2, 0x73, 0xa6, 0xef, 0x3f, 0xd4, 0x69, 0x6e, 0xb5, 0xa3, 0x7c, 0xad, 0xbf, 0x4e, 0xa1, 0xb0, 0x79, 0xad, 0xc7, 0xa5, 0x79, 0x3e, 0xa7, 0x2d, 0x9e, 0xc9, 0xbc, 0x61, 0xbb, 0xa5, 0x6c, 0xe3, 0x6f, 0xf4, 0x75, 0xd4, 0xf0, 0xd9, 0x2c, 0xad, 0x31, 0xb3, 0x8e, 0x50, 0xc0, 0x9b, 0xf1, 0xfd, 0x56, 0x5f, 0x4d, 0x3f, 0xe3, 0x5c, 0x22, 0xd9, 0xfc, 0x89, 0xc8, 0x84, 0xdd, 0x28, 0x68, 0x8a, 0x98, 0x5a, 0x62, 0x6d, 0x84, 0xd3, 0x46, 0x1b, 0x1e, 0x65, 0x80, 0xa6, 0xb0, 0x26, 0xaa, 0xd6, 0xe, 0xec, 0x37, 0x69, 0x36, 0xad, 0x76, 0xa9, 0xb3, 0x1, 0xd0, 0xbf, 0xb8, 0x5a, 0xb6, 0xfc, 0x3b, 0x5b, 0xdd, 0x27, 0x5f, 0x5b, 0xfd, 0x33, 0x86, 0xc, 0x8c, 0xf, 0xb, 0x79, 0xf9, 0x94, 0xa5, 0x97, 0xa8, 0x74, 0x20, 0xd5, 0xc6, 0xc9, 0xc6, 0x80, 0x99, 0x85, 0xb0, 0x5, 0x43, 0x13, 0x9b, 0x7c, 0x2b, 0xe9, 0xd7, 0x47, 0x75, 0x51, 0xb6, 0x10, 0x4d, 0x95, 0x15, 0xac, 0xac, 0x64, 0x3a, 0xcb, 0x85, 0x76, 0x83, 0xac, 0xd0, 0xcf, 0x6d, 0x44, 0x79, 0xdb, 0xac, 0x2f, 0x22, 0x13, 0x3a, 0xe3, 0xf7, 0x9, 0x1a, 0x9e, 0xaf, 0xd2, 0x7c, 0x8d, 0xdb, 0xc3, 0x4e, 0x64, 0x3b, 0xc5, 0x23, 0xe0, 0x1e, 0x2c, 0x1, 0x12, 0xfa, 0xf9, 0x17, 0x8, 0xcd, 0xde, 0xc5, 0xd5, 0xac, 0xc5, 0x73, 0x8d, 0x39, 0xa3, 0x1e, 0xd2, 0xbf, 0x7c, 0xc7, 0xa6, 0x39, 0xda, 0xf3, 0x44, 0xd2, 0x5b, 0xce, 0x61, 0xb, 0x86, 0x50, 0xb1, 0x69, 0x66, 0x1f, 0x16, 0x99, 0x8e, 0xa5, 0x8b, 0x6, 0x81, 0x8a, 0x1d, 0xac, 0x9c, 0xc8, 0xb4, 0x4f, 0x5b, 0x32, 0xc1, 0x4a, 0x53, 0xb1, 0xd9, 0xd6, 0x16, 0x21, 0x32, 0x61, 0xd0, 0xce, 0x1e, 0x3, 0xa6, 0xb6, 0x5e, 0xd3, 0x81, 0x53, 0x5b, 0xc2, 0x6c, 0x4a, 0x91, 0xad, 0xeb, 0x9e, 0xde, 0xeb, 0x2b, 0xe4, 0x7e, 0x10, 0x9a, 0xdd, 0x73, 0xdd, 0x22, 0x7b, 0x60, 0xc4, 0xc8, 0x6f, 0x32, 0xed, 0x5d, 0xc6, 0x21, 0xfd, 0xca, 0x4, 0x4a, 0x6f, 0x25, 0xfd, 0xb5, 0xc2, 0x75, 0xb, 0xb1, 0x79, 0x9d, 0x99, 0xd0, 0x34, 0x99, 0xb8, 0x65, 0x83, 0x36, 0x4e, 0x35, 0x58, 0x31, 0x62, 0xf3, 0x3c, 0xd0, 0x1f, 0xcf, 0xc6, 0xa, 0x54, 0x10, 0x99, 0xb0, 0x3b, 0xa8, 0x5d, 0xe, 0x70, 0x6c, 0xcc, 0xd4, 0x78, 0x93, 0xc1, 0x62, 0x1b, 0xf8, 0x2b, 0x49, 0x73, 0x41, 0xfd, 0x46, 0x3b, 0xeb, 0x90, 0xac, 0x26, 0x42, 0xb3, 0xbb, 0xf1, 0xd3, 0xf4, 0x78, 0xcf, 0x4a, 0x7e, 0x9e, 0x22, 0x9e, 0x32, 0xb3, 0x80, 0xfe, 0xb4, 0x95, 0xe7, 0x4c, 0x6d, 0x4e, 0x18, 0x5b, 0x74, 0x12, 0x68, 0x57, 0x55, 0xbb, 0x96, 0x19, 0xb4, 0x4f, 0xa1, 0x45, 0x51, 0xc8, 0xf4, 0xb8, 0x3d, 0xab, 0x93, 0x7a, 0xb0, 0x72, 0x65, 0xf9, 0xb1, 0x10, 0xfb, 0xa4, 0xfa, 0xcf, 0x25, 0x22, 0x13, 0xc6, 0x74, 0xe, 0xb1, 0x1b, 0x9d, 0x26, 0x53, 0xe3, 0x6d, 0xb2, 0x7, 0x53, 0x44, 0x19, 0xdb, 0xf7, 0xfa, 0x9e, 0x43, 0x84, 0xe6, 0x25, 0x43, 0xa0, 0xd5, 0xd8, 0x69, 0xb2, 0x49, 0xa3, 0x96, 0xb4, 0xb2, 0x2e, 0x66, 0x9c, 0xfa, 0x3a, 0xca, 0xb7, 0x92, 0x77, 0x49, 0xad, 0x4a, 0xb7, 0xc1, 0x32, 0xe0, 0x77, 0x9a, 0xee, 0xd2, 0x4f, 0x79, 0x2c, 0x6d, 0x32, 0xc, 0x56, 0xec, 0x19, 0x80, 0x90, 0x40, 0xe5, 0x4c, 0x6, 0x5c, 0xa7, 0x89, 0xc8, 0x84, 0xdd, 0x48, 0xe7, 0x50, 0xc6, 0x61, 0x2c, 0xce, 0xa4, 0xd9, 0xee, 0x5c, 0xb3, 0xe6, 0x32, 0x85, 0xec, 0x50, 0x13, 0xd6, 0xfa, 0xde, 0x1f, 0x2, 0xda, 0x79, 0xc1, 0x30, 0x68, 0xe4, 0x14, 0x6f, 0x25, 0x6c, 0xe3, 0x5c, 0xaa, 0x59, 0x17, 0xdf, 0x9d, 0xbf, 0xa7, 0xfa, 0xfe, 0xe1, 0x89, 0x95, 0xe, 0x34, 0x7c, 0xc6, 0xea, 0x91, 0x8c, 0xbc, 0x99, 0xa3, 0x47, 0x4a, 0x9, 0x5b, 0x7f, 0x69, 0x2, 0xea, 0xd0, 0x65, 0x42, 0x29, 0xb1, 0x6d, 0x10, 0xa8, 0x2c, 0xa4, 0xd9, 0x3a, 0x57, 0x44, 0x26, 0xb4, 0x76, 0x96, 0x87, 0x6, 0xf3, 0x18, 0xa2, 0xb7, 0x69, 0xf6, 0x72, 0x2d, 0xcf, 0x6b, 0x2e, 0x73, 0xae, 0x33, 0xb7, 0xd, 0x8c, 0x74, 0x53, 0x3d, 0xf3, 0x37, 0x16, 0xa7, 0xa8, 0xd8, 0x48, 0xf8, 0x54, 0xd7, 0x14, 0xb8, 0xf4, 0xec, 0x3b, 0xa7, 0x99, 0x6, 0x7d, 0x2e, 0x2a, 0xdd, 0x2f, 0x7c, 0xc6, 0xaa, 0x6a, 0xe7, 0x8b, 0xc4, 0xee, 0x3f, 0x54, 0xf8, 0x54, 0x12, 0xbe, 0x34, 0x28, 0xf5, 0x40, 0x25, 0x64, 0x66, 0x60, 0x36, 0x84, 0xd0, 0x44, 0x64, 0x82, 0x8f, 0xc0, 0x14, 0x19, 0x27, 0x93, 0xb9, 0x90, 0xf0, 0xec, 0xa5, 0x31, 0x3c, 0x14, 0x31, 0x7e, 0x46, 0x19, 0x9d, 0x90, 0xb5, 0x5f, 0x4d, 0xeb, 0xd0, 0xe5, 0x46, 0xa8, 0x53, 0xb4, 0xb3, 0x2e, 0xa9, 0xf5, 0x4d, 0x35, 0x4e, 0x7d, 0x4a, 0x88, 0x21, 0x30, 0xdd, 0x7d, 0xc4, 0x77, 0xac, 0x9e, 0x49, 0x3a, 0xeb, 0x33, 0x17, 0x12, 0xb6, 0x2e, 0xfc, 0x4a, 0xb7, 0x53, 0x4d, 0x97, 0xd9, 0x9b, 0x54, 0xf0, 0x1d, 0x63, 0xbd, 0xb, 0x4d, 0x44, 0x26, 0x18, 0xa, 0xc7, 0xeb, 0xdf, 0x47, 0x32, 0xb8, 0x21, 0xef, 0x6d, 0xb2, 0x90, 0x1e, 0xe7, 0xb5, 0xbf, 0x2f, 0xb0, 0x11, 0xe8, 0x30, 0xf3, 0xc0, 0x36, 0x52, 0xc1, 0x4f, 0x93, 0x32, 0x53, 0x53, 0xc1, 0x67, 0x3d, 0x2f, 0x2, 0xb3, 0xfb, 0xb1, 0x9a, 0xc2, 0x38, 0xd, 0x11, 0x98, 0x26, 0x50, 0x63, 0xa9, 0x85, 0xdb, 0x17, 0xfa, 0xb6, 0x51, 0xaf, 0x42, 0x13, 0x91, 0x9, 0x86, 0x37, 0x1e, 0x11, 0xd2, 0xd0, 0xac, 0x3d, 0x9d, 0xf2, 0x5a, 0xf2, 0xd9, 0xd4, 0x33, 0x94, 0xf3, 0x9a, 0x4b, 0x4, 0xa7, 0x45, 0x44, 0x4a, 0xc8, 0xee, 0x69, 0xc5, 0x52, 0xfc, 0xd7, 0xdb, 0x4d, 0x91, 0x33, 0x71, 0xcf, 0x84, 0x5c, 0x31, 0x3e, 0x7b, 0x19, 0xab, 0x85, 0x4c, 0x7b, 0xd6, 0x21, 0x64, 0x2c, 0x99, 0x36, 0x61, 0x7a, 0xdc, 0xf, 0x73, 0x32, 0xd8, 0x83, 0xe7, 0x73, 0xe8, 0x45, 0x68, 0x22, 0x32, 0xc1, 0x50, 0x46, 0x28, 0x32, 0x45, 0xe, 0x17, 0x9f, 0xad, 0xb5, 0xd1, 0x61, 0x6a, 0x3c, 0x3c, 0xca, 0xf5, 0x69, 0xaf, 0x54, 0x37, 0x17, 0xb4, 0x75, 0x8a, 0xbe, 0xc6, 0xd8, 0x64, 0x5d, 0x56, 0x9, 0xb7, 0x87, 0x6a, 0x7, 0xd7, 0xda, 0x40, 0x73, 0x62, 0x11, 0x74, 0x3f, 0x56, 0x97, 0x13, 0xb6, 0x7d, 0x66, 0x2c, 0xf9, 0xb0, 0x15, 0xff, 0xf5, 0xaa, 0xf0, 0xcc, 0x26, 0x20, 0xc0, 0xed, 0x45, 0x68, 0x22, 0x32, 0xc1, 0x47, 0x64, 0xd6, 0x23, 0x1a, 0x32, 0xe3, 0xa8, 0xf7, 0x45, 0x69, 0x39, 0x14, 0x6f, 0xee, 0x83, 0xed, 0xb, 0x6d, 0xba, 0x4f, 0x40, 0x50, 0xd6, 0xe8, 0x99, 0x22, 0x50, 0x60, 0xe6, 0x90, 0x75, 0xf9, 0xe8, 0xd1, 0x1e, 0xa7, 0x74, 0x9d, 0x56, 0x63, 0xf5, 0x7c, 0x4f, 0xdf, 0x32, 0x75, 0x12, 0x57, 0x19, 0x8c, 0xa5, 0xad, 0xa4, 0x3d, 0x13, 0x30, 0x44, 0x1f, 0xf2, 0x5d, 0xbf, 0x1a, 0x22, 0xfc, 0x11, 0x99, 0x10, 0x14, 0x51, 0xba, 0x3a, 0x69, 0x2c, 0x86, 0xd6, 0x5e, 0x93, 0x83, 0xd1, 0x69, 0x8e, 0x12, 0xe7, 0x4b, 0x8f, 0xf7, 0x2d, 0x84, 0x73, 0x93, 0x8d, 0xe0, 0xf6, 0xdd, 0x10, 0x95, 0x53, 0xd6, 0x65, 0xe1, 0x78, 0x7d, 0x29, 0x64, 0x9f, 0xda, 0xb2, 0xd6, 0xe3, 0xb5, 0x96, 0xe7, 0x63, 0x70, 0xa7, 0x7c, 0x1a, 0x54, 0xe8, 0x58, 0x42, 0x60, 0x76, 0xe3, 0x43, 0x7d, 0xab, 0x8c, 0x84, 0x2e, 0x7, 0x3a, 0xc8, 0x2b, 0xda, 0x1e, 0x1e, 0x79, 0xe7, 0x78, 0xfd, 0x2e, 0x82, 0xcf, 0x78, 0x65, 0x19, 0x5c, 0xc, 0x4e, 0x37, 0xac, 0xb4, 0x80, 0x74, 0x89, 0xc8, 0xb, 0x21, 0x63, 0xec, 0x5b, 0xda, 0x29, 0x27, 0xa7, 0xa8, 0x4, 0x66, 0x71, 0xe0, 0x75, 0xd5, 0x6, 0x9f, 0x18, 0x66, 0x9d, 0x90, 0x52, 0x45, 0x82, 0x4b, 0xc6, 0xd2, 0x28, 0x98, 0xd9, 0x95, 0xaf, 0xe2, 0xde, 0xe8, 0xab, 0xc6, 0xf6, 0xbd, 0x74, 0x50, 0x66, 0x8d, 0x4c, 0x26, 0x28, 0x5c, 0x1b, 0x3c, 0x62, 0x11, 0x18, 0xa9, 0x1e, 0x5, 0x39, 0x26, 0x3e, 0xeb, 0x33, 0x7d, 0x84, 0x68, 0xca, 0x5c, 0x88, 0xdf, 0x26, 0xa8, 0xdc, 0x9c, 0xe2, 0x3b, 0xc6, 0xeb, 0xa0, 0x2, 0x21, 0x5, 0x7c, 0xf, 0x7c, 0x40, 0x60, 0xf6, 0xd7, 0x8f, 0x7c, 0x3, 0x96, 0xcb, 0x2e, 0xec, 0x3e, 0x22, 0x13, 0x8e, 0xc4, 0x5d, 0x84, 0x9d, 0xe9, 0xae, 0x74, 0xa9, 0xc5, 0x6f, 0x5d, 0xd7, 0x45, 0xa6, 0xed, 0xa3, 0x8c, 0xec, 0x12, 0xa7, 0xb8, 0xd7, 0x6e, 0xcc, 0x1d, 0x76, 0x83, 0x2c, 0x26, 0xec, 0x8e, 0x25, 0x9f, 0x35, 0xde, 0xf, 0x8, 0xcc, 0x5e, 0x9, 0xb1, 0x55, 0x6a, 0x59, 0x43, 0x81, 0xc8, 0x84, 0x36, 0xb8, 0x32, 0x34, 0x94, 0x8b, 0x48, 0x9f, 0x2b, 0x8f, 0x40, 0xa2, 0x94, 0xfc, 0xb2, 0x99, 0x66, 0xed, 0x18, 0x4e, 0x31, 0xdc, 0x6e, 0xac, 0x11, 0x9, 0xb0, 0x33, 0x96, 0xae, 0x19, 0x4b, 0x51, 0x9, 0x4d, 0xdf, 0xcd, 0x9f, 0x37, 0x6d, 0xfe, 0x23, 0x44, 0x26, 0x4c, 0x61, 0x3d, 0x26, 0xf4, 0x8f, 0x4f, 0x89, 0x99, 0x8f, 0x99, 0xb5, 0x89, 0xcf, 0xe6, 0x84, 0x5c, 0x9d, 0xa2, 0xcb, 0x6e, 0x7c, 0x66, 0x48, 0x81, 0x85, 0x12, 0x98, 0x85, 0xc7, 0xfb, 0x4e, 0x85, 0x99, 0xb3, 0xa1, 0xa8, 0xc4, 0xaf, 0xf2, 0x43, 0xab, 0x23, 0x4c, 0x11, 0x99, 0x79, 0xa3, 0x6, 0x3d, 0x99, 0x4c, 0x30, 0x6, 0xa7, 0x72, 0xbc, 0x67, 0x2e, 0x2d, 0xa7, 0x4e, 0x26, 0x84, 0xef, 0x91, 0x7d, 0xb9, 0x3a, 0xc5, 0x43, 0x6d, 0x53, 0x23, 0x14, 0x60, 0xc7, 0x6e, 0xf8, 0xac, 0x69, 0x5e, 0xe2, 0x6f, 0x6, 0x67, 0x2d, 0x7e, 0x87, 0x24, 0x2c, 0xa5, 0xe1, 0x4c, 0x16, 0x22, 0x93, 0xc1, 0xef, 0x12, 0x98, 0x4c, 0x5b, 0xe4, 0x83, 0xcf, 0xda, 0xcc, 0xf, 0x99, 0x4, 0x5f, 0x3e, 0x91, 0x7b, 0xae, 0x4e, 0x51, 0x65, 0x36, 0x8e, 0x8, 0x4c, 0xc1, 0x3, 0xdf, 0x69, 0xf2, 0x8d, 0xa4, 0x7d, 0x68, 0x41, 0xcc, 0x9c, 0x8b, 0xff, 0x11, 0xa6, 0xc1, 0x85, 0xda, 0x11, 0x99, 0x79, 0xe3, 0x9a, 0xfe, 0xfc, 0x42, 0x13, 0x65, 0x45, 0x25, 0xee, 0x6c, 0xe6, 0x22, 0x83, 0x76, 0xf0, 0x31, 0xa6, 0x39, 0x3b, 0xc5, 0xd2, 0xf1, 0x3a, 0x4b, 0x6c, 0xc0, 0x70, 0x29, 0x7e, 0x4b, 0x4e, 0x28, 0xd8, 0x3f, 0x1e, 0xbe, 0xa7, 0xc0, 0x15, 0xd2, 0x60, 0xc9, 0x14, 0x22, 0x33, 0x5f, 0x5c, 0x53, 0x9f, 0xaa, 0xc3, 0xad, 0x69, 0xa6, 0xec, 0x70, 0x9, 0xa7, 0x42, 0xd2, 0xde, 0x0, 0x34, 0xf7, 0xb8, 0xbf, 0xdc, 0x9d, 0xe2, 0xef, 0x1e, 0xc1, 0xa, 0x40, 0xe9, 0x19, 0x94, 0xa6, 0x54, 0x3, 0x74, 0xaa, 0x6c, 0x3d, 0x83, 0xe6, 0xa5, 0xf8, 0xd5, 0x38, 0x45, 0x64, 0x82, 0x33, 0x22, 0x61, 0xca, 0x2b, 0x4f, 0x94, 0x40, 0xa8, 0x1d, 0xef, 0x49, 0x75, 0xca, 0xdc, 0x77, 0x6a, 0x2f, 0x77, 0xa7, 0xe8, 0x2a, 0x79, 0x86, 0x60, 0x0, 0xf1, 0x1c, 0x4b, 0x57, 0x4, 0x25, 0xd1, 0x70, 0xe5, 0xe9, 0xf7, 0x83, 0x8e, 0x1a, 0x46, 0x64, 0xe6, 0x1b, 0x61, 0x96, 0x8e, 0xf7, 0x50, 0xe3, 0x2e, 0x5f, 0x3e, 0x79, 0xf4, 0x9f, 0x54, 0x3, 0x2f, 0xd7, 0xd4, 0x1e, 0x4e, 0xf1, 0xf0, 0xc, 0x8, 0x1b, 0x7e, 0x40, 0x71, 0x26, 0xee, 0x4d, 0x82, 0x2a, 0x98, 0x65, 0x1d, 0x66, 0x5c, 0xf8, 0x1e, 0xce, 0xb1, 0x40, 0x64, 0xc2, 0x21, 0x5c, 0x9b, 0x1a, 0x2a, 0x9c, 0x45, 0xd6, 0xb8, 0xa2, 0x59, 0xe5, 0x3c, 0x66, 0x9, 0xde, 0xb7, 0x4b, 0x3c, 0xe3, 0x14, 0x11, 0x99, 0xe0, 0xe6, 0x48, 0xfc, 0x36, 0xce, 0xf9, 0x8, 0x1a, 0x18, 0x96, 0x7, 0xf1, 0x3f, 0x9c, 0xc3, 0x6b, 0x13, 0x10, 0x22, 0x33, 0x3f, 0x4a, 0xf, 0x67, 0x4a, 0x8d, 0xbb, 0xbc, 0xa9, 0x3d, 0x84, 0xe6, 0xbb, 0xc, 0xdb, 0xe5, 0x1c, 0xa7, 0xe8, 0xb5, 0x89, 0x3, 0xf2, 0xc6, 0x67, 0x46, 0x40, 0xd9, 0x97, 0x8a, 0xa6, 0x8a, 0x12, 0x9f, 0xd9, 0x9a, 0x42, 0x3c, 0x37, 0x1, 0x21, 0x32, 0xf3, 0xc3, 0x15, 0x61, 0x2a, 0x81, 0xb1, 0xa6, 0x99, 0xb2, 0xc7, 0x55, 0x59, 0xa0, 0xcc, 0xac, 0x3d, 0x2a, 0x61, 0x9d, 0xb2, 0x62, 0x46, 0x13, 0x80, 0x23, 0x8, 0x39, 0xf3, 0x8, 0x44, 0xce, 0x69, 0xaa, 0xa8, 0xf1, 0xc9, 0x66, 0x9e, 0xfd, 0xe5, 0x6f, 0xff, 0x70, 0x66, 0x33, 0x11, 0x99, 0x79, 0xe1, 0xb3, 0x73, 0x96, 0xe9, 0x40, 0x10, 0x8f, 0x48, 0x36, 0x37, 0x91, 0x49, 0x89, 0x15, 0x3f, 0xbe, 0xd3, 0x4, 0x59, 0xe3, 0xbb, 0xae, 0xb9, 0xa6, 0xa9, 0xa2, 0xb7, 0xff, 0x6b, 0x8f, 0x80, 0xc2, 0x99, 0xcd, 0x44, 0x64, 0xe6, 0x15, 0x61, 0xba, 0x76, 0x85, 0xd5, 0x42, 0x16, 0x13, 0x9e, 0xfb, 0x82, 0xcf, 0x79, 0xe6, 0x39, 0xb0, 0xc6, 0x29, 0x6, 0xf5, 0x1b, 0xc8, 0xd7, 0xc7, 0xf8, 0x64, 0x31, 0xd9, 0x54, 0x3a, 0xd, 0x3a, 0xc9, 0x66, 0x22, 0x32, 0xf3, 0x8a, 0x30, 0x8b, 0xe, 0x3a, 0x15, 0xe4, 0x15, 0xcd, 0x1e, 0x22, 0x87, 0xa9, 0x53, 0xa6, 0xf6, 0x0, 0xfc, 0x7d, 0x8c, 0x4f, 0x16, 0x93, 0x75, 0xbb, 0xd3, 0x9, 0x18, 0x97, 0x1e, 0x81, 0xc5, 0x47, 0x44, 0x26, 0xcc, 0x3c, 0x3a, 0x8b, 0x12, 0x14, 0x6b, 0x9a, 0xa, 0x2c, 0x5c, 0x27, 0xb7, 0xbc, 0xc9, 0xa0, 0xd, 0x70, 0x8a, 0x61, 0x1c, 0xd3, 0x4, 0xd9, 0xb2, 0xf0, 0x8, 0xd8, 0xc8, 0x62, 0x4e, 0x8b, 0x4f, 0x1e, 0xf6, 0xef, 0x60, 0x36, 0x13, 0x91, 0x99, 0x3e, 0xbe, 0x5, 0xa6, 0xc9, 0x62, 0xc2, 0xbe, 0xc0, 0xc3, 0x15, 0xbc, 0xa4, 0xc, 0x4e, 0x11, 0xc0, 0x5f, 0x60, 0x16, 0x4, 0x6c, 0x49, 0xda, 0xc0, 0xb5, 0x87, 0xc6, 0x98, 0x23, 0x32, 0xf3, 0xe5, 0xc2, 0x43, 0xc, 0xac, 0x85, 0x72, 0x12, 0xb0, 0xdf, 0xc0, 0xd4, 0x19, 0x8b, 0xcc, 0xd, 0x4e, 0x11, 0xc0, 0xb, 0xd7, 0x29, 0x60, 0x4, 0x6c, 0xd3, 0xc5, 0xe7, 0xb9, 0x5d, 0x20, 0x32, 0xf3, 0x8d, 0x2e, 0x29, 0x27, 0x1, 0x6d, 0xa8, 0x1d, 0xaf, 0x17, 0x9, 0xdf, 0x3b, 0xd9, 0xfd, 0xfd, 0xf6, 0x42, 0x32, 0xe, 0x3c, 0x60, 0xbf, 0xd, 0x28, 0x1d, 0xef, 0x59, 0x13, 0xb0, 0x4d, 0xda, 0x7, 0xac, 0x5d, 0x7d, 0xe0, 0x2f, 0x7f, 0xfb, 0x47, 0x89, 0xc8, 0xcc, 0xb, 0x65, 0xec, 0x7d, 0xce, 0x18, 0xe5, 0xd4, 0x5, 0x38, 0x44, 0x95, 0xa9, 0xc8, 0xdc, 0x8, 0x3b, 0xa5, 0xf7, 0xb1, 0xcd, 0xb4, 0x3f, 0xc0, 0xcb, 0xf8, 0x14, 0xe5, 0x26, 0x8b, 0x39, 0x6d, 0x7c, 0x9e, 0xdf, 0x7, 0x44, 0x66, 0x3e, 0xa8, 0x35, 0x12, 0x37, 0xe2, 0x77, 0xea, 0x2, 0x5, 0xa6, 0xe1, 0x10, 0xae, 0xba, 0x87, 0xa9, 0x8a, 0xa, 0x9c, 0x62, 0xf3, 0xe0, 0x16, 0xf2, 0x62, 0x4e, 0xc0, 0x96, 0x45, 0x70, 0xe9, 0xa, 0x30, 0x17, 0xfb, 0x36, 0x0, 0x21, 0x32, 0xd3, 0x14, 0x98, 0xb7, 0x1e, 0xce, 0x5f, 0x65, 0x2f, 0x29, 0x30, 0xd, 0x2e, 0x5c, 0xce, 0xe1, 0xf7, 0x44, 0xef, 0xb9, 0xe2, 0xd1, 0xbf, 0x88, 0xab, 0x6d, 0x4a, 0x9a, 0x28, 0xab, 0xa0, 0xc2, 0xe5, 0x6b, 0x38, 0xa6, 0x38, 0x9f, 0xc0, 0x7b, 0x8e, 0xc8, 0xcc, 0x43, 0x60, 0xfa, 0x64, 0x13, 0xde, 0xb, 0xd3, 0xe4, 0xd0, 0x5e, 0x64, 0xe6, 0x6a, 0x4c, 0xe9, 0x13, 0x2f, 0xf3, 0x8e, 0x26, 0xca, 0x6, 0x9f, 0xd, 0x3f, 0xcc, 0x96, 0xa5, 0x81, 0xcf, 0x46, 0xc8, 0x77, 0x88, 0x4c, 0x4, 0xa6, 0x62, 0x29, 0x64, 0x6a, 0xa0, 0x1b, 0x41, 0x51, 0x26, 0x6a, 0x4c, 0xe1, 0x65, 0xee, 0x1d, 0xaf, 0xcf, 0x69, 0xa2, 0x6c, 0x70, 0x3d, 0xeb, 0x35, 0x4d, 0x94, 0xc, 0x3e, 0x1, 0xc3, 0x7c, 0x77, 0xca, 0x1c, 0x91, 0x99, 0x9f, 0xc0, 0x54, 0x83, 0x9e, 0x5d, 0xb3, 0x0, 0xfb, 0xa9, 0x84, 0xf5, 0x63, 0x3e, 0x6d, 0x74, 0x88, 0x2, 0xa1, 0x99, 0x5, 0x4c, 0x95, 0xe7, 0xc7, 0x97, 0xd0, 0xc0, 0x3, 0x91, 0x99, 0x97, 0xc0, 0x54, 0xb, 0x77, 0x29, 0x57, 0x4, 0xa1, 0x6c, 0x33, 0xba, 0x57, 0x9c, 0xa2, 0x5f, 0x7f, 0x70, 0x9, 0xf1, 0x8f, 0x34, 0x53, 0xf2, 0x94, 0x8e, 0xd7, 0xeb, 0xcc, 0x6c, 0x47, 0xe, 0x4, 0x4f, 0x99, 0x23, 0x32, 0xa7, 0x1f, 0x49, 0x7e, 0xb, 0x10, 0x98, 0x27, 0xc2, 0x3a, 0x4c, 0x8, 0x27, 0xa7, 0x3e, 0xc3, 0x54, 0x79, 0x37, 0xed, 0xa4, 0x4, 0xc8, 0x19, 0xcd, 0x94, 0x34, 0xef, 0x18, 0x4b, 0x8c, 0xfd, 0x3d, 0x90, 0xc9, 0x4c, 0x4, 0x65, 0xc0, 0xbf, 0x8a, 0xbb, 0x4c, 0x91, 0x11, 0x9, 0xd4, 0xc3, 0x4, 0x70, 0x7, 0x62, 0x8c, 0x11, 0x3f, 0x7c, 0x32, 0xbe, 0xea, 0x14, 0x90, 0x82, 0xa6, 0x4a, 0x96, 0xd2, 0xf1, 0xfa, 0x1d, 0x4d, 0x94, 0x24, 0xce, 0x29, 0x73, 0xbb, 0x30, 0x3b, 0x22, 0x73, 0x7a, 0x98, 0x1a, 0x98, 0x97, 0x9e, 0xef, 0x57, 0x4e, 0xf3, 0x44, 0x98, 0xb6, 0x80, 0x71, 0x1c, 0x4d, 0x6a, 0xc2, 0x9, 0x9e, 0x5, 0x79, 0xe5, 0x69, 0xab, 0x8e, 0x68, 0xae, 0x2c, 0xc7, 0x3d, 0x99, 0xcc, 0x34, 0xa9, 0x3c, 0xde, 0x73, 0x8c, 0xc8, 0x9c, 0x26, 0x2a, 0xd, 0xfd, 0x4d, 0xfc, 0x17, 0xd5, 0x23, 0x30, 0x1, 0xc2, 0x84, 0x13, 0xf8, 0xe3, 0xb3, 0x81, 0x50, 0x2d, 0xe5, 0xb9, 0x45, 0x68, 0x26, 0xc7, 0x31, 0x2, 0x33, 0x5b, 0x1e, 0x24, 0xa0, 0x56, 0x2e, 0x22, 0x73, 0x1a, 0x14, 0xda, 0x50, 0x87, 0x64, 0x5, 0x94, 0xc3, 0x7c, 0x8b, 0xe3, 0x4, 0x80, 0x9e, 0xa8, 0xc4, 0x2f, 0xab, 0x61, 0x84, 0x66, 0x41, 0x93, 0x25, 0x43, 0xe9, 0x78, 0x9d, 0xa9, 0xf2, 0xf4, 0xc7, 0x3e, 0x22, 0x33, 0x11, 0x71, 0x79, 0x2d, 0x4f, 0xd9, 0xcb, 0x32, 0xe0, 0xf7, 0xcc, 0x26, 0x9f, 0x9a, 0x26, 0x4, 0x80, 0x1e, 0xf1, 0x5d, 0xeb, 0xad, 0x84, 0xe6, 0x57, 0xe1, 0x34, 0xa0, 0x54, 0x70, 0x6d, 0x36, 0x25, 0xb9, 0x91, 0x36, 0xce, 0x20, 0xc2, 0xac, 0xcb, 0x44, 0x64, 0xc6, 0x2f, 0x2e, 0x17, 0x81, 0xbf, 0xbb, 0x96, 0xa7, 0xc, 0x26, 0x1b, 0x18, 0x0, 0xa0, 0x6f, 0x54, 0x20, 0xeb, 0x5b, 0x77, 0xd7, 0x94, 0x5b, 0xbb, 0x14, 0xa6, 0xcf, 0xa7, 0xee, 0x9f, 0x5c, 0xcf, 0xaf, 0xa2, 0x99, 0x92, 0xc6, 0xe7, 0xf9, 0xce, 0x10, 0x99, 0x71, 0x71, 0xa4, 0x5, 0xe5, 0x6d, 0x43, 0x71, 0x69, 0xb2, 0xa, 0x9c, 0x47, 0xe, 0x0, 0x43, 0x72, 0x25, 0x61, 0x27, 0xbb, 0x9c, 0x69, 0x1b, 0x47, 0x89, 0xa3, 0xe9, 0x8a, 0x4c, 0x4, 0x26, 0xb8, 0x9e, 0xf3, 0x1b, 0x44, 0x66, 0x1c, 0x83, 0x55, 0x89, 0x49, 0xb5, 0xd6, 0xf2, 0x87, 0x3c, 0x65, 0x2f, 0xcb, 0x6, 0x7f, 0xc7, 0xac, 0xbf, 0x5c, 0xd3, 0xa4, 0x0, 0x30, 0x2, 0xa7, 0x81, 0xe2, 0x42, 0x5, 0xd5, 0x97, 0x96, 0xd8, 0x24, 0xb3, 0x39, 0x1d, 0x5c, 0x9b, 0x7e, 0x98, 0x2a, 0xcf, 0x3, 0xd7, 0x73, 0x26, 0x93, 0x39, 0x30, 0x47, 0xf2, 0x5c, 0xa0, 0xd8, 0x4c, 0x85, 0x7f, 0xd3, 0xdf, 0xb7, 0x39, 0x82, 0x4d, 0x65, 0x11, 0xd8, 0x41, 0xe, 0x0, 0x63, 0xf3, 0x5e, 0xc2, 0xb3, 0x58, 0x85, 0x25, 0x36, 0xaf, 0xc5, 0xef, 0x60, 0x9, 0x18, 0x97, 0xc2, 0xf1, 0xfa, 0x3d, 0x4d, 0x94, 0x5, 0xae, 0xe7, 0xfc, 0xbf, 0xb1, 0xfc, 0xca, 0xe3, 0x4d, 0xb7, 0xb4, 0x65, 0x6b, 0x71, 0xd9, 0x87, 0xe1, 0xdc, 0xea, 0xec, 0x1, 0xe2, 0x12, 0x0, 0x62, 0xc0, 0x94, 0x4c, 0x53, 0x62, 0x71, 0xd1, 0xc0, 0x4e, 0x2e, 0xf4, 0xa5, 0x6c, 0x9a, 0xaa, 0x59, 0xaa, 0xca, 0xe0, 0xd4, 0x34, 0xeb, 0xe4, 0x44, 0x26, 0xcf, 0x2c, 0xf, 0x9c, 0xda, 0xe3, 0x2f, 0x7f, 0xfb, 0xc7, 0xec, 0x95, 0xc7, 0xc0, 0x2f, 0x69, 0xcb, 0xa8, 0x50, 0x3, 0x58, 0x2d, 0xb4, 0x5f, 0xd3, 0x14, 0xa3, 0x51, 0x5a, 0x41, 0xd8, 0x9f, 0x76, 0xfe, 0xed, 0x50, 0x80, 0xf1, 0x70, 0x60, 0x60, 0xd6, 0x3b, 0xc6, 0xf9, 0xce, 0xe3, 0x77, 0x0, 0x62, 0xe4, 0x54, 0xf7, 0xdf, 0xa6, 0x1b, 0x7c, 0x66, 0xfa, 0xba, 0x44, 0x70, 0x4e, 0x52, 0x64, 0x56, 0x34, 0x11, 0x22, 0xd3, 0xf8, 0xc0, 0x57, 0xb4, 0xd3, 0xa4, 0xb2, 0x4, 0x6a, 0x6a, 0xfc, 0x93, 0xb0, 0x73, 0x7c, 0x8, 0x8c, 0x40, 0x3c, 0xd6, 0x46, 0xb5, 0xe8, 0x20, 0xe0, 0x6a, 0x1b, 0xb4, 0x6d, 0x2d, 0xd1, 0xa9, 0xbe, 0xde, 0x8b, 0x5f, 0x61, 0x5c, 0x80, 0xa1, 0x59, 0xeb, 0x7e, 0x79, 0xdd, 0xb2, 0xcf, 0xef, 0xa, 0x4e, 0xf5, 0x37, 0xbf, 0xd0, 0xe7, 0xa3, 0x15, 0x99, 0x4, 0x2, 0xf9, 0x9, 0xcd, 0x43, 0x33, 0xb5, 0xc7, 0x88, 0xcc, 0xf8, 0x51, 0x83, 0x76, 0xa5, 0x23, 0x79, 0xc4, 0x65, 0x7f, 0xcc, 0xb4, 0x33, 0x3c, 0xd6, 0xdf, 0x17, 0x91, 0x7e, 0x46, 0x79, 0xc1, 0x69, 0xd7, 0xfa, 0x52, 0xce, 0xf7, 0xbb, 0xf5, 0x3d, 0xc0, 0x98, 0xb6, 0x4b, 0x4d, 0x9f, 0x2f, 0xa4, 0x9b, 0xb2, 0x45, 0x46, 0x70, 0x9a, 0x5d, 0xe9, 0xca, 0x26, 0xde, 0xe9, 0x7e, 0x4e, 0xb6, 0x7f, 0xb8, 0xe0, 0xdb, 0xf5, 0xcc, 0x21, 0x1f, 0x9c, 0x9a, 0x4, 0x91, 0x19, 0x2f, 0xca, 0x80, 0x9a, 0x69, 0x22, 0xe8, 0xc7, 0x58, 0xce, 0xb5, 0xa8, 0x9c, 0xcb, 0xf4, 0x77, 0xb7, 0x16, 0xb2, 0x3f, 0xdb, 0xba, 0xd5, 0xd7, 0x3d, 0xce, 0x18, 0x46, 0x62, 0xad, 0xed, 0xd8, 0x47, 0xe9, 0x76, 0x27, 0xf9, 0x5c, 0x9e, 0x37, 0x4d, 0x9a, 0x8c, 0x3e, 0xa2, 0x73, 0x98, 0x40, 0x17, 0x91, 0x9, 0xa2, 0xc7, 0x5a, 0x79, 0xa8, 0xbf, 0x20, 0x32, 0xe3, 0x13, 0x96, 0x5f, 0x84, 0xac, 0x65, 0xdf, 0xc2, 0xf2, 0x9d, 0xb4, 0xdb, 0xd1, 0x3f, 0x35, 0xa7, 0x30, 0xdb, 0x63, 0x18, 0x6c, 0x87, 0xc, 0xd0, 0x37, 0xca, 0x9e, 0xa9, 0x19, 0x19, 0xb5, 0xdc, 0x67, 0xa1, 0x5, 0x67, 0xd1, 0xc3, 0xd8, 0x46, 0x74, 0x8e, 0xb, 0x22, 0x13, 0x7e, 0x1a, 0x97, 0x88, 0xcc, 0xf1, 0xa3, 0x80, 0xad, 0x36, 0x84, 0x64, 0x2c, 0xfb, 0x43, 0x39, 0x9e, 0xf, 0x19, 0x9, 0x4b, 0x17, 0xe5, 0x4e, 0xf4, 0x59, 0xc9, 0xf3, 0x3a, 0x37, 0x9c, 0x31, 0xf4, 0x2d, 0x36, 0xaf, 0xf4, 0xd5, 0xe7, 0xb8, 0xdc, 0x15, 0x9d, 0x4, 0x57, 0xc3, 0xf0, 0x1f, 0x9a, 0x20, 0x2b, 0x9c, 0xc7, 0x4b, 0x22, 0x32, 0xfb, 0x8f, 0xea, 0x4c, 0x64, 0x67, 0x6f, 0xd6, 0xa8, 0x71, 0xe6, 0xfd, 0x47, 0x50, 0xf2, 0x94, 0x2d, 0x59, 0x48, 0x9c, 0xeb, 0x2b, 0x63, 0x15, 0x9d, 0xb5, 0x25, 0x3a, 0x9, 0x7c, 0xa0, 0x4f, 0x36, 0xfa, 0x2a, 0x2c, 0xc1, 0x39, 0x1b, 0xa8, 0x9f, 0x1b, 0x9b, 0x6c, 0x8b, 0x4e, 0x66, 0x8f, 0xdc, 0x50, 0x88, 0x1d, 0x82, 0xc6, 0x9c, 0x4b, 0x64, 0xaa, 0x81, 0x77, 0x42, 0x3b, 0xc1, 0x84, 0x50, 0xe, 0xeb, 0x42, 0xc6, 0x5b, 0x67, 0x59, 0x35, 0xf8, 0xbc, 0x45, 0x64, 0xed, 0xb7, 0xd0, 0xd7, 0x83, 0x25, 0x2, 0x0, 0xfa, 0xc, 0xc6, 0x4d, 0x76, 0x73, 0x28, 0xc1, 0x29, 0xf2, 0xeb, 0x46, 0xa2, 0x7a, 0x47, 0x74, 0xd6, 0x3c, 0x1a, 0x80, 0x76, 0x41, 0x5, 0x99, 0x4c, 0x48, 0x5, 0x73, 0x4c, 0xdd, 0xa2, 0x47, 0x47, 0x58, 0xcb, 0xaf, 0xe5, 0x83, 0xea, 0x8e, 0x9d, 0x91, 0x5d, 0x5b, 0x53, 0x7d, 0xff, 0xc6, 0x72, 0x88, 0x47, 0xd6, 0xd7, 0xa1, 0xda, 0x74, 0x41, 0xd7, 0x82, 0x91, 0x4, 0xa7, 0xbd, 0x86, 0xba, 0x1c, 0xa0, 0xdf, 0xdb, 0x1, 0x16, 0xa2, 0xb3, 0x19, 0x1c, 0xde, 0x2, 0x88, 0x4c, 0x48, 0xe, 0x95, 0xb9, 0xec, 0x72, 0xd7, 0xaa, 0x71, 0x2e, 0xf7, 0xf2, 0x3c, 0xa5, 0x36, 0x14, 0xbb, 0x75, 0x2f, 0x5f, 0x9a, 0xb2, 0x2e, 0x2d, 0x11, 0x6a, 0x4a, 0x2e, 0x71, 0x24, 0x1f, 0xa4, 0x84, 0x1a, 0xb, 0x6b, 0x79, 0x3e, 0x78, 0x42, 0xf5, 0xf9, 0x63, 0xf9, 0x75, 0xda, 0x1b, 0xd1, 0x9, 0x80, 0xc8, 0x4, 0xe8, 0x1c, 0xe5, 0x68, 0xae, 0xa5, 0xfd, 0x74, 0xae, 0xed, 0x3c, 0xa6, 0xb2, 0xb3, 0xbf, 0x7a, 0x41, 0x84, 0x96, 0xba, 0x3d, 0x4c, 0xbd, 0x4f, 0x84, 0x27, 0xa4, 0x42, 0xa5, 0xaf, 0x95, 0xfe, 0x79, 0x6e, 0x89, 0xce, 0x21, 0xfa, 0xf9, 0xae, 0xe8, 0xdc, 0xee, 0xd8, 0xd, 0x0, 0x40, 0x64, 0x42, 0x2, 0xa8, 0xc, 0x9e, 0xc9, 0x5e, 0xb6, 0x11, 0x96, 0xa6, 0x16, 0x69, 0x4a, 0x8b, 0xd5, 0x8d, 0xf8, 0x5c, 0xef, 0x8, 0xcf, 0x21, 0x33, 0x40, 0x0, 0x43, 0xb0, 0xb1, 0xc4, 0x9d, 0x7d, 0x42, 0xd7, 0x50, 0xfd, 0x7c, 0x5f, 0x71, 0x78, 0x53, 0xa5, 0xa1, 0xe6, 0xf1, 0x0, 0x20, 0x32, 0x61, 0x7a, 0x28, 0xa3, 0x7e, 0x23, 0xcd, 0xb2, 0x97, 0x66, 0xfa, 0x2d, 0x35, 0x61, 0xe9, 0x23, 0x3c, 0xd5, 0xb5, 0xb2, 0x44, 0xa7, 0x59, 0xe7, 0x46, 0xa6, 0x13, 0x52, 0xe0, 0xe1, 0x85, 0x7e, 0x6e, 0x32, 0xfa, 0xa5, 0xf4, 0xbf, 0xa6, 0xd3, 0x2e, 0x99, 0xc4, 0x99, 0xeb, 0x0, 0x88, 0x4c, 0x98, 0x18, 0x2a, 0x63, 0x70, 0xd9, 0xe0, 0xf7, 0x6a, 0xed, 0x78, 0xd6, 0x34, 0xe1, 0x4f, 0xa2, 0x53, 0xe4, 0x79, 0x37, 0xef, 0xb1, 0x50, 0x47, 0x14, 0xd2, 0xed, 0xe7, 0x62, 0x89, 0xcd, 0x37, 0xf2, 0xbc, 0xac, 0xa4, 0xcf, 0x60, 0xd8, 0x3e, 0x73, 0x1d, 0xc1, 0x9, 0x88, 0x4c, 0x80, 0x88, 0x51, 0x6b, 0x2f, 0x17, 0xd, 0x9c, 0xcc, 0x4a, 0x28, 0xbc, 0xec, 0x12, 0xe0, 0xfb, 0x76, 0xf3, 0x22, 0x38, 0x21, 0x35, 0xcc, 0x11, 0xab, 0x62, 0x5, 0x58, 0xb6, 0xe8, 0xec, 0x2b, 0xab, 0x6f, 0xb, 0xce, 0xca, 0x12, 0x9c, 0xd4, 0xe5, 0x4, 0x44, 0x26, 0xc0, 0xc8, 0x28, 0xe1, 0x73, 0x1b, 0xe8, 0x0, 0x94, 0x70, 0x3a, 0x45, 0x5c, 0x6, 0x63, 0xef, 0xe6, 0x35, 0xe5, 0x8b, 0x86, 0xa8, 0x57, 0x8, 0x30, 0x56, 0x80, 0xb5, 0xde, 0xb1, 0x35, 0x4a, 0x6c, 0xf6, 0xb9, 0x99, 0xa8, 0xd4, 0xd7, 0xa5, 0xfe, 0xbf, 0x3f, 0xc9, 0xb4, 0xb2, 0x9b, 0xf7, 0x1e, 0x42, 0x1e, 0xf1, 0xc, 0x86, 0xcf, 0x88, 0x4c, 0x48, 0x49, 0x60, 0x9a, 0xf3, 0x91, 0xaf, 0x68, 0xba, 0x4e, 0x4, 0xa7, 0xc9, 0x70, 0xde, 0xa, 0x1b, 0x86, 0x20, 0x8f, 0x3e, 0xbf, 0xbb, 0x99, 0xc8, 0x88, 0x4e, 0x95, 0xd9, 0x2f, 0x3a, 0xb6, 0x6d, 0x67, 0xfa, 0xda, 0x68, 0xb1, 0x59, 0x4d, 0xa4, 0x8d, 0xe, 0x71, 0x4e, 0x70, 0x9f, 0xf, 0xff, 0xfe, 0xd7, 0x3f, 0x9d, 0xef, 0xf9, 0x23, 0xcd, 0x4, 0x89, 0x8, 0x4c, 0x65, 0xd8, 0xde, 0x22, 0x30, 0x1, 0xa0, 0x63, 0xd1, 0xa9, 0x84, 0xd3, 0x6b, 0x7d, 0xa9, 0x19, 0x92, 0xb5, 0x74, 0x9b, 0xad, 0x9b, 0x6b, 0x5b, 0x97, 0x42, 0x30, 0xc7, 0xac, 0x7, 0x20, 0x32, 0x21, 0x29, 0x81, 0xf9, 0xa0, 0x9d, 0x80, 0x3a, 0xfe, 0xb4, 0xa6, 0xe9, 0x0, 0xa0, 0x27, 0x6a, 0x2d, 0x30, 0x95, 0xd0, 0xfc, 0x4d, 0x7, 0xb5, 0x4b, 0xe9, 0xae, 0x52, 0x45, 0xa9, 0xed, 0x5e, 0xd3, 0xea, 0x19, 0x43, 0xb5, 0xc1, 0x21, 0xfe, 0x44, 0x37, 0x1, 0x44, 0x26, 0xc4, 0xce, 0xb5, 0xa7, 0xc0, 0xac, 0xb5, 0xb8, 0x24, 0x7b, 0x9, 0x0, 0x43, 0xa3, 0xc4, 0xe5, 0x4a, 0x8b, 0xcd, 0xd7, 0x3a, 0xd8, 0xed, 0x42, 0x70, 0xaa, 0xcc, 0xe6, 0x37, 0x79, 0xaa, 0x5, 0x3c, 0x35, 0x91, 0x49, 0x26, 0x13, 0x10, 0x99, 0x10, 0x35, 0x97, 0xe2, 0xb7, 0xb3, 0x79, 0xab, 0x8d, 0xfb, 0x96, 0x26, 0x3, 0x80, 0x8, 0xc4, 0xd7, 0x55, 0xc7, 0x82, 0x73, 0xf9, 0x78, 0x7d, 0x8d, 0x50, 0xb8, 0x1d, 0x5a, 0x2a, 0x70, 0x44, 0x57, 0x0, 0x44, 0x26, 0xc4, 0x8a, 0x12, 0x97, 0x3e, 0xa7, 0xf8, 0xa8, 0x75, 0x52, 0x27, 0xc2, 0x2e, 0x46, 0x0, 0x88, 0x5f, 0x70, 0x5e, 0xb5, 0xb0, 0x55, 0x33, 0x2d, 0x34, 0xcf, 0x22, 0xba, 0xbf, 0x43, 0xe2, 0xb9, 0xe4, 0xf1, 0x3, 0x22, 0x13, 0x62, 0x44, 0x45, 0xc0, 0xd7, 0x9e, 0x2, 0xf3, 0x3d, 0x2, 0x13, 0x0, 0x26, 0x22, 0x38, 0x55, 0x56, 0x53, 0xad, 0xe1, 0x6c, 0x53, 0x56, 0xed, 0xd2, 0xd3, 0x3e, 0xe, 0x81, 0xcb, 0xf6, 0x16, 0x3c, 0x76, 0x40, 0x64, 0x42, 0x6c, 0x5c, 0x8b, 0x7b, 0xaa, 0x65, 0xab, 0xd, 0x35, 0x0, 0xc0, 0xd4, 0x58, 0xcb, 0xd3, 0xc, 0xcc, 0x49, 0x43, 0xb1, 0xb9, 0x90, 0xa7, 0x8d, 0x41, 0x63, 0x4f, 0x49, 0xbb, 0x96, 0x1, 0x20, 0x32, 0x1, 0x91, 0x9, 0x51, 0x61, 0x9f, 0xf9, 0x7b, 0x28, 0x23, 0xc0, 0x14, 0x39, 0x0, 0x4c, 0x9d, 0xaa, 0x85, 0xd8, 0x2c, 0x23, 0x10, 0x9a, 0xae, 0x82, 0xec, 0xc7, 0x3c, 0x62, 0x40, 0x64, 0x42, 0x4c, 0xb8, 0xce, 0x23, 0x57, 0xc2, 0x92, 0x29, 0x72, 0x0, 0x48, 0x51, 0x6c, 0xbe, 0x97, 0xb0, 0xf2, 0x6b, 0x6a, 0x9d, 0xe6, 0x98, 0x53, 0xe7, 0xae, 0xcf, 0x5a, 0xf2, 0x68, 0x1, 0x91, 0x9, 0xb1, 0xb0, 0x10, 0xf7, 0xf4, 0x8a, 0x2a, 0x13, 0xc2, 0x2e, 0x72, 0x0, 0x48, 0x11, 0xb5, 0xce, 0x3c, 0xf4, 0x20, 0x89, 0xb9, 0x47, 0x70, 0xde, 0x17, 0x2e, 0x5b, 0x4c, 0x19, 0x23, 0x40, 0x64, 0x42, 0x34, 0xb8, 0x6a, 0xc1, 0x55, 0x42, 0x1d, 0x4c, 0x0, 0x48, 0x1b, 0xfb, 0x50, 0x9, 0xdf, 0x19, 0x9b, 0x33, 0xf1, 0x2b, 0xf7, 0xd6, 0x7, 0xd5, 0x81, 0xd7, 0x8e, 0x10, 0x9a, 0x80, 0xc8, 0x84, 0x18, 0x58, 0x88, 0x3b, 0x8b, 0xc9, 0x46, 0x1f, 0x0, 0xc8, 0x5, 0x25, 0xde, 0x42, 0xea, 0xff, 0xfa, 0x6c, 0x98, 0xec, 0x3, 0xd7, 0xe7, 0x2b, 0x79, 0x94, 0x80, 0xc8, 0x84, 0xb1, 0xf9, 0xe8, 0x78, 0x7d, 0x2d, 0x1c, 0x15, 0x9, 0x0, 0x79, 0xa1, 0x6c, 0xde, 0x89, 0xa7, 0xd0, 0x54, 0x2, 0x73, 0x8c, 0x93, 0x81, 0xee, 0x1c, 0xaf, 0xb3, 0xf9, 0x7, 0x10, 0x99, 0x30, 0x2a, 0x33, 0x71, 0x4f, 0xa9, 0xac, 0x68, 0x26, 0x0, 0xc8, 0x90, 0x87, 0x0, 0xa1, 0xa9, 0xa6, 0xcd, 0x8b, 0x81, 0x3f, 0x5f, 0xe5, 0x78, 0xbd, 0xe4, 0x11, 0x2, 0x22, 0x13, 0xc6, 0xe4, 0x83, 0xe3, 0xf5, 0xb5, 0x90, 0xc5, 0x4, 0x0, 0x84, 0xa6, 0xcf, 0x1a, 0xcd, 0x8b, 0x11, 0x3e, 0xdb, 0x21, 0x1, 0x7c, 0x84, 0xd0, 0x4, 0x44, 0x26, 0x8c, 0x89, 0xcb, 0x0, 0x7d, 0xa6, 0x89, 0x0, 0x0, 0xa1, 0xf9, 0x3f, 0xa1, 0xe9, 0x62, 0x21, 0xf1, 0x65, 0x33, 0xdf, 0xf1, 0xf8, 0x0, 0x91, 0x9, 0x63, 0xa0, 0x8c, 0xe1, 0xa1, 0xa9, 0xf2, 0x5a, 0x9a, 0x1f, 0xbf, 0x6, 0x0, 0x90, 0x12, 0x2a, 0x63, 0xb8, 0xf4, 0x78, 0xdf, 0xd0, 0x3b, 0xcd, 0xef, 0x22, 0xfb, 0x3c, 0x80, 0xc8, 0x4, 0xf8, 0x1f, 0xae, 0xb5, 0x98, 0x1b, 0x9a, 0x8, 0x0, 0xe0, 0xff, 0x7c, 0x12, 0xf7, 0xb4, 0xf9, 0x87, 0x81, 0x3f, 0x93, 0xcb, 0x4e, 0x17, 0x42, 0x29, 0x23, 0x44, 0x26, 0x4d, 0x0, 0x23, 0xe0, 0xda, 0x79, 0x78, 0x47, 0x13, 0x1, 0x0, 0xfc, 0x1f, 0x25, 0x30, 0x5d, 0x1b, 0x21, 0x67, 0x32, 0xfc, 0x94, 0xb9, 0x4b, 0x68, 0x7e, 0xe4, 0xd1, 0x21, 0x32, 0x1, 0x86, 0xc6, 0x15, 0xdd, 0x56, 0x34, 0x11, 0x0, 0xc0, 0x4f, 0xac, 0xc5, 0x9d, 0xcd, 0x2c, 0x7, 0xfe, 0x4c, 0x5f, 0x1c, 0xaf, 0x33, 0x65, 0x8e, 0xc8, 0x4, 0x18, 0x9c, 0x43, 0xd1, 0xf6, 0x56, 0x38, 0xa3, 0x1c, 0x0, 0x60, 0x17, 0x65, 0x17, 0x5d, 0x99, 0xc3, 0xa1, 0xeb, 0x53, 0xba, 0x3e, 0x8f, 0xda, 0x65, 0xbe, 0xe0, 0xd1, 0x21, 0x32, 0x1, 0x62, 0x11, 0x99, 0x8, 0x4c, 0x0, 0x80, 0xfd, 0xb8, 0x32, 0x87, 0x43, 0xaf, 0x81, 0x54, 0xf6, 0x7a, 0xed, 0x78, 0xcf, 0x7, 0x1e, 0x1b, 0x22, 0x13, 0x60, 0x28, 0x5c, 0x47, 0xa0, 0x55, 0x34, 0x11, 0x0, 0xc0, 0x5e, 0x5c, 0x99, 0xc3, 0x31, 0x36, 0xda, 0xb8, 0x84, 0x6f, 0x29, 0xc3, 0xaf, 0x15, 0x5, 0x44, 0x26, 0x64, 0xa, 0xbb, 0xd, 0x1, 0x0, 0x9a, 0xe3, 0x3a, 0x5, 0x68, 0xe8, 0xb3, 0xcc, 0x95, 0xf0, 0xad, 0x1d, 0xef, 0xb9, 0xe0, 0xb1, 0x21, 0x32, 0x1, 0x0, 0x0, 0x60, 0xda, 0x22, 0x73, 0x8c, 0x40, 0x7e, 0xed, 0x78, 0x7d, 0x3e, 0x82, 0xf8, 0x5, 0x44, 0x26, 0x0, 0x0, 0x0, 0x4, 0x50, 0x47, 0xf8, 0x99, 0x5c, 0x27, 0xb4, 0x29, 0x81, 0x49, 0x39, 0x23, 0x44, 0x26, 0x0, 0x0, 0x0, 0x44, 0xcc, 0xf7, 0x48, 0x85, 0xef, 0xda, 0xf1, 0x9e, 0x33, 0x21, 0x9b, 0x89, 0xc8, 0x4, 0x0, 0x0, 0x80, 0x68, 0xa9, 0x23, 0xfd, 0x5c, 0x64, 0x33, 0x1, 0x91, 0x9, 0x18, 0x48, 0x0, 0x0, 0xe8, 0x9c, 0x4a, 0xdc, 0xd5, 0x41, 0xc8, 0x66, 0x22, 0x32, 0x1, 0x46, 0x15, 0x99, 0x5, 0x4d, 0x14, 0x1d, 0x25, 0x4d, 0x0, 0x0, 0x1e, 0xb8, 0x8e, 0xbe, 0x54, 0x2, 0x93, 0x9d, 0xe6, 0x88, 0x4c, 0x80, 0xd1, 0x40, 0x64, 0xc6, 0x5, 0x25, 0xa7, 0x0, 0xc0, 0x97, 0x4a, 0xfc, 0xb2, 0x99, 0xd8, 0x79, 0x44, 0x26, 0x40, 0xaf, 0x86, 0x8, 0x51, 0x33, 0xd, 0x4a, 0x9a, 0x0, 0x20, 0x2a, 0x62, 0x9f, 0x6e, 0x5e, 0x79, 0xbc, 0xe7, 0x9a, 0xc7, 0x88, 0xc8, 0x4, 0xe8, 0x8b, 0x7, 0x87, 0x1, 0x65, 0xcd, 0x4e, 0x3c, 0x70, 0x24, 0x1c, 0x40, 0x5c, 0xbc, 0x89, 0xfc, 0xf3, 0x55, 0xe2, 0xce, 0x66, 0xaa, 0xe0, 0x75, 0xce, 0xa3, 0x8c, 0x9a, 0x85, 0x74, 0x90, 0x71, 0x46, 0x64, 0xc2, 0x18, 0xc4, 0x58, 0x4c, 0x18, 0xf6, 0x3f, 0x7, 0x9e, 0x5, 0xc0, 0xb4, 0xa8, 0x22, 0xf8, 0xc, 0xbe, 0xd9, 0x4c, 0x12, 0xa, 0xf1, 0xa, 0x4c, 0xf5, 0x7c, 0xbe, 0xe9, 0xeb, 0x52, 0x1a, 0x16, 0xd4, 0x47, 0x64, 0xc2, 0x18, 0xdc, 0x3b, 0x5e, 0x7f, 0x47, 0x13, 0x45, 0x1, 0xe5, 0x46, 0x0, 0xe2, 0xa3, 0x3c, 0xf0, 0xda, 0x43, 0x44, 0x42, 0x77, 0xed, 0x78, 0xcf, 0x91, 0x30, 0x6d, 0x1e, 0x2b, 0xf6, 0xc, 0x56, 0x21, 0x4f, 0xeb, 0x68, 0x6f, 0x1e, 0xaf, 0x1f, 0x5a, 0x70, 0x22, 0x32, 0x21, 0x6a, 0x5c, 0x99, 0xcc, 0x92, 0x26, 0x1a, 0x9d, 0x99, 0x8e, 0x66, 0x1, 0x20, 0xbe, 0xb1, 0xd9, 0xd4, 0xb6, 0xe, 0xc9, 0xca, 0x43, 0xf4, 0xce, 0x85, 0x69, 0xf3, 0xd8, 0x98, 0x3b, 0x7c, 0xf0, 0x19, 0x22, 0x13, 0x62, 0xa7, 0x96, 0xc3, 0xa5, 0x8c, 0x66, 0xc2, 0xee, 0xc3, 0xb1, 0xb9, 0xa4, 0x9, 0x0, 0xa2, 0x43, 0xd9, 0xc5, 0xa3, 0x89, 0x88, 0xcc, 0x5a, 0x98, 0x36, 0x4f, 0xd1, 0xf6, 0x57, 0x88, 0x4c, 0x98, 0x2, 0xae, 0x8e, 0x4a, 0x74, 0x3b, 0x1e, 0x67, 0x42, 0x36, 0x19, 0x20, 0x46, 0x5c, 0xe3, 0xf2, 0x3e, 0xb2, 0xcf, 0x7b, 0xe5, 0x21, 0x7c, 0x95, 0xc0, 0xbc, 0xe1, 0xd1, 0x46, 0x63, 0xfb, 0xb, 0xc7, 0x7b, 0x3e, 0x21, 0x32, 0x61, 0xa, 0x7c, 0x71, 0xbc, 0xce, 0xae, 0xe6, 0x71, 0x50, 0x6, 0x86, 0x62, 0xc9, 0x0, 0x71, 0xe2, 0x5a, 0xaf, 0x5e, 0x45, 0xf8, 0x99, 0x4f, 0x3d, 0xc5, 0x33, 0x76, 0x27, 0x7e, 0xdb, 0x5f, 0x3f, 0x5e, 0x1b, 0x44, 0x26, 0x4c, 0x1, 0xd5, 0x51, 0xf, 0xad, 0xd7, 0x61, 0x67, 0xf3, 0x38, 0xa8, 0x8c, 0x2, 0x53, 0x57, 0x0, 0xf1, 0xa1, 0xc6, 0xe5, 0xdc, 0x21, 0x0, 0xea, 0x8, 0x3f, 0xb7, 0xca, 0x64, 0x2e, 0x3d, 0xde, 0xb7, 0x14, 0x66, 0xb0, 0xc6, 0xc4, 0x67, 0xd9, 0xc2, 0x2a, 0xf4, 0x8f, 0x22, 0x32, 0x61, 0x6c, 0xa1, 0x79, 0x8, 0x76, 0x37, 0xf, 0xcb, 0x5, 0xc2, 0x1e, 0x20, 0x5a, 0x5c, 0x2, 0xac, 0x8a, 0xf8, 0xb3, 0xaf, 0xc4, 0x6f, 0xbd, 0xe8, 0x35, 0x36, 0x68, 0x14, 0x7c, 0x96, 0x48, 0xa9, 0xfe, 0xb5, 0x46, 0x64, 0xc2, 0x94, 0x70, 0x4d, 0x99, 0x2f, 0x84, 0xd, 0x40, 0x43, 0x3a, 0xb0, 0x25, 0xcd, 0x0, 0x2f, 0x70, 0x24, 0x81, 0xbb, 0x4a, 0xa1, 0x73, 0x5c, 0x41, 0xf7, 0x97, 0xc8, 0x3f, 0xff, 0x7b, 0x71, 0xef, 0x36, 0x37, 0xeb, 0x33, 0x99, 0x4d, 0x19, 0xe, 0x25, 0xea, 0x7d, 0x36, 0x7a, 0xae, 0x9a, 0xfc, 0x71, 0x44, 0x26, 0x8c, 0x89, 0xca, 0x64, 0xd6, 0x8e, 0xf7, 0xb0, 0x4e, 0x67, 0x18, 0x23, 0x43, 0xbd, 0x3a, 0x38, 0xd4, 0x3f, 0xbe, 0x6a, 0x47, 0x44, 0x3f, 0x19, 0x87, 0x52, 0xe, 0x67, 0xf8, 0x6a, 0x9, 0x5c, 0x2b, 0x37, 0x2, 0xea, 0x33, 0x9e, 0x7b, 0xbc, 0xaf, 0x78, 0xbc, 0x6e, 0x11, 0x9a, 0x83, 0x5, 0x8f, 0x3e, 0x9b, 0xae, 0xd6, 0xd2, 0x30, 0x53, 0x8e, 0xc8, 0x84, 0xb1, 0x71, 0xed, 0x54, 0x5b, 0x8, 0xd9, 0xcc, 0x3e, 0xc1, 0xa0, 0x83, 0x4b, 0xdc, 0xdc, 0x5a, 0x63, 0x70, 0x81, 0xd0, 0x1c, 0x5, 0x57, 0xb0, 0xbd, 0x99, 0xc8, 0x7d, 0x28, 0xb1, 0x72, 0xe5, 0x19, 0xd8, 0xb0, 0xe3, 0xbc, 0x7f, 0x6e, 0x3d, 0xfc, 0xeb, 0x83, 0x67, 0x70, 0x80, 0xc8, 0x84, 0x68, 0x8d, 0x8e, 0x6b, 0xa, 0x5, 0xa7, 0xd6, 0x6f, 0x14, 0x8b, 0xc0, 0x84, 0x97, 0x2, 0xbc, 0x7d, 0x1, 0x8, 0x42, 0x73, 0xf8, 0xe7, 0x50, 0xb6, 0xc, 0xd6, 0x63, 0x42, 0x9, 0x16, 0x9f, 0xf5, 0x99, 0x25, 0xfd, 0xac, 0x57, 0x7c, 0xd7, 0xbf, 0x9e, 0x4a, 0x8b, 0x93, 0xa4, 0x10, 0x99, 0x30, 0x36, 0xf, 0x1e, 0x91, 0x6d, 0x29, 0x9c, 0x3e, 0xd3, 0x87, 0xc0, 0xbc, 0xf5, 0x34, 0x32, 0xf, 0x34, 0x57, 0x76, 0x9c, 0x39, 0x1c, 0xfc, 0x4b, 0x2, 0x14, 0xba, 0x1f, 0xa7, 0xae, 0xf5, 0x72, 0x2a, 0x50, 0xaf, 0x27, 0x76, 0x5f, 0x27, 0x9e, 0x9f, 0x99, 0x80, 0xa6, 0x3f, 0x81, 0xe9, 0xe3, 0x53, 0x55, 0xdf, 0x6a, 0x95, 0x25, 0x47, 0x64, 0x42, 0xc, 0x7c, 0xf2, 0x10, 0x32, 0xca, 0xd0, 0x16, 0x34, 0x55, 0x27, 0x28, 0x61, 0xf9, 0xcd, 0x53, 0x60, 0xfa, 0x14, 0x53, 0x86, 0xf4, 0x78, 0xe7, 0xf1, 0x9e, 0x12, 0xa1, 0x39, 0x88, 0x18, 0xe8, 0xbc, 0xac, 0x4c, 0x24, 0xc9, 0x5, 0x9f, 0x8d, 0x40, 0xb6, 0xd0, 0xa4, 0x9f, 0xd, 0x2b, 0x30, 0x95, 0xdd, 0x3f, 0x6f, 0xfb, 0x9f, 0x21, 0x32, 0x21, 0x16, 0x83, 0xe3, 0x32, 0x94, 0x9c, 0xa, 0xd1, 0xd, 0x21, 0xc2, 0x60, 0xdd, 0x85, 0x91, 0x81, 0x49, 0xe2, 0xbb, 0x53, 0x39, 0x24, 0x60, 0x81, 0x30, 0x94, 0x10, 0x98, 0x7b, 0x8c, 0xd1, 0x7a, 0xa2, 0xf7, 0xb7, 0xd5, 0x42, 0xd3, 0xb7, 0x2d, 0x8, 0x68, 0x86, 0x13, 0x98, 0xca, 0x27, 0xb7, 0x9a, 0x26, 0x47, 0x64, 0x42, 0x6c, 0xf8, 0x64, 0xcc, 0x7c, 0x4b, 0x2d, 0x40, 0x7b, 0x43, 0xbd, 0x16, 0xbf, 0x93, 0x3a, 0x20, 0xdd, 0xf1, 0xb8, 0xf6, 0x7c, 0xaf, 0x59, 0x7a, 0xb1, 0xa0, 0xd9, 0x3a, 0xc3, 0xa7, 0xe2, 0x83, 0x4f, 0x70, 0x1e, 0x3b, 0x55, 0x80, 0x9d, 0x99, 0x89, 0xdf, 0x46, 0x15, 0xd8, 0x3f, 0x46, 0xaf, 0x3, 0xc6, 0xe8, 0x7b, 0xe9, 0x68, 0x6, 0xb, 0x91, 0x9, 0x31, 0xe1, 0x93, 0x35, 0x3b, 0xc3, 0x99, 0x35, 0x36, 0x30, 0xbe, 0x6b, 0x9b, 0x10, 0x98, 0x20, 0xba, 0xf, 0xac, 0x3, 0xfb, 0x18, 0x41, 0x60, 0x37, 0x2, 0xf3, 0xd6, 0xe3, 0x7d, 0x4a, 0x60, 0xd6, 0x9, 0xdc, 0x6f, 0x88, 0xbd, 0x31, 0xe5, 0xb4, 0xc8, 0x9c, 0x87, 0xd9, 0xff, 0x90, 0x20, 0xf0, 0x54, 0x3a, 0x2c, 0xec, 0x8f, 0xc8, 0x84, 0xd8, 0xa2, 0x5a, 0x9f, 0xf2, 0x16, 0x21, 0x11, 0x19, 0xe, 0xeb, 0xc9, 0x28, 0xfb, 0xb6, 0x17, 0x2, 0x13, 0x9a, 0xa, 0x4d, 0x13, 0x4, 0xaa, 0xfe, 0x56, 0xd0, 0x74, 0xad, 0x4, 0xa6, 0x6b, 0xb6, 0x61, 0xeb, 0x69, 0x2b, 0x53, 0x14, 0x9a, 0x47, 0x81, 0x36, 0xd, 0xfb, 0xef, 0x2f, 0xca, 0x43, 0xc7, 0x3b, 0x22, 0x13, 0x26, 0x87, 0x6f, 0x79, 0xb, 0x84, 0xa6, 0x9b, 0x8b, 0x40, 0x87, 0x7f, 0x85, 0xc0, 0x84, 0xe, 0x1c, 0x8f, 0x71, 0x6c, 0x9c, 0x10, 0xd4, 0x8f, 0xc0, 0x34, 0x9b, 0x66, 0x52, 0x23, 0x34, 0xc0, 0x35, 0xb3, 0x33, 0xac, 0xd3, 0xec, 0x26, 0xe0, 0xeb, 0x5c, 0x60, 0x22, 0x32, 0x21, 0x66, 0xa7, 0xe6, 0xb3, 0xe0, 0x18, 0xa1, 0xb9, 0x9f, 0x52, 0x9e, 0x36, 0x63, 0x2c, 0x3, 0xdb, 0x9c, 0x4d, 0x3e, 0xd0, 0x55, 0xff, 0x30, 0xa5, 0x77, 0x6e, 0x84, 0xac, 0xa6, 0xf, 0x73, 0xf1, 0x5f, 0x2f, 0xad, 0x9e, 0x45, 0x9d, 0x68, 0x3b, 0x84, 0xa, 0xcd, 0x85, 0x16, 0x52, 0x25, 0x5d, 0xe8, 0xa7, 0xb1, 0x77, 0x23, 0x61, 0x4b, 0x57, 0x7a, 0x11, 0x98, 0x88, 0x4c, 0x88, 0x95, 0x6d, 0x80, 0xa1, 0xb9, 0x16, 0x32, 0x26, 0x86, 0x42, 0x1b, 0x97, 0x90, 0xc5, 0xf1, 0x4a, 0xcc, 0x9f, 0xf4, 0x65, 0x60, 0x20, 0x29, 0x9a, 0x64, 0xba, 0xe7, 0x42, 0x56, 0xd3, 0xc5, 0x85, 0xf8, 0x1f, 0x8a, 0xa0, 0x2, 0xc7, 0x4d, 0xe2, 0xed, 0xb1, 0xd6, 0x36, 0xc9, 0x77, 0x67, 0x73, 0xa1, 0x6d, 0xde, 0x85, 0x90, 0xd5, 0x5c, 0xe8, 0x4, 0xc3, 0x3c, 0xe0, 0x77, 0x7a, 0x13, 0x98, 0x88, 0x4c, 0x88, 0x19, 0x65, 0x48, 0x7d, 0x33, 0x27, 0xe6, 0x4c, 0xe5, 0x5c, 0xd, 0x8c, 0xc9, 0x1a, 0x85, 0x1a, 0x17, 0x25, 0xe6, 0xdf, 0x4a, 0x87, 0x8b, 0xbc, 0x21, 0x79, 0xd6, 0xba, 0xcf, 0x3c, 0x34, 0xe8, 0x9f, 0x64, 0x9c, 0xf6, 0x8b, 0xa3, 0x65, 0x40, 0xdb, 0xaf, 0x32, 0x69, 0x9b, 0x2a, 0x50, 0x68, 0x1a, 0x1, 0x9e, 0x6b, 0x1f, 0x33, 0x7d, 0x29, 0xc4, 0xf, 0x3e, 0xf4, 0x2d, 0x30, 0x11, 0x99, 0x10, 0x3b, 0x21, 0x65, 0x54, 0x16, 0xe2, 0x7f, 0x82, 0x4d, 0x4a, 0xe2, 0xf2, 0x42, 0x8b, 0xcb, 0xd0, 0x4c, 0x91, 0xc9, 0x16, 0xd4, 0x74, 0x33, 0x8, 0xc4, 0x4, 0x27, 0xa1, 0x25, 0x4e, 0x66, 0x96, 0x23, 0x2c, 0x32, 0x6f, 0xc3, 0x8b, 0x40, 0x41, 0xb4, 0x96, 0xfc, 0xd6, 0x4b, 0xab, 0xfe, 0xf5, 0x3a, 0xb0, 0x9f, 0x19, 0xb1, 0x95, 0xcb, 0x32, 0xd, 0x3b, 0xc1, 0x10, 0x22, 0xae, 0x7, 0x9b, 0xc1, 0x42, 0x64, 0x42, 0xec, 0x84, 0x44, 0x5a, 0x66, 0xc3, 0xc1, 0x45, 0x6, 0x51, 0xab, 0x72, 0xd4, 0x3f, 0x74, 0xf4, 0x1e, 0x92, 0xc1, 0x35, 0x9b, 0x6, 0x3a, 0x29, 0xb4, 0xb, 0xd9, 0x52, 0xb7, 0x70, 0x52, 0xb, 0xed, 0x14, 0x73, 0x14, 0x9b, 0x73, 0x79, 0x5e, 0x2f, 0xed, 0x3b, 0x6e, 0x73, 0x14, 0x98, 0xb6, 0xbd, 0x7a, 0xdb, 0xa0, 0x9f, 0x99, 0x76, 0xbe, 0x94, 0x34, 0x67, 0xb8, 0xda, 0x24, 0x18, 0xaa, 0x6, 0xe2, 0x1d, 0x91, 0x9, 0x8, 0x4d, 0x8b, 0xa5, 0xa4, 0x39, 0x6d, 0xb2, 0xd0, 0x51, 0xfa, 0x37, 0x69, 0xb6, 0xe1, 0x69, 0xa3, 0x8d, 0x4b, 0xe8, 0x9a, 0x2e, 0x97, 0x18, 0x65, 0x77, 0x67, 0xbe, 0x2, 0xe0, 0xb4, 0x45, 0xc0, 0x62, 0x8b, 0xcd, 0xd4, 0x67, 0x20, 0x4a, 0x69, 0x96, 0x61, 0xcb, 0x59, 0x60, 0xee, 0xfa, 0x80, 0x26, 0xfd, 0xec, 0x4c, 0xf7, 0xb1, 0x8b, 0x44, 0x2, 0x1a, 0x5b, 0x5c, 0x86, 0x26, 0x18, 0x8c, 0x6f, 0x3c, 0x91, 0x1, 0x13, 0xc, 0x88, 0x4c, 0x48, 0x55, 0x68, 0xce, 0x24, 0x8d, 0x69, 0x13, 0x73, 0xf2, 0xc7, 0xf, 0xfd, 0xb5, 0x89, 0x70, 0x36, 0xd9, 0x4b, 0xdf, 0xb3, 0x82, 0x77, 0x71, 0x45, 0xbc, 0x73, 0xba, 0x67, 0xd6, 0xa8, 0x71, 0xd9, 0x66, 0x6d, 0xef, 0x42, 0x7, 0x85, 0xa9, 0x9d, 0x1a, 0x74, 0x64, 0x9, 0xe9, 0xdb, 0x6, 0x63, 0xf7, 0x1c, 0x81, 0xf9, 0x4b, 0x3f, 0x3b, 0x91, 0xf0, 0xc, 0xdc, 0x91, 0x16, 0x57, 0x53, 0xe, 0x68, 0x6c, 0x3f, 0xd0, 0x44, 0x5c, 0xd6, 0xba, 0xed, 0x6, 0x5f, 0xd3, 0x8b, 0xc8, 0x84, 0x29, 0x46, 0xb3, 0x21, 0x98, 0x69, 0x93, 0x1b, 0x99, 0x4e, 0x66, 0x53, 0x7d, 0x4e, 0xb3, 0xce, 0xc6, 0x14, 0x1d, 0x6e, 0x9a, 0x2d, 0x54, 0xeb, 0x5a, 0x9b, 0x64, 0x2f, 0x6d, 0xee, 0x1d, 0xaf, 0xb3, 0xab, 0x13, 0x8c, 0x13, 0x3b, 0x97, 0xe6, 0x59, 0x92, 0x72, 0x27, 0xa0, 0x9a, 0x6a, 0x76, 0x73, 0xae, 0x3f, 0x7f, 0xd3, 0x25, 0x1, 0x66, 0xbd, 0xdc, 0x15, 0xdd, 0x6a, 0x6f, 0xc0, 0xfb, 0x56, 0xc2, 0xca, 0xb3, 0xed, 0xb, 0x68, 0xda, 0xda, 0xd5, 0xa1, 0x82, 0x94, 0xb3, 0x9d, 0xcf, 0xdb, 0xd4, 0x7, 0x8c, 0xb6, 0xc1, 0x13, 0x91, 0x9, 0x53, 0x8d, 0x66, 0x43, 0x1d, 0x99, 0xa9, 0x43, 0x67, 0xca, 0xa9, 0xc4, 0x64, 0x5c, 0x66, 0xfa, 0x33, 0xdd, 0x68, 0x7, 0x7b, 0xab, 0x7f, 0x2e, 0x5a, 0xfc, 0xcd, 0x4a, 0x8b, 0xcb, 0x36, 0x4e, 0xdf, 0xfe, 0x5b, 0x87, 0x28, 0x2c, 0x87, 0x4a, 0x56, 0x33, 0x6f, 0x8c, 0x43, 0x6b, 0x13, 0xd4, 0x1c, 0x59, 0x62, 0xc0, 0xee, 0x57, 0xb1, 0xa, 0x82, 0x23, 0x4b, 0x58, 0xfe, 0xd0, 0xe3, 0xb8, 0xa9, 0x80, 0x31, 0xe3, 0xb6, 0xa2, 0x2b, 0x1d, 0x64, 0x25, 0xcd, 0x36, 0x9f, 0xd9, 0x36, 0xb7, 0x8b, 0xe7, 0xd5, 0x35, 0xc5, 0x8e, 0x2f, 0xb8, 0x6c, 0x11, 0x6c, 0x6d, 0x3b, 0x8, 0xfc, 0x5a, 0xf3, 0x8a, 0xbe, 0xa, 0x13, 0xc4, 0x18, 0xe2, 0x26, 0xd9, 0xc9, 0x99, 0xbe, 0x2e, 0xb5, 0x23, 0xbc, 0xd3, 0x5f, 0xeb, 0x1, 0x1d, 0x92, 0xfa, 0xff, 0x8f, 0xf5, 0xd7, 0xb2, 0x63, 0xe3, 0x56, 0x69, 0x3, 0xdc, 0xa5, 0x93, 0x7a, 0xd0, 0x6d, 0x34, 0xf7, 0x10, 0x6, 0x8b, 0x96, 0xc6, 0x1f, 0xa6, 0x8f, 0x1a, 0x4b, 0xef, 0xe5, 0x39, 0x23, 0xdf, 0x26, 0x23, 0x59, 0x58, 0xfd, 0xca, 0xf4, 0x6f, 0x75, 0xdd, 0xeb, 0x3e, 0x56, 0x8f, 0x70, 0x7f, 0x85, 0x35, 0x86, 0x4b, 0xe9, 0x26, 0xe3, 0xfa, 0xa0, 0xc7, 0x2d, 0xd9, 0xcb, 0x30, 0x11, 0xf5, 0x56, 0x8b, 0xb2, 0x36, 0xb3, 0x29, 0x73, 0x2b, 0x48, 0x30, 0xfd, 0xeb, 0x6e, 0x20, 0xa1, 0x9f, 0x7c, 0x5f, 0x42, 0x64, 0xc2, 0x54, 0x31, 0x53, 0x4a, 0xca, 0xb8, 0x2c, 0x5b, 0x1a, 0x97, 0x4b, 0xed, 0xac, 0xb6, 0xfa, 0xba, 0xd3, 0x3f, 0xd7, 0x2d, 0xc, 0x47, 0xa1, 0x8d, 0xde, 0x1b, 0xeb, 0xe7, 0x59, 0x8f, 0xd1, 0x72, 0x1f, 0xe2, 0xd2, 0xe6, 0x93, 0xf8, 0x67, 0x29, 0x99, 0x3a, 0x7, 0xd3, 0x27, 0xdf, 0x6a, 0x81, 0xd8, 0xd5, 0xc6, 0x8b, 0x72, 0x27, 0xb0, 0x7c, 0xb0, 0xc6, 0xed, 0x77, 0x2b, 0xb8, 0x69, 0x3b, 0xe, 0x8e, 0x2c, 0x87, 0x7f, 0x6c, 0xfd, 0xdc, 0xc7, 0x18, 0x5e, 0xcb, 0xc8, 0xd9, 0xa6, 0x89, 0x63, 0x4a, 0xdd, 0x5d, 0x48, 0xfb, 0xa2, 0xff, 0xbb, 0xfd, 0x6b, 0x6b, 0x5, 0x33, 0x77, 0x56, 0x7f, 0x6b, 0xda, 0x9f, 0xd4, 0x18, 0xf8, 0x5d, 0xff, 0x1f, 0x85, 0x74, 0xbf, 0x5f, 0x60, 0xa9, 0x6d, 0x75, 0x34, 0x7d, 0x9, 0x91, 0x9, 0x53, 0x47, 0x9, 0xab, 0xcf, 0xd2, 0x7c, 0x53, 0xcc, 0xae, 0x30, 0x9c, 0xef, 0x11, 0xb3, 0xdb, 0x80, 0xdf, 0x1f, 0x9a, 0x8d, 0x36, 0x2a, 0x7d, 0x47, 0xdd, 0x26, 0xc2, 0x2f, 0xe9, 0x72, 0xd0, 0x40, 0x44, 0xad, 0x3b, 0x16, 0x9b, 0xb6, 0xf3, 0x2e, 0x1d, 0xfd, 0xb2, 0xa, 0xf8, 0x5b, 0x43, 0xae, 0x3, 0xed, 0x3b, 0x30, 0xcc, 0x2d, 0xe9, 0x70, 0xae, 0x6d, 0xe1, 0x85, 0x74, 0xb7, 0x81, 0x6c, 0x76, 0xa0, 0x4f, 0x6c, 0x3d, 0xc4, 0x5c, 0x9f, 0x89, 0x85, 0xdd, 0x31, 0xb6, 0x92, 0x8, 0xeb, 0x1e, 0x23, 0x32, 0x21, 0x5, 0xd4, 0xc0, 0x3a, 0x91, 0xe7, 0xac, 0x64, 0x1f, 0x4e, 0x2c, 0x36, 0x83, 0xba, 0xd6, 0x6, 0x75, 0x48, 0xa3, 0xa2, 0xa6, 0x40, 0xbf, 0x9, 0x99, 0x4a, 0x68, 0x2f, 0x36, 0x3f, 0xc, 0x38, 0xae, 0x62, 0x1b, 0xbf, 0x88, 0xcb, 0x7e, 0x7d, 0xc1, 0xa9, 0x6e, 0xdf, 0x2e, 0xc5, 0xe6, 0x4b, 0x2, 0x72, 0x6c, 0x3f, 0xa0, 0xb2, 0xb8, 0x9f, 0x25, 0xe2, 0x43, 0x35, 0xd8, 0xf8, 0x3, 0x29, 0x61, 0xea, 0x40, 0x9e, 0x4a, 0x9a, 0x27, 0xd9, 0x6c, 0xb4, 0xd0, 0xfb, 0x4d, 0x47, 0xed, 0x43, 0xdf, 0xa3, 0x59, 0xa2, 0xc0, 0xb4, 0x1e, 0xb4, 0x15, 0x9b, 0xaa, 0x1f, 0x99, 0x22, 0xdb, 0xf, 0x19, 0xdd, 0xf7, 0x5b, 0x7d, 0xef, 0x8, 0xcc, 0x61, 0xc4, 0xa6, 0xf2, 0x7, 0xcb, 0xc4, 0xfa, 0xd8, 0xd6, 0xba, 0xb7, 0x28, 0xb3, 0x97, 0x88, 0x4c, 0xc8, 0xc1, 0x98, 0xa7, 0x22, 0x36, 0x37, 0xfa, 0x3e, 0x7e, 0xd3, 0x2, 0x73, 0x33, 0xf2, 0xe7, 0x31, 0x3b, 0x16, 0xd9, 0xd8, 0x3, 0x5d, 0x39, 0xcb, 0xdf, 0xf4, 0xd7, 0x4d, 0xa2, 0xf7, 0x78, 0x6e, 0xdd, 0x23, 0xe3, 0x66, 0x78, 0xb1, 0xb9, 0xb2, 0xda, 0x7f, 0xaa, 0xe2, 0xfe, 0xc1, 0xa, 0x52, 0x26, 0x15, 0x9c, 0xfd, 0xd1, 0xd1, 0xe9, 0x19, 0x10, 0x90, 0x82, 0xd8, 0x1c, 0xe4, 0x8c, 0xd6, 0xe, 0x8d, 0xe2, 0x5a, 0x9e, 0x33, 0x96, 0xef, 0x23, 0x34, 0x28, 0x66, 0x57, 0xe7, 0x3e, 0x11, 0xff, 0x10, 0xb1, 0xb0, 0xff, 0x72, 0xa0, 0x1d, 0xb7, 0xd8, 0xbb, 0xd1, 0xc7, 0xea, 0x7b, 0x4b, 0xc, 0x4c, 0xc6, 0x89, 0x1e, 0x10, 0x96, 0xaf, 0xf5, 0x38, 0xb9, 0x12, 0xb2, 0xff, 0xb1, 0xf4, 0xb1, 0x13, 0x79, 0x2e, 0xed, 0x16, 0xfb, 0x78, 0x7f, 0xd8, 0x33, 0x2e, 0x26, 0x67, 0xa3, 0xfe, 0xf0, 0xe7, 0xbf, 0xfe, 0x5d, 0x7d, 0x2d, 0x5f, 0x78, 0xbd, 0xa2, 0x5f, 0x42, 0x42, 0x98, 0x7a, 0x76, 0xef, 0xa4, 0xfb, 0xd2, 0x41, 0x6d, 0x44, 0xa5, 0x1a, 0x67, 0xa6, 0x64, 0x46, 0x3d, 0xc1, 0x76, 0x2d, 0xe4, 0x79, 0x1d, 0x6c, 0x1d, 0xf9, 0x3d, 0xd8, 0x9f, 0x75, 0x57, 0x18, 0x20, 0x4, 0xe2, 0x63, 0x66, 0x8d, 0xd7, 0x99, 0xc4, 0xb9, 0x1e, 0xd8, 0x1e, 0xc3, 0x1b, 0xfa, 0xd1, 0xe4, 0x6c, 0x57, 0x19, 0x91, 0x4f, 0xa8, 0xf4, 0xf5, 0x65, 0xa, 0x82, 0xf2, 0xdf, 0xff, 0xfa, 0xa7, 0xb7, 0xc8, 0x4, 0xc8, 0x11, 0x65, 0x54, 0x8e, 0x7, 0x74, 0x60, 0x95, 0x25, 0xc2, 0xee, 0x10, 0x36, 0x0, 0x8d, 0x44, 0xa7, 0xba, 0xfa, 0x2c, 0x3, 0x73, 0x8, 0x53, 0xce, 0x66, 0xcb, 0x18, 0x4e, 0xb6, 0x7f, 0xa9, 0x7e, 0xf5, 0x46, 0xe, 0xef, 0x2c, 0xef, 0xb2, 0x2f, 0x99, 0x7e, 0x54, 0x4d, 0xad, 0xb1, 0x7c, 0x44, 0x26, 0xbb, 0xcb, 0x21, 0x67, 0x4c, 0xd4, 0xb8, 0xb2, 0xa2, 0xda, 0x42, 0xb, 0x4f, 0x23, 0x42, 0x65, 0xe7, 0x35, 0x9b, 0x7d, 0xe5, 0x8d, 0x6c, 0xa7, 0x73, 0xb7, 0xe7, 0xdf, 0x0, 0xa0, 0x9d, 0x63, 0x36, 0x63, 0x6e, 0xb5, 0x13, 0x30, 0x8a, 0x35, 0x76, 0xf7, 0x95, 0x23, 0x7a, 0x29, 0x90, 0x34, 0x81, 0xdf, 0xae, 0x6d, 0x50, 0x7c, 0xb7, 0x5e, 0xaf, 0x69, 0xfe, 0xac, 0xfa, 0xd7, 0x6e, 0xbf, 0x39, 0x7e, 0xa1, 0x1f, 0x95, 0xe, 0xbf, 0x60, 0xff, 0xdc, 0xa6, 0xd6, 0xe6, 0x24, 0xf9, 0xaf, 0x0, 0x3, 0x0, 0x58, 0xbf, 0x46, 0x22, 0x7, 0x65, 0x51, 0xc8, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)parse_logo3x_png { + return [NSData dataWithBytes:parse_logo3x_png length:sizeof(parse_logo3x_png)]; + } + + const unsigned char facebook_icon_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x16, 0x8, 0x6, 0x0, 0x0, 0x0, 0xc4, 0xb4, 0x6c, 0x3b, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x0, 0x0, 0x3, 0x23, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0x20, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2d, 0x63, 0x30, 0x31, 0x34, 0x20, 0x37, 0x39, 0x2e, 0x31, 0x35, 0x31, 0x34, 0x38, 0x31, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2f, 0x30, 0x33, 0x2f, 0x31, 0x33, 0x2d, 0x31, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x31, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3d, 0x22, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x4d, 0x61, 0x63, 0x69, 0x6e, 0x74, 0x6f, 0x73, 0x68, 0x29, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x37, 0x46, 0x34, 0x42, 0x46, 0x44, 0x38, 0x36, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x20, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x46, 0x34, 0x42, 0x46, 0x44, 0x38, 0x37, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x3e, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x69, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x37, 0x46, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3d, 0x22, 0x78, 0x6d, 0x70, 0x2e, 0x64, 0x69, 0x64, 0x3a, 0x37, 0x32, 0x42, 0x32, 0x44, 0x31, 0x38, 0x30, 0x33, 0x44, 0x43, 0x39, 0x31, 0x31, 0x45, 0x34, 0x38, 0x33, 0x35, 0x43, 0x42, 0x42, 0x36, 0x30, 0x39, 0x42, 0x42, 0x31, 0x39, 0x35, 0x32, 0x44, 0x22, 0x2f, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x20, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x20, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0x98, 0x50, 0xa6, 0xc2, 0x0, 0x0, 0x1, 0x78, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x62, 0xd8, 0xbc, 0x79, 0xf3, 0x9a, 0xbf, 0x7f, 0xff, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xf, 0x6a, 0xe0, 0x7f, 0xff, 0xfe, 0x7d, 0xdf, 0xb5, 0x6b, 0xd7, 0x26, 0x46, 0x20, 0xe7, 0x2f, 0x3, 0x3, 0x3, 0x13, 0x3, 0x95, 0x1, 0xc8, 0xc0, 0x3f, 0xc, 0xd4, 0x7, 0x7f, 0x59, 0xc8, 0xd1, 0xf5, 0xf9, 0xf3, 0xe7, 0xf, 0x6b, 0xd7, 0xae, 0x5d, 0x75, 0xf6, 0xec, 0xd9, 0x73, 0xbf, 0x7f, 0xff, 0xfe, 0xc9, 0xc1, 0xc1, 0xc1, 0xe5, 0xe1, 0xe1, 0xe1, 0xe, 0xc4, 0x7e, 0x70, 0x45, 0xc0, 0xa0, 0xf8, 0xf9, 0x9f, 0x4, 0x70, 0xef, 0xde, 0xbd, 0x5b, 0x3a, 0x3a, 0x3a, 0x1a, 0xe8, 0x96, 0x65, 0x65, 0x65, 0x25, 0x21, 0x29, 0xfb, 0x43, 0x72, 0xd8, 0xe6, 0xe4, 0xe4, 0x64, 0x5d, 0xb9, 0x72, 0xe5, 0x6, 0x16, 0x29, 0x46, 0x64, 0xe, 0x49, 0x41, 0xf1, 0xe8, 0xd1, 0xa3, 0x7b, 0x7b, 0xf6, 0xec, 0x39, 0x0, 0xe3, 0xeb, 0xeb, 0xeb, 0x6b, 0x45, 0x46, 0x46, 0x46, 0x70, 0x73, 0x73, 0x73, 0x18, 0x1a, 0x1a, 0x1a, 0xa1, 0x28, 0x26, 0x25, 0x28, 0x4e, 0x9e, 0x3c, 0x79, 0x18, 0x59, 0xef, 0x91, 0x23, 0x47, 0xf6, 0xe1, 0x50, 0x4a, 0x5a, 0x50, 0x30, 0x32, 0xa2, 0xf8, 0x96, 0x41, 0x0, 0x8, 0xf0, 0x25, 0x37, 0xa2, 0x1, 0x33, 0x33, 0x33, 0xba, 0xfa, 0xff, 0xb8, 0xd4, 0x12, 0xc, 0xe3, 0x97, 0x2f, 0x5f, 0x3e, 0xf, 0xb, 0xb, 0xb, 0xf9, 0xf9, 0xf3, 0xe7, 0xef, 0x2f, 0x5f, 0xbe, 0x7c, 0x46, 0x96, 0x3, 0x86, 0x6f, 0x2c, 0x17, 0x17, 0x17, 0x37, 0x88, 0x5d, 0x55, 0x55, 0x55, 0xee, 0xe7, 0xe7, 0x17, 0x4c, 0xb4, 0xc1, 0x40, 0x3, 0x7f, 0x1c, 0x3a, 0x74, 0xe8, 0x18, 0x36, 0xb9, 0xcb, 0x97, 0x2f, 0x5f, 0xc3, 0xe5, 0x1b, 0x82, 0x41, 0x1, 0x8a, 0x9, 0x62, 0x82, 0x49, 0x5e, 0x5e, 0x5e, 0x81, 0xa4, 0xa0, 0x10, 0x14, 0x14, 0x14, 0xea, 0xe8, 0xe8, 0x68, 0xfd, 0x3, 0x4, 0x4f, 0x9e, 0x3c, 0x79, 0x38, 0x63, 0xc6, 0x8c, 0x79, 0x48, 0x69, 0x3a, 0x4d, 0x42, 0x42, 0x42, 0x9a, 0x8d, 0x8d, 0x8d, 0x55, 0x51, 0x51, 0x51, 0x99, 0xec, 0xe4, 0x76, 0xed, 0xda, 0xb5, 0x73, 0xc8, 0x7a, 0xef, 0xde, 0xbd, 0x7b, 0x9d, 0x2a, 0xc9, 0xd, 0x18, 0x79, 0x5f, 0xd1, 0xf8, 0x5f, 0xa8, 0x92, 0xdc, 0x48, 0x2d, 0x36, 0x47, 0xd, 0x86, 0x1b, 0x4c, 0x74, 0x9, 0x47, 0x64, 0x92, 0x6, 0xe7, 0x17, 0xa6, 0xdd, 0xbb, 0x77, 0x6f, 0x5, 0x6a, 0xf8, 0x5, 0xad, 0xa2, 0x88, 0xc1, 0xc8, 0x16, 0x61, 0xc8, 0x3, 0xc5, 0x7e, 0x1f, 0x3c, 0x78, 0x70, 0x17, 0x40, 0x80, 0x1, 0x0, 0x86, 0x7f, 0xa9, 0xd0, 0x39, 0x35, 0xd1, 0x78, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)facebook_icon_png { + return [NSData dataWithBytes:facebook_icon_png length:sizeof(facebook_icon_png)]; + } + + const unsigned char twitter_icon_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x12, 0x8, 0x6, 0x0, 0x0, 0x0, 0x5f, 0x25, 0x2e, 0x2d, 0x0, 0x0, 0x0, 0x1, 0x73, 0x52, 0x47, 0x42, 0x0, 0xae, 0xce, 0x1c, 0xe9, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0x2e, 0x23, 0x0, 0x0, 0x2e, 0x23, 0x1, 0x78, 0xa5, 0x3f, 0x76, 0x0, 0x0, 0x1, 0xcb, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x3c, 0x2f, 0x78, 0x6d, 0x70, 0x3a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x6f, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0xa, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0xa, 0x29, 0x2e, 0xcd, 0x3d, 0x0, 0x0, 0x2, 0x34, 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0x8d, 0x93, 0xbd, 0x6b, 0x54, 0x41, 0x14, 0x47, 0xdf, 0x6e, 0x36, 0x51, 0x8b, 0xa0, 0x9d, 0x44, 0x8b, 0x10, 0xb4, 0x10, 0xac, 0x4, 0xb1, 0x52, 0x50, 0x10, 0x8b, 0x80, 0x76, 0x36, 0x16, 0xfe, 0x7, 0xda, 0x5a, 0x58, 0x8b, 0xad, 0x88, 0x20, 0x16, 0x82, 0x36, 0x36, 0x12, 0x10, 0x24, 0xda, 0x89, 0x48, 0x10, 0x92, 0x42, 0xc1, 0x2e, 0x5a, 0xaa, 0x85, 0x22, 0x2a, 0x4a, 0xd4, 0xe8, 0xee, 0xbe, 0x9c, 0x33, 0x6f, 0xee, 0x63, 0xb2, 0x1b, 0xd4, 0xb, 0xe7, 0xdd, 0x3b, 0x77, 0x66, 0x7e, 0xef, 0xce, 0x57, 0x55, 0x61, 0x75, 0x5d, 0x77, 0xb3, 0x9f, 0xd0, 0x6f, 0x65, 0x8c, 0x49, 0x7d, 0xf8, 0x39, 0xb8, 0xb, 0xef, 0xe0, 0x3b, 0x2c, 0xc3, 0xf9, 0x98, 0x43, 0x7c, 0x6, 0xe, 0xa4, 0x36, 0xc1, 0x7d, 0x38, 0x6e, 0x3, 0x3f, 0x5, 0xe9, 0x47, 0xa9, 0xb3, 0xc9, 0x85, 0xe8, 0x41, 0xfa, 0xbe, 0xc0, 0x56, 0xf6, 0x80, 0xe4, 0x4b, 0x58, 0x84, 0x6d, 0xa, 0xed, 0x86, 0x3e, 0xfc, 0x84, 0x13, 0x85, 0xd8, 0x24, 0xed, 0x2e, 0x74, 0x8a, 0xdc, 0x12, 0x6d, 0x6d, 0xd, 0xfa, 0x43, 0xc, 0xff, 0x1b, 0xfe, 0x80, 0xe6, 0xa, 0xae, 0xc2, 0x39, 0x85, 0x67, 0x21, 0x55, 0xd1, 0x8c, 0xab, 0x2f, 0xd3, 0xee, 0x15, 0x62, 0x29, 0x26, 0x77, 0x18, 0x14, 0x1a, 0x64, 0x41, 0xe3, 0x1c, 0xa6, 0xfc, 0x2f, 0xda, 0xda, 0x2b, 0x98, 0x4b, 0xf3, 0x9, 0x56, 0xcd, 0x60, 0x56, 0xae, 0xbd, 0x81, 0x8b, 0xb0, 0xb7, 0xf8, 0xc1, 0x31, 0x3b, 0xb0, 0x41, 0x26, 0x9, 0x13, 0xeb, 0xa3, 0x62, 0x75, 0xb6, 0xc7, 0x1c, 0xab, 0x3e, 0xb, 0x9a, 0x3, 0x5c, 0x5a, 0xd8, 0x57, 0x82, 0x27, 0x70, 0x5, 0x6e, 0xe4, 0x64, 0x2b, 0x98, 0x57, 0x68, 0x3a, 0xe6, 0x3c, 0x56, 0x94, 0x76, 0xaf, 0xc7, 0xc7, 0xaa, 0x76, 0xc0, 0x23, 0x98, 0x87, 0x1, 0xf4, 0xa1, 0x86, 0x9d, 0xe0, 0xbe, 0xa7, 0xbd, 0x57, 0xa1, 0xd3, 0x69, 0xb7, 0xbc, 0x22, 0x76, 0x4c, 0x69, 0x6b, 0xb9, 0x31, 0x74, 0xff, 0xd6, 0xe1, 0x16, 0x78, 0x13, 0xdc, 0x8a, 0x5e, 0x31, 0x61, 0x48, 0xce, 0x1f, 0x69, 0x5d, 0xf2, 0x9b, 0x6e, 0x8b, 0xc9, 0xe6, 0x5f, 0xed, 0xf, 0x56, 0xcd, 0x61, 0x13, 0xe, 0xfe, 0x44, 0x70, 0x1b, 0xa6, 0x60, 0x58, 0x88, 0xd2, 0xac, 0x3a, 0x4c, 0x9c, 0xc4, 0x5b, 0xc0, 0x98, 0x68, 0x1a, 0xd0, 0x54, 0x1d, 0x7d, 0xcb, 0xe6, 0xb0, 0x66, 0x25, 0x4c, 0x9e, 0x86, 0xa7, 0x10, 0xe6, 0x1, 0xb5, 0x7b, 0xf9, 0x8f, 0x38, 0xe, 0xfc, 0x3d, 0xe3, 0x76, 0x25, 0x55, 0xdf, 0x1, 0x8d, 0xb8, 0xfc, 0x33, 0xc4, 0x37, 0xc1, 0x3b, 0xaa, 0xfd, 0xaf, 0xf8, 0x7a, 0x33, 0xbc, 0xbe, 0x96, 0x45, 0x9b, 0xd7, 0x4b, 0x32, 0x84, 0x4f, 0x12, 0x7f, 0x0, 0x85, 0xbd, 0x1d, 0x9b, 0x84, 0xf3, 0xd, 0x18, 0x5d, 0x45, 0x54, 0xfb, 0x8d, 0xf1, 0xb3, 0x6d, 0xb5, 0x6, 0x1a, 0xc9, 0x74, 0xd4, 0xf8, 0x3b, 0xd0, 0x5a, 0xf9, 0x10, 0x48, 0x8e, 0x8a, 0xda, 0x8e, 0x6a, 0x2f, 0x64, 0x9d, 0xf6, 0x61, 0x25, 0x51, 0x6, 0xb4, 0x9, 0xe2, 0x4b, 0xf0, 0x2, 0x3e, 0x43, 0x3c, 0xad, 0x52, 0xd4, 0xb4, 0xab, 0x89, 0x97, 0xb6, 0x90, 0xaa, 0x2b, 0xa, 0x8c, 0xf6, 0x98, 0x67, 0x92, 0xcf, 0xf7, 0x35, 0x68, 0xb1, 0x5c, 0xc5, 0x15, 0xf4, 0x31, 0x44, 0xee, 0x21, 0x71, 0x6c, 0x65, 0xdc, 0x8c, 0x46, 0x8f, 0x8e, 0x3d, 0xb0, 0xf, 0xf6, 0xc3, 0x69, 0xb8, 0x7, 0x3f, 0x40, 0x73, 0xaf, 0x15, 0x51, 0x4c, 0x14, 0xf, 0xbb, 0x1e, 0x15, 0x91, 0x68, 0xe, 0x2c, 0x12, 0x7a, 0x92, 0x87, 0x60, 0x1, 0xa2, 0xa, 0xc2, 0xbf, 0xda, 0x33, 0x7a, 0x4f, 0x85, 0x6, 0xf1, 0xb8, 0x28, 0x9d, 0x71, 0x68, 0xfa, 0xa3, 0xe0, 0x84, 0x23, 0xe0, 0x9, 0xcf, 0x80, 0xaf, 0xee, 0x23, 0xbc, 0x85, 0x15, 0x58, 0xe4, 0x1, 0x3d, 0xc7, 0x5b, 0x90, 0x4b, 0xf7, 0x89, 0x8f, 0x3e, 0x6b, 0xbb, 0xab, 0xd, 0x23, 0x5c, 0xa6, 0xb2, 0xce, 0x6d, 0xd4, 0xea, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + + + + + (NSData *)twitter_icon_png { + return [NSData dataWithBytes:twitter_icon_png length:sizeof(twitter_icon_png)]; + } + +@end diff --git a/ParseUI/Other/ParseUI.h b/ParseUI/Other/ParseUI.h new file mode 100644 index 000000000..788a4b03b --- /dev/null +++ b/ParseUI/Other/ParseUI.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/ParseUI/Other/ParseUIConstants.h b/ParseUI/Other/ParseUIConstants.h new file mode 100644 index 000000000..edd970a4f --- /dev/null +++ b/ParseUI/Other/ParseUIConstants.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import +#import + +#ifndef ParseUI_ParseUIConstants_h +#define ParseUI_ParseUIConstants_h + +///-------------------------------------- +/// @name Deprecated Macros +///-------------------------------------- + +#ifndef PARSE_UI_DEPRECATED +# ifdef __deprecated_msg +# define PARSE_UI_DEPRECATED(_MSG) (deprecated(_MSG)) +# else +# ifdef __deprecated +# define PARSE_UI_DEPRECATED(_MSG) (deprecated) +# else +# define PARSE_UI_DEPRECATED(_MSG) +# endif +# endif +#endif + +#endif diff --git a/ParseUI/ParseUI.xcodeproj/project.pbxproj b/ParseUI/ParseUI.xcodeproj/project.pbxproj new file mode 100644 index 000000000..b76d3b840 --- /dev/null +++ b/ParseUI/ParseUI.xcodeproj/project.pbxproj @@ -0,0 +1,1618 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4A0ECBD8200D22C700BA84A3 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0ECBD9200D22C700BA84A3 /* Bolts.framework */; }; + 4A9A941A200D01F1005D8F4B /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A941E200D01F1005D8F4B /* Bolts.framework */; }; + 4A9A941B200D01F1005D8F4B /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A941F200D01F1005D8F4B /* Parse.framework */; }; + 4A9A941C200D01F1005D8F4B /* ParseFacebookUtilsV4.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A9420200D01F1005D8F4B /* ParseFacebookUtilsV4.framework */; }; + 4A9A941D200D01F1005D8F4B /* ParseTwitterUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A9421200D01F1005D8F4B /* ParseTwitterUtils.framework */; }; + 4A9A949F200D0365005D8F4B /* ParseUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A9497200D0329005D8F4B /* ParseUI.framework */; }; + 4A9A94A0200D03AA005D8F4B /* PFResources.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9424200D0226005D8F4B /* PFResources.m */; }; + 4A9A94A1200D03AA005D8F4B /* PFResources.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9425200D0226005D8F4B /* PFResources.h */; }; + 4A9A94A2200D03B6005D8F4B /* PFProductTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9428200D0226005D8F4B /* PFProductTableViewController.m */; }; + 4A9A94A3200D03B6005D8F4B /* PFProductTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9429200D0226005D8F4B /* PFProductTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94A4200D03B6005D8F4B /* PFPurchaseTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A942B200D0226005D8F4B /* PFPurchaseTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94A5200D03B6005D8F4B /* PFCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A942C200D0226005D8F4B /* PFCollectionViewCell.m */; }; + 4A9A94A6200D03B6005D8F4B /* PFTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A942D200D0226005D8F4B /* PFTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94A7200D03B6005D8F4B /* PFPurchaseTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A942E200D0226005D8F4B /* PFPurchaseTableViewCell.m */; }; + 4A9A94A8200D03B6005D8F4B /* PFCollectionViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A942F200D0226005D8F4B /* PFCollectionViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94A9200D03B6005D8F4B /* PFTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9430200D0226005D8F4B /* PFTableViewCell.m */; }; + 4A9A94AA200D03B6005D8F4B /* PFSignUpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9432200D0226005D8F4B /* PFSignUpViewController.m */; }; + 4A9A94AB200D03B6005D8F4B /* PFSignUpView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9433200D0226005D8F4B /* PFSignUpView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94AC200D03B6005D8F4B /* PFSignUpView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9434200D0226005D8F4B /* PFSignUpView.m */; }; + 4A9A94AD200D03B6005D8F4B /* PFSignUpViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9435200D0226005D8F4B /* PFSignUpViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94AE200D03B6005D8F4B /* PFActivityIndicatorTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9438200D0226005D8F4B /* PFActivityIndicatorTableViewCell.m */; }; + 4A9A94AF200D03B6005D8F4B /* PFActivityIndicatorCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9439200D0226005D8F4B /* PFActivityIndicatorCollectionReusableView.h */; }; + 4A9A94B0200D03B6005D8F4B /* PFActivityIndicatorTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A943A200D0226005D8F4B /* PFActivityIndicatorTableViewCell.h */; }; + 4A9A94B1200D03B6005D8F4B /* PFActivityIndicatorCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A943B200D0226005D8F4B /* PFActivityIndicatorCollectionReusableView.m */; }; + 4A9A94B2200D03B6005D8F4B /* PFImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A943C200D0226005D8F4B /* PFImageCache.h */; }; + 4A9A94B3200D03B6005D8F4B /* PFLocalization.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A943D200D0226005D8F4B /* PFLocalization.m */; }; + 4A9A94B4200D03B6005D8F4B /* PFColor.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A943F200D0226005D8F4B /* PFColor.h */; }; + 4A9A94B5200D03B6005D8F4B /* PFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9440200D0226005D8F4B /* PFImage.m */; }; + 4A9A94B6200D03B6005D8F4B /* PFUIAlertView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9441200D0226005D8F4B /* PFUIAlertView.h */; }; + 4A9A94B7200D03B6005D8F4B /* PFRect.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9442200D0226005D8F4B /* PFRect.h */; }; + 4A9A94B8200D03B6005D8F4B /* PFUIAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9443200D0226005D8F4B /* PFUIAlertView.m */; }; + 4A9A94B9200D03B6005D8F4B /* PFImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9444200D0226005D8F4B /* PFImage.h */; }; + 4A9A94BA200D03B6005D8F4B /* PFColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9445200D0226005D8F4B /* PFColor.m */; }; + 4A9A94BB200D03B6005D8F4B /* PFRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9446200D0226005D8F4B /* PFRect.m */; }; + 4A9A94BC200D03B6005D8F4B /* PFLocalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9447200D0226005D8F4B /* PFLocalization.h */; }; + 4A9A94BD200D03B6005D8F4B /* PFImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9448200D0226005D8F4B /* PFImageCache.m */; }; + 4A9A94BE200D03BA005D8F4B /* PFDismissButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A944B200D0226005D8F4B /* PFDismissButton.h */; }; + 4A9A94BF200D03BA005D8F4B /* PFTextButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A944C200D0226005D8F4B /* PFTextButton.m */; }; + 4A9A94C0200D03BA005D8F4B /* PFPrimaryButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A944D200D0226005D8F4B /* PFPrimaryButton.m */; }; + 4A9A94C1200D03BA005D8F4B /* PFActionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A944E200D0226005D8F4B /* PFActionButton.m */; }; + 4A9A94C2200D03BA005D8F4B /* PFTextButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A944F200D0226005D8F4B /* PFTextButton.h */; }; + 4A9A94C3200D03BA005D8F4B /* PFDismissButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9450200D0226005D8F4B /* PFDismissButton.m */; }; + 4A9A94C4200D03BA005D8F4B /* PFPrimaryButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9451200D0226005D8F4B /* PFPrimaryButton.h */; }; + 4A9A94C5200D03BA005D8F4B /* PFActionButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9452200D0226005D8F4B /* PFActionButton.h */; }; + 4A9A94C6200D03BA005D8F4B /* PFLoadingView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9453200D0226005D8F4B /* PFLoadingView.h */; }; + 4A9A94C7200D03BA005D8F4B /* PFLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9454200D0226005D8F4B /* PFLoadingView.m */; }; + 4A9A94C8200D03BE005D8F4B /* PFQueryCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9456200D0226005D8F4B /* PFQueryCollectionViewController.m */; }; + 4A9A94C9200D03BE005D8F4B /* PFQueryCollectionViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9457200D0226005D8F4B /* PFQueryCollectionViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94CA200D03C8005D8F4B /* PFQueryTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9459200D0226005D8F4B /* PFQueryTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94CB200D03C8005D8F4B /* PFQueryTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A945A200D0226005D8F4B /* PFQueryTableViewController.m */; }; + 4A9A94CC200D03C8005D8F4B /* PFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A945C200D0226005D8F4B /* PFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94CD200D03C8005D8F4B /* PFImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A945D200D0226005D8F4B /* PFImageView.m */; }; + 4A9A94CE200D03C8005D8F4B /* PFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A945E200D0226005D8F4B /* PFTextField.m */; }; + 4A9A94CF200D03C8005D8F4B /* PFImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A945F200D0226005D8F4B /* PFImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94D0200D03C8005D8F4B /* PFLogInViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9461200D0226005D8F4B /* PFLogInViewController.m */; }; + 4A9A94D1200D03C8005D8F4B /* PFLogInView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A9462200D0226005D8F4B /* PFLogInView.m */; }; + 4A9A94D2200D03C8005D8F4B /* PFLogInView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9463200D0226005D8F4B /* PFLogInView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94D3200D03C8005D8F4B /* PFLogInViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9464200D0226005D8F4B /* PFLogInViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94D4200D03C8005D8F4B /* PFLogInView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A9465200D0226005D8F4B /* PFLogInView_Private.h */; }; + 4A9A94D5200D0413005D8F4B /* ParseUIConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A947B200D0226005D8F4B /* ParseUIConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94D6200D0413005D8F4B /* ParseUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A9A947C200D0226005D8F4B /* ParseUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4A9A94D9200D04DE005D8F4B /* ParseFacebookUtilsV4.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94DA200D04DE005D8F4B /* ParseFacebookUtilsV4.framework */; }; + 4A9A94E2200D0574005D8F4B /* FBSDKCoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E1200D056A005D8F4B /* FBSDKCoreKit.framework */; }; + 4A9A94E3200D0574005D8F4B /* FBSDKLoginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E0200D056A005D8F4B /* FBSDKLoginKit.framework */; }; + 4A9A94E5200D0598005D8F4B /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E4200D0598005D8F4B /* libsqlite3.tbd */; }; + 4A9A94E7200D05A5005D8F4B /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E6200D05A5005D8F4B /* SystemConfiguration.framework */; }; + 4A9A94E9200D05AA005D8F4B /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E8200D05AA005D8F4B /* Social.framework */; }; + 4A9A94EA200D068C005D8F4B /* ParseUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A9497200D0329005D8F4B /* ParseUI.framework */; }; + 4A9A94EB200D06A3005D8F4B /* FBSDKCoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E1200D056A005D8F4B /* FBSDKCoreKit.framework */; }; + 4A9A94EC200D06A3005D8F4B /* FBSDKLoginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E0200D056A005D8F4B /* FBSDKLoginKit.framework */; }; + 4A9A94ED200D1237005D8F4B /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94F0200D1237005D8F4B /* Parse.framework */; }; + 4A9A94EE200D1237005D8F4B /* ParseFacebookUtilsV4.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94F1200D1237005D8F4B /* ParseFacebookUtilsV4.framework */; }; + 4A9A94EF200D1237005D8F4B /* ParseTwitterUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94F2200D1237005D8F4B /* ParseTwitterUtils.framework */; }; + 4A9A94F3200D1241005D8F4B /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E4200D0598005D8F4B /* libsqlite3.tbd */; }; + 4A9A94F4200D1248005D8F4B /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E8200D05AA005D8F4B /* Social.framework */; }; + 4A9A94F5200D1252005D8F4B /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94E6200D05A5005D8F4B /* SystemConfiguration.framework */; }; + 4A9A94F7200D125A005D8F4B /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9A94F6200D125A005D8F4B /* AudioToolbox.framework */; }; + 8129E5F51A9CB067006752BC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 81472FA71A1AB37500FD6EED /* Images.xcassets */; }; + 8129E5F71A9CB067006752BC /* 0.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FA91A1AB37500FD6EED /* 0.png */; }; + 8129E5F81A9CB067006752BC /* 1.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FAA1A1AB37500FD6EED /* 1.png */; }; + 8129E5F91A9CB067006752BC /* 2.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FAB1A1AB37500FD6EED /* 2.png */; }; + 8129E5FA1A9CB067006752BC /* SimpleQueryCollectionStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 812E5C001A7A8EB4000FBDE1 /* SimpleQueryCollectionStoryboard.storyboard */; }; + 8129E5FB1A9CB067006752BC /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FA81A1AB37500FD6EED /* Icon.png */; }; + 8129E6091A9CB1BE006752BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8129E6081A9CB1BE006752BC /* AppDelegate.swift */; }; + 8129E63A1A9CB320006752BC /* UIDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8129E6391A9CB320006752BC /* UIDemoViewController.swift */; }; + 812E5C011A7A8EB4000FBDE1 /* SimpleQueryCollectionStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 812E5C001A7A8EB4000FBDE1 /* SimpleQueryCollectionStoryboard.storyboard */; }; + 812E5C041A7A8EFB000FBDE1 /* StoryboardCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 812E5C031A7A8EFB000FBDE1 /* StoryboardCollectionViewController.m */; }; + 814155B11A9CD9CA002D9683 /* SimpleQueryTableStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 814155B01A9CD9CA002D9683 /* SimpleQueryTableStoryboard.storyboard */; }; + 81472FB11A1AB37500FD6EED /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F8E1A1AB37500FD6EED /* AppDelegate.m */; }; + 81472FB21A1AB37500FD6EED /* PFUIDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F901A1AB37500FD6EED /* PFUIDemoViewController.m */; }; + 81472FB31A1AB37500FD6EED /* CustomSignUpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F921A1AB37500FD6EED /* CustomSignUpViewController.m */; }; + 81472FB41A1AB37500FD6EED /* CustomLogInViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F951A1AB37500FD6EED /* CustomLogInViewController.m */; }; + 81472FB51A1AB37500FD6EED /* SimpleTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F981A1AB37500FD6EED /* SimpleTableViewController.m */; }; + 81472FB61A1AB37500FD6EED /* PaginatedTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F9A1A1AB37500FD6EED /* PaginatedTableViewController.m */; }; + 81472FB71A1AB37500FD6EED /* SectionedTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F9C1A1AB37500FD6EED /* SectionedTableViewController.m */; }; + 81472FB81A1AB37500FD6EED /* SubtitleImageTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472F9E1A1AB37500FD6EED /* SubtitleImageTableViewController.m */; }; + 81472FB91A1AB37500FD6EED /* CustomProductTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472FA01A1AB37500FD6EED /* CustomProductTableViewController.m */; }; + 81472FBB1A1AB37500FD6EED /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 81472FA51A1AB37500FD6EED /* main.m */; }; + 81472FBC1A1AB37500FD6EED /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 81472FA71A1AB37500FD6EED /* Images.xcassets */; }; + 81472FBD1A1AB37500FD6EED /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FA81A1AB37500FD6EED /* Icon.png */; }; + 81472FBE1A1AB37500FD6EED /* 0.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FA91A1AB37500FD6EED /* 0.png */; }; + 81472FBF1A1AB37500FD6EED /* 1.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FAA1A1AB37500FD6EED /* 1.png */; }; + 81472FC01A1AB37500FD6EED /* 2.png in Resources */ = {isa = PBXBuildFile; fileRef = 81472FAB1A1AB37500FD6EED /* 2.png */; }; + 819A4B3A1A6808EA00D01241 /* SubtitleImageCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 819A4B391A6808EA00D01241 /* SubtitleImageCollectionViewController.m */; }; + 81C5993F1A64346000F574E8 /* SimpleCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C5993E1A64346000F574E8 /* SimpleCollectionViewController.m */; }; + 81C599421A6454C900F574E8 /* PaginatedCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C599411A6454C900F574E8 /* PaginatedCollectionViewController.m */; }; + 81C599491A64636200F574E8 /* SectionedCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C599481A64636200F574E8 /* SectionedCollectionViewController.m */; }; + 81C8D9BD1A890BCA007B8DCF /* StoryboardTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C8D9BC1A890BCA007B8DCF /* StoryboardTableViewController.m */; }; + 81FC6B4A1A9D1ADB00104E45 /* CustomLogInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B3A1A9D1ADB00104E45 /* CustomLogInViewController.swift */; }; + 81FC6B561A9D1ADB00104E45 /* CustomSignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B491A9D1ADB00104E45 /* CustomSignUpViewController.swift */; }; + 81FC6B5F1A9D1BFC00104E45 /* PaginatedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B591A9D1BFC00104E45 /* PaginatedTableViewController.swift */; }; + 81FC6B601A9D1BFC00104E45 /* SectionedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B5A1A9D1BFC00104E45 /* SectionedTableViewController.swift */; }; + 81FC6B611A9D1BFC00104E45 /* SimpleTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B5B1A9D1BFC00104E45 /* SimpleTableViewController.swift */; }; + 81FC6B621A9D1BFC00104E45 /* StoryboardTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B5C1A9D1BFC00104E45 /* StoryboardTableViewController.swift */; }; + 81FC6B631A9D1BFC00104E45 /* SubtitleImageTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B5D1A9D1BFC00104E45 /* SubtitleImageTableViewController.swift */; }; + 81FC6B651A9D1C2000104E45 /* SimpleQueryTableStoryboard-Swift.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81FC6B641A9D1C2000104E45 /* SimpleQueryTableStoryboard-Swift.storyboard */; }; + 81FC6B6C1A9D1CE900104E45 /* PaginatedCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B671A9D1CE900104E45 /* PaginatedCollectionViewController.swift */; }; + 81FC6B6D1A9D1CE900104E45 /* SectionedCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B681A9D1CE900104E45 /* SectionedCollectionViewController.swift */; }; + 81FC6B6E1A9D1CE900104E45 /* SimpleCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B691A9D1CE900104E45 /* SimpleCollectionViewController.swift */; }; + 81FC6B6F1A9D1CE900104E45 /* StoryboardCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B6A1A9D1CE900104E45 /* StoryboardCollectionViewController.swift */; }; + 81FC6B701A9D1CE900104E45 /* SubtitleImageCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B6B1A9D1CE900104E45 /* SubtitleImageCollectionViewController.swift */; }; + 81FC6B721A9D1CFD00104E45 /* SimpleQueryCollectionStoryboard-Swift.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81FC6B711A9D1CFD00104E45 /* SimpleQueryCollectionStoryboard-Swift.storyboard */; }; + 81FC6B751A9D1D4700104E45 /* CustomProductTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC6B741A9D1D4700104E45 /* CustomProductTableViewController.swift */; }; + F57F3D231B0C03D40087F60B /* DeletionCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F57F3D201B0C03C90087F60B /* DeletionCollectionViewController.m */; }; + F57F3D241B0C03DB0087F60B /* DeletionTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F57F3D1C1B0C03BA0087F60B /* DeletionTableViewController.m */; }; + F57F3D271B0C050C0087F60B /* DeletionTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F3D251B0C04EF0087F60B /* DeletionTableViewController.swift */; }; + F57F3D291B0C05280087F60B /* DeletionCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F3D281B0C05280087F60B /* DeletionCollectionViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 06A1EF631BB6799700BA55EC /* ParseUIDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ParseUIDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 06A1EF641BB6799700BA55EC /* ParseUIDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ParseUIDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A0ECBD9200D22C700BA84A3 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A941E200D01F1005D8F4B /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A941F200D01F1005D8F4B /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A9420200D01F1005D8F4B /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A9421200D01F1005D8F4B /* ParseTwitterUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ParseTwitterUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A9424200D0226005D8F4B /* PFResources.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFResources.m; sourceTree = ""; }; + 4A9A9425200D0226005D8F4B /* PFResources.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFResources.h; sourceTree = ""; }; + 4A9A9428200D0226005D8F4B /* PFProductTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFProductTableViewController.m; sourceTree = ""; }; + 4A9A9429200D0226005D8F4B /* PFProductTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFProductTableViewController.h; sourceTree = ""; }; + 4A9A942B200D0226005D8F4B /* PFPurchaseTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFPurchaseTableViewCell.h; sourceTree = ""; }; + 4A9A942C200D0226005D8F4B /* PFCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFCollectionViewCell.m; sourceTree = ""; }; + 4A9A942D200D0226005D8F4B /* PFTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFTableViewCell.h; sourceTree = ""; }; + 4A9A942E200D0226005D8F4B /* PFPurchaseTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFPurchaseTableViewCell.m; sourceTree = ""; }; + 4A9A942F200D0226005D8F4B /* PFCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFCollectionViewCell.h; sourceTree = ""; }; + 4A9A9430200D0226005D8F4B /* PFTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFTableViewCell.m; sourceTree = ""; }; + 4A9A9432200D0226005D8F4B /* PFSignUpViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFSignUpViewController.m; sourceTree = ""; }; + 4A9A9433200D0226005D8F4B /* PFSignUpView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFSignUpView.h; sourceTree = ""; }; + 4A9A9434200D0226005D8F4B /* PFSignUpView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFSignUpView.m; sourceTree = ""; }; + 4A9A9435200D0226005D8F4B /* PFSignUpViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFSignUpViewController.h; sourceTree = ""; }; + 4A9A9438200D0226005D8F4B /* PFActivityIndicatorTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFActivityIndicatorTableViewCell.m; sourceTree = ""; }; + 4A9A9439200D0226005D8F4B /* PFActivityIndicatorCollectionReusableView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFActivityIndicatorCollectionReusableView.h; sourceTree = ""; }; + 4A9A943A200D0226005D8F4B /* PFActivityIndicatorTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFActivityIndicatorTableViewCell.h; sourceTree = ""; }; + 4A9A943B200D0226005D8F4B /* PFActivityIndicatorCollectionReusableView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFActivityIndicatorCollectionReusableView.m; sourceTree = ""; }; + 4A9A943C200D0226005D8F4B /* PFImageCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFImageCache.h; sourceTree = ""; }; + 4A9A943D200D0226005D8F4B /* PFLocalization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFLocalization.m; sourceTree = ""; }; + 4A9A943F200D0226005D8F4B /* PFColor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFColor.h; sourceTree = ""; }; + 4A9A9440200D0226005D8F4B /* PFImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFImage.m; sourceTree = ""; }; + 4A9A9441200D0226005D8F4B /* PFUIAlertView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFUIAlertView.h; sourceTree = ""; }; + 4A9A9442200D0226005D8F4B /* PFRect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFRect.h; sourceTree = ""; }; + 4A9A9443200D0226005D8F4B /* PFUIAlertView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFUIAlertView.m; sourceTree = ""; }; + 4A9A9444200D0226005D8F4B /* PFImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFImage.h; sourceTree = ""; }; + 4A9A9445200D0226005D8F4B /* PFColor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFColor.m; sourceTree = ""; }; + 4A9A9446200D0226005D8F4B /* PFRect.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFRect.m; sourceTree = ""; }; + 4A9A9447200D0226005D8F4B /* PFLocalization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFLocalization.h; sourceTree = ""; }; + 4A9A9448200D0226005D8F4B /* PFImageCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFImageCache.m; sourceTree = ""; }; + 4A9A944B200D0226005D8F4B /* PFDismissButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFDismissButton.h; sourceTree = ""; }; + 4A9A944C200D0226005D8F4B /* PFTextButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFTextButton.m; sourceTree = ""; }; + 4A9A944D200D0226005D8F4B /* PFPrimaryButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFPrimaryButton.m; sourceTree = ""; }; + 4A9A944E200D0226005D8F4B /* PFActionButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFActionButton.m; sourceTree = ""; }; + 4A9A944F200D0226005D8F4B /* PFTextButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFTextButton.h; sourceTree = ""; }; + 4A9A9450200D0226005D8F4B /* PFDismissButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFDismissButton.m; sourceTree = ""; }; + 4A9A9451200D0226005D8F4B /* PFPrimaryButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFPrimaryButton.h; sourceTree = ""; }; + 4A9A9452200D0226005D8F4B /* PFActionButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFActionButton.h; sourceTree = ""; }; + 4A9A9453200D0226005D8F4B /* PFLoadingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFLoadingView.h; sourceTree = ""; }; + 4A9A9454200D0226005D8F4B /* PFLoadingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFLoadingView.m; sourceTree = ""; }; + 4A9A9456200D0226005D8F4B /* PFQueryCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFQueryCollectionViewController.m; sourceTree = ""; }; + 4A9A9457200D0226005D8F4B /* PFQueryCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFQueryCollectionViewController.h; sourceTree = ""; }; + 4A9A9459200D0226005D8F4B /* PFQueryTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFQueryTableViewController.h; sourceTree = ""; }; + 4A9A945A200D0226005D8F4B /* PFQueryTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFQueryTableViewController.m; sourceTree = ""; }; + 4A9A945C200D0226005D8F4B /* PFTextField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFTextField.h; sourceTree = ""; }; + 4A9A945D200D0226005D8F4B /* PFImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFImageView.m; sourceTree = ""; }; + 4A9A945E200D0226005D8F4B /* PFTextField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFTextField.m; sourceTree = ""; }; + 4A9A945F200D0226005D8F4B /* PFImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFImageView.h; sourceTree = ""; }; + 4A9A9461200D0226005D8F4B /* PFLogInViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFLogInViewController.m; sourceTree = ""; }; + 4A9A9462200D0226005D8F4B /* PFLogInView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFLogInView.m; sourceTree = ""; }; + 4A9A9463200D0226005D8F4B /* PFLogInView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFLogInView.h; sourceTree = ""; }; + 4A9A9464200D0226005D8F4B /* PFLogInViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFLogInViewController.h; sourceTree = ""; }; + 4A9A9465200D0226005D8F4B /* PFLogInView_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFLogInView_Private.h; sourceTree = ""; }; + 4A9A9468200D0226005D8F4B /* twitter_icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "twitter_icon@3x.png"; sourceTree = ""; }; + 4A9A9469200D0226005D8F4B /* facebook_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "facebook_icon@2x.png"; sourceTree = ""; }; + 4A9A946A200D0226005D8F4B /* parse_logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = parse_logo.png; sourceTree = ""; }; + 4A9A946B200D0226005D8F4B /* facebook_icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "facebook_icon@3x.png"; sourceTree = ""; }; + 4A9A946C200D0226005D8F4B /* twitter_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "twitter_icon@2x.png"; sourceTree = ""; }; + 4A9A946D200D0226005D8F4B /* parse_logo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "parse_logo@2x.png"; sourceTree = ""; }; + 4A9A946E200D0226005D8F4B /* parse_logo@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "parse_logo@3x.png"; sourceTree = ""; }; + 4A9A946F200D0226005D8F4B /* facebook_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = facebook_icon.png; sourceTree = ""; }; + 4A9A9470200D0226005D8F4B /* twitter_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = twitter_icon.png; sourceTree = ""; }; + 4A9A9471200D0226005D8F4B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4A9A9474200D0226005D8F4B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ParseUI.strings; sourceTree = ""; }; + 4A9A9475200D0226005D8F4B /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/ParseUI.strings; sourceTree = ""; }; + 4A9A9476200D0226005D8F4B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ParseUI.strings; sourceTree = ""; }; + 4A9A9477200D0226005D8F4B /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/ParseUI.strings; sourceTree = ""; }; + 4A9A9478200D0226005D8F4B /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/ParseUI.strings"; sourceTree = ""; }; + 4A9A9479200D0226005D8F4B /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/ParseUI.strings; sourceTree = ""; }; + 4A9A947B200D0226005D8F4B /* ParseUIConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParseUIConstants.h; sourceTree = ""; }; + 4A9A947C200D0226005D8F4B /* ParseUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParseUI.h; sourceTree = ""; }; + 4A9A947E200D0226005D8F4B /* convert_images.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = convert_images.rb; sourceTree = ""; }; + 4A9A9480200D02CE005D8F4B /* ParseUI-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseUI-iOS.xcconfig"; sourceTree = ""; }; + 4A9A9482200D02CE005D8F4B /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; + 4A9A9484200D02CE005D8F4B /* macOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = ""; }; + 4A9A9485200D02CE005D8F4B /* tvOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = tvOS.xcconfig; sourceTree = ""; }; + 4A9A9486200D02CE005D8F4B /* iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = iOS.xcconfig; sourceTree = ""; }; + 4A9A9487200D02CE005D8F4B /* watchOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = watchOS.xcconfig; sourceTree = ""; }; + 4A9A9489200D02CE005D8F4B /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = ""; }; + 4A9A948A200D02CE005D8F4B /* DynamicFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DynamicFramework.xcconfig; sourceTree = ""; }; + 4A9A948B200D02CE005D8F4B /* StaticFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticFramework.xcconfig; sourceTree = ""; }; + 4A9A948C200D02CE005D8F4B /* LogicTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = LogicTests.xcconfig; sourceTree = ""; }; + 4A9A948E200D02CE005D8F4B /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 4A9A948F200D02CE005D8F4B /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 4A9A9490200D02CE005D8F4B /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 4A9A9491200D02CE005D8F4B /* ParseUI-iOS-Dynamic.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ParseUI-iOS-Dynamic.xcconfig"; sourceTree = ""; }; + 4A9A9497200D0329005D8F4B /* ParseUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94D8200D0470005D8F4B /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94DA200D04DE005D8F4B /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94E0200D056A005D8F4B /* FBSDKLoginKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FBSDKLoginKit.framework; path = Vendor/FBSDKLoginKit.framework; sourceTree = SOURCE_ROOT; }; + 4A9A94E1200D056A005D8F4B /* FBSDKCoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FBSDKCoreKit.framework; path = Vendor/FBSDKCoreKit.framework; sourceTree = SOURCE_ROOT; }; + 4A9A94E4200D0598005D8F4B /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; + 4A9A94E6200D05A5005D8F4B /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + 4A9A94E8200D05AA005D8F4B /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; + 4A9A94F0200D1237005D8F4B /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94F1200D1237005D8F4B /* ParseFacebookUtilsV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ParseFacebookUtilsV4.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94F2200D1237005D8F4B /* ParseTwitterUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ParseTwitterUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A9A94F6200D125A005D8F4B /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 720149E3A3C18665E2F10DC8 /* Pods_ParseUIDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ParseUIDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7C1041A7E103153B26236744 /* Pods-ParseUIDemo-Swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ParseUIDemo-Swift.release.xcconfig"; path = "Pods/Target Support Files/Pods-ParseUIDemo-Swift/Pods-ParseUIDemo-Swift.release.xcconfig"; sourceTree = ""; }; + 8129E6081A9CB1BE006752BC /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8129E6391A9CB320006752BC /* UIDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDemoViewController.swift; sourceTree = ""; }; + 812E5C001A7A8EB4000FBDE1 /* SimpleQueryCollectionStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SimpleQueryCollectionStoryboard.storyboard; path = ../../../Resources/SimpleQueryCollectionStoryboard.storyboard; sourceTree = ""; }; + 812E5C021A7A8EFB000FBDE1 /* StoryboardCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoryboardCollectionViewController.h; sourceTree = ""; }; + 812E5C031A7A8EFB000FBDE1 /* StoryboardCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoryboardCollectionViewController.m; sourceTree = ""; }; + 814155B01A9CD9CA002D9683 /* SimpleQueryTableStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SimpleQueryTableStoryboard.storyboard; path = ParseUIDemo/Resources/SimpleQueryTableStoryboard.storyboard; sourceTree = SOURCE_ROOT; }; + 81472F8D1A1AB37500FD6EED /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 81472F8E1A1AB37500FD6EED /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 81472F8F1A1AB37500FD6EED /* PFUIDemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFUIDemoViewController.h; sourceTree = ""; }; + 81472F901A1AB37500FD6EED /* PFUIDemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFUIDemoViewController.m; sourceTree = ""; }; + 81472F911A1AB37500FD6EED /* CustomSignUpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomSignUpViewController.h; sourceTree = ""; }; + 81472F921A1AB37500FD6EED /* CustomSignUpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomSignUpViewController.m; sourceTree = ""; }; + 81472F941A1AB37500FD6EED /* CustomLogInViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomLogInViewController.h; sourceTree = ""; }; + 81472F951A1AB37500FD6EED /* CustomLogInViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomLogInViewController.m; sourceTree = ""; }; + 81472F971A1AB37500FD6EED /* SimpleTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimpleTableViewController.h; sourceTree = ""; }; + 81472F981A1AB37500FD6EED /* SimpleTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimpleTableViewController.m; sourceTree = ""; }; + 81472F991A1AB37500FD6EED /* PaginatedTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PaginatedTableViewController.h; sourceTree = ""; }; + 81472F9A1A1AB37500FD6EED /* PaginatedTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaginatedTableViewController.m; sourceTree = ""; }; + 81472F9B1A1AB37500FD6EED /* SectionedTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SectionedTableViewController.h; sourceTree = ""; }; + 81472F9C1A1AB37500FD6EED /* SectionedTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SectionedTableViewController.m; sourceTree = ""; }; + 81472F9D1A1AB37500FD6EED /* SubtitleImageTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SubtitleImageTableViewController.h; sourceTree = ""; }; + 81472F9E1A1AB37500FD6EED /* SubtitleImageTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SubtitleImageTableViewController.m; sourceTree = ""; }; + 81472F9F1A1AB37500FD6EED /* CustomProductTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomProductTableViewController.h; sourceTree = ""; }; + 81472FA01A1AB37500FD6EED /* CustomProductTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomProductTableViewController.m; sourceTree = ""; }; + 81472FA41A1AB37500FD6EED /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 81472FA51A1AB37500FD6EED /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 81472FA71A1AB37500FD6EED /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ../Images.xcassets; sourceTree = ""; }; + 81472FA81A1AB37500FD6EED /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; + 81472FA91A1AB37500FD6EED /* 0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 0.png; sourceTree = ""; }; + 81472FAA1A1AB37500FD6EED /* 1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 1.png; sourceTree = ""; }; + 81472FAB1A1AB37500FD6EED /* 2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 2.png; sourceTree = ""; }; + 81472FB01A1AB37500FD6EED /* ParseUIDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; path = ParseUIDemo.app; sourceTree = SOURCE_ROOT; }; + 819A4B381A6808EA00D01241 /* SubtitleImageCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SubtitleImageCollectionViewController.h; sourceTree = ""; }; + 819A4B391A6808EA00D01241 /* SubtitleImageCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SubtitleImageCollectionViewController.m; sourceTree = ""; }; + 81C5993D1A64346000F574E8 /* SimpleCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimpleCollectionViewController.h; sourceTree = ""; }; + 81C5993E1A64346000F574E8 /* SimpleCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimpleCollectionViewController.m; sourceTree = ""; }; + 81C599401A6454C900F574E8 /* PaginatedCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PaginatedCollectionViewController.h; sourceTree = ""; }; + 81C599411A6454C900F574E8 /* PaginatedCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaginatedCollectionViewController.m; sourceTree = ""; }; + 81C599471A64636200F574E8 /* SectionedCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SectionedCollectionViewController.h; sourceTree = ""; }; + 81C599481A64636200F574E8 /* SectionedCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SectionedCollectionViewController.m; sourceTree = ""; }; + 81C8D9BB1A890BCA007B8DCF /* StoryboardTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoryboardTableViewController.h; sourceTree = ""; }; + 81C8D9BC1A890BCA007B8DCF /* StoryboardTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoryboardTableViewController.m; sourceTree = ""; }; + 81FC6B3A1A9D1ADB00104E45 /* CustomLogInViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLogInViewController.swift; sourceTree = ""; }; + 81FC6B491A9D1ADB00104E45 /* CustomSignUpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSignUpViewController.swift; sourceTree = ""; }; + 81FC6B591A9D1BFC00104E45 /* PaginatedTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginatedTableViewController.swift; sourceTree = ""; }; + 81FC6B5A1A9D1BFC00104E45 /* SectionedTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedTableViewController.swift; sourceTree = ""; }; + 81FC6B5B1A9D1BFC00104E45 /* SimpleTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleTableViewController.swift; sourceTree = ""; }; + 81FC6B5C1A9D1BFC00104E45 /* StoryboardTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardTableViewController.swift; sourceTree = ""; }; + 81FC6B5D1A9D1BFC00104E45 /* SubtitleImageTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubtitleImageTableViewController.swift; sourceTree = ""; }; + 81FC6B641A9D1C2000104E45 /* SimpleQueryTableStoryboard-Swift.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "SimpleQueryTableStoryboard-Swift.storyboard"; path = "ParseUIDemo/Swift/Resources/SimpleQueryTableStoryboard-Swift.storyboard"; sourceTree = SOURCE_ROOT; }; + 81FC6B671A9D1CE900104E45 /* PaginatedCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginatedCollectionViewController.swift; sourceTree = ""; }; + 81FC6B681A9D1CE900104E45 /* SectionedCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedCollectionViewController.swift; sourceTree = ""; }; + 81FC6B691A9D1CE900104E45 /* SimpleCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleCollectionViewController.swift; sourceTree = ""; }; + 81FC6B6A1A9D1CE900104E45 /* StoryboardCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardCollectionViewController.swift; sourceTree = ""; }; + 81FC6B6B1A9D1CE900104E45 /* SubtitleImageCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubtitleImageCollectionViewController.swift; sourceTree = ""; }; + 81FC6B711A9D1CFD00104E45 /* SimpleQueryCollectionStoryboard-Swift.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "SimpleQueryCollectionStoryboard-Swift.storyboard"; path = "ParseUIDemo/Swift/Resources/SimpleQueryCollectionStoryboard-Swift.storyboard"; sourceTree = SOURCE_ROOT; }; + 81FC6B741A9D1D4700104E45 /* CustomProductTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomProductTableViewController.swift; sourceTree = ""; }; + AB8B60E7098474B437486F70 /* Pods_ParseUIDemo_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ParseUIDemo_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B91892E5A1364B4A9F064902 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CCDBBFB547B75B4BD95F39F3 /* Pods-ParseUIDemo-Swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ParseUIDemo-Swift.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ParseUIDemo-Swift/Pods-ParseUIDemo-Swift.debug.xcconfig"; sourceTree = ""; }; + D665FA86F98330E6614E174A /* Pods-ParseUIDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ParseUIDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ParseUIDemo/Pods-ParseUIDemo.debug.xcconfig"; sourceTree = ""; }; + D6B620324851E8A17694D8AC /* Pods-ParseUIDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ParseUIDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-ParseUIDemo/Pods-ParseUIDemo.release.xcconfig"; sourceTree = ""; }; + F57F3D1B1B0C03BA0087F60B /* DeletionTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DeletionTableViewController.h; sourceTree = ""; }; + F57F3D1C1B0C03BA0087F60B /* DeletionTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DeletionTableViewController.m; sourceTree = ""; }; + F57F3D1F1B0C03C90087F60B /* DeletionCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DeletionCollectionViewController.h; sourceTree = ""; }; + F57F3D201B0C03C90087F60B /* DeletionCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DeletionCollectionViewController.m; sourceTree = ""; }; + F57F3D251B0C04EF0087F60B /* DeletionTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeletionTableViewController.swift; sourceTree = ""; }; + F57F3D281B0C05280087F60B /* DeletionCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeletionCollectionViewController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4A9A9493200D0329005D8F4B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A9A94D9200D04DE005D8F4B /* ParseFacebookUtilsV4.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8129E5F11A9CB067006752BC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A0ECBD8200D22C700BA84A3 /* Bolts.framework in Frameworks */, + 4A9A94F7200D125A005D8F4B /* AudioToolbox.framework in Frameworks */, + 4A9A94F5200D1252005D8F4B /* SystemConfiguration.framework in Frameworks */, + 4A9A94F4200D1248005D8F4B /* Social.framework in Frameworks */, + 4A9A94F3200D1241005D8F4B /* libsqlite3.tbd in Frameworks */, + 4A9A94ED200D1237005D8F4B /* Parse.framework in Frameworks */, + 4A9A94EE200D1237005D8F4B /* ParseFacebookUtilsV4.framework in Frameworks */, + 4A9A94EF200D1237005D8F4B /* ParseTwitterUtils.framework in Frameworks */, + 4A9A94EA200D068C005D8F4B /* ParseUI.framework in Frameworks */, + 4A9A94EB200D06A3005D8F4B /* FBSDKCoreKit.framework in Frameworks */, + 4A9A94EC200D06A3005D8F4B /* FBSDKLoginKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81472F641A1AB33800FD6EED /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A9A94E9200D05AA005D8F4B /* Social.framework in Frameworks */, + 4A9A94E7200D05A5005D8F4B /* SystemConfiguration.framework in Frameworks */, + 4A9A94E5200D0598005D8F4B /* libsqlite3.tbd in Frameworks */, + 4A9A949F200D0365005D8F4B /* ParseUI.framework in Frameworks */, + 4A9A94E2200D0574005D8F4B /* FBSDKCoreKit.framework in Frameworks */, + 4A9A941A200D01F1005D8F4B /* Bolts.framework in Frameworks */, + 4A9A941B200D01F1005D8F4B /* Parse.framework in Frameworks */, + 4A9A94E3200D0574005D8F4B /* FBSDKLoginKit.framework in Frameworks */, + 4A9A941C200D01F1005D8F4B /* ParseFacebookUtilsV4.framework in Frameworks */, + 4A9A941D200D01F1005D8F4B /* ParseTwitterUtils.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0867D691FE84028FC02AAC07 /* Breakpad */ = { + isa = PBXGroup; + children = ( + 4A9A9422200D0226005D8F4B /* ParseUI */, + 81472F621A1AB31F00FD6EED /* ParseUIDemo */, + 3F5307328F84566C90FBCF25 /* Frameworks */, + 06A1EF631BB6799700BA55EC /* ParseUIDemo.app */, + 06A1EF641BB6799700BA55EC /* ParseUIDemo.app */, + DC15AC42FDE929B85D4188E3 /* Pods */, + 4A9A9497200D0329005D8F4B /* ParseUI.framework */, + ); + name = Breakpad; + sourceTree = ""; + }; + 3F5307328F84566C90FBCF25 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4A0ECBD9200D22C700BA84A3 /* Bolts.framework */, + 4A9A94F6200D125A005D8F4B /* AudioToolbox.framework */, + 4A9A94F0200D1237005D8F4B /* Parse.framework */, + 4A9A94F1200D1237005D8F4B /* ParseFacebookUtilsV4.framework */, + 4A9A94F2200D1237005D8F4B /* ParseTwitterUtils.framework */, + 4A9A94E8200D05AA005D8F4B /* Social.framework */, + 4A9A94E6200D05A5005D8F4B /* SystemConfiguration.framework */, + 4A9A94E4200D0598005D8F4B /* libsqlite3.tbd */, + 4A9A94DA200D04DE005D8F4B /* ParseFacebookUtilsV4.framework */, + 4A9A94D8200D0470005D8F4B /* ParseFacebookUtilsV4.framework */, + 4A9A941E200D01F1005D8F4B /* Bolts.framework */, + 4A9A941F200D01F1005D8F4B /* Parse.framework */, + 4A9A9420200D01F1005D8F4B /* ParseFacebookUtilsV4.framework */, + 4A9A9421200D01F1005D8F4B /* ParseTwitterUtils.framework */, + B91892E5A1364B4A9F064902 /* Pods.framework */, + 720149E3A3C18665E2F10DC8 /* Pods_ParseUIDemo.framework */, + AB8B60E7098474B437486F70 /* Pods_ParseUIDemo_Swift.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4A9A9422200D0226005D8F4B /* ParseUI */ = { + isa = PBXGroup; + children = ( + 4A9A947F200D02CE005D8F4B /* Configurations */, + 4A9A9423200D0226005D8F4B /* Generated */, + 4A9A9426200D0226005D8F4B /* Classes */, + 4A9A9466200D0226005D8F4B /* Resources */, + 4A9A947A200D0226005D8F4B /* Other */, + 4A9A947D200D0226005D8F4B /* Scripts */, + 4A9A94DF200D055D005D8F4B /* Vendor */, + ); + name = ParseUI; + sourceTree = ""; + }; + 4A9A9423200D0226005D8F4B /* Generated */ = { + isa = PBXGroup; + children = ( + 4A9A9424200D0226005D8F4B /* PFResources.m */, + 4A9A9425200D0226005D8F4B /* PFResources.h */, + ); + path = Generated; + sourceTree = ""; + }; + 4A9A9426200D0226005D8F4B /* Classes */ = { + isa = PBXGroup; + children = ( + 4A9A9427200D0226005D8F4B /* ProductTableViewController */, + 4A9A942A200D0226005D8F4B /* Cells */, + 4A9A9431200D0226005D8F4B /* SignUpViewController */, + 4A9A9436200D0226005D8F4B /* Internal */, + 4A9A9455200D0226005D8F4B /* QueryCollectionViewController */, + 4A9A9458200D0226005D8F4B /* QueryTableViewController */, + 4A9A945B200D0226005D8F4B /* Views */, + 4A9A9460200D0226005D8F4B /* LogInViewController */, + ); + path = Classes; + sourceTree = ""; + }; + 4A9A9427200D0226005D8F4B /* ProductTableViewController */ = { + isa = PBXGroup; + children = ( + 4A9A9428200D0226005D8F4B /* PFProductTableViewController.m */, + 4A9A9429200D0226005D8F4B /* PFProductTableViewController.h */, + ); + path = ProductTableViewController; + sourceTree = ""; + }; + 4A9A942A200D0226005D8F4B /* Cells */ = { + isa = PBXGroup; + children = ( + 4A9A942B200D0226005D8F4B /* PFPurchaseTableViewCell.h */, + 4A9A942C200D0226005D8F4B /* PFCollectionViewCell.m */, + 4A9A942D200D0226005D8F4B /* PFTableViewCell.h */, + 4A9A942E200D0226005D8F4B /* PFPurchaseTableViewCell.m */, + 4A9A942F200D0226005D8F4B /* PFCollectionViewCell.h */, + 4A9A9430200D0226005D8F4B /* PFTableViewCell.m */, + ); + path = Cells; + sourceTree = ""; + }; + 4A9A9431200D0226005D8F4B /* SignUpViewController */ = { + isa = PBXGroup; + children = ( + 4A9A9432200D0226005D8F4B /* PFSignUpViewController.m */, + 4A9A9433200D0226005D8F4B /* PFSignUpView.h */, + 4A9A9434200D0226005D8F4B /* PFSignUpView.m */, + 4A9A9435200D0226005D8F4B /* PFSignUpViewController.h */, + ); + path = SignUpViewController; + sourceTree = ""; + }; + 4A9A9436200D0226005D8F4B /* Internal */ = { + isa = PBXGroup; + children = ( + 4A9A9437200D0226005D8F4B /* Cells */, + 4A9A943C200D0226005D8F4B /* PFImageCache.h */, + 4A9A943D200D0226005D8F4B /* PFLocalization.m */, + 4A9A943E200D0226005D8F4B /* Extensions */, + 4A9A9447200D0226005D8F4B /* PFLocalization.h */, + 4A9A9448200D0226005D8F4B /* PFImageCache.m */, + 4A9A9449200D0226005D8F4B /* Views */, + ); + path = Internal; + sourceTree = ""; + }; + 4A9A9437200D0226005D8F4B /* Cells */ = { + isa = PBXGroup; + children = ( + 4A9A9438200D0226005D8F4B /* PFActivityIndicatorTableViewCell.m */, + 4A9A9439200D0226005D8F4B /* PFActivityIndicatorCollectionReusableView.h */, + 4A9A943A200D0226005D8F4B /* PFActivityIndicatorTableViewCell.h */, + 4A9A943B200D0226005D8F4B /* PFActivityIndicatorCollectionReusableView.m */, + ); + path = Cells; + sourceTree = ""; + }; + 4A9A943E200D0226005D8F4B /* Extensions */ = { + isa = PBXGroup; + children = ( + 4A9A943F200D0226005D8F4B /* PFColor.h */, + 4A9A9440200D0226005D8F4B /* PFImage.m */, + 4A9A9441200D0226005D8F4B /* PFUIAlertView.h */, + 4A9A9442200D0226005D8F4B /* PFRect.h */, + 4A9A9443200D0226005D8F4B /* PFUIAlertView.m */, + 4A9A9444200D0226005D8F4B /* PFImage.h */, + 4A9A9445200D0226005D8F4B /* PFColor.m */, + 4A9A9446200D0226005D8F4B /* PFRect.m */, + ); + path = Extensions; + sourceTree = ""; + }; + 4A9A9449200D0226005D8F4B /* Views */ = { + isa = PBXGroup; + children = ( + 4A9A944A200D0226005D8F4B /* Buttons */, + 4A9A9453200D0226005D8F4B /* PFLoadingView.h */, + 4A9A9454200D0226005D8F4B /* PFLoadingView.m */, + ); + path = Views; + sourceTree = ""; + }; + 4A9A944A200D0226005D8F4B /* Buttons */ = { + isa = PBXGroup; + children = ( + 4A9A944B200D0226005D8F4B /* PFDismissButton.h */, + 4A9A944C200D0226005D8F4B /* PFTextButton.m */, + 4A9A944D200D0226005D8F4B /* PFPrimaryButton.m */, + 4A9A944E200D0226005D8F4B /* PFActionButton.m */, + 4A9A944F200D0226005D8F4B /* PFTextButton.h */, + 4A9A9450200D0226005D8F4B /* PFDismissButton.m */, + 4A9A9451200D0226005D8F4B /* PFPrimaryButton.h */, + 4A9A9452200D0226005D8F4B /* PFActionButton.h */, + ); + path = Buttons; + sourceTree = ""; + }; + 4A9A9455200D0226005D8F4B /* QueryCollectionViewController */ = { + isa = PBXGroup; + children = ( + 4A9A9456200D0226005D8F4B /* PFQueryCollectionViewController.m */, + 4A9A9457200D0226005D8F4B /* PFQueryCollectionViewController.h */, + ); + path = QueryCollectionViewController; + sourceTree = ""; + }; + 4A9A9458200D0226005D8F4B /* QueryTableViewController */ = { + isa = PBXGroup; + children = ( + 4A9A9459200D0226005D8F4B /* PFQueryTableViewController.h */, + 4A9A945A200D0226005D8F4B /* PFQueryTableViewController.m */, + ); + path = QueryTableViewController; + sourceTree = ""; + }; + 4A9A945B200D0226005D8F4B /* Views */ = { + isa = PBXGroup; + children = ( + 4A9A945C200D0226005D8F4B /* PFTextField.h */, + 4A9A945D200D0226005D8F4B /* PFImageView.m */, + 4A9A945E200D0226005D8F4B /* PFTextField.m */, + 4A9A945F200D0226005D8F4B /* PFImageView.h */, + ); + path = Views; + sourceTree = ""; + }; + 4A9A9460200D0226005D8F4B /* LogInViewController */ = { + isa = PBXGroup; + children = ( + 4A9A9461200D0226005D8F4B /* PFLogInViewController.m */, + 4A9A9462200D0226005D8F4B /* PFLogInView.m */, + 4A9A9463200D0226005D8F4B /* PFLogInView.h */, + 4A9A9464200D0226005D8F4B /* PFLogInViewController.h */, + 4A9A9465200D0226005D8F4B /* PFLogInView_Private.h */, + ); + path = LogInViewController; + sourceTree = ""; + }; + 4A9A9466200D0226005D8F4B /* Resources */ = { + isa = PBXGroup; + children = ( + 4A9A9467200D0226005D8F4B /* Images */, + 4A9A9471200D0226005D8F4B /* Info.plist */, + 4A9A9472200D0226005D8F4B /* Localization */, + ); + path = Resources; + sourceTree = ""; + }; + 4A9A9467200D0226005D8F4B /* Images */ = { + isa = PBXGroup; + children = ( + 4A9A9468200D0226005D8F4B /* twitter_icon@3x.png */, + 4A9A9469200D0226005D8F4B /* facebook_icon@2x.png */, + 4A9A946A200D0226005D8F4B /* parse_logo.png */, + 4A9A946B200D0226005D8F4B /* facebook_icon@3x.png */, + 4A9A946C200D0226005D8F4B /* twitter_icon@2x.png */, + 4A9A946D200D0226005D8F4B /* parse_logo@2x.png */, + 4A9A946E200D0226005D8F4B /* parse_logo@3x.png */, + 4A9A946F200D0226005D8F4B /* facebook_icon.png */, + 4A9A9470200D0226005D8F4B /* twitter_icon.png */, + ); + path = Images; + sourceTree = ""; + }; + 4A9A9472200D0226005D8F4B /* Localization */ = { + isa = PBXGroup; + children = ( + 4A9A9473200D0226005D8F4B /* ParseUI.strings */, + ); + path = Localization; + sourceTree = ""; + }; + 4A9A947A200D0226005D8F4B /* Other */ = { + isa = PBXGroup; + children = ( + 4A9A947B200D0226005D8F4B /* ParseUIConstants.h */, + 4A9A947C200D0226005D8F4B /* ParseUI.h */, + ); + path = Other; + sourceTree = ""; + }; + 4A9A947D200D0226005D8F4B /* Scripts */ = { + isa = PBXGroup; + children = ( + 4A9A947E200D0226005D8F4B /* convert_images.rb */, + ); + path = Scripts; + sourceTree = ""; + }; + 4A9A947F200D02CE005D8F4B /* Configurations */ = { + isa = PBXGroup; + children = ( + 4A9A9491200D02CE005D8F4B /* ParseUI-iOS-Dynamic.xcconfig */, + 4A9A9480200D02CE005D8F4B /* ParseUI-iOS.xcconfig */, + 4A9A9481200D02CE005D8F4B /* Shared */, + ); + path = Configurations; + sourceTree = ""; + }; + 4A9A9481200D02CE005D8F4B /* Shared */ = { + isa = PBXGroup; + children = ( + 4A9A9482200D02CE005D8F4B /* Common.xcconfig */, + 4A9A9483200D02CE005D8F4B /* Platform */, + 4A9A9488200D02CE005D8F4B /* Product */, + 4A9A948D200D02CE005D8F4B /* Project */, + 4A9A9490200D02CE005D8F4B /* Warnings.xcconfig */, + ); + path = Shared; + sourceTree = ""; + }; + 4A9A9483200D02CE005D8F4B /* Platform */ = { + isa = PBXGroup; + children = ( + 4A9A9484200D02CE005D8F4B /* macOS.xcconfig */, + 4A9A9485200D02CE005D8F4B /* tvOS.xcconfig */, + 4A9A9486200D02CE005D8F4B /* iOS.xcconfig */, + 4A9A9487200D02CE005D8F4B /* watchOS.xcconfig */, + ); + path = Platform; + sourceTree = ""; + }; + 4A9A9488200D02CE005D8F4B /* Product */ = { + isa = PBXGroup; + children = ( + 4A9A9489200D02CE005D8F4B /* Application.xcconfig */, + 4A9A948A200D02CE005D8F4B /* DynamicFramework.xcconfig */, + 4A9A948B200D02CE005D8F4B /* StaticFramework.xcconfig */, + 4A9A948C200D02CE005D8F4B /* LogicTests.xcconfig */, + ); + path = Product; + sourceTree = ""; + }; + 4A9A948D200D02CE005D8F4B /* Project */ = { + isa = PBXGroup; + children = ( + 4A9A948E200D02CE005D8F4B /* Debug.xcconfig */, + 4A9A948F200D02CE005D8F4B /* Release.xcconfig */, + ); + path = Project; + sourceTree = ""; + }; + 4A9A94DF200D055D005D8F4B /* Vendor */ = { + isa = PBXGroup; + children = ( + 4A9A94E1200D056A005D8F4B /* FBSDKCoreKit.framework */, + 4A9A94E0200D056A005D8F4B /* FBSDKLoginKit.framework */, + ); + path = Vendor; + sourceTree = ""; + }; + 8129E5DD1A9CB052006752BC /* Obj-C */ = { + isa = PBXGroup; + children = ( + 81472F8D1A1AB37500FD6EED /* AppDelegate.h */, + 81472F8E1A1AB37500FD6EED /* AppDelegate.m */, + 81472F8F1A1AB37500FD6EED /* PFUIDemoViewController.h */, + 81472F901A1AB37500FD6EED /* PFUIDemoViewController.m */, + 81472FA21A1AB37500FD6EED /* CustomViewControllers */, + ); + name = "Obj-C"; + sourceTree = ""; + }; + 8129E6021A9CB14E006752BC /* Swift */ = { + isa = PBXGroup; + children = ( + 8129E6081A9CB1BE006752BC /* AppDelegate.swift */, + 8129E6391A9CB320006752BC /* UIDemoViewController.swift */, + 81FC6B381A9D1ADB00104E45 /* CustomViewControllers */, + ); + name = Swift; + path = ParseUIDemo/Swift; + sourceTree = SOURCE_ROOT; + }; + 81472F621A1AB31F00FD6EED /* ParseUIDemo */ = { + isa = PBXGroup; + children = ( + 81472FA31A1AB37500FD6EED /* Classes */, + 81472FA61A1AB37500FD6EED /* Other */, + 81472FAC1A1AB37500FD6EED /* Resources */, + 81472FB01A1AB37500FD6EED /* ParseUIDemo.app */, + ); + name = ParseUIDemo; + sourceTree = ""; + }; + 81472F931A1AB37500FD6EED /* SignUpViewController */ = { + isa = PBXGroup; + children = ( + 81472F911A1AB37500FD6EED /* CustomSignUpViewController.h */, + 81472F921A1AB37500FD6EED /* CustomSignUpViewController.m */, + ); + path = SignUpViewController; + sourceTree = ""; + }; + 81472F961A1AB37500FD6EED /* LogInViewController */ = { + isa = PBXGroup; + children = ( + 81472F941A1AB37500FD6EED /* CustomLogInViewController.h */, + 81472F951A1AB37500FD6EED /* CustomLogInViewController.m */, + ); + path = LogInViewController; + sourceTree = ""; + }; + 81472FA11A1AB37500FD6EED /* QueryTableViewController */ = { + isa = PBXGroup; + children = ( + F57F3D1B1B0C03BA0087F60B /* DeletionTableViewController.h */, + F57F3D1C1B0C03BA0087F60B /* DeletionTableViewController.m */, + 81472F971A1AB37500FD6EED /* SimpleTableViewController.h */, + 81472F981A1AB37500FD6EED /* SimpleTableViewController.m */, + 81472F991A1AB37500FD6EED /* PaginatedTableViewController.h */, + 81472F9A1A1AB37500FD6EED /* PaginatedTableViewController.m */, + 81472F9B1A1AB37500FD6EED /* SectionedTableViewController.h */, + 81472F9C1A1AB37500FD6EED /* SectionedTableViewController.m */, + 81472F9D1A1AB37500FD6EED /* SubtitleImageTableViewController.h */, + 81472F9E1A1AB37500FD6EED /* SubtitleImageTableViewController.m */, + 81472F9F1A1AB37500FD6EED /* CustomProductTableViewController.h */, + 81472FA01A1AB37500FD6EED /* CustomProductTableViewController.m */, + 81C8D9BB1A890BCA007B8DCF /* StoryboardTableViewController.h */, + 81C8D9BC1A890BCA007B8DCF /* StoryboardTableViewController.m */, + 814155B01A9CD9CA002D9683 /* SimpleQueryTableStoryboard.storyboard */, + ); + path = QueryTableViewController; + sourceTree = ""; + }; + 81472FA21A1AB37500FD6EED /* CustomViewControllers */ = { + isa = PBXGroup; + children = ( + 81472F931A1AB37500FD6EED /* SignUpViewController */, + 81472F961A1AB37500FD6EED /* LogInViewController */, + 81472FA11A1AB37500FD6EED /* QueryTableViewController */, + 81C5993C1A64344800F574E8 /* QueryCollectionViewController */, + ); + path = CustomViewControllers; + sourceTree = ""; + }; + 81472FA31A1AB37500FD6EED /* Classes */ = { + isa = PBXGroup; + children = ( + 8129E6021A9CB14E006752BC /* Swift */, + 8129E5DD1A9CB052006752BC /* Obj-C */, + ); + name = Classes; + path = ParseUIDemo/Classes; + sourceTree = ""; + }; + 81472FA61A1AB37500FD6EED /* Other */ = { + isa = PBXGroup; + children = ( + 81472FA41A1AB37500FD6EED /* Info.plist */, + 81472FA51A1AB37500FD6EED /* main.m */, + ); + name = Other; + path = ParseUIDemo/Other; + sourceTree = ""; + }; + 81472FAC1A1AB37500FD6EED /* Resources */ = { + isa = PBXGroup; + children = ( + 81472FA71A1AB37500FD6EED /* Images.xcassets */, + 81472FA81A1AB37500FD6EED /* Icon.png */, + 81472FA91A1AB37500FD6EED /* 0.png */, + 81472FAA1A1AB37500FD6EED /* 1.png */, + 81472FAB1A1AB37500FD6EED /* 2.png */, + ); + name = Resources; + path = ParseUIDemo/Resources; + sourceTree = ""; + }; + 81C5993C1A64344800F574E8 /* QueryCollectionViewController */ = { + isa = PBXGroup; + children = ( + F57F3D1F1B0C03C90087F60B /* DeletionCollectionViewController.h */, + F57F3D201B0C03C90087F60B /* DeletionCollectionViewController.m */, + 81C5993D1A64346000F574E8 /* SimpleCollectionViewController.h */, + 81C5993E1A64346000F574E8 /* SimpleCollectionViewController.m */, + 81C599401A6454C900F574E8 /* PaginatedCollectionViewController.h */, + 81C599411A6454C900F574E8 /* PaginatedCollectionViewController.m */, + 81C599471A64636200F574E8 /* SectionedCollectionViewController.h */, + 81C599481A64636200F574E8 /* SectionedCollectionViewController.m */, + 819A4B381A6808EA00D01241 /* SubtitleImageCollectionViewController.h */, + 819A4B391A6808EA00D01241 /* SubtitleImageCollectionViewController.m */, + 812E5C021A7A8EFB000FBDE1 /* StoryboardCollectionViewController.h */, + 812E5C031A7A8EFB000FBDE1 /* StoryboardCollectionViewController.m */, + 812E5C001A7A8EB4000FBDE1 /* SimpleQueryCollectionStoryboard.storyboard */, + ); + path = QueryCollectionViewController; + sourceTree = ""; + }; + 81FC6B381A9D1ADB00104E45 /* CustomViewControllers */ = { + isa = PBXGroup; + children = ( + 81FC6B391A9D1ADB00104E45 /* LogInViewController */, + 81FC6B481A9D1ADB00104E45 /* SignUpViewController */, + 81FC6B571A9D1BFC00104E45 /* QueryTableViewController */, + 81FC6B661A9D1CE900104E45 /* QueryCollectionViewController */, + 81FC6B731A9D1D4700104E45 /* ProductTableViewController */, + ); + path = CustomViewControllers; + sourceTree = ""; + }; + 81FC6B391A9D1ADB00104E45 /* LogInViewController */ = { + isa = PBXGroup; + children = ( + 81FC6B3A1A9D1ADB00104E45 /* CustomLogInViewController.swift */, + ); + path = LogInViewController; + sourceTree = ""; + }; + 81FC6B481A9D1ADB00104E45 /* SignUpViewController */ = { + isa = PBXGroup; + children = ( + 81FC6B491A9D1ADB00104E45 /* CustomSignUpViewController.swift */, + ); + path = SignUpViewController; + sourceTree = ""; + }; + 81FC6B571A9D1BFC00104E45 /* QueryTableViewController */ = { + isa = PBXGroup; + children = ( + F57F3D251B0C04EF0087F60B /* DeletionTableViewController.swift */, + 81FC6B5B1A9D1BFC00104E45 /* SimpleTableViewController.swift */, + 81FC6B591A9D1BFC00104E45 /* PaginatedTableViewController.swift */, + 81FC6B5A1A9D1BFC00104E45 /* SectionedTableViewController.swift */, + 81FC6B5C1A9D1BFC00104E45 /* StoryboardTableViewController.swift */, + 81FC6B5D1A9D1BFC00104E45 /* SubtitleImageTableViewController.swift */, + 81FC6B641A9D1C2000104E45 /* SimpleQueryTableStoryboard-Swift.storyboard */, + ); + path = QueryTableViewController; + sourceTree = ""; + }; + 81FC6B661A9D1CE900104E45 /* QueryCollectionViewController */ = { + isa = PBXGroup; + children = ( + F57F3D281B0C05280087F60B /* DeletionCollectionViewController.swift */, + 81FC6B691A9D1CE900104E45 /* SimpleCollectionViewController.swift */, + 81FC6B671A9D1CE900104E45 /* PaginatedCollectionViewController.swift */, + 81FC6B681A9D1CE900104E45 /* SectionedCollectionViewController.swift */, + 81FC6B6A1A9D1CE900104E45 /* StoryboardCollectionViewController.swift */, + 81FC6B6B1A9D1CE900104E45 /* SubtitleImageCollectionViewController.swift */, + 81FC6B711A9D1CFD00104E45 /* SimpleQueryCollectionStoryboard-Swift.storyboard */, + ); + path = QueryCollectionViewController; + sourceTree = ""; + }; + 81FC6B731A9D1D4700104E45 /* ProductTableViewController */ = { + isa = PBXGroup; + children = ( + 81FC6B741A9D1D4700104E45 /* CustomProductTableViewController.swift */, + ); + path = ProductTableViewController; + sourceTree = ""; + }; + DC15AC42FDE929B85D4188E3 /* Pods */ = { + isa = PBXGroup; + children = ( + D665FA86F98330E6614E174A /* Pods-ParseUIDemo.debug.xcconfig */, + D6B620324851E8A17694D8AC /* Pods-ParseUIDemo.release.xcconfig */, + CCDBBFB547B75B4BD95F39F3 /* Pods-ParseUIDemo-Swift.debug.xcconfig */, + 7C1041A7E103153B26236744 /* Pods-ParseUIDemo-Swift.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 4A9A9494200D0329005D8F4B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A9A94C6200D03BA005D8F4B /* PFLoadingView.h in Headers */, + 4A9A94C9200D03BE005D8F4B /* PFQueryCollectionViewController.h in Headers */, + 4A9A94B6200D03B6005D8F4B /* PFUIAlertView.h in Headers */, + 4A9A94AD200D03B6005D8F4B /* PFSignUpViewController.h in Headers */, + 4A9A94CC200D03C8005D8F4B /* PFTextField.h in Headers */, + 4A9A94D2200D03C8005D8F4B /* PFLogInView.h in Headers */, + 4A9A94D5200D0413005D8F4B /* ParseUIConstants.h in Headers */, + 4A9A94BC200D03B6005D8F4B /* PFLocalization.h in Headers */, + 4A9A94A6200D03B6005D8F4B /* PFTableViewCell.h in Headers */, + 4A9A94CF200D03C8005D8F4B /* PFImageView.h in Headers */, + 4A9A94A8200D03B6005D8F4B /* PFCollectionViewCell.h in Headers */, + 4A9A94BE200D03BA005D8F4B /* PFDismissButton.h in Headers */, + 4A9A94AF200D03B6005D8F4B /* PFActivityIndicatorCollectionReusableView.h in Headers */, + 4A9A94CA200D03C8005D8F4B /* PFQueryTableViewController.h in Headers */, + 4A9A94D3200D03C8005D8F4B /* PFLogInViewController.h in Headers */, + 4A9A94A4200D03B6005D8F4B /* PFPurchaseTableViewCell.h in Headers */, + 4A9A94B2200D03B6005D8F4B /* PFImageCache.h in Headers */, + 4A9A94C4200D03BA005D8F4B /* PFPrimaryButton.h in Headers */, + 4A9A94A3200D03B6005D8F4B /* PFProductTableViewController.h in Headers */, + 4A9A94C2200D03BA005D8F4B /* PFTextButton.h in Headers */, + 4A9A94B9200D03B6005D8F4B /* PFImage.h in Headers */, + 4A9A94D4200D03C8005D8F4B /* PFLogInView_Private.h in Headers */, + 4A9A94B4200D03B6005D8F4B /* PFColor.h in Headers */, + 4A9A94A1200D03AA005D8F4B /* PFResources.h in Headers */, + 4A9A94C5200D03BA005D8F4B /* PFActionButton.h in Headers */, + 4A9A94AB200D03B6005D8F4B /* PFSignUpView.h in Headers */, + 4A9A94B7200D03B6005D8F4B /* PFRect.h in Headers */, + 4A9A94D6200D0413005D8F4B /* ParseUI.h in Headers */, + 4A9A94B0200D03B6005D8F4B /* PFActivityIndicatorTableViewCell.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 4A9A9496200D0329005D8F4B /* ParseUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4A9A949C200D0329005D8F4B /* Build configuration list for PBXNativeTarget "ParseUI" */; + buildPhases = ( + 4A9A9492200D0329005D8F4B /* Sources */, + 4A9A9493200D0329005D8F4B /* Frameworks */, + 4A9A9494200D0329005D8F4B /* Headers */, + 4A9A9495200D0329005D8F4B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ParseUI; + productName = ParseUI; + productReference = 4A9A9497200D0329005D8F4B /* ParseUI.framework */; + productType = "com.apple.product-type.framework"; + }; + 8129E5DE1A9CB067006752BC /* ParseUIDemo-Swift */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8129E5FD1A9CB067006752BC /* Build configuration list for PBXNativeTarget "ParseUIDemo-Swift" */; + buildPhases = ( + 8129E5E01A9CB067006752BC /* Sources */, + 8129E5F11A9CB067006752BC /* Frameworks */, + 8129E5F41A9CB067006752BC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ParseUIDemo-Swift"; + productName = ParseUIDemo; + productReference = 06A1EF641BB6799700BA55EC /* ParseUIDemo.app */; + productType = "com.apple.product-type.application"; + }; + 81472F661A1AB33800FD6EED /* ParseUIDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81472F871A1AB33800FD6EED /* Build configuration list for PBXNativeTarget "ParseUIDemo" */; + buildPhases = ( + 4A9A94DB200D0510005D8F4B /* ShellScript */, + 81472F631A1AB33800FD6EED /* Sources */, + 81472F641A1AB33800FD6EED /* Frameworks */, + 81472F651A1AB33800FD6EED /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ParseUIDemo; + productName = ParseUIDemo; + productReference = 06A1EF631BB6799700BA55EC /* ParseUIDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = PF; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Parse Inc."; + TargetAttributes = { + 4A9A9496200D0329005D8F4B = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; + 81472F661A1AB33800FD6EED = { + CreatedOnToolsVersion = 6.1; + }; + }; + }; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "ParseUI" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + da, + de, + es, + fr, + it, + ja, + nl, + no, + sl, + sv, + tr, + en, + Base, + he, + "pt-BR", + ); + mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */; + productRefGroup = 0867D691FE84028FC02AAC07 /* Breakpad */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 81472F661A1AB33800FD6EED /* ParseUIDemo */, + 8129E5DE1A9CB067006752BC /* ParseUIDemo-Swift */, + 4A9A9496200D0329005D8F4B /* ParseUI */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4A9A9495200D0329005D8F4B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8129E5F41A9CB067006752BC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8129E5F51A9CB067006752BC /* Images.xcassets in Resources */, + 8129E5F71A9CB067006752BC /* 0.png in Resources */, + 81FC6B651A9D1C2000104E45 /* SimpleQueryTableStoryboard-Swift.storyboard in Resources */, + 8129E5F81A9CB067006752BC /* 1.png in Resources */, + 8129E5F91A9CB067006752BC /* 2.png in Resources */, + 8129E5FA1A9CB067006752BC /* SimpleQueryCollectionStoryboard.storyboard in Resources */, + 81FC6B721A9D1CFD00104E45 /* SimpleQueryCollectionStoryboard-Swift.storyboard in Resources */, + 8129E5FB1A9CB067006752BC /* Icon.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81472F651A1AB33800FD6EED /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81472FBC1A1AB37500FD6EED /* Images.xcassets in Resources */, + 814155B11A9CD9CA002D9683 /* SimpleQueryTableStoryboard.storyboard in Resources */, + 81472FBE1A1AB37500FD6EED /* 0.png in Resources */, + 81472FBF1A1AB37500FD6EED /* 1.png in Resources */, + 81472FC01A1AB37500FD6EED /* 2.png in Resources */, + 812E5C011A7A8EB4000FBDE1 /* SimpleQueryCollectionStoryboard.storyboard in Resources */, + 81472FBD1A1AB37500FD6EED /* Icon.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 4A9A94DB200D0510005D8F4B /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ ! -d $SRCROOT/Vendor ]; then\nmkdir $SRCROOT/Vendor\nfi\n\ncd $SRCROOT/Vendor\n\nif [[ ! -d \"FBSDKCoreKit.framework\" || ! -d \"FBSDKLoginKit.framework\" ]]; then\nARCHIVE_NAME=FBSDK.zip\n\nARCHIVE_URL=\"https://origincache.facebook.com/developers/resources/?id=facebook-ios-sdk-current.zip\"\ncurl $ARCHIVE_URL -o $ARCHIVE_NAME\n\nunzip $ARCHIVE_NAME -d fbsdk\nmv fbsdk/FBSDKCoreKit.framework .\nmv fbsdk/FBSDKLoginKit.framework .\n\nrm $ARCHIVE_NAME\nrm -r fbsdk\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4A9A9492200D0329005D8F4B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A9A94BF200D03BA005D8F4B /* PFTextButton.m in Sources */, + 4A9A94B1200D03B6005D8F4B /* PFActivityIndicatorCollectionReusableView.m in Sources */, + 4A9A94AE200D03B6005D8F4B /* PFActivityIndicatorTableViewCell.m in Sources */, + 4A9A94BA200D03B6005D8F4B /* PFColor.m in Sources */, + 4A9A94B3200D03B6005D8F4B /* PFLocalization.m in Sources */, + 4A9A94C7200D03BA005D8F4B /* PFLoadingView.m in Sources */, + 4A9A94D1200D03C8005D8F4B /* PFLogInView.m in Sources */, + 4A9A94C3200D03BA005D8F4B /* PFDismissButton.m in Sources */, + 4A9A94C0200D03BA005D8F4B /* PFPrimaryButton.m in Sources */, + 4A9A94A7200D03B6005D8F4B /* PFPurchaseTableViewCell.m in Sources */, + 4A9A94BB200D03B6005D8F4B /* PFRect.m in Sources */, + 4A9A94CD200D03C8005D8F4B /* PFImageView.m in Sources */, + 4A9A94A0200D03AA005D8F4B /* PFResources.m in Sources */, + 4A9A94A2200D03B6005D8F4B /* PFProductTableViewController.m in Sources */, + 4A9A94A5200D03B6005D8F4B /* PFCollectionViewCell.m in Sources */, + 4A9A94C1200D03BA005D8F4B /* PFActionButton.m in Sources */, + 4A9A94B5200D03B6005D8F4B /* PFImage.m in Sources */, + 4A9A94BD200D03B6005D8F4B /* PFImageCache.m in Sources */, + 4A9A94C8200D03BE005D8F4B /* PFQueryCollectionViewController.m in Sources */, + 4A9A94AA200D03B6005D8F4B /* PFSignUpViewController.m in Sources */, + 4A9A94CB200D03C8005D8F4B /* PFQueryTableViewController.m in Sources */, + 4A9A94B8200D03B6005D8F4B /* PFUIAlertView.m in Sources */, + 4A9A94A9200D03B6005D8F4B /* PFTableViewCell.m in Sources */, + 4A9A94D0200D03C8005D8F4B /* PFLogInViewController.m in Sources */, + 4A9A94AC200D03B6005D8F4B /* PFSignUpView.m in Sources */, + 4A9A94CE200D03C8005D8F4B /* PFTextField.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8129E5E01A9CB067006752BC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81FC6B4A1A9D1ADB00104E45 /* CustomLogInViewController.swift in Sources */, + 81FC6B601A9D1BFC00104E45 /* SectionedTableViewController.swift in Sources */, + 81FC6B5F1A9D1BFC00104E45 /* PaginatedTableViewController.swift in Sources */, + 81FC6B611A9D1BFC00104E45 /* SimpleTableViewController.swift in Sources */, + F57F3D291B0C05280087F60B /* DeletionCollectionViewController.swift in Sources */, + 81FC6B6E1A9D1CE900104E45 /* SimpleCollectionViewController.swift in Sources */, + F57F3D271B0C050C0087F60B /* DeletionTableViewController.swift in Sources */, + 81FC6B6D1A9D1CE900104E45 /* SectionedCollectionViewController.swift in Sources */, + 81FC6B561A9D1ADB00104E45 /* CustomSignUpViewController.swift in Sources */, + 81FC6B621A9D1BFC00104E45 /* StoryboardTableViewController.swift in Sources */, + 81FC6B751A9D1D4700104E45 /* CustomProductTableViewController.swift in Sources */, + 81FC6B6C1A9D1CE900104E45 /* PaginatedCollectionViewController.swift in Sources */, + 81FC6B6F1A9D1CE900104E45 /* StoryboardCollectionViewController.swift in Sources */, + 81FC6B701A9D1CE900104E45 /* SubtitleImageCollectionViewController.swift in Sources */, + 8129E6091A9CB1BE006752BC /* AppDelegate.swift in Sources */, + 8129E63A1A9CB320006752BC /* UIDemoViewController.swift in Sources */, + 81FC6B631A9D1BFC00104E45 /* SubtitleImageTableViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 81472F631A1AB33800FD6EED /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81C5993F1A64346000F574E8 /* SimpleCollectionViewController.m in Sources */, + 81472FBB1A1AB37500FD6EED /* main.m in Sources */, + 81C599421A6454C900F574E8 /* PaginatedCollectionViewController.m in Sources */, + 81472FB31A1AB37500FD6EED /* CustomSignUpViewController.m in Sources */, + 81472FB41A1AB37500FD6EED /* CustomLogInViewController.m in Sources */, + 81472FB91A1AB37500FD6EED /* CustomProductTableViewController.m in Sources */, + 819A4B3A1A6808EA00D01241 /* SubtitleImageCollectionViewController.m in Sources */, + F57F3D241B0C03DB0087F60B /* DeletionTableViewController.m in Sources */, + 812E5C041A7A8EFB000FBDE1 /* StoryboardCollectionViewController.m in Sources */, + 81472FB51A1AB37500FD6EED /* SimpleTableViewController.m in Sources */, + F57F3D231B0C03D40087F60B /* DeletionCollectionViewController.m in Sources */, + 81472FB61A1AB37500FD6EED /* PaginatedTableViewController.m in Sources */, + 81C8D9BD1A890BCA007B8DCF /* StoryboardTableViewController.m in Sources */, + 81472FB71A1AB37500FD6EED /* SectionedTableViewController.m in Sources */, + 81472FB11A1AB37500FD6EED /* AppDelegate.m in Sources */, + 81472FB21A1AB37500FD6EED /* PFUIDemoViewController.m in Sources */, + 81C599491A64636200F574E8 /* SectionedCollectionViewController.m in Sources */, + 81472FB81A1AB37500FD6EED /* SubtitleImageTableViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 4A9A9473200D0226005D8F4B /* ParseUI.strings */ = { + isa = PBXVariantGroup; + children = ( + 4A9A9474200D0226005D8F4B /* de */, + 4A9A9475200D0226005D8F4B /* he */, + 4A9A9476200D0226005D8F4B /* en */, + 4A9A9477200D0226005D8F4B /* sv */, + 4A9A9478200D0226005D8F4B /* pt-BR */, + 4A9A9479200D0226005D8F4B /* fr */, + ); + name = ParseUI.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + 4A9A949D200D0329005D8F4B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4A9A9480200D02CE005D8F4B /* ParseUI-iOS.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 4A9A949E200D0329005D8F4B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4A9A9480200D02CE005D8F4B /* ParseUI-iOS.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 8129E5FE1A9CB067006752BC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Vendor", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = ParseUIDemo/Other/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.parse.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = ParseUIDemo; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8129E5FF1A9CB067006752BC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Vendor", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = ParseUIDemo/Other/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.parse.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = ParseUIDemo; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 81472F881A1AB33800FD6EED /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Vendor", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = ParseUIDemo/Other/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.parse.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 81472F891A1AB33800FD6EED /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Vendor", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = ParseUIDemo/Other/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.parse.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "ParseUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4A9A949C200D0329005D8F4B /* Build configuration list for PBXNativeTarget "ParseUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4A9A949D200D0329005D8F4B /* Debug */, + 4A9A949E200D0329005D8F4B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8129E5FD1A9CB067006752BC /* Build configuration list for PBXNativeTarget "ParseUIDemo-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8129E5FE1A9CB067006752BC /* Debug */, + 8129E5FF1A9CB067006752BC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 81472F871A1AB33800FD6EED /* Build configuration list for PBXNativeTarget "ParseUIDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81472F881A1AB33800FD6EED /* Debug */, + 81472F891A1AB33800FD6EED /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/ParseUI/ParseUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ParseUI/ParseUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1b8e778f1 --- /dev/null +++ b/ParseUI/ParseUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseStarter-tvOS.xcscheme b/ParseUI/ParseUI.xcodeproj/xcshareddata/xcschemes/ParseUIDemo-Swift.xcscheme similarity index 59% rename from ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseStarter-tvOS.xcscheme rename to ParseUI/ParseUI.xcodeproj/xcshareddata/xcschemes/ParseUIDemo-Swift.xcscheme index 451e28db6..0b09a46c6 100644 --- a/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter-Swift.xcodeproj/xcshareddata/xcschemes/ParseStarter-tvOS.xcscheme +++ b/ParseUI/ParseUI.xcodeproj/xcshareddata/xcschemes/ParseUIDemo-Swift.xcscheme @@ -1,10 +1,10 @@ + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> - - - - + BlueprintIdentifier = "8129E5DE1A9CB067006752BC" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo-Swift" + ReferencedContainer = "container:ParseUI.xcodeproj"> @@ -47,10 +33,10 @@ + BlueprintIdentifier = "8129E5DE1A9CB067006752BC" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo-Swift" + ReferencedContainer = "container:ParseUI.xcodeproj"> @@ -71,10 +57,10 @@ runnableDebuggingMode = "0"> + BlueprintIdentifier = "8129E5DE1A9CB067006752BC" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo-Swift" + ReferencedContainer = "container:ParseUI.xcodeproj"> @@ -90,10 +76,10 @@ runnableDebuggingMode = "0"> + BlueprintIdentifier = "8129E5DE1A9CB067006752BC" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo-Swift" + ReferencedContainer = "container:ParseUI.xcodeproj"> diff --git a/ParseStarterProject/iOS/ParseStarterProject/ParseStarterProject.xcodeproj/xcshareddata/xcschemes/ParseStarterProject.xcscheme b/ParseUI/ParseUI.xcodeproj/xcshareddata/xcschemes/ParseUIDemo.xcscheme similarity index 58% rename from ParseStarterProject/iOS/ParseStarterProject/ParseStarterProject.xcodeproj/xcshareddata/xcschemes/ParseStarterProject.xcscheme rename to ParseUI/ParseUI.xcodeproj/xcshareddata/xcschemes/ParseUIDemo.xcscheme index 0841d0add..66648fe96 100644 --- a/ParseStarterProject/iOS/ParseStarterProject/ParseStarterProject.xcodeproj/xcshareddata/xcschemes/ParseStarterProject.xcscheme +++ b/ParseUI/ParseUI.xcodeproj/xcshareddata/xcschemes/ParseUIDemo.xcscheme @@ -1,10 +1,10 @@ + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> - - - - + BlueprintIdentifier = "81472F661A1AB33800FD6EED" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo" + ReferencedContainer = "container:ParseUI.xcodeproj"> @@ -47,10 +33,10 @@ + BlueprintIdentifier = "81472F661A1AB33800FD6EED" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo" + ReferencedContainer = "container:ParseUI.xcodeproj"> @@ -71,10 +57,10 @@ runnableDebuggingMode = "0"> + BlueprintIdentifier = "81472F661A1AB33800FD6EED" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo" + ReferencedContainer = "container:ParseUI.xcodeproj"> @@ -90,10 +76,10 @@ runnableDebuggingMode = "0"> + BlueprintIdentifier = "81472F661A1AB33800FD6EED" + BuildableName = "ParseUIDemo.app" + BlueprintName = "ParseUIDemo" + ReferencedContainer = "container:ParseUI.xcodeproj"> diff --git a/ParseUI/ParseUIDemo/Classes/AppDelegate.h b/ParseUI/ParseUIDemo/Classes/AppDelegate.h new file mode 100644 index 000000000..935592be2 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/AppDelegate.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface AppDelegate : UIResponder + +@property (nonatomic, strong) UIWindow *window; + +@end diff --git a/ParseUI/ParseUIDemo/Classes/AppDelegate.m b/ParseUI/ParseUIDemo/Classes/AppDelegate.m new file mode 100644 index 000000000..e6707ba50 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/AppDelegate.m @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "AppDelegate.h" + +#import +#import +#import +#import + +#import "PFUIDemoViewController.h" + +@implementation AppDelegate + +#pragma mark - +#pragma mark UIApplicationDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [Parse setApplicationId:@"UdNpOP2XFoEiXLZEBDl6xONmCMH8VjETmnEsl0xJ" + clientKey:@"wNJFho0fQaQFQ2Fe1x9b67lVBakJiAtFj1Uz30A9"]; + [PFFacebookUtils initializeFacebookWithApplicationLaunchOptions:launchOptions]; + [PFTwitterUtils initializeWithConsumerKey:@"3Q9hMEKqqSg4ie2pibZ2sVJuv" + consumerSecret:@"IEZ9wv2d1EpXNGFKGp7sAGdxRtyqtPwygyciFZwTHTGhPp4FMj"]; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[PFUIDemoViewController alloc] init]]; + [self.window makeKeyAndVisible]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self _setupTestData]; + }); + + return YES; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + return [[FBSDKApplicationDelegate sharedInstance] application:application + openURL:url + sourceApplication:sourceApplication + annotation:annotation]; +} + +#pragma mark - +#pragma mark Test Data + +- (void)_setupTestData { + NSArray *todoTitles = @[ @"Build Parse", + @"Make everything awesome", + @"Go out for the longest run", + @"Do more stuff", + @"Conquer the world", + @"Build a house", + @"Grow a tree", + @"Be awesome", + @"Setup an app", + @"Do stuff", + @"Buy groceries", + @"Wash clothes" ]; + + NSMutableArray *objects = [NSMutableArray array]; + + PFQuery *query = [PFQuery queryWithClassName:@"Todo"]; + NSArray *todos = [query findObjects]; + if ([todos count] == 0) { + int count = 0; + for (NSString *title in todoTitles) { + int priority = count % 3; + + PFObject *todo = [[PFObject alloc] initWithClassName:@"Todo"]; + todo[@"title"] = title; + todo[@"priority"] = @(priority); + [objects addObject:todo]; + + count++; + } + } + + NSArray *appNames = @[ @"Anypic", + @"Anywall", + @"f8" ]; + + PFQuery *appsQuery = [PFQuery queryWithClassName:@"App"]; + NSArray *apps = [appsQuery findObjects]; + if ([apps count] == 0) { + for (NSUInteger i = 0; i < 3; i++) { + NSString *name = [NSString stringWithFormat:@"%d", (int)i]; + NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"png"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + + PFFile *file = [PFFile fileWithName:[path lastPathComponent] data:data]; + + PFObject *object = [[PFObject alloc] initWithClassName:@"App"]; + object[@"icon"] = file; + object[@"name"] = appNames[i]; + [objects addObject:object]; + } + } + + if ([objects count] != 0) { + [PFObject saveAll:objects]; + } +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/LogInViewController/CustomLogInViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/LogInViewController/CustomLogInViewController.h new file mode 100644 index 000000000..045430fa9 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/LogInViewController/CustomLogInViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface CustomLogInViewController : PFLogInViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/LogInViewController/CustomLogInViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/LogInViewController/CustomLogInViewController.m new file mode 100644 index 000000000..8e3f4feb3 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/LogInViewController/CustomLogInViewController.m @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "CustomLogInViewController.h" + +@implementation CustomLogInViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor blueColor]; + + UILabel *label = [[UILabel alloc] init]; + label.textColor = [UIColor whiteColor]; + label.text = @"All Custom!"; + [label sizeToFit]; + self.logInView.logo = label; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.h new file mode 100644 index 000000000..e9e1e11ff --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface DeletionCollectionViewController : PFQueryCollectionViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.m new file mode 100644 index 000000000..1f94e9869 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.m @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "DeletionCollectionViewController.h" + +#import + +#import + +@interface DeletionCollectionViewController() + +@end + +@implementation DeletionCollectionViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (self) { + self.title = @"Deletion Collection"; + self.pullToRefreshEnabled = YES; + self.objectsPerPage = 10; + self.paginationEnabled = YES; + } + return self; +} + +#pragma mark - +#pragma mark View Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + layout.sectionInset = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f); + layout.minimumInteritemSpacing = 5.0f; + + self.collectionView.allowsMultipleSelection = YES; + self.navigationItem.rightBarButtonItems = @[ + self.editButtonItem, + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(addTodo:)] + ]; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + const CGRect bounds = UIEdgeInsetsInsetRect(self.view.bounds, layout.sectionInset); + CGFloat sideSize = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)) / 2.0f - layout.minimumInteritemSpacing; + layout.itemSize = CGSizeMake(sideSize, sideSize); +} + + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated { + [super setEditing:editing animated:animated]; + + if (editing) { + self.navigationItem.leftBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash + target:self + action:@selector(deleteSelectedItems:)]; + } else { + self.navigationItem.leftBarButtonItem = self.navigationItem.backBarButtonItem; + } +} + +- (void)addTodo:(id)sender { + if ([UIAlertController class]) { + UIAlertController *alertDialog = [UIAlertController alertControllerWithTitle:@"Add Todo" + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + + __block UITextField *titleTextField = nil; + [alertDialog addTextFieldWithConfigurationHandler:^(UITextField *textField) { + titleTextField = textField; + + titleTextField.placeholder = @"Name"; + }]; + + [alertDialog addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [alertDialog addAction: + [UIAlertAction actionWithTitle:@"Save" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + PFObject *object = [PFObject objectWithClassName:self.parseClassName + dictionary:@{ @"title":titleTextField.text }]; + + [[object saveInBackground] continueWithSuccessBlock:^id(BFTask *task) { + return [self loadObjects]; + }]; + }]]; + + [self presentViewController:alertDialog animated:YES completion:nil]; + } else { + UIAlertView *view = [[UIAlertView alloc] initWithTitle:@"Add Todo" + message:nil + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Save", nil]; + + [view setAlertViewStyle:UIAlertViewStylePlainTextInput]; + [view textFieldAtIndex:0].placeholder = @"Name"; + + [view show]; + } +} + +- (void)deleteSelectedItems:(id)sender { + [self removeObjectsAtIndexPaths:self.collectionView.indexPathsForSelectedItems]; +} + +#pragma mark - UICollectionViewDataSource + +- (PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + PFCollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath object:object]; + cell.textLabel.textAlignment = NSTextAlignmentCenter; + cell.textLabel.text = object[@"title"]; + + cell.contentView.layer.borderWidth = 1.0f; + cell.contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; + + return cell; +} + +#pragma mark - +#pragma mark UIAlertViewDelegate + +- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { + if (buttonIndex == alertView.cancelButtonIndex) return; + + PFObject *object = [PFObject objectWithClassName:self.parseClassName + dictionary:@{ @"title": [alertView textFieldAtIndex:0].text }]; + + [[object saveEventually] continueWithSuccessBlock:^id(BFTask *task) { + return [self loadObjects]; + }]; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.h new file mode 100644 index 000000000..c82f3ca13 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface PaginatedCollectionViewController : PFQueryCollectionViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.m new file mode 100644 index 000000000..61e0dfdb7 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.m @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PaginatedCollectionViewController.h" + +#import +#import + +#import + +@implementation PaginatedCollectionViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (!self) return nil; + + self.title = @"Paginated Collection"; + self.pullToRefreshEnabled = YES; + self.objectsPerPage = 10; + self.paginationEnabled = YES; + + return self; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + layout.sectionInset = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f); + layout.minimumInteritemSpacing = 5.0f; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + const CGRect bounds = UIEdgeInsetsInsetRect(self.view.bounds, layout.sectionInset); + CGFloat sideSize = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)) / 3.0f - layout.minimumInteritemSpacing * 2.0f; + layout.itemSize = CGSizeMake(sideSize, sideSize); +} + +#pragma mark - +#pragma mark Data + +- (PFQuery *)queryForCollection { + PFQuery *query = [super queryForCollection]; + [query orderByAscending:@"priority"]; + return query; +} + +#pragma mark - +#pragma mark CollectionView + +- (PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + PFCollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath object:object]; + cell.textLabel.textAlignment = NSTextAlignmentCenter; + + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:object[@"title"] attributes:nil]; + NSAttributedString *priorityString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\nPriority: %@", object[@"priority"]] + attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:13.0f], + NSForegroundColorAttributeName : [UIColor grayColor] }]; + [title appendAttributedString:priorityString]; + cell.textLabel.attributedText = title; + + cell.contentView.layer.borderWidth = 1.0f; + cell.contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.h new file mode 100644 index 000000000..622008748 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface SectionedCollectionViewController : PFQueryCollectionViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.m new file mode 100644 index 000000000..7bb7e486c --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.m @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "SectionedCollectionViewController.h" + +#import +#import + +#import + +#pragma mark - +#pragma mark SimpleCollectionReusableView + +@interface SimpleCollectionReusableView : UICollectionReusableView + +@property (nonatomic, strong, readonly) UILabel *label; + +@end + +@implementation SimpleCollectionReusableView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (!self) return nil; + + _label = [[UILabel alloc] initWithFrame:CGRectZero]; + _label.textAlignment = NSTextAlignmentCenter; + [self addSubview:_label]; + + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + _label.frame = self.bounds; +} + +@end + +@interface SectionedCollectionViewController () +{ + NSArray *_sectionSortedKeys; + NSMutableDictionary *_sections; +} + +@end + +#pragma mark - +#pragma mark SectionedCollectionViewController + +@implementation SectionedCollectionViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (!self) return nil; + + self.title = @"Sectioned Collection"; + self.pullToRefreshEnabled = YES; + + _sections = [NSMutableDictionary dictionary]; + + return self; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + layout.sectionInset = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f); + layout.minimumInteritemSpacing = 5.0f; + + [self.collectionView registerClass:[SimpleCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"]; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + const CGRect bounds = UIEdgeInsetsInsetRect(self.view.bounds, layout.sectionInset); + CGFloat sideSize = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)) / 3.0f - layout.minimumInteritemSpacing * 2.0f; + layout.itemSize = CGSizeMake(sideSize, sideSize); +} + +#pragma mark - +#pragma mark Data + +- (void)objectsDidLoad:(NSError *)error { + [super objectsDidLoad:error]; + + [_sections removeAllObjects]; + for (PFObject *object in self.objects) { + NSNumber *priority = object[@"priority"]; + + NSMutableArray *array = _sections[priority]; + if (array) { + [array addObject:object]; + } else { + _sections[priority] = [NSMutableArray arrayWithObject:object]; + } + } + + _sectionSortedKeys = [[_sections allKeys] sortedArrayUsingSelector:@selector(compare:)]; + [self.collectionView reloadData]; +} + +- (PFObject *)objectAtIndexPath:(NSIndexPath *)indexPath { + NSArray *sectionAray = _sections[_sectionSortedKeys[indexPath.section]]; + return sectionAray[indexPath.row]; +} + +#pragma mark - +#pragma mark CollectionView + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_sections count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + NSArray *sectionAray = _sections[_sectionSortedKeys[section]]; + return [sectionAray count]; +} + +- (PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + PFCollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath object:object]; + + cell.textLabel.textAlignment = NSTextAlignmentCenter; + cell.textLabel.text = object[@"title"]; + + cell.contentView.layer.borderWidth = 1.0f; + cell.contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; + + return cell; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { + SimpleCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"header" forIndexPath:indexPath]; + NSNumber *priority = _sectionSortedKeys[indexPath.section]; + view.label.text = [NSString stringWithFormat:@"Priority %@", [priority stringValue]]; + return view; + } + return [super collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + if ([_sections count]) { + return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), 40.0f); + } + return CGSizeZero; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.h new file mode 100644 index 000000000..edc697e8a --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface SimpleCollectionViewController : PFQueryCollectionViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.m new file mode 100644 index 000000000..6738bbb8e --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.m @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "SimpleCollectionViewController.h" + +#import + +#import + +@implementation SimpleCollectionViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (!self) return nil; + + self.title = @"Simple Collection"; + self.pullToRefreshEnabled = YES; + self.paginationEnabled = NO; + + return self; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + layout.sectionInset = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f); + layout.minimumInteritemSpacing = 5.0f; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + const CGRect bounds = UIEdgeInsetsInsetRect(self.view.bounds, layout.sectionInset); + CGFloat sideSize = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)) / 2.0f - layout.minimumInteritemSpacing; + layout.itemSize = CGSizeMake(sideSize, sideSize); +} + +#pragma mark - +#pragma mark Data + +- (PFQuery *)queryForCollection { + PFQuery *query = [super queryForCollection]; + [query orderByAscending:@"priority"]; + return query; +} + +#pragma mark - +#pragma mark CollectionView + +- (PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + PFCollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath object:object]; + cell.textLabel.textAlignment = NSTextAlignmentCenter; + + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:object[@"title"] attributes:nil]; + NSAttributedString *priorityString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\nPriority: %@", object[@"priority"]] + attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:13.0f], + NSForegroundColorAttributeName : [UIColor grayColor] }]; + [title appendAttributedString:priorityString]; + cell.textLabel.attributedText = title; + + cell.contentView.layer.borderWidth = 1.0f; + cell.contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.h new file mode 100644 index 000000000..a4defdb4f --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface StoryboardCollectionViewController : PFQueryCollectionViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.m new file mode 100644 index 000000000..b64ccb403 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.m @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "StoryboardCollectionViewController.h" + +#import + +#import + +@implementation StoryboardCollectionViewController + +#pragma mark - +#pragma mark UIViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + layout.sectionInset = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f); + layout.minimumInteritemSpacing = 5.0f; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + const CGRect bounds = UIEdgeInsetsInsetRect(self.view.bounds, layout.sectionInset); + CGFloat sideSize = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)) / 2.0f - layout.minimumInteritemSpacing; + layout.itemSize = CGSizeMake(sideSize, sideSize); +} + + +#pragma mark - +#pragma mark Query + +- (PFQuery *)queryForCollection { + PFQuery *query = [super queryForCollection]; + [query orderByAscending:@"priority"]; + return query; +} + +#pragma mark - +#pragma mark CollectionView + +- (PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + PFCollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath object:object]; + cell.textLabel.textAlignment = NSTextAlignmentCenter; + + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:object[@"title"] attributes:nil]; + NSAttributedString *priorityString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\nPriority: %@", object[@"priority"]] + attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:13.0f], + NSForegroundColorAttributeName : [UIColor grayColor] }]; + [title appendAttributedString:priorityString]; + cell.textLabel.attributedText = title; + + cell.contentView.layer.borderWidth = 1.0f; + cell.contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.h new file mode 100644 index 000000000..390f3bc9a --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface SubtitleImageCollectionViewController : PFQueryCollectionViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.m new file mode 100644 index 000000000..bc22796d3 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.m @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "SubtitleImageCollectionViewController.h" + +#import + +#import +#import + +@implementation SubtitleImageCollectionViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (!self) return nil; + + self.title = @"Image Collection"; + self.pullToRefreshEnabled = YES; + self.paginationEnabled = NO; + + return self; +} + +#pragma mark - +#pragma mark UIViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + layout.sectionInset = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f); + layout.minimumInteritemSpacing = 5.0f; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + + const CGRect bounds = UIEdgeInsetsInsetRect(self.view.bounds, layout.sectionInset); + CGFloat sideSize = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)) / 2.0f - layout.minimumInteritemSpacing; + layout.itemSize = CGSizeMake(sideSize, sideSize); +} + +#pragma mark - +#pragma mark CollectionView + +- (PFCollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + PFCollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath object:object]; + + cell.textLabel.textAlignment = NSTextAlignmentCenter; + cell.textLabel.text = object[@"name"]; + + cell.imageView.file = object[@"icon"]; + // If the image is nil - set the placeholder + if (cell.imageView.image == nil) { + cell.imageView.image = [UIImage imageNamed:@"Icon.png"]; + [cell.imageView loadInBackground]; + } + + cell.contentView.layer.borderWidth = 1.0f; + cell.contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/CustomProductTableViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/CustomProductTableViewController.h new file mode 100644 index 000000000..212bf8883 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/CustomProductTableViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface CustomProductTableViewController : PFProductTableViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/CustomProductTableViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/CustomProductTableViewController.m new file mode 100644 index 000000000..ed97cbf80 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/CustomProductTableViewController.m @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "CustomProductTableViewController.h" + +#import +#import + +@implementation CustomProductTableViewController + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ + PFObject *purchase = self.objects[indexPath.row]; + NSString *productIdentifier = purchase[@"productIdentifier"]; + + if ([productIdentifier isEqualToString:@"Cooper"]) { + [PFPurchase buyProduct:@"Cooper" block:^(NSError *error) { + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Success!" + message:@"YES!" + delegate:nil + cancelButtonTitle:@"OK!" + otherButtonTitles:nil]; + [alertView show]; + }]; + } else { + [super tableView:tableView didSelectRowAtIndexPath:indexPath]; + } +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/DeletionTableViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/DeletionTableViewController.h new file mode 100644 index 000000000..70cbb378b --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/DeletionTableViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface DeletionTableViewController : PFQueryTableViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/DeletionTableViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/DeletionTableViewController.m new file mode 100644 index 000000000..d2bfca95b --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/DeletionTableViewController.m @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "DeletionTableViewController.h" + +#import + +#import + +@interface DeletionTableViewController() + +@end + +@implementation DeletionTableViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (!self) return nil; + + self.title = @"Deletion Table"; + self.pullToRefreshEnabled = YES; + + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsMultipleSelectionDuringEditing = YES; + self.navigationItem.rightBarButtonItems = @[ + self.editButtonItem, + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(addTodo:)] + ]; +} + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated { + [super setEditing:editing animated:animated]; + + if (editing) { + self.navigationItem.leftBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash + target:self + action:@selector(deleteSelectedItems:)]; + } else { + self.navigationItem.leftBarButtonItem = self.navigationItem.backBarButtonItem; + } +} + +- (void)addTodo:(id)sender { + if ([UIAlertController class]) { + UIAlertController *alertDialog = [UIAlertController alertControllerWithTitle:@"Add Todo" + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + + __block UITextField *titleTextField = nil; + [alertDialog addTextFieldWithConfigurationHandler:^(UITextField *textField) { + titleTextField = textField; + + titleTextField.placeholder = @"Name"; + }]; + + [alertDialog addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [alertDialog addAction: + [UIAlertAction actionWithTitle:@"Save" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + PFObject *object = [PFObject objectWithClassName:self.parseClassName + dictionary:@{ @"title":titleTextField.text }]; + + [[object saveInBackground] continueWithSuccessBlock:^id(BFTask *task) { + return [self loadObjects]; + }]; + }]]; + + [self presentViewController:alertDialog animated:YES completion:nil]; + } else { + UIAlertView *view = [[UIAlertView alloc] initWithTitle:@"Add Todo" + message:nil + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Save", nil]; + + [view setAlertViewStyle:UIAlertViewStylePlainTextInput]; + [view textFieldAtIndex:0].placeholder = @"Name"; + + [view show]; + } +} + +- (void)deleteSelectedItems:(id)sender { + [self removeObjectsAtIndexPaths:self.tableView.indexPathsForSelectedRows]; +} + +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + if (editingStyle == UITableViewCellEditingStyleDelete) { + [self removeObjectAtIndexPath:indexPath]; + } +} + +#pragma mark - +#pragma mark UIAlertViewDelegate + +- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { + if (buttonIndex == alertView.cancelButtonIndex) return; + + PFObject *object = [PFObject objectWithClassName:self.parseClassName + dictionary:@{ @"title": [alertView textFieldAtIndex:0].text }]; + + [[object saveEventually] continueWithSuccessBlock:^id(BFTask *task) { + return [self loadObjects]; + }]; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.h new file mode 100644 index 000000000..f10817747 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import + +@interface PaginatedTableViewController : PFQueryTableViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.m new file mode 100644 index 000000000..04c9af70b --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.m @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PaginatedTableViewController.h" + +#import +#import + +#import + +@implementation PaginatedTableViewController + +#pragma mark - +#pragma mark init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (self) { + self.title = @"Paginated Table"; + self.pullToRefreshEnabled = YES; + self.objectsPerPage = 10; + self.paginationEnabled = YES; + } + return self; +} + +#pragma mark - +#pragma mark Data + +- (PFQuery *)queryForTable { + PFQuery *query = [super queryForTable]; + [query orderByAscending:@"priority"]; + return query; +} + +#pragma mark - +#pragma mark TableView + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + static NSString *cellIdentifier = @"cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; + } + + cell.textLabel.text = object[@"title"]; + cell.detailTextLabel.text = [NSString stringWithFormat:@"Priority: %@", object[@"priority"]]; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SectionedTableViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SectionedTableViewController.h new file mode 100644 index 000000000..ca7406f04 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SectionedTableViewController.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import + +@interface SectionedTableViewController : PFQueryTableViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SectionedTableViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SectionedTableViewController.m new file mode 100644 index 000000000..6002e0733 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SectionedTableViewController.m @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "SectionedTableViewController.h" + +#import +#import + +#import + +@interface SectionedTableViewController () +{ + NSArray *_sectionSortedKeys; + NSMutableDictionary *_sections; +} + +@end + +@implementation SectionedTableViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (self) { + self.title = @"Sectioned Table"; + self.pullToRefreshEnabled = YES; + + _sections = [NSMutableDictionary dictionary]; + } + return self; +} + +#pragma mark - +#pragma mark Data + +- (void)objectsDidLoad:(NSError *)error { + [super objectsDidLoad:error]; + + [_sections removeAllObjects]; + for (PFObject *object in self.objects) { + NSNumber *priority = object[@"priority"]; + + NSMutableArray *array = _sections[priority]; + if (array) { + [array addObject:object]; + } else { + _sections[priority] = [NSMutableArray arrayWithObject:object]; + } + } + + _sectionSortedKeys = [[_sections allKeys] sortedArrayUsingSelector:@selector(compare:)]; + [self.tableView reloadData]; +} + +- (PFObject *)objectAtIndexPath:(NSIndexPath *)indexPath { + NSArray *sectionArray = _sections[_sectionSortedKeys[indexPath.section]]; + return sectionArray[indexPath.row]; +} + +#pragma mark - +#pragma mark UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return [_sections count]; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return [NSString stringWithFormat:@"Priority %@", [_sectionSortedKeys[section] stringValue]]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSArray *sectionAray = _sections[_sectionSortedKeys[section]]; + return [sectionAray count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + static NSString *cellIdentifier = @"cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; + } + + cell.textLabel.text = object[@"title"]; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SimpleTableViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SimpleTableViewController.h new file mode 100644 index 000000000..329ff4a70 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SimpleTableViewController.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import + +@interface SimpleTableViewController : PFQueryTableViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SimpleTableViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SimpleTableViewController.m new file mode 100644 index 000000000..bc5ca174d --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SimpleTableViewController.m @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "SimpleTableViewController.h" + +#import +#import + +#import + +@implementation SimpleTableViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)initWithClassName:(NSString *)className { + self = [super initWithClassName:className]; + if (self) { + self.title = @"Simple Table"; + self.pullToRefreshEnabled = YES; + self.paginationEnabled = NO; + } + return self; +} + +#pragma mark - +#pragma mark Data + +- (PFQuery *)queryForTable { + PFQuery *query = [super queryForTable]; + [query orderByAscending:@"priority"]; + return query; +} + +#pragma mark - +#pragma mark TableView + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + static NSString *cellIdentifier = @"cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; + } + + cell.textLabel.text = object[@"title"]; + cell.detailTextLabel.text = [NSString stringWithFormat:@"Priority: %@", object[@"priority"]]; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.h new file mode 100644 index 000000000..4a446519b --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface StoryboardTableViewController : PFQueryTableViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.m new file mode 100644 index 000000000..02c770b95 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.m @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "StoryboardTableViewController.h" + +#import +#import + +#import + +@implementation StoryboardTableViewController + +#pragma mark - +#pragma mark Data + +- (PFQuery *)queryForTable { + PFQuery *query = [super queryForTable]; + [query orderByAscending:@"priority"]; + return query; +} + +#pragma mark - +#pragma mark TableView + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + static NSString *cellIdentifier = @"cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; + } + + cell.textLabel.text = object[@"title"]; + cell.detailTextLabel.text = [NSString stringWithFormat:@"Priority: %@", object[@"priority"]]; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.h new file mode 100644 index 000000000..0a231a533 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface SubtitleImageTableViewController : PFQueryTableViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.m new file mode 100644 index 000000000..7dbb0d3f1 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.m @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "SubtitleImageTableViewController.h" + +#import + +#import + +@implementation SubtitleImageTableViewController + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + object:(PFObject *)object { + static NSString *cellIdentifier = @"cell"; + + PFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier]; + } + + cell.textLabel.text = object[@"name"]; + cell.detailTextLabel.text = @"@parseit"; + + cell.imageView.image = [UIImage imageNamed:@"Icon.png"]; + cell.imageView.file = object[@"icon"]; + + return cell; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/SignUpViewController/CustomSignUpViewController.h b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/SignUpViewController/CustomSignUpViewController.h new file mode 100644 index 000000000..e1a80ba84 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/SignUpViewController/CustomSignUpViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface CustomSignUpViewController : PFSignUpViewController + +@end diff --git a/ParseUI/ParseUIDemo/Classes/CustomViewControllers/SignUpViewController/CustomSignUpViewController.m b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/SignUpViewController/CustomSignUpViewController.m new file mode 100644 index 000000000..b2e3530ef --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/CustomViewControllers/SignUpViewController/CustomSignUpViewController.m @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "CustomSignUpViewController.h" + +@implementation CustomSignUpViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor blueColor]; + + UILabel *label = [[UILabel alloc] init]; + label.text = @"All Custom!"; + [label sizeToFit]; + self.signUpView.logo = label; +} + +@end diff --git a/ParseUI/ParseUIDemo/Classes/PFUIDemoViewController.h b/ParseUI/ParseUIDemo/Classes/PFUIDemoViewController.h new file mode 100644 index 000000000..7b94f6366 --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/PFUIDemoViewController.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +@interface PFUIDemoViewController : UITableViewController + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end diff --git a/ParseUI/ParseUIDemo/Classes/PFUIDemoViewController.m b/ParseUI/ParseUIDemo/Classes/PFUIDemoViewController.m new file mode 100644 index 000000000..c325a9e7a --- /dev/null +++ b/ParseUI/ParseUIDemo/Classes/PFUIDemoViewController.m @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import "PFUIDemoViewController.h" + +#import + +#import "CustomLogInViewController.h" +#import "CustomProductTableViewController.h" +#import "CustomSignUpViewController.h" +#import "DeletionCollectionViewController.h" +#import "DeletionTableViewController.h" +#import "PaginatedCollectionViewController.h" +#import "PaginatedTableViewController.h" +#import "SectionedCollectionViewController.h" +#import "SectionedTableViewController.h" +#import "SimpleCollectionViewController.h" +#import "SimpleTableViewController.h" +#import "StoryboardCollectionViewController.h" +#import "StoryboardTableViewController.h" +#import "SubtitleImageCollectionViewController.h" +#import "SubtitleImageTableViewController.h" + +typedef NS_ENUM(uint8_t, PFUIDemoType) { + PFUIDemoTypeSimpleTable, + PFUIDemoTypePaginatedTable, + PFUIDemoTypeSectionedTable, + PFUIDemoTypeStoryboardTable, + PFUIDemoTypeDeletionTable, + PFUIDemoTypeSimpleCollection, + PFUIDemoTypePaginatedCollection, + PFUIDemoTypeSectionedCollection, + PFUIDemoTypeStoryboardCollection, + PFUIDemoTypeDeletionCollection, + PFUIDemoTypeLogInDefault, + PFUIDemoTypeLogInUsernamePassword, + PFUIDemoTypeLogInPasswordForgotten, + PFUIDemoTypeLogInDone, + PFUIDemoTypeLogInEmailAsUsername, + PFUIDemoTypeLogInFacebook, + PFUIDemoTypeLogInFacebookAndTwitter, + PFUIDemoTypeLogInAll, + PFUIDemoTypeLogInAllNavigation, + PFUIDemoTypeLogInCustomizedLogoAndBackground, + PFUIDemoTypeSignUpDefault, + PFUIDemoTypeSignUpUsernamePassword, + PFUIDemoTypeSignUpUsernamePasswordEmail, + PFUIDemoTypeSignUpUsernamePasswordEmailSignUp, + PFUIDemoTypeSignUpAll, + PFUIDemoTypeSignUpEmailAsUsername, + PFUIDemoTypeSignUpMinPasswordLength, + PFUIDemoTypeImageTableDefaultStyle, + PFUIDemoTypeImageTableSubtitleStyle, + PFUIDemoTypeImageCollection, + PFUIDemoTypePurchase, + PFUIDemoTypeCustomizedPurchase +}; + +@interface PFUIDemoViewController () +{ + NSArray *_descriptions; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; + +@end + +@implementation PFUIDemoViewController + +#pragma mark - +#pragma mark Init + +- (instancetype)init { + return [super initWithStyle:UITableViewStylePlain]; +} + +- (instancetype)initWithStyle:(UITableViewStyle)style { + return [self init]; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + return [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + return [self init]; +} + +#pragma mark - +#pragma mark View + +- (void)viewDidLoad { + [super viewDidLoad]; + + if (!self.title) { + self.title = @"ParseUI Demo"; + } + if (!_descriptions) { + _descriptions = @[ @"Simple Table", + @"Paginated Table", + @"Sectioned Table", + @"Simple Storyboard Table", + @"Deletion Table", + @"Simple Collection", + @"Paginated Collection", + @"Sectioned Collection", + @"Simple Storyboard Collection", + @"Deletion Collection", + @"Log In Default", + @"Log In Username and Password", + @"Log In Password Forgotten", + @"Log In Done Button", + @"Log In Email as Username", + @"Log In Facebook", + @"Log In Facebook and Twitter", + @"Log In All", + @"Log In All as Navigation", + @"Log In Customized Background", + @"Sign Up Default", + @"Sign Up Username and Password", + @"Sign Up Email", + @"Sign Up Email And SignUp", + @"Sign Up All", + @"Sign Up Email as Username", + @"Sign Up Minimum Password Length", + @"Remote Image Table Default Style", + @"Remote Image Table Subtitle Style", + @"Remote Image Collection", + @"Purchase", + @"Custom Purchase" ]; + } +} + +#pragma mark - +#pragma mark UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [_descriptions count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *identifier = @"cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; + } + + cell.textLabel.text = _descriptions[indexPath.row]; + + return cell; +} + +#pragma mark - +#pragma mark UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + switch (indexPath.row) { + case PFUIDemoTypeSimpleTable: { + PFQueryTableViewController *controller = [[SimpleTableViewController alloc] initWithClassName:@"Todo"]; + [self.navigationController pushViewController:controller animated:YES]; + break; + } + case PFUIDemoTypePaginatedTable: { + PFQueryTableViewController *controller = [[PaginatedTableViewController alloc] initWithClassName:@"Todo"]; + [self.navigationController pushViewController:controller animated:YES]; + break; + } + case PFUIDemoTypeSectionedTable: { + PFQueryTableViewController *controller = [[SectionedTableViewController alloc] initWithClassName:@"Todo"]; + [self.navigationController pushViewController:controller animated:YES]; + break; + } + case PFUIDemoTypeStoryboardTable: { + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"SimpleQueryTableStoryboard" bundle:NULL]; + StoryboardTableViewController *controller = [storyboard instantiateViewControllerWithIdentifier:@"StoryboardTableViewController"]; + [self.navigationController pushViewController:controller animated:YES]; + break; + } + case PFUIDemoTypeDeletionTable: { + PFQueryTableViewController *controller = [[DeletionTableViewController alloc] initWithClassName:@"PublicTodo"]; + [self.navigationController pushViewController:controller animated:YES]; + break; + } + case PFUIDemoTypeSimpleCollection: { + SimpleCollectionViewController *controller = [[SimpleCollectionViewController alloc] initWithClassName:@"Todo"]; + [self.navigationController pushViewController:controller animated:YES]; + } + break; + case PFUIDemoTypePaginatedCollection: { + PaginatedCollectionViewController *controller = [[PaginatedCollectionViewController alloc] initWithClassName:@"Todo"]; + [self.navigationController pushViewController:controller animated:YES]; + } + break; + case PFUIDemoTypeSectionedCollection: { + SectionedCollectionViewController *controller = [[SectionedCollectionViewController alloc] initWithClassName:@"Todo"]; + [self.navigationController pushViewController:controller animated:YES]; + } + break; + case PFUIDemoTypeStoryboardCollection: { + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"SimpleQueryCollectionStoryboard" bundle:NULL]; + StoryboardCollectionViewController *controller = [storyboard instantiateViewControllerWithIdentifier:@"StoryboardCollectionViewController"]; + [self.navigationController pushViewController:controller animated:YES]; + break; + } + case PFUIDemoTypeDeletionCollection: { + PFQueryCollectionViewController *controller = [[DeletionCollectionViewController alloc] initWithClassName:@"PublicTodo"]; + [self.navigationController pushViewController:controller animated:YES]; + break; + } + case PFUIDemoTypeLogInDefault: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.delegate = self; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInUsernamePassword: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = PFLogInFieldsUsernameAndPassword | PFLogInFieldsDismissButton; + logInController.delegate = self; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInPasswordForgotten: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = (PFLogInFieldsUsernameAndPassword + | PFLogInFieldsPasswordForgotten + | PFLogInFieldsDismissButton); + logInController.delegate = self; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInDone: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = (PFLogInFieldsUsernameAndPassword + | PFLogInFieldsLogInButton + | PFLogInFieldsDismissButton); + logInController.delegate = self; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInEmailAsUsername: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = (PFLogInFieldsUsernameAndPassword + | PFLogInFieldsLogInButton + | PFLogInFieldsDismissButton + | PFLogInFieldsSignUpButton); + logInController.emailAsUsername = YES; + logInController.delegate = self; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInFacebook: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = (PFLogInFieldsUsernameAndPassword + | PFLogInFieldsFacebook + | PFLogInFieldsDismissButton); + logInController.delegate = self; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInFacebookAndTwitter: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = PFLogInFieldsFacebook | PFLogInFieldsTwitter | PFLogInFieldsDismissButton; + logInController.delegate = self; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInAll: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = (PFLogInFieldsUsernameAndPassword + | PFLogInFieldsLogInButton + | PFLogInFieldsPasswordForgotten + | PFLogInFieldsFacebook + | PFLogInFieldsTwitter + | PFLogInFieldsSignUpButton + | PFLogInFieldsDismissButton); + logInController.delegate = self; + + logInController.signUpController.fields = (PFSignUpFieldsUsernameAndPassword + | PFSignUpFieldsEmail + | PFSignUpFieldsAdditional + | PFSignUpFieldsDismissButton + | PFSignUpFieldsSignUpButton); + logInController.signUpController.delegate = self; + + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeLogInAllNavigation: { + PFLogInViewController *logInController = [[PFLogInViewController alloc] init]; + logInController.fields = (PFLogInFieldsUsernameAndPassword + | PFLogInFieldsLogInButton + | PFLogInFieldsPasswordForgotten + | PFLogInFieldsFacebook + | PFLogInFieldsTwitter + | PFLogInFieldsSignUpButton + | PFLogInFieldsDismissButton); + logInController.delegate = self; + + logInController.signUpController.fields = (PFSignUpFieldsUsernameAndPassword + | PFSignUpFieldsEmail + | PFSignUpFieldsAdditional + | PFSignUpFieldsDismissButton + | PFSignUpFieldsSignUpButton); + logInController.signUpController.delegate = self; + [self.navigationController pushViewController:logInController animated:YES]; + break; + } + case PFUIDemoTypeLogInCustomizedLogoAndBackground: { + PFLogInViewController *logInController = [[CustomLogInViewController alloc] init]; + logInController.fields = PFLogInFieldsDefault | PFLogInFieldsFacebook | PFLogInFieldsTwitter; + logInController.delegate= self; + + PFSignUpViewController *signUpController = [[CustomSignUpViewController alloc] init]; + signUpController.fields = PFSignUpFieldsDefault; + signUpController.delegate = self; + + logInController.signUpController = signUpController; + [self presentViewController:logInController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeSignUpDefault: { + PFSignUpViewController *signUpController = [[PFSignUpViewController alloc] init]; + signUpController.delegate = self; + [self presentViewController:signUpController animated:YES completion:nil]; + } + break; + case PFUIDemoTypeSignUpUsernamePassword: { + PFSignUpViewController *signUpController = [[PFSignUpViewController alloc] init]; + signUpController.fields = PFSignUpFieldsUsernameAndPassword | PFSignUpFieldsDismissButton; + signUpController.delegate = self; + [self presentViewController:signUpController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeSignUpUsernamePasswordEmail: { + PFSignUpViewController *signUpController = [[PFSignUpViewController alloc] init]; + signUpController.fields = (PFSignUpFieldsUsernameAndPassword + | PFSignUpFieldsEmail + | PFSignUpFieldsDismissButton); + signUpController.delegate = self; + [self presentViewController:signUpController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeSignUpUsernamePasswordEmailSignUp: { + PFSignUpViewController *signUpController = [[PFSignUpViewController alloc] init]; + signUpController.fields = (PFSignUpFieldsUsernameAndPassword + | PFSignUpFieldsEmail + | PFSignUpFieldsSignUpButton + | PFSignUpFieldsDismissButton); + signUpController.delegate = self; + [self presentViewController:signUpController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeSignUpAll: { + PFSignUpViewController *signUpController = [[PFSignUpViewController alloc] init]; + signUpController.fields = (PFSignUpFieldsEmail + | PFSignUpFieldsAdditional + | PFSignUpFieldsSignUpButton + | PFSignUpFieldsDismissButton); + signUpController.delegate = self; + signUpController.modalPresentationStyle = UIModalPresentationFormSheet; + [self presentViewController:signUpController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeSignUpEmailAsUsername: { + PFSignUpViewController *signUpController = [[PFSignUpViewController alloc] init]; + signUpController.fields = (PFSignUpFieldsUsernameAndPassword + | PFSignUpFieldsSignUpButton + | PFSignUpFieldsDismissButton); + signUpController.emailAsUsername = YES; + signUpController.delegate = self; + [self presentViewController:signUpController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeSignUpMinPasswordLength: { + PFSignUpViewController *signUpController = [[PFSignUpViewController alloc] init]; + signUpController.fields = (PFSignUpFieldsUsernameAndPassword + | PFSignUpFieldsSignUpButton + | PFSignUpFieldsDismissButton); + signUpController.minPasswordLength = 6; + signUpController.delegate = self; + [self presentViewController:signUpController animated:YES completion:nil]; + break; + } + case PFUIDemoTypeImageTableDefaultStyle: { + PFQueryTableViewController *tableViewController = [[PFQueryTableViewController alloc] initWithClassName:@"App"]; + tableViewController.imageKey = @"icon"; + tableViewController.textKey = @"name"; + tableViewController.paginationEnabled = NO; + tableViewController.placeholderImage = [UIImage imageNamed:@"Icon.png"]; + [self.navigationController pushViewController:tableViewController animated:YES]; + break; + } + case PFUIDemoTypeImageTableSubtitleStyle: { + SubtitleImageTableViewController *tableViewController = [[SubtitleImageTableViewController alloc] initWithClassName:@"App"]; + tableViewController.imageKey = @"icon"; + tableViewController.textKey = @"name"; + tableViewController.paginationEnabled = NO; + tableViewController.placeholderImage = [UIImage imageNamed:@"Icon.png"]; + [self.navigationController pushViewController:tableViewController animated:YES]; + break; + } + case PFUIDemoTypeImageCollection: { + SubtitleImageCollectionViewController *controller = [[SubtitleImageCollectionViewController alloc] initWithClassName:@"App"]; + [self.navigationController pushViewController:controller animated:YES]; + } + break; + case PFUIDemoTypePurchase: { + PFProductTableViewController *purchaseController = [[PFProductTableViewController alloc] init]; + [self.navigationController pushViewController:purchaseController animated:YES]; + break; + } + case PFUIDemoTypeCustomizedPurchase: { + CustomProductTableViewController *purchaseController = [[CustomProductTableViewController alloc] init]; + [self.navigationController pushViewController:purchaseController animated:YES]; + } + } +} + +#pragma mark - +#pragma mark PFLogInViewControllerDelegate + +- (void)logInViewController:(PFLogInViewController *)logInController didLogInUser:(PFUser *)user { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)logInViewControllerDidCancelLogIn:(PFLogInViewController *)logInController { + // Do nothing, as the view controller dismisses itself +} + +#pragma mark - +#pragma mark PFSignUpViewControllerDelegate + +- (void)signUpViewController:(PFSignUpViewController *)signUpController didSignUpUser:(PFUser *)user { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)signUpViewControllerDidCancelSignUp:(PFSignUpViewController *)signUpController { + // Do nothing, as the view controller dismisses itself +} + +@end diff --git a/ParseUI/ParseUIDemo/Images.xcassets/LaunchImage.launchimage/Contents.json b/ParseUI/ParseUIDemo/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 000000000..f62085cfc --- /dev/null +++ b/ParseUI/ParseUIDemo/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,89 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "1x", + "orientation" : "portrait" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "orientation" : "portrait" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "minimum-system-version" : "7.0", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "minimum-system-version" : "7.0", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "minimum-system-version" : "7.0", + "extent" : "full-screen", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "minimum-system-version" : "7.0", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "minimum-system-version" : "7.0", + "extent" : "full-screen", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ParseUI/ParseUIDemo/Other/Info.plist b/ParseUI/ParseUIDemo/Other/Info.plist new file mode 100644 index 000000000..28445b958 --- /dev/null +++ b/ParseUI/ParseUIDemo/Other/Info.plist @@ -0,0 +1,96 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + Icon.png + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.1.1 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + fb838719136190681 + + + + CFBundleVersion + 1.1.1 + FacebookAppID + 838719136190681 + LSApplicationQueriesSchemes + + fbapi + fb-messenger-api + fbauth2 + fbshareextension + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSExceptionDomains + + akamaihd.net + + NSExceptionRequiresForwardSecrecy + + NSIncludesSubdomains + + + facebook.com + + NSExceptionRequiresForwardSecrecy + + NSIncludesSubdomains + + + fbcdn.net + + NSExceptionRequiresForwardSecrecy + + NSIncludesSubdomains + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ParseUI/ParseUIDemo/Other/main.m b/ParseUI/ParseUIDemo/Other/main.m new file mode 100644 index 000000000..91134264b --- /dev/null +++ b/ParseUI/ParseUIDemo/Other/main.m @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/ParseUI/ParseUIDemo/Resources/0.png b/ParseUI/ParseUIDemo/Resources/0.png new file mode 100644 index 000000000..c2c9bc03b Binary files /dev/null and b/ParseUI/ParseUIDemo/Resources/0.png differ diff --git a/ParseUI/ParseUIDemo/Resources/1.png b/ParseUI/ParseUIDemo/Resources/1.png new file mode 100644 index 000000000..57115b9a4 Binary files /dev/null and b/ParseUI/ParseUIDemo/Resources/1.png differ diff --git a/ParseUI/ParseUIDemo/Resources/2.png b/ParseUI/ParseUIDemo/Resources/2.png new file mode 100644 index 000000000..c0b0f5ea8 Binary files /dev/null and b/ParseUI/ParseUIDemo/Resources/2.png differ diff --git a/ParseUI/ParseUIDemo/Resources/Icon.png b/ParseUI/ParseUIDemo/Resources/Icon.png new file mode 100644 index 000000000..a4414e2cc Binary files /dev/null and b/ParseUI/ParseUIDemo/Resources/Icon.png differ diff --git a/ParseUI/ParseUIDemo/Resources/SimpleQueryCollectionStoryboard.storyboard b/ParseUI/ParseUIDemo/Resources/SimpleQueryCollectionStoryboard.storyboard new file mode 100644 index 000000000..f2265e62a --- /dev/null +++ b/ParseUI/ParseUIDemo/Resources/SimpleQueryCollectionStoryboard.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParseUI/ParseUIDemo/Resources/SimpleQueryTableStoryboard.storyboard b/ParseUI/ParseUIDemo/Resources/SimpleQueryTableStoryboard.storyboard new file mode 100644 index 000000000..0cbb66c5d --- /dev/null +++ b/ParseUI/ParseUIDemo/Resources/SimpleQueryTableStoryboard.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParseUI/ParseUIDemo/Swift/AppDelegate.swift b/ParseUI/ParseUIDemo/Swift/AppDelegate.swift new file mode 100644 index 000000000..78e7d9c48 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/AppDelegate.swift @@ -0,0 +1,111 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit +import Parse +import ParseFacebookUtilsV4 +import ParseTwitterUtils + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + // MARK: UIApplicationDelegate + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { + Parse.setApplicationId("UdNpOP2XFoEiXLZEBDl6xONmCMH8VjETmnEsl0xJ", clientKey: "wNJFho0fQaQFQ2Fe1x9b67lVBakJiAtFj1Uz30A9") + PFFacebookUtils.initializeFacebook(applicationLaunchOptions: launchOptions) + PFTwitterUtils.initialize(withConsumerKey: "3Q9hMEKqqSg4ie2pibZ2sVJuv", consumerSecret: "IEZ9wv2d1EpXNGFKGp7sAGdxRtyqtPwygyciFZwTHTGhPp4FMj") + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = UINavigationController(rootViewController: UIDemoViewController()) + window?.makeKeyAndVisible() + + DispatchQueue.global(qos: .default).async() { + self.setupTestData() + } + + return true + } + + func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { + return FBSDKApplicationDelegate.sharedInstance().application(application, open:url, sourceApplication: sourceApplication, annotation: annotation) + } + + // MARK: Test Data + + private func setupTestData() { + let todoTitles = [ + "Build Parse", + "Make everything awesome", + "Go out for the longest run", + "Do more stuff", + "Conquer the world", + "Build a house", + "Grow a tree", + "Be awesome", + "Setup an app", + "Do stuff", + "Buy groceries", + "Wash clothes" + ]; + + var objects: [PFObject] = Array() + + do { + let todos = try PFQuery(className: "Todo").findObjects() + if todos.count == 0 { + for (index, title) in todoTitles.enumerated() { + let todo = PFObject(className: "Todo") + todo["title"] = title + todo["priority"] = index % 3 + objects.append(todo) + } + } + } catch {} + + let appNames = [ "Anypic", "Anywall", "f8" ] + do { + let apps = try PFQuery(className: "App").findObjects() + if apps.count == 0 { + for (index, appName) in appNames.enumerated() { + let bundle = Bundle.main + if let fileURL = bundle.url(forResource: String(index), withExtension: "png") { + if let data = try? Data(contentsOf: fileURL) { + let file = PFFile(name: fileURL.lastPathComponent, data: data) + let object = PFObject(className: "App") + object["icon"] = file + object["name"] = appName + objects.append(object) + } + } + } + } + } catch {} + + if objects.count != 0 { + do { + try PFObject.saveAll(objects) + } catch {} + } + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/LogInViewController/CustomLogInViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/LogInViewController/CustomLogInViewController.swift new file mode 100644 index 000000000..1a3689b09 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/LogInViewController/CustomLogInViewController.swift @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class CustomLogInViewController: PFLogInViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .blue + + let label = UILabel() + label.textColor = .white + label.text = "All Custom!" + label.sizeToFit() + logInView?.logo = label + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/ProductTableViewController/CustomProductTableViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/ProductTableViewController/CustomProductTableViewController.swift new file mode 100644 index 000000000..82faeb50a --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/ProductTableViewController/CustomProductTableViewController.swift @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class CustomProductTableViewController: PFProductTableViewController { + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let product = objects?[indexPath.row] + if let identifier = product?["productIdentifier"] as? String, + identifier == "Cooper" { + PFPurchase.buyProduct(identifier) { error in + if error == nil { + UIAlertView(title: "Success!", message: "Yes!", delegate: nil, cancelButtonTitle: "OK").show() + } + } + } + super.tableView(tableView, didSelectRowAt: indexPath) + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.swift new file mode 100644 index 000000000..57e34ec44 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/DeletionCollectionViewController.swift @@ -0,0 +1,136 @@ +// +// DeletionCollectionViewController.swift +// ParseUIDemo +// +// Created by Richard Ross III on 5/14/15. +// Copyright (c) 2015 Parse Inc. All rights reserved. +// + +import UIKit + +import Parse +import ParseUI + +import Bolts.BFTask + +class DeletionCollectionViewController: PFQueryCollectionViewController, UIAlertViewDelegate { + convenience init(className: String?) { + let layout = UICollectionViewFlowLayout() + layout.sectionInset = UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0) + layout.minimumInteritemSpacing = 5.0 + + self.init(collectionViewLayout: layout, className: className) + + title = "Deletion Collection" + pullToRefreshEnabled = true + objectsPerPage = 10 + paginationEnabled = true + + collectionView?.allowsMultipleSelection = true + + navigationItem.rightBarButtonItems = [ + editButtonItem, + UIBarButtonItem(barButtonSystemItem: .add, target: self, action:#selector(addTodo)) + ] + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + if let layout = collectionViewLayout as? UICollectionViewFlowLayout { + let bounds = UIEdgeInsetsInsetRect(view.bounds, layout.sectionInset) + let sideLength = min(bounds.width, bounds.height) / 2.0 - layout.minimumInteritemSpacing + layout.itemSize = CGSize(width: sideLength, height: sideLength) + } + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + if (editing) { + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .trash, + target: self, + action: #selector(deleteSelectedItems) + ) + } else { + navigationItem.leftBarButtonItem = navigationItem.backBarButtonItem + } + } + + @objc + func addTodo() { + if #available(iOS 8.0, *) { + let alertDialog = UIAlertController(title: "Add Todo", message: nil, preferredStyle: .alert) + + var titleTextField : UITextField? = nil + alertDialog.addTextField(configurationHandler: { + titleTextField = $0 + }) + + alertDialog.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + alertDialog.addAction(UIAlertAction(title: "Save", style: .default) { action in + if let title = titleTextField?.text { + let object = PFObject(className: self.parseClassName!, dictionary: [ "title": title ]) + object.saveEventually().continueOnSuccessWith { _ -> AnyObject! in + return self.loadObjects() + } + } + }) + + present(alertDialog, animated: true, completion: nil) + } else { + let alertView = UIAlertView( + title: "Add Todo", + message: "", + delegate: self, + cancelButtonTitle: "Cancel", + otherButtonTitles: "Save" + ) + + alertView.alertViewStyle = .plainTextInput + alertView.textField(at: 0)?.placeholder = "Name" + + alertView.show() + } + } + + @objc + func deleteSelectedItems() { + guard let paths = collectionView?.indexPathsForSelectedItems else { return } + removeObjects(at: paths) + } + + // MARK - UICollectionViewDataSource + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, object: PFObject?) -> PFCollectionViewCell? { + let cell = super.collectionView(collectionView, cellForItemAt: indexPath, object: object) + cell?.textLabel.textAlignment = .center + cell?.textLabel.text = object?["title"] as? String + + cell?.contentView.layer.borderWidth = 1.0 + cell?.contentView.layer.borderColor = UIColor.lightGray.cgColor + + return cell + } + + // MARK - UIAlertViewDelegate + + @objc + func alertView(_ alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { + if (buttonIndex == alertView.cancelButtonIndex) { + return + } + + if let title = alertView.textField(at: 0)?.text { + let object = PFObject( + className: self.parseClassName!, + dictionary: [ "title": title ] + ) + + object.saveEventually().continueOnSuccessWith { _ -> AnyObject! in + return self.loadObjects() + } + } + } +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.swift new file mode 100644 index 000000000..2353d9267 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/PaginatedCollectionViewController.swift @@ -0,0 +1,85 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class PaginatedCollectionViewController: PFQueryCollectionViewController { + + // MARK: Init + + convenience init(className: String?) { + let layout = UICollectionViewFlowLayout() + layout.sectionInset = UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0) + layout.minimumInteritemSpacing = 5.0 + self.init(collectionViewLayout: layout, className: className) + + title = "Paginated Collection" + pullToRefreshEnabled = true + objectsPerPage = 10 + paginationEnabled = true + } + + // MARK: UIViewController + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + if let layout = collectionViewLayout as? UICollectionViewFlowLayout { + let bounds = UIEdgeInsetsInsetRect(view.bounds, layout.sectionInset) + let sideLength = min(bounds.width, bounds.height) / 2.0 - layout.minimumInteritemSpacing + layout.itemSize = CGSize(width: sideLength, height: sideLength) + } + } + + // MARK: Data + + override func queryForCollection() -> PFQuery { + return super.queryForCollection().order(byAscending: "priority") + } + + // MARK: CollectionView + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, object: PFObject?) -> PFCollectionViewCell? { + let cell = super.collectionView(collectionView, cellForItemAt: indexPath, object: object) + cell?.textLabel.textAlignment = .center + + if let title = object?["title"] as? String { + let attributedTitle = NSMutableAttributedString(string: title) + if let priority = object?["priority"] as? Int { + let attributes = [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 13.0), NSAttributedStringKey.foregroundColor : UIColor.gray] + let string = NSAttributedString(string: "\nPriority: \(priority)", attributes: attributes) + attributedTitle.append(string) + } + cell?.textLabel.attributedText = attributedTitle + } else { + cell?.textLabel.attributedText = NSAttributedString() + } + + cell?.contentView.layer.borderWidth = 1.0 + cell?.contentView.layer.borderColor = UIColor.lightGray.cgColor + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.swift new file mode 100644 index 000000000..4099162ea --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SectionedCollectionViewController.swift @@ -0,0 +1,155 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class SimpleCollectionReusableView : UICollectionReusableView { + let label: UILabel = UILabel(frame: .zero) + + override init(frame: CGRect) { + super.init(frame: frame) + + label.textAlignment = .center + addSubview(label) + } + + required init?(coder decoder: NSCoder) { + super.init(coder: decoder) + + label.textAlignment = .center + addSubview(label) + } + + override func layoutSubviews() { + super.layoutSubviews() + + label.frame = bounds + } +} + +class SectionedCollectionViewController: PFQueryCollectionViewController { + + var sections: [Int: [PFObject]] = Dictionary() + var sectionKeys: [Int] = Array() + + // MARK: Init + + convenience init(className: String?) { + let layout = UICollectionViewFlowLayout() + layout.sectionInset = UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0) + layout.minimumInteritemSpacing = 5.0 + self.init(collectionViewLayout: layout, className: className) + + title = "Sectioned Collection" + pullToRefreshEnabled = true + } + + // MARK: UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + + collectionView?.register(SimpleCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header") + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + if let layout = collectionViewLayout as? UICollectionViewFlowLayout { + let bounds = UIEdgeInsetsInsetRect(view.bounds, layout.sectionInset) + let sideLength = min(bounds.width, bounds.height) / 2.0 - layout.minimumInteritemSpacing + layout.itemSize = CGSize(width: sideLength, height: sideLength) + } + } + + // MARK: Data + + override func objectsDidLoad(_ error: Error?) { + super.objectsDidLoad(error) + + sections.removeAll(keepingCapacity: false) + for object in objects { + let priority = (object["priority"] as? Int) ?? 0 + var array = sections[priority] ?? Array() + array.append(object) + sections[priority] = array + } + sectionKeys = sections.keys.sorted(by: <) + + collectionView?.reloadData() + } + + override func object(at indexPath: IndexPath?) -> PFObject? { + if let indexPath = indexPath { + let array = sections[sectionKeys[indexPath.section]] + return array?[indexPath.row] + } + return nil + } + +} + +extension SectionedCollectionViewController { + + + override func numberOfSections(in collectionView: UICollectionView) -> Int { + return sections.count + } + + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + let array = sections[sectionKeys[section]] + return array?.count ?? 0 + } + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, object: PFObject?) -> PFCollectionViewCell? { + let cell = super.collectionView(collectionView, cellForItemAt: indexPath, object: object) + + cell?.textLabel.textAlignment = .center + cell?.textLabel.text = object?["title"] as? String + + cell?.contentView.layer.borderWidth = 1.0 + cell?.contentView.layer.borderColor = UIColor.lightGray.cgColor + + return cell + } + + override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + if kind == UICollectionElementKindSectionHeader, + let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as? SimpleCollectionReusableView { + view.label.text = "Priority \(sectionKeys[indexPath.section])" + return view + } + return super.collectionView(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) + } + + override func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + referenceSizeForHeaderInSection section: Int) -> CGSize { + if sections.count > 0 { + return CGSize(width: collectionView.bounds.width, height: 40.0) + } + return .zero + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.swift new file mode 100644 index 000000000..c9a0f791b --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SimpleCollectionViewController.swift @@ -0,0 +1,84 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class SimpleCollectionViewController: PFQueryCollectionViewController { + + // MARK: Init + + convenience init(className: String?) { + let layout = UICollectionViewFlowLayout() + layout.sectionInset = UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0) + layout.minimumInteritemSpacing = 5.0 + self.init(collectionViewLayout: layout, className: className) + + title = "Simple Collection" + pullToRefreshEnabled = true + paginationEnabled = false + } + + // MARK: UIViewController + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + if let layout = collectionViewLayout as? UICollectionViewFlowLayout { + let bounds = UIEdgeInsetsInsetRect(view.bounds, layout.sectionInset) + let sideLength = min(bounds.width, bounds.height) / 2.0 - layout.minimumInteritemSpacing + layout.itemSize = CGSize(width: sideLength, height: sideLength) + } + } + + // MARK: Data + + override func queryForCollection() -> PFQuery { + return super.queryForCollection().order(byAscending: "priority") + } + + // MARK: CollectionView + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, object: PFObject?) -> PFCollectionViewCell? { + let cell = super.collectionView(collectionView, cellForItemAt: indexPath, object: object) + cell?.textLabel.textAlignment = .center + + if let title = object?["title"] as? String { + let attributedTitle = NSMutableAttributedString(string: title) + if let priority = object?["priority"] as? Int { + let attributes = [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 13.0), NSAttributedStringKey.foregroundColor : UIColor.gray] + let string = NSAttributedString(string: "\nPriority: \(priority)", attributes: attributes) + attributedTitle.append(string) + } + cell?.textLabel.attributedText = attributedTitle + } else { + cell?.textLabel.attributedText = NSAttributedString() + } + + cell?.contentView.layer.borderWidth = 1.0 + cell?.contentView.layer.borderColor = UIColor.lightGray.cgColor + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.swift new file mode 100644 index 000000000..f339e4f9c --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/StoryboardCollectionViewController.swift @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class StoryboardCollectionViewController: PFQueryCollectionViewController { + + // MARK: UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + + if let layout = collectionViewLayout as? UICollectionViewFlowLayout { + layout.sectionInset = UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0) + layout.minimumInteritemSpacing = 5.0 + } + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + if let layout = collectionViewLayout as? UICollectionViewFlowLayout { + let bounds = UIEdgeInsetsInsetRect(view.bounds, layout.sectionInset) + let sideLength = min(bounds.width, bounds.height) / 2.0 - layout.minimumInteritemSpacing + layout.itemSize = CGSize(width: sideLength, height: sideLength) + } + } + + // MARK: Data + + override func queryForCollection() -> PFQuery { + return super.queryForCollection().order(byAscending: "priority") + } + + // MARK: CollectionView + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, object: PFObject?) -> PFCollectionViewCell? { + let cell = super.collectionView(collectionView, cellForItemAt: indexPath, object: object) + cell?.textLabel.textAlignment = .center + + if let title = object?["title"] as? String { + let attributedTitle = NSMutableAttributedString(string: title) + if let priority = object?["priority"] as? Int { + let attributes = [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 13.0), NSAttributedStringKey.foregroundColor : UIColor.gray] + let string = NSAttributedString(string: "\nPriority: \(priority)", attributes: attributes) + attributedTitle.append(string) + } + cell?.textLabel.attributedText = attributedTitle + } else { + cell?.textLabel.attributedText = NSAttributedString() + } + + cell?.contentView.layer.borderWidth = 1.0 + cell?.contentView.layer.borderColor = UIColor.lightGray.cgColor + + return cell + } +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.swift new file mode 100644 index 000000000..9525d4f87 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryCollectionViewController/SubtitleImageCollectionViewController.swift @@ -0,0 +1,74 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class SubtitleImageCollectionViewController: PFQueryCollectionViewController { + + // MARK: Init + + convenience init(className: String?) { + let layout = UICollectionViewFlowLayout() + layout.sectionInset = UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0) + layout.minimumInteritemSpacing = 5.0 + self.init(collectionViewLayout: layout, className: className) + + title = "Image Collection" + pullToRefreshEnabled = true + paginationEnabled = false + } + + // MARK: UIViewController + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + if let layout = collectionViewLayout as? UICollectionViewFlowLayout { + let bounds = UIEdgeInsetsInsetRect(view.bounds, layout.sectionInset) + let sideLength = min(bounds.width, bounds.height) / 2.0 - layout.minimumInteritemSpacing + layout.itemSize = CGSize(width: sideLength, height: sideLength) + } + } + + // MARK: CollectionView + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, object: PFObject?) -> PFCollectionViewCell? { + let cell = super.collectionView(collectionView, cellForItemAt: indexPath, object: object) + + cell?.textLabel.textAlignment = .center + cell?.textLabel.text = object?["name"] as? String + + cell?.imageView.file = object?["icon"] as? PFFile + if cell?.imageView.image == nil { + cell?.imageView.image = UIImage(named: "Icon.png") + cell?.imageView.loadInBackground() + } + + cell?.contentView.layer.borderWidth = 1.0 + cell?.contentView.layer.borderColor = UIColor.lightGray.cgColor + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/DeletionTableViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/DeletionTableViewController.swift new file mode 100644 index 000000000..b891a0fd0 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/DeletionTableViewController.swift @@ -0,0 +1,118 @@ +// +// DeletionTableViewController.swift +// ParseUIDemo +// +// Created by Richard Ross III on 5/13/15. +// Copyright (c) 2015 Parse Inc. All rights reserved. +// + +import UIKit + +import Parse +import ParseUI + +import Bolts.BFTask + +class DeletionTableViewController: PFQueryTableViewController, UIAlertViewDelegate { + + // MARK: Init + + convenience init(className: String?) { + self.init(style: .plain, className: className) + + title = "Deletion Table" + pullToRefreshEnabled = true + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.allowsMultipleSelectionDuringEditing = true + + navigationItem.rightBarButtonItems = [ + editButtonItem, + UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTodo)) + ] + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + if (editing) { + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .trash, + target: self, + action: #selector(deleteSelectedItems) + ) + } else { + navigationItem.leftBarButtonItem = navigationItem.backBarButtonItem + } + } + + override func tableView(_ tableView: UITableView, + commit editingStyle: UITableViewCellEditingStyle, + forRowAt indexPath: IndexPath) { + if (editingStyle == .delete) { + removeObject(at: indexPath) + } + } + + @objc + func addTodo() { + + if #available(iOS 8.0, *) { + let alertDialog = UIAlertController(title: "Add Todo", message: nil, preferredStyle: .alert) + + var titleTextField : UITextField! = nil + alertDialog.addTextField(configurationHandler: { + titleTextField = $0 + }) + + alertDialog.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + alertDialog.addAction(UIAlertAction(title: "Save", style: .default) { _ in + if let title = titleTextField.text { + let object = PFObject(className: self.parseClassName!, dictionary: [ "title": title ]) + object.saveInBackground().continueOnSuccessWith { _ -> AnyObject! in + return self.loadObjects() + } + } + }) + + present(alertDialog, animated: true, completion: nil) + } else { + let alertView = UIAlertView( + title: "Add Todo", + message: "", + delegate: self, + cancelButtonTitle: "Cancel", + otherButtonTitles: "Save" + ) + + alertView.alertViewStyle = .plainTextInput + alertView.textField(at: 0)?.placeholder = "Name" + + alertView.show() + } + } + + @objc + func deleteSelectedItems() { + removeObjects(at: tableView.indexPathsForSelectedRows) + } + + // MARK - UIAlertViewDelegate + + @objc + func alertView(_ alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { + if (buttonIndex == alertView.cancelButtonIndex) { + return + } + + if let title = alertView.textField(at: 0)?.text { + let object = PFObject(className: self.parseClassName!, dictionary: [ "title": title ]) + object.saveEventually().continueOnSuccessWith { _ -> AnyObject! in + return self.loadObjects() + } + } + } +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.swift new file mode 100644 index 000000000..c8d5f705a --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/PaginatedTableViewController.swift @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class PaginatedTableViewController: PFQueryTableViewController { + + // MARK: Init + + convenience init(className: String?) { + self.init(style: .plain, className: className) + + title = "Paginated Table" + pullToRefreshEnabled = true + objectsPerPage = 10 + paginationEnabled = true + } + + // MARK: Data + + override func queryForTable() -> PFQuery { + return super.queryForTable().order(byAscending: "priority") + } + + // MARK: TableView + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, object: PFObject?) -> PFTableViewCell? { + let cellIdentifier = "cell" + + var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? PFTableViewCell + if cell == nil { + cell = PFTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier) + } + + cell?.textLabel?.text = object?["title"] as? String + + var subtitle: String + if let priority = object?["priority"] as? Int { + subtitle = "Priority: \(priority)" + } else { + subtitle = "No Priority" + } + cell?.detailTextLabel?.text = subtitle + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SectionedTableViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SectionedTableViewController.swift new file mode 100644 index 000000000..c1774fbe1 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SectionedTableViewController.swift @@ -0,0 +1,97 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class SectionedTableViewController: PFQueryTableViewController { + + var sections: [Int: [PFObject]] = Dictionary() + var sectionKeys: [Int] = Array() + + // MARK: Init + + convenience init(className: String?) { + self.init(style: .plain, className: className) + + title = "Sectioned Table" + pullToRefreshEnabled = true + } + + // MARK: Data + + override func objectsDidLoad(_ error: Error?) { + super.objectsDidLoad(error) + + sections.removeAll(keepingCapacity: false) + if let objects = objects { + for object in objects { + let priority = (object["priority"] as? Int) ?? 0 + var array = sections[priority] ?? Array() + array.append(object) + sections[priority] = array + } + } + sectionKeys = sections.keys.sorted(by: <) + + tableView.reloadData() + } + + override func object(at indexPath: IndexPath?) -> PFObject? { + if let indexPath = indexPath { + let array = sections[sectionKeys[indexPath.section]] + return array?[indexPath.row] + } + return nil + } +} + +extension SectionedTableViewController { + + override func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let array = sections[sectionKeys[section]] + return array?.count ?? 0 + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return "Priority \(sectionKeys[section])" + } + + override func tableView(_ tableView: UITableView, cellForRowAt: IndexPath, object: PFObject?) -> PFTableViewCell? { + let cellIdentifier = "cell" + + var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? PFTableViewCell + if cell == nil { + cell = PFTableViewCell(style: .default, reuseIdentifier: cellIdentifier) + } + + cell?.textLabel?.text = object?["title"] as? String + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SimpleTableViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SimpleTableViewController.swift new file mode 100644 index 000000000..993d0d8c5 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SimpleTableViewController.swift @@ -0,0 +1,68 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class SimpleTableViewController: PFQueryTableViewController { + + // MARK: Init + + convenience init(className: String?) { + self.init(style: .plain, className: className) + + title = "Simple Table" + pullToRefreshEnabled = true + paginationEnabled = false + } + + // MARK: Data + + override func queryForTable() -> PFQuery { + return super.queryForTable().order(byAscending: "priority") + } + + // MARK: TableView + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, object: PFObject?) -> PFTableViewCell? { + let cellIdentifier = "cell" + + var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? PFTableViewCell + if cell == nil { + cell = PFTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier) + } + + cell?.textLabel?.text = object?["title"] as? String + + var subtitle: String + if let priority = object?["priority"] as? Int { + subtitle = "Priority: \(priority)" + } else { + subtitle = "No Priority" + } + cell?.detailTextLabel?.text = subtitle + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.swift new file mode 100644 index 000000000..43f103331 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/StoryboardTableViewController.swift @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class StoryboardTableViewController: PFQueryTableViewController { + + // MARK: Data + + override func queryForTable() -> PFQuery { + return super.queryForTable().order(byAscending: "priority") + } + + // MARK: TableView + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, object: PFObject?) -> PFTableViewCell? { + let cellIdentifier = "cell" + + var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? PFTableViewCell + if cell == nil { + cell = PFTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier) + } + + var title: String? + var subtitle: String? + + if let object = object { + title = object["title"] as? String + if let priority = object["priority"] as? Int { + subtitle = "Priority: \(priority)" + } else { + subtitle = "No Priority" + } + } + + cell?.textLabel?.text = title + cell?.detailTextLabel?.text = subtitle + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.swift new file mode 100644 index 000000000..d6e8f173f --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/QueryTableViewController/SubtitleImageTableViewController.swift @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class SubtitleImageTableViewController: PFQueryTableViewController { + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, object: PFObject?) -> PFTableViewCell? { + let cellIdentifier = "cell" + + var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? PFTableViewCell + if cell == nil { + cell = PFTableViewCell(style: .value1, reuseIdentifier: cellIdentifier) + } + + cell?.textLabel?.text = object?["name"] as? String + cell?.detailTextLabel?.text = "@parseit" + + cell?.imageView?.image = UIImage(named: "Icon.png") + cell?.imageView?.file = object?["icon"] as? PFFile + + return cell + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/CustomViewControllers/SignUpViewController/CustomSignUpViewController.swift b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/SignUpViewController/CustomSignUpViewController.swift new file mode 100644 index 000000000..bf885db07 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/CustomViewControllers/SignUpViewController/CustomSignUpViewController.swift @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +class CustomSignUpViewController: PFSignUpViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .blue + + let label = UILabel() + label.textColor = .white + label.text = "All Custom!" + label.sizeToFit() + signUpView?.logo = label + } + +} diff --git a/ParseUI/ParseUIDemo/Swift/Resources/SimpleQueryCollectionStoryboard-Swift.storyboard b/ParseUI/ParseUIDemo/Swift/Resources/SimpleQueryCollectionStoryboard-Swift.storyboard new file mode 100644 index 000000000..51a145098 --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/Resources/SimpleQueryCollectionStoryboard-Swift.storyboard @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParseUI/ParseUIDemo/Swift/Resources/SimpleQueryTableStoryboard-Swift.storyboard b/ParseUI/ParseUIDemo/Swift/Resources/SimpleQueryTableStoryboard-Swift.storyboard new file mode 100644 index 000000000..00105a56d --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/Resources/SimpleQueryTableStoryboard-Swift.storyboard @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParseUI/ParseUIDemo/Swift/UIDemoViewController.swift b/ParseUI/ParseUIDemo/Swift/UIDemoViewController.swift new file mode 100644 index 000000000..01543634c --- /dev/null +++ b/ParseUI/ParseUIDemo/Swift/UIDemoViewController.swift @@ -0,0 +1,366 @@ +/* +* Copyright (c) 2015, Parse, LLC. All rights reserved. +* +* You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +* copy, modify, and distribute this software in source code or binary form for use +* in connection with the web services and APIs provided by Parse. +* +* As with any software that integrates with the Parse platform, your use of +* this software is subject to the Parse Terms of Service +* [https://www.parse.com/about/terms]. This copyright notice shall be +* included in all copies or substantial portions of the software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +import UIKit + +import Parse +import ParseUI + +enum UIDemoType : Int { + case LogInDefault + case LogInUsernamePassword + case LogInPasswordForgotten + case LogInDone + case LogInEmailAsUsername + case LogInFacebook + case LogInFacebookAndTwitter + case LogInAll + case LogInAllNavigation + case LogInCustomizedLogoAndBackground + case SignUpDefault + case SignUpUsernamePassword + case SignUpUsernamePasswordEmail + case SignUpUsernamePasswordEmailSignUp + case SignUpAll + case SignUpEmailAsUsername + case SignUpMinPasswordLength + case SimpleTable + case PaginatedTable + case SectionedTable + case StoryboardTable + case DeletionTable + case ImageTableDefaultStyle + case ImageTableSubtitleStyle + case SimpleCollection + case PaginatedCollection + case SectionedCollection + case StoryboardCollection + case DeletionCollection + case ImageCollection + case Product + case CustomizedProduct + + static var count: Int { + var count = 0 + while let _ = self.init(rawValue: count) { + count = count + 1 + } + return count + } +} + +extension UIDemoType : CustomStringConvertible { + + var description: String { + switch (self) { + case .LogInDefault: + return "Log In Default" + case .LogInUsernamePassword: + return "Log In Username and Password" + case .LogInPasswordForgotten: + return "Log In Password Forgotten" + case .LogInDone: + return "Log In Done Button" + case .LogInEmailAsUsername: + return "Log In Email as Username" + case .LogInFacebook: + return "Log In Facebook" + case .LogInFacebookAndTwitter: + return "Log In Facebook and Twitter" + case .LogInAll: + return "Log In All" + case .LogInAllNavigation: + return "Log In All as Navigation" + case .LogInCustomizedLogoAndBackground: + return "Log In Customized Background" + case .SignUpDefault: + return "Sign Up Default" + case .SignUpUsernamePassword: + return "Sign Up Username and Password" + case .SignUpUsernamePasswordEmail: + return "Sign Up Email" + case .SignUpUsernamePasswordEmailSignUp: + return "Sign Up Email And SignUp" + case .SignUpAll: + return "Sign Up All" + case .SignUpEmailAsUsername: + return "Sign Up Email as Username" + case .SignUpMinPasswordLength: + return "Sign Up Minimum Password Length" + case .SimpleTable: + return "Simple Table" + case .PaginatedTable: + return "Paginated Table" + case .SectionedTable: + return "Sectioned Table" + case .StoryboardTable: + return "Simple Storyboard Table" + case .DeletionTable: + return "Deletion Table" + case .ImageTableDefaultStyle: + return "Remote Image Table Default Style" + case .ImageTableSubtitleStyle: + return "Remote Image Table Subtitle Style" + case .SimpleCollection: + return "Simple Collection" + case .PaginatedCollection: + return "Paginated Collection" + case .SectionedCollection: + return "Sectioned Collection" + case .StoryboardCollection: + return "Simple Storyboard Collection" + case .DeletionCollection: + return "Deletion Collection" + case .ImageCollection: + return "Remote Image Collection" + case .Product: + return "Product" + case .CustomizedProduct: + return "Customized Product" + } + } + +} + +class UIDemoViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + title = "ParseUI Demo" + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + } + +} + +extension UIDemoViewController { + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return UIDemoType.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + cell.textLabel?.text = UIDemoType(rawValue: indexPath.row)?.description + return cell + } +} + +extension UIDemoViewController { + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let demoType = UIDemoType(rawValue: indexPath.row) { + switch (demoType) { + // ----- + // PFLogInViewController + // ----- + case .LogInDefault: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + present(logInViewController, animated: true, completion: nil) + case .LogInUsernamePassword: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.usernameAndPassword, .dismissButton] + present(logInViewController, animated: true, completion: nil) + case .LogInPasswordForgotten: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.usernameAndPassword, .passwordForgotten, .dismissButton] + present(logInViewController, animated: true, completion: nil) + case .LogInDone: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.usernameAndPassword, .logInButton, .dismissButton] + present(logInViewController, animated: true, completion: nil) + case .LogInEmailAsUsername: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.usernameAndPassword, .logInButton, .signUpButton, .dismissButton] + logInViewController.emailAsUsername = true + present(logInViewController, animated: true, completion: nil) + case .LogInFacebook: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.usernameAndPassword, .facebook, .dismissButton] + present(logInViewController, animated: true, completion: nil) + case .LogInFacebookAndTwitter: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.facebook, .twitter, .dismissButton] + present(logInViewController, animated: true, completion: nil) + case .LogInAll: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.usernameAndPassword, .passwordForgotten, .logInButton, .facebook, .twitter, .signUpButton, .dismissButton] + if let signUpController = logInViewController.signUpController { + signUpController.delegate = self + signUpController.fields = [.usernameAndPassword, .email, .additional, .signUpButton, .dismissButton] + } + present(logInViewController, animated: true, completion: nil) + case .LogInAllNavigation: + let logInViewController = PFLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.usernameAndPassword, .passwordForgotten, .logInButton, .facebook, .twitter, .signUpButton, .dismissButton] + if let signUpViewController = logInViewController.signUpController { + signUpViewController.delegate = self + signUpViewController.fields = [.usernameAndPassword, .email, .additional, .signUpButton, .dismissButton] + } + navigationController?.pushViewController(logInViewController, animated: true) + case .LogInCustomizedLogoAndBackground: + let logInViewController = CustomLogInViewController() + logInViewController.delegate = self + logInViewController.fields = [.default, .facebook, .twitter] + + let signUpViewController = CustomSignUpViewController() + signUpViewController.delegate = self + signUpViewController.fields = .default + + logInViewController.signUpController = signUpViewController + present(logInViewController, animated: true, completion: nil) + // ----- + // PFSignUpViewController + // ----- + case .SignUpDefault: + let signUpViewController = PFSignUpViewController() + signUpViewController.delegate = self + present(signUpViewController, animated: true, completion: nil) + case .SignUpUsernamePassword: + let signUpViewController = PFSignUpViewController() + signUpViewController.delegate = self + signUpViewController.fields = [.usernameAndPassword, .dismissButton] + present(signUpViewController, animated: true, completion: nil) + case .SignUpUsernamePasswordEmail: + let signUpViewController = PFSignUpViewController() + signUpViewController.delegate = self + signUpViewController.fields = [.usernameAndPassword, .email, .dismissButton] + present(signUpViewController, animated: true, completion: nil) + case .SignUpUsernamePasswordEmailSignUp: + let signUpViewController = PFSignUpViewController() + signUpViewController.delegate = self + signUpViewController.fields = [.usernameAndPassword, .email, .signUpButton, .dismissButton] + present(signUpViewController, animated: true, completion: nil) + case .SignUpAll: + let signUpViewController = PFSignUpViewController() + signUpViewController.delegate = self + signUpViewController.fields = [.usernameAndPassword, .email, .additional, .signUpButton, .dismissButton] + present(signUpViewController, animated: true, completion: nil) + case .SignUpEmailAsUsername: + let signUpViewController = PFSignUpViewController() + signUpViewController.delegate = self + signUpViewController.fields = [.usernameAndPassword, .signUpButton, .dismissButton] + signUpViewController.emailAsUsername = true + present(signUpViewController, animated: true, completion: nil) + case .SignUpMinPasswordLength: + let signUpViewController = PFSignUpViewController() + signUpViewController.delegate = self + signUpViewController.fields = [.usernameAndPassword, .signUpButton, .dismissButton] + signUpViewController.minPasswordLength = 6 + present(signUpViewController, animated: true, completion: nil) + // ----- + // PFQueryTableViewController + // ----- + case .SimpleTable: + let tableViewController = SimpleTableViewController(className: "Todo") + navigationController?.pushViewController(tableViewController, animated: true) + case .PaginatedTable: + let tableViewController = PaginatedTableViewController(className: "Todo") + navigationController?.pushViewController(tableViewController, animated: true) + case .SectionedTable: + let tableViewController = SectionedTableViewController(className: "Todo") + navigationController?.pushViewController(tableViewController, animated: true) + case .StoryboardTable: + let storyboard = UIStoryboard(name: "SimpleQueryTableStoryboard-Swift", bundle: nil) + let tableViewController = storyboard.instantiateViewController(withIdentifier: "StoryboardTableViewController") as? StoryboardTableViewController + navigationController?.pushViewController(tableViewController!, animated: true) + case .DeletionTable: + let tableViewController = DeletionTableViewController(className: "PublicTodo"); + navigationController?.pushViewController(tableViewController, animated: true); + case .ImageTableDefaultStyle: + let tableViewController = PFQueryTableViewController(className: "App") + tableViewController.imageKey = "icon" + tableViewController.textKey = "name" + tableViewController.paginationEnabled = false + tableViewController.placeholderImage = UIImage(named: "Icon.png") + navigationController?.pushViewController(tableViewController, animated: true) + case .ImageTableSubtitleStyle: + let tableViewController = SubtitleImageTableViewController(className: "App") + tableViewController.imageKey = "icon" + tableViewController.textKey = "name" + tableViewController.paginationEnabled = false + tableViewController.placeholderImage = UIImage(named: "Icon.png") + navigationController?.pushViewController(tableViewController, animated: true) + // ----- + // PFQueryCollectionViewController + // ----- + case .SimpleCollection: + let collectionViewController = SimpleCollectionViewController(className: "Todo") + navigationController?.pushViewController(collectionViewController, animated: true) + case .PaginatedCollection: + let collectionViewController = PaginatedCollectionViewController(className: "Todo") + navigationController?.pushViewController(collectionViewController, animated: true) + case .SectionedCollection: + let collectionViewController = SectionedCollectionViewController(className: "Todo") + navigationController?.pushViewController(collectionViewController, animated: true) + case .StoryboardCollection: + let storyboard = UIStoryboard(name: "SimpleQueryCollectionStoryboard-Swift", bundle: nil) + let collectionViewController = storyboard.instantiateViewController(withIdentifier: "StoryboardCollectionViewController") as? StoryboardCollectionViewController + navigationController?.pushViewController(collectionViewController!, animated: true) + case .DeletionCollection: + let collectionViewController = DeletionCollectionViewController(className: "PublicTodo"); + navigationController?.pushViewController(collectionViewController, animated: true) + case .ImageCollection: + let collectionViewController = SubtitleImageCollectionViewController(className: "App") + navigationController?.pushViewController(collectionViewController, animated: true) + // ----- + // PFProductTableViewController + // ----- + case .Product: + let productTableViewController = PFProductTableViewController() + navigationController?.pushViewController(productTableViewController, animated: true) + case .CustomizedProduct: + let productTableViewController = CustomProductTableViewController() + navigationController?.pushViewController(productTableViewController, animated: true) + } + } + } + +} + +extension UIDemoViewController : PFLogInViewControllerDelegate { + + func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) { + dismiss(animated: true, completion: nil) + } + +} + +extension UIDemoViewController : PFSignUpViewControllerDelegate { + + func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser) { + dismiss(animated: true, completion: nil) + } + +} diff --git a/ParseUI/Resources/Images/facebook_icon.png b/ParseUI/Resources/Images/facebook_icon.png new file mode 100644 index 000000000..1d236b317 Binary files /dev/null and b/ParseUI/Resources/Images/facebook_icon.png differ diff --git a/ParseUI/Resources/Images/facebook_icon@2x.png b/ParseUI/Resources/Images/facebook_icon@2x.png new file mode 100644 index 000000000..de09d02b7 Binary files /dev/null and b/ParseUI/Resources/Images/facebook_icon@2x.png differ diff --git a/ParseUI/Resources/Images/facebook_icon@3x.png b/ParseUI/Resources/Images/facebook_icon@3x.png new file mode 100644 index 000000000..287456bb8 Binary files /dev/null and b/ParseUI/Resources/Images/facebook_icon@3x.png differ diff --git a/ParseUI/Resources/Images/parse_logo.png b/ParseUI/Resources/Images/parse_logo.png new file mode 100644 index 000000000..68d8befc2 Binary files /dev/null and b/ParseUI/Resources/Images/parse_logo.png differ diff --git a/ParseUI/Resources/Images/parse_logo@2x.png b/ParseUI/Resources/Images/parse_logo@2x.png new file mode 100644 index 000000000..f2c3b419d Binary files /dev/null and b/ParseUI/Resources/Images/parse_logo@2x.png differ diff --git a/ParseUI/Resources/Images/parse_logo@3x.png b/ParseUI/Resources/Images/parse_logo@3x.png new file mode 100644 index 000000000..80629f4e6 Binary files /dev/null and b/ParseUI/Resources/Images/parse_logo@3x.png differ diff --git a/ParseUI/Resources/Images/twitter_icon.png b/ParseUI/Resources/Images/twitter_icon.png new file mode 100644 index 000000000..386d40b5c Binary files /dev/null and b/ParseUI/Resources/Images/twitter_icon.png differ diff --git a/ParseUI/Resources/Images/twitter_icon@2x.png b/ParseUI/Resources/Images/twitter_icon@2x.png new file mode 100644 index 000000000..a319f37bc Binary files /dev/null and b/ParseUI/Resources/Images/twitter_icon@2x.png differ diff --git a/ParseUI/Resources/Images/twitter_icon@3x.png b/ParseUI/Resources/Images/twitter_icon@3x.png new file mode 100644 index 000000000..d6cc3bd3f Binary files /dev/null and b/ParseUI/Resources/Images/twitter_icon@3x.png differ diff --git a/ParseUI/Resources/Info.plist b/ParseUI/Resources/Info.plist new file mode 100644 index 000000000..7cead4508 --- /dev/null +++ b/ParseUI/Resources/Info.plist @@ -0,0 +1,29 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ParseUI + CFBundleIdentifier + com.parse.parseui + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.2.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + iPhoneSimulator + iPhoneOS + + CFBundleVersion + 1.2.0 + MinimumOSVersion + 7.0 + + diff --git a/ParseUI/Resources/Localization/de.lproj/ParseUI.strings b/ParseUI/Resources/Localization/de.lproj/ParseUI.strings new file mode 100644 index 000000000..88587dcca --- /dev/null +++ b/ParseUI/Resources/Localization/de.lproj/ParseUI.strings @@ -0,0 +1,110 @@ +/* Additional */ +"Additional" = "Zusätzlich"; + +/* Password reset message in PFLogInViewController */ +"An email with reset instructions has been sent to '%@'." = "Eine E-Mail mit Anweisungen zum Zurücksetzen wurde gesendet an '%@' gesendet."; + +/* Cancel */ +"Cancel" = "Abbrechen"; + +/* Download Error */ +"Download Error" = "Download-Fehler"; + +/* DOWNLOADING */ +"DOWNLOADING" = "DOWNLOADING"; + +/* Email */ +"Email" = "E-Mail"; + +/* Error */ +"Error" = "Fehler"; + +/* Error occurred during deletion */ +"Error occurred during deletion" = "Fehler beim Löschen"; + +/* Facebook */ +"Facebook" = "Facebook"; + +/* Forgot Password? */ +"Forgot Password?" = "Passwort vergessen?"; + +/* INSTALLED */ +"INSTALLED" = "INSTALLED"; + +/* Load more... */ +"Load more..." = "Laden Sie mehr ..."; + +/* Loading message of PFQueryTableViewController */ +"Loading..." = "Laden..."; + +/* Log In */ +"Log In" = "Einloggen"; + +/* Log In with Facebook */ +"Log In with Facebook" = "Einloggen mit Facebook"; + +/* Log In with Twitter */ +"Log In with Twitter" = "Einloggen mit Twitter"; + +/* Login failed alert title in PFLogInViewController */ +"Login Failed" = "Anmeldung fehlgeschlagen"; + +/* OK */ +"OK" = "OK"; + +/* Password */ +"Password" = "Passwort"; + +/* Password too short error message in PFSignUpViewController */ +"Password must be at least %d characters." = "Das Passwort muss mindestens %d Zeichen lang sein."; + +/* Password reset success alert title in PFLogInViewController. */ +"Password Reset" = "Passwort zurücksetzen"; + +/* Password reset error alert title in PFLogInViewController. */ +"Password Reset Failed" = "Passwort rücksetzen gescheitert"; + +/* Password missing error message in PFSignUpViewController */ +"Please enter a password." = "Bitte ein Passwort eingeben."; + +/* Username missing error message in PFSignUpViewController */ +"Please enter a username." = "Bitte einen Benutzernamen eingeben."; + +/* Email missing error message in PFSignUpViewController */ +"Please enter an email." = "Bitte eine E-Mail Adresse eingeben."; + +/* Email request message in PFLogInViewController */ +"Please enter the email address for your account." = "Bitte geben Sie die E-Mail -Adresse für Ihr Konto ein."; + +/* Generic login failed alert message in PFLogInViewController */ +"Please try again" = "Bitte versuche es erneut"; + +/* Purchase Error */ +"Purchase Error" = "Kauf Fehler"; + +/* Forgot password request title in PFLogInViewController */ +"Reset Password" = "Passwort zurücksetzen"; + +/* Sign Up */ +"Sign Up" = "Anmelden"; + +/* Sign Up Error */ +"Sign Up Error" = "Fehler bei der Registrierung"; + +/* Email is taken error format in PFSignUpViewController. */ +"The email '%@' is taken. Please try using a different email." = "Die E-Mail-Adresse '%@' wird bereits benutzt. Bitte versuchen Sie eine andere E-Mail-Adresse."; + +/* Invalid email address error message in PFSignUpViewControllers */ +"The email address is invalid. Please enter a valid email." = "Die E-Mail Adresse ist ungültig. Bitte geben Sie eine gültige E-Mail-Adresse ein."; + +/* Username taken error format in PFSignUpViewController */ +"The username '%@' is taken. Please try choosing a different username." = "Der Benutzername '%@' wird bereits benutzt. Bitte versuchen Sie einen anderen Benutzernamen."; + +/* Invalid login credentials alert message in PFLogInViewController */ +"The username and password you entered don't match" = "Der Benutzername und das eingegebene Passwort stimmen nicht überein"; + +/* Twitter */ +"Twitter" = "Twitter"; + +/* Username */ +"Username" = "Benutzername"; diff --git a/ParseUI/Resources/Localization/en.lproj/ParseUI.strings b/ParseUI/Resources/Localization/en.lproj/ParseUI.strings new file mode 100644 index 000000000..998662295 Binary files /dev/null and b/ParseUI/Resources/Localization/en.lproj/ParseUI.strings differ diff --git a/ParseUI/Resources/Localization/fr.lproj/ParseUI.strings b/ParseUI/Resources/Localization/fr.lproj/ParseUI.strings new file mode 100644 index 000000000..5e1918b67 Binary files /dev/null and b/ParseUI/Resources/Localization/fr.lproj/ParseUI.strings differ diff --git a/ParseUI/Resources/Localization/he.lproj/ParseUI.strings b/ParseUI/Resources/Localization/he.lproj/ParseUI.strings new file mode 100644 index 000000000..a3e85e3aa Binary files /dev/null and b/ParseUI/Resources/Localization/he.lproj/ParseUI.strings differ diff --git a/ParseUI/Resources/Localization/pt-BR.lproj/ParseUI.strings b/ParseUI/Resources/Localization/pt-BR.lproj/ParseUI.strings new file mode 100644 index 000000000..9fb6f02bc --- /dev/null +++ b/ParseUI/Resources/Localization/pt-BR.lproj/ParseUI.strings @@ -0,0 +1,111 @@ +/* Additional */ +"Additional" = "Adicional"; + +/* Password reset message in PFLogInViewController */ +"An email with reset instructions has been sent to '%@'." = "Um email com as instruções para redefinir a senha foi enviado para '%@'"; + +/* Cancel */ +"Cancel" = "Cancelar"; + +/* Downloading Error */ +"Downloading Error" = "Erro de Download"; + +/* DOWNLOADING */ +"DOWNLOADING" = "FAZENDO DOWNLOAD"; + +/* Email */ +"Email" = "Email"; + +/* Error */ +"Error" = "Erro"; + +/* Error occurred during deletion */ +"Error occurred during deletion" = "Ocorreu um erro ao apagar"; + +/* Facebook */ +"Facebook" = "Facebook"; + +/* Forgot Password? */ +"Forgot Password?" = "Esqueceu a senha?"; + +/* INSTALLED */ +"INSTALLED" = "INSTALADO"; + +/* Load more... */ +"Load more..." = "Carregar mais..."; + +/* Loading message of PFQueryTableViewController */ +"Loading..." = "Carregando..."; + +/* Log In */ +"Log In" = "Inicial Sessão"; + +/* Log In with Facebook */ +"Log In with Facebook" = "Iniciar Sessão com o Facebook"; + +/* Log In with Twitter */ +"Log In with Twitter" = "Iniciar Sessão com o Twitter"; + +/* Login failed alert title in PFLogInViewController */ +"Login Failed" = "Falha ao Entrar"; + +/* OK */ +"OK" = "OK"; + +/* Password */ +"Password" = "Senha"; + +/* Password too short error message in PFSignUpViewController */ +"Password must be at least %d characters." = "A senha deve ter ao menos %d caracteres."; + +/* Password reset success alert title in PFLogInViewController. */ +"Password Reset" = "Senha Redefinida"; + +/* Password reset error alert title in PFLogInViewController. */ +"Password Reset Failed" = "Falha ao Redefinir a Senha"; + +/* Password missing error message in PFSignUpViewController */ +"Please enter a password." = "Por favor insira a senha."; + +/* Username missing error message in PFSignUpViewController */ +"Please enter a username." = "Por favor insira o nome de usuário."; + +/* Email missing error message in PFSignUpViewController */ +"Please enter an email." = "Por favor insira o email."; + +/* Email request message in PFLogInViewController */ +"Please enter the email address for your account." = "Por favor insira o endereço de email da sua conta."; + +/* Generic login failed alert message in PFLogInViewController */ +"Please try again" = "Por favor tente novamente"; + +/* Purchase Error */ +"Purchase Error" = "Erro na Compra"; + +/* Forgot password title in PFLogInViewController */ +"Reset Password" = "Redefinir Senha"; + +/* Sign Up */ +"Sign Up" = "Cadastre-se"; + +/* Sign Up Error */ +"Sign Up Error" = "Erro no Cadastro"; + +/* Email is taken error format in PFSignUpViewController. */ +"The email '%@' is taken. Please try using a different email." = "O email '%@' já estea cadastrado. Por favor tente com outro email"; + +/* Invalid email address error message in PGSighUpViewController */ +"The email address is invalid. Please enter a valid email." = "O endereço de email é inválido. Por favor coloque um endereço válido de email."; + +/* Username taken error format in PFSignUpViewController */ +"The username '%@' is taken. Please try choosing a different username." = "O nome de usuário '%@' já está cadastrado. Por favir tente utilizando um nome de usuário diferente."; + +/* Invalid login credentials alert message in PFLogInViewController */ +"The username and password you entered don't match" = "O nome de usuário e a senha fornecidas não batem"; + +/* Twitter */ +"Twitter" = "Twitter"; + +/* Username */ +"Username" = "Nome de usuário"; + diff --git a/ParseUI/Resources/Localization/sv.lproj/ParseUI.strings b/ParseUI/Resources/Localization/sv.lproj/ParseUI.strings new file mode 100644 index 000000000..2ab3c1b9b Binary files /dev/null and b/ParseUI/Resources/Localization/sv.lproj/ParseUI.strings differ diff --git a/ParseUI/Scripts/convert_images.rb b/ParseUI/Scripts/convert_images.rb new file mode 100755 index 000000000..19f91869e --- /dev/null +++ b/ParseUI/Scripts/convert_images.rb @@ -0,0 +1,163 @@ +#!/usr/bin/env ruby +# +# Copyright (c) 2014, Parse, LLC. All rights reserved. +# +# You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +# copy, modify, and distribute this software in source code or binary form for use +# in connection with the web services and APIs provided by Parse. +# +# As with any software that integrates with the Parse platform, your use of +# this software is subject to the Parse Terms of Service +# [https://www.parse.com/about/terms]. This copyright notice shall be +# included in all copies or substantial portions of the software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# This script generates Objective-C byte arrays from the png images. + +require 'time' +require 'fileutils' + +PATH_PREFIX = ARGV[0] +SEARCH_PATH = "#{PATH_PREFIX}**/*.{png,jpg,jpeg,gif}" + +def make_varname(filename) + varname = filename[PATH_PREFIX.length...filename.length] + varname.gsub!("@", "") + varname.gsub!(/[.\/\-]/, "_") + varname +end + +def get_modified_time_from_header(header_filename_with_path, source_filename_with_path) + resource_modified_time = {} # maps resource filename to the file's modified time + if File.exist?(header_filename_with_path) && File.exist?(source_filename_with_path) + File.open(header_filename_with_path, "r") do |header_file| + puts "Collect timestamps of previously generated resource files" + header_file.each_line do |line| + # puts line + match = %r{NSData\s\*\)(.*);//modified:(.*)$}.match(line) + next unless match + + resource_filename = Regexp.last_match[1] + mtime = Regexp.last_match[2] + puts "resource_filename #{resource_filename}" + puts "time #{mtime}" + resource_modified_time[resource_filename] = Time.parse mtime + end + end + end + resource_modified_time +end + +def files_differ(resource_modified_time) + Dir.glob(SEARCH_PATH) do |filename| + filename_varname = make_varname(filename) + if resource_modified_time[filename_varname].nil? + puts "New file #{filename_varname} is added to resources" + return true + end + + if resource_modified_time[filename_varname] != File.mtime(filename) + puts "A resource file #{filename_varname} had been modified since the header was created" + return true + end + resource_modified_time.delete filename_varname + end + + if resource_modified_time.keys.count != 0 + puts "Some files are present in the header but are not present in resources." + puts "This indicates some resource files have been removed since last time PFResource.h was generated." + return true + end + + puts "The set of files in resources and the set of files in header are identical matches" + puts "Not generating resource files" + false +end + +output_path = File.dirname(ARGV[1]) +output_classname = File.basename(ARGV[1]) + +# We write to temp files and copy over temp files to the final files in the end. +# This way, Xcode does not pick up the intermediate result files and flag them as build errors. +# If developers see these temporary build errors, they will inevitably think the script is broken. +FileUtils.mkdir_p(output_path) + +output_header_filename = output_classname + "Temp.h" +output_source_filename = output_classname + "Temp.m" +final_output_header_filename = output_classname + ".h" +final_output_source_filename = output_classname + ".m" +header_filename_with_path = File.join(output_path, output_header_filename) +source_filename_with_path = File.join(output_path, output_source_filename) +final_output_header_filename_with_path = File.join(output_path, final_output_header_filename) +final_output_source_filename_with_path = File.join(output_path, final_output_source_filename) + +if ARGV.nil? || !(ARGV.include?("-f") || ARGV.include?("-forced")) + resource_modified_time = get_modified_time_from_header(final_output_header_filename_with_path, + final_output_source_filename_with_path) + exit if !files_differ(resource_modified_time) +end + +puts "Regenerating resource byte array files" +# generate header and source files +output_header_file = File.open(header_filename_with_path, "w") +output_source_file = File.open(source_filename_with_path, "w") + +output_header_file.write < +@interface #{output_classname} : NSObject +HEREDOC + +output_source_file.write < 0 + result << ", " + end + result << "0x#{byte.to_s(16)}" + count += 1 + end + result << " };\n\n" + + output_header_file.write < true) +FileUtils.rm(source_filename_with_path, :force => true) + +puts "Done regenerating resource byte array files" diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/FBSDKCoreKit b/ParseUI/Vendor/FBSDKCoreKit.framework/FBSDKCoreKit new file mode 100644 index 000000000..875e42401 Binary files /dev/null and b/ParseUI/Vendor/FBSDKCoreKit.framework/FBSDKCoreKit differ diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAccessToken.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAccessToken.h new file mode 100644 index 000000000..ef54b1516 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAccessToken.h @@ -0,0 +1,173 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import +#import +#import + +/** + Notification indicating that the `currentAccessToken` has changed. + + the userInfo dictionary of the notification will contain keys + `FBSDKAccessTokenChangeOldKey` and + `FBSDKAccessTokenChangeNewKey`. + */ +FBSDK_EXTERN NSString *const FBSDKAccessTokenDidChangeNotification; + +/** + A key in the notification's userInfo that will be set + if and only if the user ID changed between the old and new tokens. + + Token refreshes can occur automatically with the SDK + which do not change the user. If you're only interested in user + changes (such as logging out), you should check for the existence + of this key. The value is a NSNumber with a boolValue. + + On a fresh start of the app where the SDK reads in the cached value + of an access token, this key will also exist since the access token + is moving from a null state (no user) to a non-null state (user). + */ +FBSDK_EXTERN NSString *const FBSDKAccessTokenDidChangeUserID; + +/* + key in notification's userInfo object for getting the old token. + + If there was no old token, the key will not be present. + */ +FBSDK_EXTERN NSString *const FBSDKAccessTokenChangeOldKey; + +/* + key in notification's userInfo object for getting the new token. + + If there is no new token, the key will not be present. + */ +FBSDK_EXTERN NSString *const FBSDKAccessTokenChangeNewKey; + + +/** + Represents an immutable access token for using Facebook services. + */ +@interface FBSDKAccessToken : NSObject + +/** + Returns the app ID. + */ +@property (readonly, copy, nonatomic) NSString *appID; + +/** + Returns the known declined permissions. + */ +@property (readonly, copy, nonatomic) NSSet *declinedPermissions; + +/** + Returns the expiration date. + */ +@property (readonly, copy, nonatomic) NSDate *expirationDate; + +/** + Returns the known granted permissions. + */ +@property (readonly, copy, nonatomic) NSSet *permissions; + +/** + Returns the date the token was last refreshed. +*/ +@property (readonly, copy, nonatomic) NSDate *refreshDate; + +/** + Returns the opaque token string. + */ +@property (readonly, copy, nonatomic) NSString *tokenString; + +/** + Returns the user ID. + */ +@property (readonly, copy, nonatomic) NSString *userID; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + Initializes a new instance. + - Parameter tokenString: the opaque token string. + - Parameter permissions: the granted permissions. Note this is converted to NSSet and is only + an NSArray for the convenience of literal syntax. + - Parameter declinedPermissions: the declined permissions. Note this is converted to NSSet and is only + an NSArray for the convenience of literal syntax. + - Parameter appID: the app ID. + - Parameter userID: the user ID. + - Parameter expirationDate: the optional expiration date (defaults to distantFuture). + - Parameter refreshDate: the optional date the token was last refreshed (defaults to today). + + This initializer should only be used for advanced apps that + manage tokens explicitly. Typical login flows only need to use `FBSDKLoginManager` + along with `+currentAccessToken`. + */ +- (instancetype)initWithTokenString:(NSString *)tokenString + permissions:(NSArray *)permissions + declinedPermissions:(NSArray *)declinedPermissions + appID:(NSString *)appID + userID:(NSString *)userID + expirationDate:(NSDate *)expirationDate + refreshDate:(NSDate *)refreshDate +NS_DESIGNATED_INITIALIZER; + +/** + Convenience getter to determine if a permission has been granted + - Parameter permission: The permission to check. + */ +- (BOOL)hasGranted:(NSString *)permission; + +/** + Compares the receiver to another FBSDKAccessToken + - Parameter token: The other token + - Returns: YES if the receiver's values are equal to the other token's values; otherwise NO + */ +- (BOOL)isEqualToAccessToken:(FBSDKAccessToken *)token; + +/** + Returns the "global" access token that represents the currently logged in user. + + The `currentAccessToken` is a convenient representation of the token of the + current user and is used by other SDK components (like `FBSDKLoginManager`). + */ ++ (FBSDKAccessToken *)currentAccessToken; + +/** + Sets the "global" access token that represents the currently logged in user. + - Parameter token: The access token to set. + + This will broadcast a notification and save the token to the app keychain. + */ ++ (void)setCurrentAccessToken:(FBSDKAccessToken *)token; + +/** + Refresh the current access token's permission state and extend the token's expiration date, + if possible. + - Parameter completionHandler: an optional callback handler that can surface any errors related to permission refreshing. + + On a successful refresh, the currentAccessToken will be updated so you typically only need to + observe the `FBSDKAccessTokenDidChangeNotification` notification. + + If a token is already expired, it cannot be refreshed. + */ ++ (void)refreshCurrentAccessToken:(FBSDKGraphRequestHandler)completionHandler; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppEvents.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppEvents.h new file mode 100644 index 000000000..71260ca8c --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppEvents.h @@ -0,0 +1,523 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +#import "FBSDKMacros.h" + +@class FBSDKAccessToken; +@class FBSDKGraphRequest; + +/** NSNotificationCenter name indicating a result of a failed log flush attempt. The posted object will be an NSError instance. */ +FBSDK_EXTERN NSString *const FBSDKAppEventsLoggingResultNotification; + +/** optional plist key ("FacebookLoggingOverrideAppID") for setting `loggingOverrideAppID` */ +FBSDK_EXTERN NSString *const FBSDKAppEventsOverrideAppIDBundleKey; + +/** + + NS_ENUM (NSUInteger, FBSDKAppEventsFlushBehavior) + + Specifies when `FBSDKAppEvents` sends log events to the server. + + */ +typedef NS_ENUM(NSUInteger, FBSDKAppEventsFlushBehavior) +{ + + /** Flush automatically: periodically (once a minute or every 100 logged events) and always at app reactivation. */ + FBSDKAppEventsFlushBehaviorAuto = 0, + + /** Only flush when the `flush` method is called. When an app is moved to background/terminated, the + events are persisted and re-established at activation, but they will only be written with an + explicit call to `flush`. */ + FBSDKAppEventsFlushBehaviorExplicitOnly, + +}; + +/** + @methodgroup Predefined event names for logging events common to many apps. Logging occurs through the `logEvent` family of methods on `FBSDKAppEvents`. + Common event parameters are provided in the `FBSDKAppEventsParameterNames*` constants. + */ + +/** Log this event when the user has achieved a level in the app. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameAchievedLevel; + +/** Log this event when the user has entered their payment info. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameAddedPaymentInfo; + +/** Log this event when the user has added an item to their cart. The valueToSum passed to logEvent should be the item's price. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameAddedToCart; + +/** Log this event when the user has added an item to their wishlist. The valueToSum passed to logEvent should be the item's price. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameAddedToWishlist; + +/** Log this event when a user has completed registration with the app. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameCompletedRegistration; + +/** Log this event when the user has completed a tutorial in the app. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameCompletedTutorial; + +/** Log this event when the user has entered the checkout process. The valueToSum passed to logEvent should be the total price in the cart. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameInitiatedCheckout; + +/** Log this event when the user has rated an item in the app. The valueToSum passed to logEvent should be the numeric rating. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameRated; + +/** Log this event when a user has performed a search within the app. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameSearched; + +/** Log this event when the user has spent app credits. The valueToSum passed to logEvent should be the number of credits spent. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameSpentCredits; + +/** Log this event when the user has unlocked an achievement in the app. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameUnlockedAchievement; + +/** Log this event when a user has viewed a form of content in the app. */ +FBSDK_EXTERN NSString *const FBSDKAppEventNameViewedContent; + +/** + @methodgroup Predefined event name parameters for common additional information to accompany events logged through the `logEvent` family + of methods on `FBSDKAppEvents`. Common event names are provided in the `FBAppEventName*` constants. + */ + + /** + * Parameter key used to specify data for the one or more pieces of content being logged about. + * Data should be a JSON encoded string. + * Example: + * "[{\"id\": \"1234\", \"quantity\": 2, \"item_price\": 5.99}, {\"id\": \"5678\", \"quantity\": 1, \"item_price\": 9.99}]" + */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameContent; + +/** Parameter key used to specify an ID for the specific piece of content being logged about. Could be an EAN, article identifier, etc., depending on the nature of the app. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameContentID; + +/** Parameter key used to specify a generic content type/family for the logged event, e.g. "music", "photo", "video". Options to use will vary based upon what the app is all about. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameContentType; + +/** Parameter key used to specify currency used with logged event. E.g. "USD", "EUR", "GBP". See ISO-4217 for specific values. One reference for these is . */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameCurrency; + +/** Parameter key used to specify a description appropriate to the event being logged. E.g., the name of the achievement unlocked in the `FBAppEventNameAchievementUnlocked` event. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameDescription; + +/** Parameter key used to specify the level achieved in a `FBAppEventNameAchieved` event. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameLevel; + +/** Parameter key used to specify the maximum rating available for the `FBAppEventNameRate` event. E.g., "5" or "10". */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameMaxRatingValue; + +/** Parameter key used to specify how many items are being processed for an `FBAppEventNameInitiatedCheckout` or `FBAppEventNamePurchased` event. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameNumItems; + +/** Parameter key used to specify whether payment info is available for the `FBAppEventNameInitiatedCheckout` event. `FBSDKAppEventParameterValueYes` and `FBSDKAppEventParameterValueNo` are good canonical values to use for this parameter. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNamePaymentInfoAvailable; + +/** Parameter key used to specify method user has used to register for the app, e.g., "Facebook", "email", "Twitter", etc */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameRegistrationMethod; + +/** Parameter key used to specify the string provided by the user for a search operation. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameSearchString; + +/** Parameter key used to specify whether the activity being logged about was successful or not. `FBSDKAppEventParameterValueYes` and `FBSDKAppEventParameterValueNo` are good canonical values to use for this parameter. */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterNameSuccess; + +/* + @methodgroup Predefined values to assign to event parameters that accompany events logged through the `logEvent` family + of methods on `FBSDKAppEvents`. Common event parameters are provided in the `FBSDKAppEventParameterName*` constants. + */ + +/** Yes-valued parameter value to be used with parameter keys that need a Yes/No value */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterValueYes; + +/** No-valued parameter value to be used with parameter keys that need a Yes/No value */ +FBSDK_EXTERN NSString *const FBSDKAppEventParameterValueNo; + + +/** + + + Client-side event logging for specialized application analytics available through Facebook App Insights + and for use with Facebook Ads conversion tracking and optimization. + + + + The `FBSDKAppEvents` static class has a few related roles: + + + Logging predefined and application-defined events to Facebook App Insights with a + numeric value to sum across a large number of events, and an optional set of key/value + parameters that define "segments" for this event (e.g., 'purchaserStatus' : 'frequent', or + 'gamerLevel' : 'intermediate') + + + Logging events to later be used for ads optimization around lifetime value. + + + Methods that control the way in which events are flushed out to the Facebook servers. + + Here are some important characteristics of the logging mechanism provided by `FBSDKAppEvents`: + + + Events are not sent immediately when logged. They're cached and flushed out to the Facebook servers + in a number of situations: + - when an event count threshold is passed (currently 100 logged events). + - when a time threshold is passed (currently 15 seconds). + - when an app has gone to background and is then brought back to the foreground. + + + Events will be accumulated when the app is in a disconnected state, and sent when the connection is + restored and one of the above 'flush' conditions are met. + + + The `FBSDKAppEvents` class is thread-safe in that events may be logged from any of the app's threads. + + + The developer can set the `flushBehavior` on `FBSDKAppEvents` to force the flushing of events to only + occur on an explicit call to the `flush` method. + + + The developer can turn on console debug output for event logging and flushing to the server by using + the `FBSDKLoggingBehaviorAppEvents` value in `[FBSettings setLoggingBehavior:]`. + + Some things to note when logging events: + + + There is a limit on the number of unique event names an app can use, on the order of 1000. + + There is a limit to the number of unique parameter names in the provided parameters that can + be used per event, on the order of 25. This is not just for an individual call, but for all + invocations for that eventName. + + Event names and parameter names (the keys in the NSDictionary) must be between 2 and 40 characters, and + must consist of alphanumeric characters, _, -, or spaces. + + The length of each parameter value can be no more than on the order of 100 characters. + + */ +@interface FBSDKAppEvents : NSObject + +/* + * Basic event logging + */ + +/** + + Log an event with just an eventName. + + - Parameter eventName: The name of the event to record. Limitations on number of events and name length + are given in the `FBSDKAppEvents` documentation. + + */ ++ (void)logEvent:(NSString *)eventName; + +/** + + Log an event with an eventName and a numeric value to be aggregated with other events of this name. + + - Parameter eventName: The name of the event to record. Limitations on number of events and name length + are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants. + + - Parameter valueToSum: Amount to be aggregated into all events of this eventName, and App Insights will report + the cumulative and average value of this amount. + */ ++ (void)logEvent:(NSString *)eventName + valueToSum:(double)valueToSum; + + +/** + + Log an event with an eventName and a set of key/value pairs in the parameters dictionary. + Parameter limitations are described above. + + - Parameter eventName: The name of the event to record. Limitations on number of events and name construction + are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants. + + - Parameter parameters: Arbitrary parameter dictionary of characteristics. The keys to this dictionary must + be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of + parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names + are provided in `FBSDKAppEventParameterName*` constants. + */ ++ (void)logEvent:(NSString *)eventName + parameters:(NSDictionary *)parameters; + +/** + + Log an event with an eventName, a numeric value to be aggregated with other events of this name, + and a set of key/value pairs in the parameters dictionary. + + - Parameter eventName: The name of the event to record. Limitations on number of events and name construction + are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants. + + - Parameter valueToSum: Amount to be aggregated into all events of this eventName, and App Insights will report + the cumulative and average value of this amount. + + - Parameter parameters: Arbitrary parameter dictionary of characteristics. The keys to this dictionary must + be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of + parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names + are provided in `FBSDKAppEventParameterName*` constants. + + */ ++ (void)logEvent:(NSString *)eventName + valueToSum:(double)valueToSum + parameters:(NSDictionary *)parameters; + + +/** + + Log an event with an eventName, a numeric value to be aggregated with other events of this name, + and a set of key/value pairs in the parameters dictionary. Providing session lets the developer + target a particular . If nil is provided, then `[FBSession activeSession]` will be used. + + - Parameter eventName: The name of the event to record. Limitations on number of events and name construction + are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants. + + - Parameter valueToSum: Amount to be aggregated into all events of this eventName, and App Insights will report + the cumulative and average value of this amount. Note that this is an NSNumber, and a value of `nil` denotes + that this event doesn't have a value associated with it for summation. + + - Parameter parameters: Arbitrary parameter dictionary of characteristics. The keys to this dictionary must + be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of + parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names + are provided in `FBSDKAppEventParameterName*` constants. + + - Parameter accessToken: The optional access token to log the event as. + */ ++ (void)logEvent:(NSString *)eventName + valueToSum:(NSNumber *)valueToSum + parameters:(NSDictionary *)parameters + accessToken:(FBSDKAccessToken *)accessToken; + +/* + * Purchase logging + */ + +/** + + Log a purchase of the specified amount, in the specified currency. + + - Parameter purchaseAmount: Purchase amount to be logged, as expressed in the specified currency. This value + will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346). + + - Parameter currency: Currency, is denoted as, e.g. "USD", "EUR", "GBP". See ISO-4217 for + specific values. One reference for these is . + + + This event immediately triggers a flush of the `FBSDKAppEvents` event queue, unless the `flushBehavior` is set + to `FBSDKAppEventsFlushBehaviorExplicitOnly`. + + */ ++ (void)logPurchase:(double)purchaseAmount + currency:(NSString *)currency; + +/** + + Log a purchase of the specified amount, in the specified currency, also providing a set of + additional characteristics describing the purchase. + + - Parameter purchaseAmount: Purchase amount to be logged, as expressed in the specified currency.This value + will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346). + + - Parameter currency: Currency, is denoted as, e.g. "USD", "EUR", "GBP". See ISO-4217 for + specific values. One reference for these is . + + - Parameter parameters: Arbitrary parameter dictionary of characteristics. The keys to this dictionary must + be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of + parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names + are provided in `FBSDKAppEventParameterName*` constants. + + + This event immediately triggers a flush of the `FBSDKAppEvents` event queue, unless the `flushBehavior` is set + to `FBSDKAppEventsFlushBehaviorExplicitOnly`. + + */ ++ (void)logPurchase:(double)purchaseAmount + currency:(NSString *)currency + parameters:(NSDictionary *)parameters; + +/** + + Log a purchase of the specified amount, in the specified currency, also providing a set of + additional characteristics describing the purchase, as well as an to log to. + + - Parameter purchaseAmount: Purchase amount to be logged, as expressed in the specified currency.This value + will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346). + + - Parameter currency: Currency, is denoted as, e.g. "USD", "EUR", "GBP". See ISO-4217 for + specific values. One reference for these is . + + - Parameter parameters: Arbitrary parameter dictionary of characteristics. The keys to this dictionary must + be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of + parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names + are provided in `FBSDKAppEventParameterName*` constants. + + - Parameter accessToken: The optional access token to log the event as. + + + This event immediately triggers a flush of the `FBSDKAppEvents` event queue, unless the `flushBehavior` is set + to `FBSDKAppEventsFlushBehaviorExplicitOnly`. + + */ ++ (void)logPurchase:(double)purchaseAmount + currency:(NSString *)currency + parameters:(NSDictionary *)parameters + accessToken:(FBSDKAccessToken *)accessToken; + + +/* + * Push Notifications Logging + */ + +/** + Log an app event that tracks that the application was open via Push Notification. + + - Parameter payload: Notification payload received via `UIApplicationDelegate`. + */ ++ (void)logPushNotificationOpen:(NSDictionary *)payload; + +/** + Log an app event that tracks that a custom action was taken from a push notification. + + - Parameter payload: Notification payload received via `UIApplicationDelegate`. + - Parameter action: Name of the action that was taken. + */ ++ (void)logPushNotificationOpen:(NSDictionary *)payload action:(NSString *)action; + +/** + + Notifies the events system that the app has launched and, when appropriate, logs an "activated app" event. + This function is called automatically from FBSDKApplicationDelegate applicationDidBecomeActive, unless + one overrides 'FacebookAutoLogAppEventsEnabled' key to false in the project info plist file. + In case 'FacebookAutoLogAppEventsEnabled' is set to false, then it should typically be placed in the + app delegates' `applicationDidBecomeActive:` method. + + This method also takes care of logging the event indicating the first time this app has been launched, which, among other things, is used to + track user acquisition and app install ads conversions. + + + + `activateApp` will not log an event on every app launch, since launches happen every time the app is backgrounded and then foregrounded. + "activated app" events will be logged when the app has not been active for more than 60 seconds. This method also causes a "deactivated app" + event to be logged when sessions are "completed", and these events are logged with the session length, with an indication of how much + time has elapsed between sessions, and with the number of background/foreground interruptions that session had. This data + is all visible in your app's App Events Insights. + */ ++ (void)activateApp; + +/* + * Push Notifications Registration + */ + +/** + Sets and sends device token to register the current application for push notifications. + + + + Sets and sends a device token from `NSData` representation that you get from `UIApplicationDelegate.-application:didRegisterForRemoteNotificationsWithDeviceToken:`. + + - Parameter deviceToken: Device token data. + */ ++ (void)setPushNotificationsDeviceToken:(NSData *)deviceToken; + +/* + * Control over event batching/flushing + */ + +/** + + Get the current event flushing behavior specifying when events are sent back to Facebook servers. + */ ++ (FBSDKAppEventsFlushBehavior)flushBehavior; + +/** + + Set the current event flushing behavior specifying when events are sent back to Facebook servers. + + - Parameter flushBehavior: The desired `FBSDKAppEventsFlushBehavior` to be used. + */ ++ (void)setFlushBehavior:(FBSDKAppEventsFlushBehavior)flushBehavior; + +/** + Set the 'override' App ID for App Event logging. + + + + In some cases, apps want to use one Facebook App ID for login and social presence and another + for App Event logging. (An example is if multiple apps from the same company share an app ID for login, but + want distinct logging.) By default, this value is `nil`, and defers to the `FBSDKAppEventsOverrideAppIDBundleKey` + plist value. If that's not set, it defaults to `[FBSDKSettings appID]`. + + This should be set before any other calls are made to `FBSDKAppEvents`. Thus, you should set it in your application + delegate's `application:didFinishLaunchingWithOptions:` delegate. + + - Parameter appID: The Facebook App ID to be used for App Event logging. + */ ++ (void)setLoggingOverrideAppID:(NSString *)appID; + +/** + Get the 'override' App ID for App Event logging. + + +- See:setLoggingOverrideAppID: + + */ ++ (NSString *)loggingOverrideAppID; + + +/** + Explicitly kick off flushing of events to Facebook. This is an asynchronous method, but it does initiate an immediate + kick off. Server failures will be reported through the NotificationCenter with notification ID `FBSDKAppEventsLoggingResultNotification`. + */ ++ (void)flush; + +/** + Creates a request representing the Graph API call to retrieve a Custom Audience "third party ID" for the app's Facebook user. + Callers will send this ID back to their own servers, collect up a set to create a Facebook Custom Audience with, + and then use the resultant Custom Audience to target ads. + + - Parameter accessToken: The access token to use to establish the user's identity for users logged into Facebook through this app. + If `nil`, then the `[FBSDKAccessToken currentAccessToken]` is used. + + + + The JSON in the request's response will include an "custom_audience_third_party_id" key/value pair, with the value being the ID retrieved. + This ID is an encrypted encoding of the Facebook user's ID and the invoking Facebook app ID. + Multiple calls with the same user will return different IDs, thus these IDs cannot be used to correlate behavior + across devices or applications, and are only meaningful when sent back to Facebook for creating Custom Audiences. + + The ID retrieved represents the Facebook user identified in the following way: if the specified access token is valid, + the ID will represent the user associated with that token; otherwise the ID will represent the user logged into the + native Facebook app on the device. If there is no native Facebook app, no one is logged into it, or the user has opted out + at the iOS level from ad tracking, then a `nil` ID will be returned. + + This method returns `nil` if either the user has opted-out (via iOS) from Ad Tracking, the app itself has limited event usage + via the `[FBSDKSettings limitEventAndDataUsage]` flag, or a specific Facebook user cannot be identified. + */ ++ (FBSDKGraphRequest *)requestForCustomAudienceThirdPartyIDWithAccessToken:(FBSDKAccessToken *)accessToken; + +/* + Sets a custom user ID to associate with all app events. + + The userID is persisted until it is cleared by passing nil. + */ ++ (void)setUserID:(NSString *)userID; + +/* + Returns the set custom user ID. + */ ++ (NSString *)userID; + +/* + Sends a request to update the properties for the current user, set by `setUserID:` + + You must call `FBSDKAppEvents setUserID:` before making this call. + - Parameter properties: the custom user properties + - Parameter handler: the optional completion handler + */ ++ (void)updateUserProperties:(NSDictionary *)properties handler:(FBSDKGraphRequestHandler)handler; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppLinkResolver.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppLinkResolver.h new file mode 100644 index 000000000..c97b6b35d --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppLinkResolver.h @@ -0,0 +1,82 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +@class BFTask; + +// Check if Bolts.framework is available for import +#if __has_include() +// Import it if it's available +# import +#else +// Otherwise - redeclare BFAppLinkResolving protocol to resolve the problem of missing symbols +// Please note: Bolts.framework is still required for AppLink resolving to work, +// but this allows FBSDKCoreKit to weakly link Bolts.framework as well as this enables clang modulemaps to work. + +/** + Implement this protocol to provide an alternate strategy for resolving + App Links that may include pre-fetching, caching, or querying for App Link + data from an index provided by a service provider. + */ +@protocol BFAppLinkResolving + +/** + Asynchronously resolves App Link data for a given URL. + + - Parameter url: The URL to resolve into an App Link. + - Returns: A BFTask that will return a BFAppLink for the given URL. + */ +- (BFTask *)appLinkFromURLInBackground:(NSURL *)url; + +@end + +#endif + +/** + + Provides an implementation of the BFAppLinkResolving protocol that uses the Facebook App Link + Index API to resolve App Links given a URL. It also provides an additional helper method that can resolve + multiple App Links in a single call. + + + + Usage of this type requires a client token. See `[FBSDKSettings setClientToken:]` and linking + Bolts.framework + */ +@interface FBSDKAppLinkResolver : NSObject + +/** + Asynchronously resolves App Link data for multiple URLs. + + - Parameter urls: An array of NSURLs to resolve into App Links. + - Returns: A BFTask that will return dictionary mapping input NSURLs to their + corresponding BFAppLink. + + + + You should set the client token before making this call. See `[FBSDKSettings setClientToken:]` + */ +- (BFTask *)appLinksFromURLsInBackground:(NSArray *)urls; + +/** + Allocates and initializes a new instance of FBSDKAppLinkResolver. + */ ++ (instancetype)resolver; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppLinkUtility.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppLinkUtility.h new file mode 100644 index 000000000..1cc549a19 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKAppLinkUtility.h @@ -0,0 +1,89 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + Describes the callback for fetchDeferredAppLink. + - Parameter url: the url representing the deferred App Link + - Parameter error: the error during the request, if any + + + The url may also have a fb_click_time_utc query parameter that + represents when the click occurred that caused the deferred App Link to be created. + */ +typedef void (^FBSDKDeferredAppLinkHandler)(NSURL *url, NSError *error); + + +/** + Describes the callback for fetchOrganicDeferredAppLink. + - Parameter url: the url representing the deferred App Link + */ +typedef void (^FBSDKDeferredAppInviteHandler)(NSURL *url); + + +/** + Class containing App Links related utility methods. + */ +@interface FBSDKAppLinkUtility : NSObject + +/** + Call this method from the main thread to fetch deferred applink data if you use Mobile App + Engagement Ads (https://developers.facebook.com/docs/ads-for-apps/mobile-app-ads-engagement). + This may require a network round trip. If successful, the handler is invoked with the link + data (this will only return a valid URL once, and future calls will result in a nil URL + value in the callback). + + - Parameter handler: the handler to be invoked if there is deferred App Link data + + + The handler may contain an NSError instance to capture any errors. In the + common case where there simply was no app link data, the NSError instance will be nil. + + This method should only be called from a location that occurs after any launching URL has + been processed (e.g., you should call this method from your application delegate's + applicationDidBecomeActive:). + */ ++ (void)fetchDeferredAppLink:(FBSDKDeferredAppLinkHandler)handler; + +/** + +- Warning:This method is no longer available and will always return NO. + */ ++ (BOOL)fetchDeferredAppInvite:(FBSDKDeferredAppInviteHandler)handler +__attribute__((deprecated("This method is no longer available.")));; + +/* + Call this method to fetch promotion code from the url, if it's present. This function + requires Bolts framework. + + Note: This throws an exception if Bolts.framework is not linked. Add '[BFURL class]' in intialize method + of your AppDelegate. + + - Parameter url: App Link url that was passed to the app. + + - Returns: Promotion code string. + + + Call this method to fetch App Invite Promotion Code from applink if present. + This can be used to fetch the promotion code that was associated with the invite when it + was created. This method should be called with the url from the openURL method. +*/ ++ (NSString*)appInvitePromotionCodeFromURL:(NSURL*)url; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKApplicationDelegate.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKApplicationDelegate.h new file mode 100644 index 000000000..ebaef4d38 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKApplicationDelegate.h @@ -0,0 +1,92 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + + The FBSDKApplicationDelegate is designed to post process the results from Facebook Login + or Facebook Dialogs (or any action that requires switching over to the native Facebook + app or Safari). + + + + The methods in this class are designed to mirror those in UIApplicationDelegate, and you + should call them in the respective methods in your AppDelegate implementation. + */ +@interface FBSDKApplicationDelegate : NSObject + +/** + Gets the singleton instance. + */ ++ (instancetype)sharedInstance; + +/** + Call this method from the [UIApplicationDelegate application:openURL:sourceApplication:annotation:] method + of the AppDelegate for your app. It should be invoked for the proper processing of responses during interaction + with the native Facebook app or Safari as part of SSO authorization flow or Facebook dialogs. + + - Parameter application: The application as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:]. + + - Parameter url: The URL as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:]. + + - Parameter sourceApplication: The sourceApplication as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:]. + + - Parameter annotation: The annotation as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:]. + + - Returns: YES if the url was intended for the Facebook SDK, NO if not. + */ +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_9_0 +/** + Call this method from the [UIApplicationDelegate application:openURL:options:] method + of the AppDelegate for your app. It should be invoked for the proper processing of responses during interaction + with the native Facebook app or Safari as part of SSO authorization flow or Facebook dialogs. + + - Parameter application: The application as passed to [UIApplicationDelegate application:openURL:options:]. + + - Parameter url: The URL as passed to [UIApplicationDelegate application:openURL:options:]. + + - Parameter options: The options dictionary as passed to [UIApplicationDelegate application:openURL:options:]. + + - Returns: YES if the url was intended for the Facebook SDK, NO if not. + */ +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options; +#endif + +/** + Call this method from the [UIApplicationDelegate application:didFinishLaunchingWithOptions:] method + of the AppDelegate for your app. It should be invoked for the proper use of the Facebook SDK. + As part of SDK initialization basic auto logging of app events will occur, this can be +controlled via 'FacebookAutoLogAppEventsEnabled' key in the project info plist file. + + - Parameter application: The application as passed to [UIApplicationDelegate application:didFinishLaunchingWithOptions:]. + + - Parameter launchOptions: The launchOptions as passed to [UIApplicationDelegate application:didFinishLaunchingWithOptions:]. + + - Returns: YES if the url was intended for the Facebook SDK, NO if not. + */ +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKButton.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKButton.h new file mode 100644 index 000000000..ca539660c --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKButton.h @@ -0,0 +1,26 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + A base class for common SDK buttons. + */ +@interface FBSDKButton : UIButton + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKConstants.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKConstants.h new file mode 100644 index 000000000..98cfd73c3 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKConstants.h @@ -0,0 +1,225 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +/** + The error domain for all errors from FBSDKCoreKit. + + Error codes from the SDK in the range 0-99 are reserved for this domain. + */ +FBSDK_EXTERN NSString *const FBSDKErrorDomain; + +/** + NS_ENUM(NSInteger, FBSDKErrorCode) + Error codes for FBSDKErrorDomain. + */ +typedef NS_ENUM(NSInteger, FBSDKErrorCode) +{ + /** + Reserved. + */ + FBSDKReservedErrorCode = 0, + + /** + The error code for errors from invalid encryption on incoming encryption URLs. + */ + FBSDKEncryptionErrorCode, + + /** + The error code for errors from invalid arguments to SDK methods. + */ + FBSDKInvalidArgumentErrorCode, + + /** + The error code for unknown errors. + */ + FBSDKUnknownErrorCode, + + /** + A request failed due to a network error. Use NSUnderlyingErrorKey to retrieve + the error object from the NSURLConnection for more information. + */ + FBSDKNetworkErrorCode, + + /** + The error code for errors encountered during an App Events flush. + */ + FBSDKAppEventsFlushErrorCode, + + /** + An endpoint that returns a binary response was used with FBSDKGraphRequestConnection. + + Endpoints that return image/jpg, etc. should be accessed using NSURLRequest + */ + FBSDKGraphRequestNonTextMimeTypeReturnedErrorCode, + + /** + The operation failed because the server returned an unexpected response. + + You can get this error if you are not using the most recent SDK, or you are accessing a version of the + Graph API incompatible with the current SDK. + */ + FBSDKGraphRequestProtocolMismatchErrorCode, + + /** + The Graph API returned an error. + + See below for useful userInfo keys (beginning with FBSDKGraphRequestError*) + */ + FBSDKGraphRequestGraphAPIErrorCode, + + /** + The specified dialog configuration is not available. + + This error may signify that the configuration for the dialogs has not yet been downloaded from the server + or that the dialog is unavailable. Subsequent attempts to use the dialog may succeed as the configuration is loaded. + */ + FBSDKDialogUnavailableErrorCode, + + /** + Indicates an operation failed because a required access token was not found. + */ + FBSDKAccessTokenRequiredErrorCode, + + /** + Indicates an app switch (typically for a dialog) failed because the destination app is out of date. + */ + FBSDKAppVersionUnsupportedErrorCode, + + /** + Indicates an app switch to the browser (typically for a dialog) failed. + */ + FBSDKBrowserUnavailableErrorCode, + + /** + +- Warning:use FBSDKBrowserUnavailableErrorCode instead + */ + FBSDKBrowswerUnavailableErrorCode __attribute__ ((deprecated("use FBSDKBrowserUnavailableErrorCode instead"))) = FBSDKBrowserUnavailableErrorCode, +}; + +/** + NS_ENUM(NSUInteger, FBSDKGraphRequestErrorCategory) + Describes the category of Facebook error. See `FBSDKGraphRequestErrorCategoryKey`. + */ +typedef NS_ENUM(NSUInteger, FBSDKGraphRequestErrorCategory) +{ + /** The default error category that is not known to be recoverable. Check `FBSDKLocalizedErrorDescriptionKey` for a user facing message. */ + FBSDKGraphRequestErrorCategoryOther = 0, + /** Indicates the error is temporary (such as server throttling). While a recoveryAttempter will be provided with the error instance, the attempt is guaranteed to succeed so you can simply retry the operation if you do not want to present an alert. */ + FBSDKGraphRequestErrorCategoryTransient = 1, + /** Indicates the error can be recovered (such as requiring a login). A recoveryAttempter will be provided with the error instance that can take UI action. */ + FBSDKGraphRequestErrorCategoryRecoverable = 2 +}; + +/* + @methodgroup error userInfo keys + */ + +/** + The userInfo key for the invalid collection for errors with FBSDKInvalidArgumentErrorCode. + + If the invalid argument is a collection, the collection can be found with this key and the individual + invalid item can be found with FBSDKErrorArgumentValueKey. + */ +FBSDK_EXTERN NSString *const FBSDKErrorArgumentCollectionKey; + +/** + The userInfo key for the invalid argument name for errors with FBSDKInvalidArgumentErrorCode. + */ +FBSDK_EXTERN NSString *const FBSDKErrorArgumentNameKey; + +/** + The userInfo key for the invalid argument value for errors with FBSDKInvalidArgumentErrorCode. + */ +FBSDK_EXTERN NSString *const FBSDKErrorArgumentValueKey; + +/** + The userInfo key for the message for developers in NSErrors that originate from the SDK. + + The developer message will not be localized and is not intended to be presented within the app. + */ +FBSDK_EXTERN NSString *const FBSDKErrorDeveloperMessageKey; + +/** + The userInfo key describing a localized description that can be presented to the user. + */ +FBSDK_EXTERN NSString *const FBSDKErrorLocalizedDescriptionKey; + +/** + The userInfo key describing a localized title that can be presented to the user, used with `FBSDKLocalizedErrorDescriptionKey`. + */ +FBSDK_EXTERN NSString *const FBSDKErrorLocalizedTitleKey; + +/* + @methodgroup FBSDKGraphRequest error userInfo keys + */ + +/** + The userInfo key describing the error category, for error recovery purposes. + + See `FBSDKGraphErrorRecoveryProcessor` and `[FBSDKGraphRequest disableErrorRecovery]`. + */ +FBSDK_EXTERN NSString *const FBSDKGraphRequestErrorCategoryKey; + +/* + The userInfo key for the Graph API error code. + */ +FBSDK_EXTERN NSString *const FBSDKGraphRequestErrorGraphErrorCode; + +/* + The userInfo key for the Graph API error subcode. + */ +FBSDK_EXTERN NSString *const FBSDKGraphRequestErrorGraphErrorSubcode; + +/* + The userInfo key for the HTTP status code. + */ +FBSDK_EXTERN NSString *const FBSDKGraphRequestErrorHTTPStatusCodeKey; + +/* + The userInfo key for the raw JSON response. + */ +FBSDK_EXTERN NSString *const FBSDKGraphRequestErrorParsedJSONResponseKey; + +/** + a formal protocol very similar to the informal protocol NSErrorRecoveryAttempting + */ +@protocol FBSDKErrorRecoveryAttempting + +/** + attempt the recovery + - Parameter error: the error + - Parameter recoveryOptionIndex: the selected option index + - Parameter delegate: the delegate + - Parameter didRecoverSelector: the callback selector, see discussion. + - Parameter contextInfo: context info to pass back to callback selector, see discussion. + + + Given that an error alert has been presented document-modally to the user, and the user has chosen one of the error's recovery options, attempt recovery from the error, and send the selected message to the specified delegate. The option index is an index into the error's array of localized recovery options. The method selected by didRecoverSelector must have the same signature as: + + - (void)didPresentErrorWithRecovery:(BOOL)didRecover contextInfo:(void *)contextInfo; + + The value passed for didRecover must be YES if error recovery was completely successful, NO otherwise. + */ +- (void)attemptRecoveryFromError:(NSError *)error optionIndex:(NSUInteger)recoveryOptionIndex delegate:(id)delegate didRecoverSelector:(SEL)didRecoverSelector contextInfo:(void *)contextInfo; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKCopying.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKCopying.h new file mode 100644 index 000000000..c84975ca4 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKCopying.h @@ -0,0 +1,34 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + Extension protocol for NSCopying that adds the copy method, which is implemented on NSObject. + + NSObject implicitly conforms to this protocol. + */ +@protocol FBSDKCopying + +/** + Implemented by NSObject as a convenience to copyWithZone:. + - Returns: A copy of the receiver. + */ +- (id)copy; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKCoreKit.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKCoreKit.h new file mode 100644 index 000000000..d32c553c9 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKCoreKit.h @@ -0,0 +1,48 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#if !TARGET_OS_TV +#import +#import +#import +#import +#import +#import +#else +#import +#import +#endif + +#define FBSDK_VERSION_STRING @"4.29.0" +#define FBSDK_TARGET_PLATFORM_VERSION @"v2.11" diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphErrorRecoveryProcessor.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphErrorRecoveryProcessor.h new file mode 100644 index 000000000..658bc46ee --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphErrorRecoveryProcessor.h @@ -0,0 +1,99 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import "FBSDKConstants.h" + +@class FBSDKGraphErrorRecoveryProcessor; +@class FBSDKGraphRequest; + +/** + Defines a delegate for `FBSDKGraphErrorRecoveryProcessor`. + */ +@protocol FBSDKGraphErrorRecoveryProcessorDelegate + +/** + Indicates the error recovery has been attempted. + - Parameter processor: the processor instance. + - Parameter didRecover: YES if the recovery was successful. + - Parameter error: the error that that was attempted to be recovered from. + */ +- (void)processorDidAttemptRecovery:(FBSDKGraphErrorRecoveryProcessor *)processor didRecover:(BOOL)didRecover error:(NSError *)error; + +@optional +/** + Indicates the processor is about to process the error. + - Parameter processor: the processor instance. + - Parameter error: the error is about to be processed. + + return NO if the processor should not process the error. For example, + if you want to prevent alerts of localized messages but otherwise perform retries and recoveries, + you could return NO for errors where userInfo[FBSDKGraphRequestErrorCategoryKey] equal to FBSDKGraphRequestErrorCategoryOther + */ +- (BOOL)processorWillProcessError:(FBSDKGraphErrorRecoveryProcessor *)processor error:(NSError *)error; + +@end + +/** + Defines a type that can process Facebook NSErrors with best practices. + + Facebook NSErrors can contain FBSDKErrorRecoveryAttempting instances to recover from errors, or + localized messages to present to the user. This class will process the instances as follows: + + 1. If the error is temporary as indicated by FBSDKGraphRequestErrorCategoryKey, assume the recovery succeeded and + notify the delegate. + 2. If a FBSDKErrorRecoveryAttempting instance is available, display an alert (dispatched to main thread) + with the recovery options and call the instance's [ attemptRecoveryFromError:optionIndex:...]. + 3. If a FBSDKErrorRecoveryAttempting is not available, check the userInfo for FBSDKLocalizedErrorDescriptionKey + and present that in an alert (dispatched to main thread). + + By default, FBSDKGraphRequests use this type to process errors and retry the request upon a successful + recovery. + + Note that Facebook recovery attempters can present UI or even cause app switches (such as to login). Any such + work is dispatched to the main thread (therefore your request handlers may then run on the main thread). + + Login recovery requires FBSDKLoginKit. Login will use FBSDKLoginBehaviorNative and will prompt the user + for all permissions last granted. If any are declined on the new request, the recovery is not successful but + the `[FBSDKAccessToken currentAccessToken]` might still have been updated. + . + */ +@interface FBSDKGraphErrorRecoveryProcessor : NSObject + +/** + Gets the delegate. Note this is a strong reference, and is nil'ed out after recovery is complete. + */ +@property (nonatomic, strong, readonly) iddelegate; + +/** + Attempts to process the error, return YES if the error can be processed. + - Parameter error: the error to process. + - Parameter request: the related request that may be reissued. + - Parameter delegate: the delegate that will be retained until recovery is complete. + */ +- (BOOL)processError:(NSError *)error request:(FBSDKGraphRequest *)request delegate:(id) delegate; + +/** + The callback for FBSDKErrorRecoveryAttempting + - Parameter didRecover: if the recovery succeeded + - Parameter contextInfo: unused + */ +- (void)didPresentErrorWithRecovery:(BOOL)didRecover contextInfo:(void *)contextInfo; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequest.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequest.h new file mode 100644 index 000000000..2743ec3ad --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequest.h @@ -0,0 +1,123 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +@class FBSDKAccessToken; + +/** + Represents a request to the Facebook Graph API. + + + `FBSDKGraphRequest` encapsulates the components of a request (the + Graph API path, the parameters, error recovery behavior) and should be + used in conjunction with `FBSDKGraphRequestConnection` to issue the request. + + Nearly all Graph APIs require an access token. Unless specified, the + `[FBSDKAccessToken currentAccessToken]` is used. Therefore, most requests + will require login first (see `FBSDKLoginManager` in FBSDKLoginKit.framework). + + A `- start` method is provided for convenience for single requests. + + By default, FBSDKGraphRequest will attempt to recover any errors returned from + Facebook. You can disable this via `disableErrorRecovery:`. + +- See:FBSDKGraphErrorRecoveryProcessor + */ +@interface FBSDKGraphRequest : NSObject + +/** + Initializes a new instance that use use `[FBSDKAccessToken currentAccessToken]`. + - Parameter graphPath: the graph path (e.g., @"me"). + - Parameter parameters: the optional parameters dictionary. + */ +- (instancetype)initWithGraphPath:(NSString *)graphPath + parameters:(NSDictionary *)parameters; + +/** + Initializes a new instance that use use `[FBSDKAccessToken currentAccessToken]`. + - Parameter graphPath: the graph path (e.g., @"me"). + - Parameter parameters: the optional parameters dictionary. + - Parameter HTTPMethod: the optional HTTP method. nil defaults to @"GET". + */ +- (instancetype)initWithGraphPath:(NSString *)graphPath + parameters:(NSDictionary *)parameters + HTTPMethod:(NSString *)HTTPMethod; + +/** + Initializes a new instance. + - Parameter graphPath: the graph path (e.g., @"me"). + - Parameter parameters: the optional parameters dictionary. + - Parameter tokenString: the token string to use. Specifying nil will cause no token to be used. + - Parameter version: the optional Graph API version (e.g., @"v2.0"). nil defaults to `[FBSDKSettings graphAPIVersion]`. + - Parameter HTTPMethod: the optional HTTP method (e.g., @"POST"). nil defaults to @"GET". + */ +- (instancetype)initWithGraphPath:(NSString *)graphPath + parameters:(NSDictionary *)parameters + tokenString:(NSString *)tokenString + version:(NSString *)version + HTTPMethod:(NSString *)HTTPMethod +NS_DESIGNATED_INITIALIZER; + +/** + The request parameters. + */ +@property (nonatomic, strong, readonly) NSMutableDictionary *parameters; + +/** + The access token string used by the request. + */ +@property (nonatomic, copy, readonly) NSString *tokenString; + +/** + The Graph API endpoint to use for the request, for example "me". + */ +@property (nonatomic, copy, readonly) NSString *graphPath; + +/** + The HTTPMethod to use for the request, for example "GET" or "POST". + */ +@property (nonatomic, copy, readonly) NSString *HTTPMethod; + +/** + The Graph API version to use (e.g., "v2.0") + */ +@property (nonatomic, copy, readonly) NSString *version; + +/** + If set, disables the automatic error recovery mechanism. + - Parameter disable: whether to disable the automatic error recovery mechanism + + By default, non-batched FBSDKGraphRequest instances will automatically try to recover + from errors by constructing a `FBSDKGraphErrorRecoveryProcessor` instance that + re-issues the request on successful recoveries. The re-issued request will call the same + handler as the receiver but may occur with a different `FBSDKGraphRequestConnection` instance. + + This will override [FBSDKSettings setGraphErrorRecoveryDisabled:]. + */ +- (void)setGraphErrorRecoveryDisabled:(BOOL)disable; + +/** + Starts a connection to the Graph API. + - Parameter handler: The handler block to call when the request completes. + */ +- (FBSDKGraphRequestConnection *)startWithCompletionHandler:(FBSDKGraphRequestHandler)handler; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequestConnection.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequestConnection.h new file mode 100644 index 000000000..3eca5840f --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequestConnection.h @@ -0,0 +1,323 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +@class FBSDKGraphRequest; +@class FBSDKGraphRequestConnection; + +/** + FBSDKGraphRequestHandler + + A block that is passed to addRequest to register for a callback with the results of that + request once the connection completes. + + + + Pass a block of this type when calling addRequest. This will be called once + the request completes. The call occurs on the UI thread. + + - Parameter connection: The `FBSDKGraphRequestConnection` that sent the request. + + - Parameter result: The result of the request. This is a translation of + JSON data to `NSDictionary` and `NSArray` objects. This + is nil if there was an error. + + - Parameter error: The `NSError` representing any error that occurred. + + */ +typedef void (^FBSDKGraphRequestHandler)(FBSDKGraphRequestConnection *connection, + id result, + NSError *error); + +/** + @protocol + + The `FBSDKGraphRequestConnectionDelegate` protocol defines the methods used to receive network + activity progress information from a . + */ +@protocol FBSDKGraphRequestConnectionDelegate + +@optional + +/** + @method + + Tells the delegate the request connection will begin loading + + + + If the is created using one of the convenience factory methods prefixed with + start, the object returned from the convenience method has already begun loading and this method + will not be called when the delegate is set. + + - Parameter connection: The request connection that is starting a network request + */ +- (void)requestConnectionWillBeginLoading:(FBSDKGraphRequestConnection *)connection; + +/** + @method + + Tells the delegate the request connection finished loading + + + + If the request connection completes without a network error occurring then this method is called. + Invocation of this method does not indicate success of every made, only that the + request connection has no further activity. Use the error argument passed to the FBSDKGraphRequestHandler + block to determine success or failure of each . + + This method is invoked after the completion handler for each . + + - Parameter connection: The request connection that successfully completed a network request + */ +- (void)requestConnectionDidFinishLoading:(FBSDKGraphRequestConnection *)connection; + +/** + @method + + Tells the delegate the request connection failed with an error + + + + If the request connection fails with a network error then this method is called. The `error` + argument specifies why the network connection failed. The `NSError` object passed to the + FBSDKGraphRequestHandler block may contain additional information. + + - Parameter connection: The request connection that successfully completed a network request + - Parameter error: The `NSError` representing the network error that occurred, if any. May be nil + in some circumstances. Consult the `NSError` for the for reliable + failure information. + */ +- (void)requestConnection:(FBSDKGraphRequestConnection *)connection + didFailWithError:(NSError *)error; + +/** + @method + + Tells the delegate how much data has been sent and is planned to send to the remote host + + + + The byte count arguments refer to the aggregated objects, not a particular . + + Like `NSURLConnection`, the values may change in unexpected ways if data needs to be resent. + + - Parameter connection: The request connection transmitting data to a remote host + - Parameter bytesWritten: The number of bytes sent in the last transmission + - Parameter totalBytesWritten: The total number of bytes sent to the remote host + - Parameter totalBytesExpectedToWrite: The total number of bytes expected to send to the remote host + */ +- (void)requestConnection:(FBSDKGraphRequestConnection *)connection + didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten +totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite; + +@end + +/** + + The `FBSDKGraphRequestConnection` represents a single connection to Facebook to service a request. + + + + The request settings are encapsulated in a reusable object. The + `FBSDKGraphRequestConnection` object encapsulates the concerns of a single communication + e.g. starting a connection, canceling a connection, or batching requests. + + */ +@interface FBSDKGraphRequestConnection : NSObject + +/** + The delegate object that receives updates. + */ +@property (nonatomic, weak) id delegate; + +/** + Gets or sets the timeout interval to wait for a response before giving up. + */ +@property (nonatomic) NSTimeInterval timeout; + +/** + The raw response that was returned from the server. (readonly) + + + + This property can be used to inspect HTTP headers that were returned from + the server. + + The property is nil until the request completes. If there was a response + then this property will be non-nil during the FBSDKGraphRequestHandler callback. + */ +@property (nonatomic, retain, readonly) NSHTTPURLResponse *URLResponse; + +/** + @methodgroup Class methods + */ + +/** + @method + + This method sets the default timeout on all FBSDKGraphRequestConnection instances. Defaults to 60 seconds. + + - Parameter defaultConnectionTimeout: The timeout interval. + */ ++ (void)setDefaultConnectionTimeout:(NSTimeInterval)defaultConnectionTimeout; + +/** + @methodgroup Adding requests + */ + +/** + @method + + This method adds an object to this connection. + + - Parameter request: A request to be included in the round-trip when start is called. + - Parameter handler: A handler to call back when the round-trip completes or times out. + + + + The completion handler is retained until the block is called upon the + completion or cancellation of the connection. + */ +- (void)addRequest:(FBSDKGraphRequest *)request + completionHandler:(FBSDKGraphRequestHandler)handler; + +/** + @method + + This method adds an object to this connection. + + - Parameter request: A request to be included in the round-trip when start is called. + + - Parameter handler: A handler to call back when the round-trip completes or times out. + The handler will be invoked on the main thread. + + - Parameter name: An optional name for this request. This can be used to feed + the results of one request to the input of another in the same + `FBSDKGraphRequestConnection` as described in + [Graph API Batch Requests]( https://developers.facebook.com/docs/reference/api/batch/ ). + + + + The completion handler is retained until the block is called upon the + completion or cancellation of the connection. This request can be named + to allow for using the request's response in a subsequent request. + */ +- (void)addRequest:(FBSDKGraphRequest *)request + completionHandler:(FBSDKGraphRequestHandler)handler + batchEntryName:(NSString *)name; + +/** + @method + + This method adds an object to this connection. + + - Parameter request: A request to be included in the round-trip when start is called. + + - Parameter handler: A handler to call back when the round-trip completes or times out. + + - Parameter batchParameters: The optional dictionary of parameters to include for this request + as described in [Graph API Batch Requests]( https://developers.facebook.com/docs/reference/api/batch/ ). + Examples include "depends_on", "name", or "omit_response_on_success". + + + + The completion handler is retained until the block is called upon the + completion or cancellation of the connection. This request can be named + to allow for using the request's response in a subsequent request. + */ +- (void)addRequest:(FBSDKGraphRequest *)request + completionHandler:(FBSDKGraphRequestHandler)handler + batchParameters:(NSDictionary *)batchParameters; + +/** + @methodgroup Instance methods + */ + +/** + @method + + Signals that a connection should be logically terminated as the + application is no longer interested in a response. + + + + Synchronously calls any handlers indicating the request was cancelled. Cancel + does not guarantee that the request-related processing will cease. It + does promise that all handlers will complete before the cancel returns. A call to + cancel prior to a start implies a cancellation of all requests associated + with the connection. + */ +- (void)cancel; + +/** + @method + + This method starts a connection with the server and is capable of handling all of the + requests that were added to the connection. + + + By default, a connection is scheduled on the current thread in the default mode when it is created. + See `setDelegateQueue:` for other options. + + This method cannot be called twice for an `FBSDKGraphRequestConnection` instance. + */ +- (void)start; + +/** + Determines the operation queue that is used to call methods on the connection's delegate. + - Parameter queue: The operation queue to use when calling delegate methods. + + By default, a connection is scheduled on the current thread in the default mode when it is created. + You cannot reschedule a connection after it has started. + + This is very similar to `[NSURLConnection setDelegateQueue:]`. + */ +- (void)setDelegateQueue:(NSOperationQueue *)queue; + +/** + @method + + Overrides the default version for a batch request + + + + The SDK automatically prepends a version part, such as "v2.0" to API paths in order to simplify API versioning + for applications. If you want to override the version part while using batch requests on the connection, call + this method to set the version for the batch request. + + - Parameter version: This is a string in the form @"v2.0" which will be used for the version part of an API path + */ +- (void)overrideVersionPartWith:(NSString *)version; + +@end + +/** + The key in the result dictionary for requests to old versions of the Graph API + whose response is not a JSON object. + + + When a request returns a non-JSON response (such as a "true" literal), that response + will be wrapped into a dictionary using this const as the key. This only applies for very few Graph API + prior to v2.1. + */ +FBSDK_EXTERN NSString *const FBSDKNonJSONResponseProperty; diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequestDataAttachment.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequestDataAttachment.h new file mode 100644 index 000000000..edf0f7815 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKGraphRequestDataAttachment.h @@ -0,0 +1,52 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + A container class for data attachments so that additional metadata can be provided about the attachment. + */ +@interface FBSDKGraphRequestDataAttachment : NSObject + +/** + Initializes the receiver with the attachment data and metadata. + - Parameter data: The attachment data (retained, not copied) + - Parameter filename: The filename for the attachment + - Parameter contentType: The content type for the attachment + */ +- (instancetype)initWithData:(NSData *)data + filename:(NSString *)filename + contentType:(NSString *)contentType +NS_DESIGNATED_INITIALIZER; + +/** + The content type for the attachment. + */ +@property (nonatomic, copy, readonly) NSString *contentType; + +/** + The attachment data. + */ +@property (nonatomic, strong, readonly) NSData *data; + +/** + The filename for the attachment. + */ +@property (nonatomic, copy, readonly) NSString *filename; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKMacros.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKMacros.h new file mode 100644 index 000000000..fd2e2fff3 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKMacros.h @@ -0,0 +1,39 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#ifdef __cplusplus +#define FBSDK_EXTERN extern "C" __attribute__((visibility ("default"))) +#else +#define FBSDK_EXTERN extern __attribute__((visibility ("default"))) +#endif + +#define FBSDK_STATIC_INLINE static inline + +#define FBSDK_NO_DESIGNATED_INITIALIZER() \ +@throw [NSException exceptionWithName:NSInvalidArgumentException \ + reason:[NSString stringWithFormat:@"unrecognized selector sent to instance %p", self] \ + userInfo:nil] + +#define FBSDK_NOT_DESIGNATED_INITIALIZER(DESIGNATED_INITIALIZER) \ +@throw [NSException exceptionWithName:NSInvalidArgumentException \ + reason:[NSString stringWithFormat:@"Please use the designated initializer [%p %@]", \ + self, \ + NSStringFromSelector(@selector(DESIGNATED_INITIALIZER))] \ + userInfo:nil] diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKMutableCopying.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKMutableCopying.h new file mode 100644 index 000000000..1b351dbdf --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKMutableCopying.h @@ -0,0 +1,36 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +/** + Extension protocol for NSMutableCopying that adds the mutableCopy method, which is implemented on NSObject. + + NSObject implicitly conforms to this protocol. + */ +@protocol FBSDKMutableCopying + +/** + Implemented by NSObject as a convenience to mutableCopyWithZone:. + - Returns: A mutable copy of the receiver. + */ +- (id)mutableCopy; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKProfile.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKProfile.h new file mode 100644 index 000000000..1f33f1689 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKProfile.h @@ -0,0 +1,166 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import "FBSDKMacros.h" +#import "FBSDKProfilePictureView.h" + +/** + Notification indicating that the `currentProfile` has changed. + + the userInfo dictionary of the notification will contain keys + `FBSDKProfileChangeOldKey` and + `FBSDKProfileChangeNewKey`. + */ +FBSDK_EXTERN NSString *const FBSDKProfileDidChangeNotification; + +/* key in notification's userInfo object for getting the old profile. + + If there was no old profile, the key will not be present. + */ +FBSDK_EXTERN NSString *const FBSDKProfileChangeOldKey; + +/* key in notification's userInfo object for getting the new profile. + + If there is no new profile, the key will not be present. + */ +FBSDK_EXTERN NSString *const FBSDKProfileChangeNewKey; + +/** + Represents an immutable Facebook profile + + This class provides a global "currentProfile" instance to more easily + add social context to your application. When the profile changes, a notification is + posted so that you can update relevant parts of your UI and is persisted to NSUserDefaults. + + Typically, you will want to call `enableUpdatesOnAccessTokenChange:YES` so that + it automatically observes changes to the `[FBSDKAccessToken currentAccessToken]`. + + You can use this class to build your own `FBSDKProfilePictureView` or in place of typical requests to "/me". + */ +@interface FBSDKProfile : NSObject + +/** + initializes a new instance. + - Parameter userID: the user ID + - Parameter firstName: the user's first name + - Parameter middleName: the user's middle name + - Parameter lastName: the user's last name + - Parameter name: the user's complete name + - Parameter linkURL: the link for this profile + - Parameter refreshDate: the optional date this profile was fetched. Defaults to [NSDate date]. + */ +- (instancetype)initWithUserID:(NSString *)userID + firstName:(NSString *)firstName + middleName:(NSString *)middleName + lastName:(NSString *)lastName + name:(NSString *)name + linkURL:(NSURL *)linkURL + refreshDate:(NSDate *)refreshDate NS_DESIGNATED_INITIALIZER; +/** + The user id + */ +@property (nonatomic, copy, readonly) NSString *userID; +/** + The user's first name + */ +@property (nonatomic, copy, readonly) NSString *firstName; +/** + The user's middle name + */ +@property (nonatomic, copy, readonly) NSString *middleName; +/** + The user's last name + */ +@property (nonatomic, copy, readonly) NSString *lastName; +/** + The user's complete name + */ +@property (nonatomic, copy, readonly) NSString *name; +/** + A URL to the user's profile. + + Consider using Bolts and `FBSDKAppLinkResolver` to resolve this + to an app link to link directly to the user's profile in the Facebook app. + */ +@property (nonatomic, readonly) NSURL *linkURL; + +/** + The last time the profile data was fetched. + */ +@property (nonatomic, readonly) NSDate *refreshDate; + +/** + Gets the current FBSDKProfile instance. + */ ++ (FBSDKProfile *)currentProfile; + +/** + Sets the current instance and posts the appropriate notification if the profile parameter is different + than the receiver. + - Parameter profile: the profile to set + + This persists the profile to NSUserDefaults. + */ ++ (void)setCurrentProfile:(FBSDKProfile *)profile; + +/** + Indicates if `currentProfile` will automatically observe `FBSDKAccessTokenDidChangeNotification` notifications + - Parameter enable: YES is observing + + If observing, this class will issue a graph request for public profile data when the current token's userID + differs from the current profile. You can observe `FBSDKProfileDidChangeNotification` for when the profile is updated. + + Note that if `[FBSDKAccessToken currentAccessToken]` is unset, the `currentProfile` instance remains. It's also possible + for `currentProfile` to return nil until the data is fetched. + */ ++ (void)enableUpdatesOnAccessTokenChange:(BOOL)enable; + +/** + Loads the current profile and passes it to the completion block. + - Parameter completion: The block to be executed once the profile is loaded + + If the profile is already loaded, this method will call the completion block synchronously, otherwise it + will begin a graph request to update `currentProfile` and then call the completion block when finished. + */ ++ (void)loadCurrentProfileWithCompletion:(void(^)(FBSDKProfile *profile, NSError *error))completion; + +/** + A convenience method for returning a complete `NSURL` for retrieving the user's profile image. + - Parameter mode: The picture mode + - Parameter size: The height and width. This will be rounded to integer precision. + */ +- (NSURL *)imageURLForPictureMode:(FBSDKProfilePictureMode)mode size:(CGSize)size; + +/** + A convenience method for returning a Graph API path for retrieving the user's profile image. + +- Warning:use `imageURLForPictureMode:size:` instead + + You can pass this to a `FBSDKGraphRequest` instance to download the image. + - Parameter mode: The picture mode + - Parameter size: The height and width. This will be rounded to integer precision. + */ +- (NSString *)imagePathForPictureMode:(FBSDKProfilePictureMode)mode size:(CGSize)size +__attribute__ ((deprecated("use imageURLForPictureMode:size: instead"))); + +/** + Returns YES if the profile is equivalent to the receiver. + - Parameter profile: the profile to compare to. + */ +- (BOOL)isEqualToProfile:(FBSDKProfile *)profile; +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKProfilePictureView.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKProfilePictureView.h new file mode 100644 index 000000000..aaf9bcaa7 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKProfilePictureView.h @@ -0,0 +1,60 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + FBSDKProfilePictureMode enum + Defines the aspect ratio mode for the source image of the profile picture. + */ +typedef NS_ENUM(NSUInteger, FBSDKProfilePictureMode) +{ + /** + A square cropped version of the image will be included in the view. + */ + FBSDKProfilePictureModeSquare, + /** + The original picture's aspect ratio will be used for the source image in the view. + */ + FBSDKProfilePictureModeNormal, +}; + +/** + A view to display a profile picture. + */ +@interface FBSDKProfilePictureView : UIView + +/** + The mode for the receiver to determine the aspect ratio of the source image. + */ +@property (nonatomic, assign) FBSDKProfilePictureMode pictureMode; + +/** + The profile ID to show the picture for. + */ +@property (nonatomic, copy) NSString *profileID; + +/** + Explicitly marks the receiver as needing to update the image. + + This method is called whenever any properties that affect the source image are modified, but this can also + be used to trigger a manual update of the image if it needs to be re-downloaded. + */ +- (void)setNeedsImageUpdate; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKSettings.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKSettings.h new file mode 100644 index 000000000..22347b709 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKSettings.h @@ -0,0 +1,244 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +/* + * Constants defining logging behavior. Use with <[FBSDKSettings setLoggingBehavior]>. + */ + +/** Include access token in logging. */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorAccessTokens; + +/** Log performance characteristics */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorPerformanceCharacteristics; + +/** Log FBSDKAppEvents interactions */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorAppEvents; + +/** Log Informational occurrences */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorInformational; + +/** Log cache errors. */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorCacheErrors; + +/** Log errors from SDK UI controls */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorUIControlErrors; + +/** Log debug warnings from API response, i.e. when friends fields requested, but user_friends permission isn't granted. */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorGraphAPIDebugWarning; + +/** Log warnings from API response, i.e. when requested feature will be deprecated in next version of API. + Info is the lowest level of severity, using it will result in logging all previously mentioned levels. + */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorGraphAPIDebugInfo; + +/** Log errors from SDK network requests */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorNetworkRequests; + +/** Log errors likely to be preventable by the developer. This is in the default set of enabled logging behaviors. */ +FBSDK_EXTERN NSString *const FBSDKLoggingBehaviorDeveloperErrors; + +@interface FBSDKSettings : NSObject + +/** + Get the Facebook App ID used by the SDK. + + If not explicitly set, the default will be read from the application's plist (FacebookAppID). + */ ++ (NSString *)appID; + +/** + Set the Facebook App ID to be used by the SDK. + - Parameter appID: The Facebook App ID to be used by the SDK. + */ ++ (void)setAppID:(NSString *)appID; + +/** + Get the default url scheme suffix used for sessions. + + If not explicitly set, the default will be read from the application's plist (FacebookUrlSchemeSuffix). + */ ++ (NSString *)appURLSchemeSuffix; + +/** + Set the app url scheme suffix used by the SDK. + - Parameter appURLSchemeSuffix: The url scheme suffix to be used by the SDK. + */ ++ (void)setAppURLSchemeSuffix:(NSString *)appURLSchemeSuffix; + +/** + Retrieve the Client Token that has been set via [FBSDKSettings setClientToken]. + + If not explicitly set, the default will be read from the application's plist (FacebookClientToken). + */ ++ (NSString *)clientToken; + +/** + Sets the Client Token for the Facebook App. + + This is needed for certain API calls when made anonymously, without a user-based access token. + - Parameter clientToken: The Facebook App's "client token", which, for a given appid can be found in the Security + section of the Advanced tab of the Facebook App settings found at + */ ++ (void)setClientToken:(NSString *)clientToken; + +/** + A convenient way to toggle error recovery for all FBSDKGraphRequest instances created after this is set. + - Parameter disableGraphErrorRecovery: YES or NO. + */ ++ (void)setGraphErrorRecoveryDisabled:(BOOL)disableGraphErrorRecovery; + +/** + Get the Facebook Display Name used by the SDK. + + If not explicitly set, the default will be read from the application's plist (FacebookDisplayName). + */ ++ (NSString *)displayName; + +/** + Set the default Facebook Display Name to be used by the SDK. + + This should match the Display Name that has been set for the app with the corresponding Facebook App ID, + in the Facebook App Dashboard. + - Parameter displayName: The Facebook Display Name to be used by the SDK. + */ ++ (void)setDisplayName:(NSString *)displayName; + +/** + Get the Facebook domain part. + + If not explicitly set, the default will be read from the application's plist (FacebookDomainPart). + */ ++ (NSString *)facebookDomainPart; + +/** + Set the subpart of the Facebook domain. + + This can be used to change the Facebook domain (e.g. @"beta") so that requests will be sent to + graph.beta.facebook.com + - Parameter facebookDomainPart: The domain part to be inserted into facebook.com. + */ ++ (void)setFacebookDomainPart:(NSString *)facebookDomainPart; + +/** + The quality of JPEG images sent to Facebook from the SDK. + + If not explicitly set, the default is 0.9. + +- See:[UIImageJPEGRepresentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKitFunctionReference/#//apple_ref/c/func/UIImageJPEGRepresentation) */ ++ (CGFloat)JPEGCompressionQuality; + +/** + Set the quality of JPEG images sent to Facebook from the SDK. + - Parameter JPEGCompressionQuality: The quality for JPEG images, expressed as a value from 0.0 to 1.0. + +- See:[UIImageJPEGRepresentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKitFunctionReference/#//apple_ref/c/func/UIImageJPEGRepresentation) */ ++ (void)setJPEGCompressionQuality:(CGFloat)JPEGCompressionQuality; + +/** + Flag which controls the auto logging of basic app events, such as activateApp and deactivateApp. + If not explicitly set, the default is 1 - true + */ ++ (NSNumber *)autoLogAppEventsEnabled; + +/** + Set the flag which controls the auto logging of basic app events, such as activateApp and deactivateApp. + - Parameter AutoLogAppEventsEnabled: Flag value, expressed as a value from 0 - false or 1 - true. + */ ++ (void)setAutoLogAppEventsEnabled:(NSNumber *)AutoLogAppEventsEnabled; + +/** + Gets whether data such as that generated through FBSDKAppEvents and sent to Facebook should be restricted from being used for other than analytics and conversions. Defaults to NO. This value is stored on the device and persists across app launches. + */ ++ (BOOL)limitEventAndDataUsage; + +/** + Sets whether data such as that generated through FBSDKAppEvents and sent to Facebook should be restricted from being used for other than analytics and conversions. Defaults to NO. This value is stored on the device and persists across app launches. + + - Parameter limitEventAndDataUsage: The desired value. + */ ++ (void)setLimitEventAndDataUsage:(BOOL)limitEventAndDataUsage; + +/** + Retrieve the current iOS SDK version. + */ ++ (NSString *)sdkVersion; + +/** + Retrieve the current Facebook SDK logging behavior. + */ ++ (NSSet *)loggingBehavior; + +/** + Set the current Facebook SDK logging behavior. This should consist of strings defined as + constants with FBSDKLoggingBehavior*. + + - Parameter loggingBehavior: A set of strings indicating what information should be logged. If nil is provided, the logging + behavior is reset to the default set of enabled behaviors. Set to an empty set in order to disable all logging. + + + You can also define this via an array in your app plist with key "FacebookLoggingBehavior" or add and remove individual values via enableLoggingBehavior: or disableLogginBehavior: + */ ++ (void)setLoggingBehavior:(NSSet *)loggingBehavior; + +/** + Enable a particular Facebook SDK logging behavior. + + - Parameter loggingBehavior: The LoggingBehavior to enable. This should be a string defined as a constant with FBSDKLoggingBehavior*. + */ ++ (void)enableLoggingBehavior:(NSString *)loggingBehavior; + +/** + Disable a particular Facebook SDK logging behavior. + + - Parameter loggingBehavior: The LoggingBehavior to disable. This should be a string defined as a constant with FBSDKLoggingBehavior*. + */ ++ (void)disableLoggingBehavior:(NSString *)loggingBehavior; + +/** + Set the user defaults key used by legacy token caches. + + - Parameter tokenInformationKeyName: the key used by legacy token caches. + + + Use this only if you customized FBSessionTokenCachingStrategy in v3.x of + the Facebook SDK for iOS. +*/ ++ (void)setLegacyUserDefaultTokenInformationKeyName:(NSString *)tokenInformationKeyName; + +/** + Get the user defaults key used by legacy token caches. +*/ ++ (NSString *)legacyUserDefaultTokenInformationKeyName; + +/** + Overrides the default Graph API version to use with `FBSDKGraphRequests`. This overrides `FBSDK_TARGET_PLATFORM_VERSION`. + + The string should be of the form `@"v2.7"`. +*/ ++ (void)setGraphAPIVersion:(NSString *)version; + +/** + Returns the default Graph API version. Defaults to `FBSDK_TARGET_PLATFORM_VERSION` +*/ ++ (NSString *)graphAPIVersion; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKTestUsersManager.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKTestUsersManager.h new file mode 100644 index 000000000..3be95297e --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKTestUsersManager.h @@ -0,0 +1,101 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +@class FBSDKAccessToken; + +/** + + Callback block for returning an array of FBSDKAccessToken instances (and possibly `NSNull` instances); or an error. + */ +typedef void (^FBSDKTestUsersManagerRetrieveTestAccountTokensHandler)(NSArray *tokens, NSError *error) ; + +/** + + Callback block for removing a test user. + */ +typedef void (^FBSDKTestUsersManagerRemoveTestAccountHandler)(NSError *error) ; + + +/** + Provides methods for managing test accounts for testing Facebook integration. + + + Facebook allows developers to create test accounts for testing their applications' + Facebook integration (see https://developers.facebook.com/docs/test_users/). This class + simplifies use of these accounts for writing tests. It is not designed for use in + production application code. + + This class will make Graph API calls on behalf of your app to manage test accounts and requires + an app id and app secret. You will typically use this class to write unit or integration tests. + Make sure you NEVER include your app secret in your production app. + */ +@interface FBSDKTestUsersManager : NSObject + +/** + construct or return the shared instance + - Parameter appID: the Facebook app id + - Parameter appSecret: the Facebook app secret + */ ++ (instancetype)sharedInstanceForAppID:(NSString *)appID appSecret:(NSString *)appSecret; + +/** + retrieve FBSDKAccessToken instances for test accounts with the specific permissions. + - Parameter arraysOfPermissions: an array of permissions sets, such as @[ [NSSet setWithObject:@"email"], [NSSet setWithObject:@"user_birthday"]] + if you needed two test accounts with email and birthday permissions, respectively. You can pass in empty nested sets + if you need two arbitrary test accounts. For convenience, passing nil is treated as @[ [NSSet set] ] + for fetching a single test user. + - Parameter createIfNotFound: if YES, new test accounts are created if no test accounts existed that fit the permissions + requirement + - Parameter handler: the callback to invoke which will return an array of `FBAccessTokenData` instances or an `NSError`. + If param `createIfNotFound` is NO, the array may contain `[NSNull null]` instances. + + + If you are requesting test accounts with differing number of permissions, try to order + `arrayOfPermissionsArrays` so that the most number of permissions come first to minimize creation of new + test accounts. + */ +- (void)requestTestAccountTokensWithArraysOfPermissions:(NSArray *)arraysOfPermissions + createIfNotFound:(BOOL)createIfNotFound + completionHandler:(FBSDKTestUsersManagerRetrieveTestAccountTokensHandler)handler; + +/** + add a test account with the specified permissions + - Parameter permissions: the set of permissions, e.g., [NSSet setWithObjects:@"email", @"user_friends"] + - Parameter handler: the callback handler + */ +- (void)addTestAccountWithPermissions:(NSSet *)permissions + completionHandler:(FBSDKTestUsersManagerRetrieveTestAccountTokensHandler)handler; + +/** + remove a test account for the given user id + - Parameter userId: the user id + - Parameter handler: the callback handler + */ +- (void)removeTestAccount:(NSString *)userId completionHandler:(FBSDKTestUsersManagerRemoveTestAccountHandler)handler; + +/** + Make two test users friends with each other. + - Parameter first: the token of the first user + - Parameter second: the token of the second user + - Parameter callback: the callback handler + */ +- (void)makeFriendsWithFirst:(FBSDKAccessToken *)first second:(FBSDKAccessToken *)second callback:(void (^)(NSError *))callback; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKUtility.h b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKUtility.h new file mode 100644 index 000000000..1a240c8f0 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Headers/FBSDKUtility.h @@ -0,0 +1,55 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + Class to contain common utility methods. + */ +@interface FBSDKUtility : NSObject + +/** + Parses a query string into a dictionary. + - Parameter queryString: The query string value. + - Returns: A dictionary with the key/value pairs. + */ ++ (NSDictionary *)dictionaryWithQueryString:(NSString *)queryString; + +/** + Constructs a query string from a dictionary. + - Parameter dictionary: The dictionary with key/value pairs for the query string. + - Parameter errorRef: If an error occurs, upon return contains an NSError object that describes the problem. + - Returns: Query string representation of the parameters. + */ ++ (NSString *)queryStringWithDictionary:(NSDictionary *)dictionary error:(NSError *__autoreleasing *)errorRef; + +/** + Decodes a value from an URL. + - Parameter value: The value to decode. + - Returns: The decoded value. + */ ++ (NSString *)URLDecode:(NSString *)value; + +/** + Encodes a value for an URL. + - Parameter value: The value to encode. + - Returns: The encoded value. + */ ++ (NSString *)URLEncode:(NSString *)value; + +@end diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Info.plist b/ParseUI/Vendor/FBSDKCoreKit.framework/Info.plist new file mode 100644 index 000000000..36ebe94ad Binary files /dev/null and b/ParseUI/Vendor/FBSDKCoreKit.framework/Info.plist differ diff --git a/ParseUI/Vendor/FBSDKCoreKit.framework/Modules/module.modulemap b/ParseUI/Vendor/FBSDKCoreKit.framework/Modules/module.modulemap new file mode 100644 index 000000000..bc59c1a82 --- /dev/null +++ b/ParseUI/Vendor/FBSDKCoreKit.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module FBSDKCoreKit { + umbrella header "FBSDKCoreKit.h" + + export * + module * { export * } +} diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/FBSDKLoginKit b/ParseUI/Vendor/FBSDKLoginKit.framework/FBSDKLoginKit new file mode 100644 index 000000000..cdd26e9b9 Binary files /dev/null and b/ParseUI/Vendor/FBSDKLoginKit.framework/FBSDKLoginKit differ diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginButton.h b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginButton.h new file mode 100644 index 000000000..adc7b9b09 --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginButton.h @@ -0,0 +1,131 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +#import + +#import "FBSDKTooltipView.h" + +@protocol FBSDKLoginButtonDelegate; + +/** + NS_ENUM(NSUInteger, FBSDKLoginButtonTooltipBehavior) + Indicates the desired login tooltip behavior. + */ +typedef NS_ENUM(NSUInteger, FBSDKLoginButtonTooltipBehavior) +{ + /** The default behavior. The tooltip will only be displayed if + the app is eligible (determined by possible server round trip) */ + FBSDKLoginButtonTooltipBehaviorAutomatic = 0, + /** Force display of the tooltip (typically for UI testing) */ + FBSDKLoginButtonTooltipBehaviorForceDisplay = 1, + /** Force disable. In this case you can still exert more refined + control by manually constructing a `FBSDKLoginTooltipView` instance. */ + FBSDKLoginButtonTooltipBehaviorDisable = 2 +}; + +/** + A button that initiates a log in or log out flow upon tapping. + + `FBSDKLoginButton` works with `[FBSDKAccessToken currentAccessToken]` to + determine what to display, and automatically starts authentication when tapped (i.e., + you do not need to manually subscribe action targets). + + Like `FBSDKLoginManager`, you should make sure your app delegate is connected to + `FBSDKApplicationDelegate` in order for the button's delegate to receive messages. + + `FBSDKLoginButton` has a fixed height of @c 30 pixels, but you may change the width. `initWithFrame:CGRectZero` + will size the button to its minimum frame. +*/ +@interface FBSDKLoginButton : FBSDKButton + +/** + The default audience to use, if publish permissions are requested at login time. + */ +@property (assign, nonatomic) FBSDKDefaultAudience defaultAudience; +/** + Gets or sets the delegate. + */ +@property (weak, nonatomic) IBOutlet id delegate; +/** + Gets or sets the login behavior to use + */ +@property (assign, nonatomic) FBSDKLoginBehavior loginBehavior; +/** + The publish permissions to request. + + + Use `defaultAudience` to specify the default audience to publish to. + Note this is converted to NSSet and is only + an NSArray for the convenience of literal syntax. + */ +@property (copy, nonatomic) NSArray *publishPermissions; +/** + The read permissions to request. + + + Note, that if read permissions are specified, then publish permissions should not be specified. This is converted to NSSet and is only + an NSArray for the convenience of literal syntax. + */ +@property (copy, nonatomic) NSArray *readPermissions; +/** + Gets or sets the desired tooltip behavior. + */ +@property (assign, nonatomic) FBSDKLoginButtonTooltipBehavior tooltipBehavior; +/** + Gets or sets the desired tooltip color style. + */ +@property (assign, nonatomic) FBSDKTooltipColorStyle tooltipColorStyle; + +@end + +/** + @protocol + A delegate for `FBSDKLoginButton` + */ +@protocol FBSDKLoginButtonDelegate + +@required +/** + Sent to the delegate when the button was used to login. + - Parameter loginButton: the sender + - Parameter result: The results of the login + - Parameter error: The error (if any) from the login + */ +- (void)loginButton:(FBSDKLoginButton *)loginButton +didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result + error:(NSError *)error; + +/** + Sent to the delegate when the button was used to logout. + - Parameter loginButton: The button that was clicked. +*/ +- (void)loginButtonDidLogOut:(FBSDKLoginButton *)loginButton; + +@optional +/** + Sent to the delegate when the button is about to login. + - Parameter loginButton: the sender + - Returns: YES if the login should be allowed to proceed, NO otherwise + */ +- (BOOL) loginButtonWillLogin:(FBSDKLoginButton *)loginButton; + +@end diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginConstants.h b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginConstants.h new file mode 100644 index 000000000..713d79858 --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginConstants.h @@ -0,0 +1,76 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +/** + The error domain for all errors from FBSDKLoginKit + + Error codes from the SDK in the range 300-399 are reserved for this domain. + */ +FBSDK_EXTERN NSString *const FBSDKLoginErrorDomain; + +/** + NS_ENUM(NSInteger, FBSDKLoginErrorCode) + Error codes for FBSDKLoginErrorDomain. + */ +typedef NS_ENUM(NSInteger, FBSDKLoginErrorCode) +{ + /** + Reserved. + */ + FBSDKLoginReservedErrorCode = 300, + /** + The error code for unknown errors. + */ + FBSDKLoginUnknownErrorCode, + + /** + The user's password has changed and must log in again + */ + FBSDKLoginPasswordChangedErrorCode, + /** + The user must log in to their account on www.facebook.com to restore access + */ + FBSDKLoginUserCheckpointedErrorCode, + /** + Indicates a failure to request new permissions because the user has changed. + */ + FBSDKLoginUserMismatchErrorCode, + /** + The user must confirm their account with Facebook before logging in + */ + FBSDKLoginUnconfirmedUserErrorCode, + + /** + The Accounts framework failed without returning an error, indicating the + app's slider in the iOS Facebook Settings (device Settings -> Facebook -> App Name) has + been disabled. + */ + FBSDKLoginSystemAccountAppDisabledErrorCode, + /** + An error occurred related to Facebook system Account store + */ + FBSDKLoginSystemAccountUnavailableErrorCode, + /** + The login response was missing a valid challenge string. + */ + FBSDKLoginBadChallengeString, +}; diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginKit.h b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginKit.h new file mode 100644 index 000000000..472394001 --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginKit.h @@ -0,0 +1,25 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import +#import +#import +#import +#import diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginManager.h b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginManager.h new file mode 100644 index 000000000..5758325cc --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginManager.h @@ -0,0 +1,208 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import +#import +#import + +@class FBSDKLoginManagerLoginResult; + +/** + Describes the call back to the FBSDKLoginManager + - Parameter result: the result of the authorization + - Parameter error: the authorization error, if any. + */ +typedef void (^FBSDKLoginManagerRequestTokenHandler)(FBSDKLoginManagerLoginResult *result, NSError *error); + + +/** + FBSDKDefaultAudience enum + + Passed to open to indicate which default audience to use for sessions that post data to Facebook. + + + + Certain operations such as publishing a status or publishing a photo require an audience. When the user + grants an application permission to perform a publish operation, a default audience is selected as the + publication ceiling for the application. This enumerated value allows the application to select which + audience to ask the user to grant publish permission for. + */ +typedef NS_ENUM(NSUInteger, FBSDKDefaultAudience) +{ + /** Indicates that the user's friends are able to see posts made by the application */ + FBSDKDefaultAudienceFriends = 0, + /** Indicates that only the user is able to see posts made by the application */ + FBSDKDefaultAudienceOnlyMe, + /** Indicates that all Facebook users are able to see posts made by the application */ + FBSDKDefaultAudienceEveryone, +}; + +/** + FBSDKLoginBehavior enum + + Passed to the \c FBSDKLoginManager to indicate how Facebook Login should be attempted. + + + + Facebook Login authorizes the application to act on behalf of the user, using the user's + Facebook account. Usually a Facebook Login will rely on an account maintained outside of + the application, by the native Facebook application, the browser, or perhaps the device + itself. This avoids the need for a user to enter their username and password directly, and + provides the most secure and lowest friction way for a user to authorize the application to + interact with Facebook. + + The \c FBSDKLoginBehavior enum specifies which log-in methods may be used. The SDK + will determine the best behavior based on the current device (such as iOS version). + */ +typedef NS_ENUM(NSUInteger, FBSDKLoginBehavior) +{ + /** + This is the default behavior, and indicates logging in through the native + Facebook app may be used. The SDK may still use Safari instead. + */ + FBSDKLoginBehaviorNative = 0, + /** + Attempts log in through the Safari or SFSafariViewController, if available. + */ + FBSDKLoginBehaviorBrowser, + /** + Attempts log in through the Facebook account currently signed in through + the device Settings. + @note If the account is not available to the app (either not configured by user or + as determined by the SDK) this behavior falls back to \c FBSDKLoginBehaviorNative. + */ + FBSDKLoginBehaviorSystemAccount, + /** + Attempts log in through a modal \c UIWebView pop up + + @note This behavior is only available to certain types of apps. Please check the Facebook + Platform Policy to verify your app meets the restrictions. + */ + FBSDKLoginBehaviorWeb, +}; + +/** + `FBSDKLoginManager` provides methods for logging the user in and out. + + `FBSDKLoginManager` works directly with `[FBSDKAccessToken currentAccessToken]` and + sets the "currentAccessToken" upon successful authorizations (or sets `nil` in case of `logOut`). + + You should check `[FBSDKAccessToken currentAccessToken]` before calling logIn* to see if there is + a cached token available (typically in your viewDidLoad). + + If you are managing your own token instances outside of "currentAccessToken", you will need to set + "currentAccessToken" before calling logIn* to authorize further permissions on your tokens. + */ +@interface FBSDKLoginManager : NSObject + +/** + the default audience. + + you should set this if you intend to ask for publish permissions. + */ +@property (assign, nonatomic) FBSDKDefaultAudience defaultAudience; + +/** + the login behavior + */ +@property (assign, nonatomic) FBSDKLoginBehavior loginBehavior; + +/** + +- Warning:use logInWithReadPermissions:fromViewController:handler: instead + */ +- (void)logInWithReadPermissions:(NSArray *)permissions handler:(FBSDKLoginManagerRequestTokenHandler)handler +__attribute__ ((deprecated("use logInWithReadPermissions:fromViewController:handler: instead"))); + +/** + +- Warning:use logInWithPublishPermissions:fromViewController:handler: instead + */ +- (void)logInWithPublishPermissions:(NSArray *)permissions handler:(FBSDKLoginManagerRequestTokenHandler)handler +__attribute__ ((deprecated("use logInWithPublishPermissions:fromViewController:handler: instead"))); + +/** + Logs the user in or authorizes additional permissions. + - Parameter permissions: the optional array of permissions. Note this is converted to NSSet and is only + an NSArray for the convenience of literal syntax. + - Parameter fromViewController: the view controller to present from. If nil, the topmost view controller will be + automatically determined as best as possible. + - Parameter handler: the callback. + + Use this method when asking for read permissions. You should only ask for permissions when they + are needed and explain the value to the user. You can inspect the result.declinedPermissions to also + provide more information to the user if they decline permissions. + + This method will present UI the user. You typically should check if `[FBSDKAccessToken currentAccessToken]` + already contains the permissions you need before asking to reduce unnecessary app switching. For example, + you could make that check at viewDidLoad. + You can only do one login call at a time. Calling a login method before the completion handler is called + on a previous login will return an error. + */ +- (void)logInWithReadPermissions:(NSArray *)permissions + fromViewController:(UIViewController *)fromViewController + handler:(FBSDKLoginManagerRequestTokenHandler)handler; + +/** + Logs the user in or authorizes additional permissions. + - Parameter permissions: the optional array of permissions. Note this is converted to NSSet and is only + an NSArray for the convenience of literal syntax. + - Parameter fromViewController: the view controller to present from. If nil, the topmost view controller will be + automatically determined as best as possible. + - Parameter handler: the callback. + + Use this method when asking for publish permissions. You should only ask for permissions when they + are needed and explain the value to the user. You can inspect the result.declinedPermissions to also + provide more information to the user if they decline permissions. + + This method will present UI the user. You typically should check if `[FBSDKAccessToken currentAccessToken]` + already contains the permissions you need before asking to reduce unnecessary app switching. For example, + you could make that check at viewDidLoad. + You can only do one login call at a time. Calling a login method before the completion handler is called + on a previous login will return an error. + */ +- (void)logInWithPublishPermissions:(NSArray *)permissions + fromViewController:(UIViewController *)fromViewController + handler:(FBSDKLoginManagerRequestTokenHandler)handler; + +/** + Logs the user out + + This calls [FBSDKAccessToken setCurrentAccessToken:nil] and [FBSDKProfile setCurrentProfile:nil]. + */ +- (void)logOut; + +/** + @method + + Issues an asynchronous renewCredentialsForAccount call to the device's Facebook account store. + + - Parameter handler: The completion handler to call when the renewal is completed. This can be invoked on an arbitrary thread. + + + This can be used to explicitly renew account credentials and is provided as a convenience wrapper around + `[ACAccountStore renewCredentialsForAccount:completion]`. Note the method will not issue the renewal call if the the + Facebook account has not been set on the device, or if access had not been granted to the account (though the handler + wil receive an error). + + If the `[FBSDKAccessToken currentAccessToken]` was from the account store, a succesful renewal will also set + a new "currentAccessToken". + */ ++ (void)renewSystemCredentials:(void (^)(ACAccountCredentialRenewResult result, NSError *error))handler; + +@end diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginManagerLoginResult.h b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginManagerLoginResult.h new file mode 100644 index 000000000..6148a0754 --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginManagerLoginResult.h @@ -0,0 +1,64 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +@class FBSDKAccessToken; + +/** + Describes the result of a login attempt. + */ +@interface FBSDKLoginManagerLoginResult : NSObject + +/** + the access token. + */ +@property (copy, nonatomic) FBSDKAccessToken *token; + +/** + whether the login was cancelled by the user. + */ +@property (readonly, nonatomic) BOOL isCancelled; + +/** + the set of permissions granted by the user in the associated request. + + inspect the token's permissions set for a complete list. + */ +@property (copy, nonatomic) NSSet *grantedPermissions; + +/** + the set of permissions declined by the user in the associated request. + + inspect the token's permissions set for a complete list. + */ +@property (copy, nonatomic) NSSet *declinedPermissions; + +/** + Initializes a new instance. + - Parameter token: the access token + - Parameter isCancelled: whether the login was cancelled by the user + - Parameter grantedPermissions: the set of granted permissions + - Parameter declinedPermissions: the set of declined permissions + */ +- (instancetype)initWithToken:(FBSDKAccessToken *)token + isCancelled:(BOOL)isCancelled + grantedPermissions:(NSSet *)grantedPermissions + declinedPermissions:(NSSet *)declinedPermissions +NS_DESIGNATED_INITIALIZER; +@end diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginTooltipView.h b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginTooltipView.h new file mode 100644 index 000000000..ad2c9d504 --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKLoginTooltipView.h @@ -0,0 +1,90 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +@protocol FBSDKLoginTooltipViewDelegate; + +/** + + Represents a tooltip to be displayed next to a Facebook login button + to highlight features for new users. + + + The `FBSDKLoginButton` may display this view automatically. If you do + not use the `FBSDKLoginButton`, you can manually call one of the `present*` methods + as appropriate and customize behavior via `FBSDKLoginTooltipViewDelegate` delegate. + + By default, the `FBSDKLoginTooltipView` is not added to the superview until it is + determined the app has migrated to the new login experience. You can override this + (e.g., to test the UI layout) by implementing the delegate or setting `forceDisplay` to YES. + + */ +@interface FBSDKLoginTooltipView : FBSDKTooltipView + +/** the delegate */ +@property (nonatomic, weak) id delegate; + +/** if set to YES, the view will always be displayed and the delegate's + `loginTooltipView:shouldAppear:` will NOT be called. */ +@property (nonatomic, assign) BOOL forceDisplay; + +@end + +/** + @protocol + + The `FBSDKLoginTooltipViewDelegate` protocol defines the methods used to receive event + notifications from `FBSDKLoginTooltipView` objects. + */ +@protocol FBSDKLoginTooltipViewDelegate + +@optional + +/** + Asks the delegate if the tooltip view should appear + + - Parameter view: The tooltip view. + - Parameter appIsEligible: The value fetched from the server identifying if the app + is eligible for the new login experience. + + + Use this method to customize display behavior. + */ +- (BOOL)loginTooltipView:(FBSDKLoginTooltipView *)view shouldAppear:(BOOL)appIsEligible; + +/** + Tells the delegate the tooltip view will appear, specifically after it's been + added to the super view but before the fade in animation. + + - Parameter view: The tooltip view. + */ +- (void)loginTooltipViewWillAppear:(FBSDKLoginTooltipView *)view; + +/** + Tells the delegate the tooltip view will not appear (i.e., was not + added to the super view). + + - Parameter view: The tooltip view. + */ +- (void)loginTooltipViewWillNotAppear:(FBSDKLoginTooltipView *)view; + + +@end diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKTooltipView.h b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKTooltipView.h new file mode 100644 index 000000000..604c840c8 --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Headers/FBSDKTooltipView.h @@ -0,0 +1,136 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +/** + FBSDKTooltipViewArrowDirection enum + + Passed on construction to determine arrow orientation. + */ +typedef NS_ENUM(NSUInteger, FBSDKTooltipViewArrowDirection) +{ + /** View is located above given point, arrow is pointing down. */ + FBSDKTooltipViewArrowDirectionDown = 0, + /** View is located below given point, arrow is pointing up. */ + FBSDKTooltipViewArrowDirectionUp = 1, +}; + +/** + FBSDKTooltipColorStyle enum + + Passed on construction to determine color styling. + */ +typedef NS_ENUM(NSUInteger, FBSDKTooltipColorStyle) +{ + /** Light blue background, white text, faded blue close button. */ + FBSDKTooltipColorStyleFriendlyBlue = 0, + /** Dark gray background, white text, light gray close button. */ + FBSDKTooltipColorStyleNeutralGray = 1, +}; + +/** + + Tooltip bubble with text in it used to display tips for UI elements, + with a pointed arrow (to refer to the UI element). + + + + The tooltip fades in and will automatically fade out. See `displayDuration`. + */ +@interface FBSDKTooltipView : UIView + +/** + Gets or sets the amount of time in seconds the tooltip should be displayed. + Set this to zero to make the display permanent until explicitly dismissed. + Defaults to six seconds. + */ +@property (nonatomic, assign) CFTimeInterval displayDuration; + +/** + Gets or sets the color style after initialization. + Defaults to value passed to -initWithTagline:message:colorStyle:. + */ +@property (nonatomic, assign) FBSDKTooltipColorStyle colorStyle; + +/** + Gets or sets the message. + */ +@property (nonatomic, copy) NSString *message; + +/** + Gets or sets the optional phrase that comprises the first part of the label (and is highlighted differently). + */ +@property (nonatomic, copy) NSString *tagline; + +/** + Designated initializer. + + - Parameter tagline: First part of the label, that will be highlighted with different color. Can be nil. + + - Parameter message: Main message to display. + + - Parameter colorStyle: Color style to use for tooltip. + + + + If you need to show a tooltip for login, consider using the `FBSDKLoginTooltipView` view. + + +- See:FBSDKLoginTooltipView + */ +- (instancetype)initWithTagline:(NSString *)tagline message:(NSString *)message colorStyle:(FBSDKTooltipColorStyle)colorStyle; + +/** + Show tooltip at the top or at the bottom of given view. + Tooltip will be added to anchorView.window.rootViewController.view + + - Parameter anchorView: view to show at, must be already added to window view hierarchy, in order to decide + where tooltip will be shown. (If there's not enough space at the top of the anchorView in window bounds - + tooltip will be shown at the bottom of it) + + + + Use this method to present the tooltip with automatic positioning or + use -presentInView:withArrowPosition:direction: for manual positioning + If anchorView is nil or has no window - this method does nothing. + */ +- (void)presentFromView:(UIView *)anchorView; + +/** + Adds tooltip to given view, with given position and arrow direction. + + - Parameter view: View to be used as superview. + + - Parameter arrowPosition: Point in view's cordinates, where arrow will be pointing + + - Parameter arrowDirection: whenever arrow should be pointing up (message bubble is below the arrow) or + down (message bubble is above the arrow). + */ +- (void)presentInView:(UIView *)view withArrowPosition:(CGPoint)arrowPosition direction:(FBSDKTooltipViewArrowDirection)arrowDirection; + +/** + Remove tooltip manually. + + + + Calling this method isn't necessary - tooltip will dismiss itself automatically after the `displayDuration`. + */ +- (void)dismiss; + +@end diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Info.plist b/ParseUI/Vendor/FBSDKLoginKit.framework/Info.plist new file mode 100644 index 000000000..bb8c3706d Binary files /dev/null and b/ParseUI/Vendor/FBSDKLoginKit.framework/Info.plist differ diff --git a/ParseUI/Vendor/FBSDKLoginKit.framework/Modules/module.modulemap b/ParseUI/Vendor/FBSDKLoginKit.framework/Modules/module.modulemap new file mode 100644 index 000000000..4b1d57bd3 --- /dev/null +++ b/ParseUI/Vendor/FBSDKLoginKit.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module FBSDKLoginKit { + umbrella header "FBSDKLoginKit.h" + + export * + module * { export * } +} diff --git a/README.md b/README.md index 84eff99e7..8b8e47130 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,13 @@ [![References][references-svg]][references-link] [![Build Status][build-status-svg]][build-status-link] +[![Build Status][circleci-status-svg]][circleci-status-link] [![Coverage Status][coverage-status-svg]][coverage-status-link] [![Join Chat][gitter-svg]][gitter-link] A library that gives you access to the powerful Parse cloud platform from your iOS or OS X app. -For more information about Parse and its features, see [the blog][blog] and public [documentation][docs]. +For more information about Parse and its features, see the public [documentation][docs]. ## Getting Started @@ -27,12 +28,30 @@ Notice the API docs aren't totally up to date when it comes to latest Swift sign - **[CocoaPods](https://cocoapods.org)** - Add the following line to your Podfile: - ```ruby - pod 'Parse' - ``` - Run `pod install`, and you should now have the latest parse release. + Add the following line to your Podfile: + ```ruby + pod 'Parse' + ``` + Run `pod install`, and you should now have the latest parse release. + + If you wish to use the Facebook or Twitter utils or ParseUI, + you can now leverage Cocoapods 'subspecs' + + ```ruby + pod 'Parse/FacebookUtils' + pod 'Parse/TwitterUtils' + pod 'Parse/UI + ``` + + Note that in this case, the Parse framework will contain all headers and classes, so you just have to use: + ```swift + import Parse + ``` + + ```objc + @import Parse; + ``` - **[Carthage](https://github.com/carthage/carthage)** @@ -42,6 +61,8 @@ Notice the API docs aren't totally up to date when it comes to latest Swift sign ``` Run `carthage update`, and you should now have the latest version of Parse SDK in your Carthage folder. + This will also compile the ParseTwitterUtils, ParseFacebookUtilsV4 as well as ParseUI frameworks. + - **Compiling for yourself** If you want to manually compile the SDK, clone it locally, and run the following commands in the root directory of the repository: @@ -58,7 +79,16 @@ Notice the API docs aren't totally up to date when it comes to latest Swift sign # Build & Package the Frameworks bundle exec rake package:frameworks - Compiled frameworks will be in 2 archives: `Parse-iOS.zip` and `Parse-OSX.zip` inside the `build/release` folder, and you can link them as you'd please. + Compiled frameworks will be in multiple archives inside the `build/release` folder: + - `Parse-iOS.zip` + - `Parse-macOS.zip` + - `Parse-tvOS.zip` + - `Parse-watchOS.zip` + - `ParseFacebookUtils-iOS.zip` + - `ParseFacebookUtils-tvOS.zip` + - `ParseTwitterUtils-iOS.zip` + - `ParseUI.zip` + - **Using Parse as a sub-project** @@ -90,8 +120,6 @@ We use the following libraries as dependencies inside of Parse: ## Other Parse Projects - [ParseUI for iOS][parseui-link] - - [ParseFacebookUtils for iOS][parsefacebookutils-link] - - [ParseTwitterUtils for iOS][parsetwitterutils-link] ## License @@ -107,12 +135,9 @@ of patent rights can be found in the PATENTS file in the same directory. As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code. [docs]: http://docs.parseplatform.org/ios/guide/ - [blog]: http://blog.parse.com/ [api]: http://parseplatform.org/Parse-SDK-iOS-OSX/api/ [parseui-link]: https://github.com/parse-community/ParseUI-iOS - [parsefacebookutils-link]: https://github.com/parse-community/ParseFacebookUtils-iOS - [parsetwitterutils-link]: https://github.com/parse-community/ParseTwitterUtils-iOS [releases]: https://github.com/parse-community/Parse-SDK-iOS-OSX/releases [contributing]: https://github.com/parse-community/Parse-SDK-iOS-OSX/blob/master/CONTRIBUTING.md @@ -123,6 +148,9 @@ As of April 5, 2017, Parse, LLC has transferred this code to the parse-community [build-status-svg]: https://img.shields.io/travis/parse-community/Parse-SDK-iOS-OSX/master.svg [build-status-link]: https://travis-ci.org/parse-community/Parse-SDK-iOS-OSX/branches + [circleci-status-svg]: https://circleci.com/gh/parse-community/Parse-SDK-iOS-OSX.svg?style=shield + [circleci-status-link]: https://circleci.com/build-insights/gh/parse-community/Parse-SDK-iOS-OSX/master + [coverage-status-svg]: https://img.shields.io/codecov/c/github/parse-community/Parse-SDK-iOS-OSX/master.svg [coverage-status-link]: https://codecov.io/github/parse-community/Parse-SDK-iOS-OSX?branch=master diff --git a/Rakefile b/Rakefile index 7ac95d3db..cd411a6f2 100644 --- a/Rakefile +++ b/Rakefile @@ -21,14 +21,17 @@ module Constants script_folder = File.expand_path(File.dirname(__FILE__)) - PARSE_CONSTANTS_HEADER = File.join(script_folder, 'Parse', 'PFConstants.h') + PARSE_CONSTANTS_HEADER = File.join(script_folder, 'Parse', 'Parse', 'PFConstants.h') PARSE_PODSPEC = File.join(script_folder, 'Parse.podspec') PLISTS = [ - File.join(script_folder, 'Parse', 'Resources', 'Parse-iOS.Info.plist'), - File.join(script_folder, 'Parse', 'Resources', 'Parse-OSX.Info.plist'), - File.join(script_folder, 'Parse', 'Resources', 'Parse-watchOS.Info.plist'), - File.join(script_folder, 'Parse', 'Resources', 'Parse-tvOS.Info.plist'), + File.join(script_folder, 'Parse','Parse', 'Resources', 'Parse-iOS.Info.plist'), + File.join(script_folder, 'Parse','Parse', 'Resources', 'Parse-OSX.Info.plist'), + File.join(script_folder, 'Parse','Parse', 'Resources', 'Parse-watchOS.Info.plist'), + File.join(script_folder, 'Parse','Parse', 'Resources', 'Parse-tvOS.Info.plist'), + File.join(script_folder, 'ParseFacebookUtils', 'Resources', 'Info-iOS.plist'), + File.join(script_folder, 'ParseFacebookUtils', 'Resources', 'Info-tvOS.plist'), + File.join(script_folder, 'ParseTwitterUtils', 'Resources', 'Info.plist'), File.join(script_folder, 'ParseStarterProject', 'iOS', 'ParseStarterProject', 'Resources', 'Info.plist'), File.join(script_folder, 'ParseStarterProject', 'iOS', 'ParseStarterProject-Swift', 'Resources', 'Info.plist'), File.join(script_folder, 'ParseStarterProject', 'OSX', 'ParseOSXStarterProject', 'Resources', 'Info.plist'), @@ -148,6 +151,132 @@ namespace :build do exit(1) end end + + namespace :facebook_utils do + desc 'Build iOS FacebookUtils framework.' + task :ios do + task = XCTask::BuildFrameworkTask.new do |t| + t.directory = script_folder + t.build_directory = File.join(build_folder, 'iOS') + t.framework_type = XCTask::FrameworkType::IOS + t.framework_name = 'ParseFacebookUtilsV4.framework' + t.workspace = 'Parse.xcworkspace' + t.scheme = 'ParseFacebookUtilsV4-iOS' + t.configuration = 'Release' + end + + result = task.execute + unless result + puts 'Failed to build iOS FacebookUtils Framework.' + exit(1) + end + end + + desc 'Build tvOS FacebookUtils framework.' + task :tvos do + task = XCTask::BuildFrameworkTask.new do |t| + t.directory = script_folder + t.build_directory = File.join(build_folder, 'tvOS') + t.framework_type = XCTask::FrameworkType::TVOS + t.framework_name = 'ParseFacebookUtilsV4.framework' + t.workspace = 'Parse.xcworkspace' + t.scheme = 'ParseFacebookUtilsV4-tvOS' + t.configuration = 'Release' + end + result = task.execute + unless result + puts 'Failed to build tvOS FacebookUtils Framework.' + exit(1) + end + end + end + + namespace :twitter_utils do + desc 'Build iOS TwitterUtils framework.' + task :ios do + task = XCTask::BuildFrameworkTask.new do |t| + t.directory = script_folder + t.build_directory = File.join(build_folder, 'iOS') + t.framework_type = XCTask::FrameworkType::IOS + t.framework_name = 'ParseTwitterUtils.framework' + t.workspace = 'Parse.xcworkspace' + t.scheme = 'ParseTwitterUtils-iOS' + t.configuration = 'Release' + end + + result = task.execute + unless result + puts 'Failed to build iOS FacebookUtils Framework.' + exit(1) + end + end + end + + namespace :parseui do + task :framework do + task = XCTask::BuildFrameworkTask.new do |t| + t.directory = script_folder + t.build_directory = File.join(build_folder, 'iOS') + t.framework_type = XCTask::FrameworkType::IOS + t.framework_name = 'ParseUI.framework' + t.workspace = 'Parse.xcworkspace' + t.scheme = 'ParseUI' + t.configuration = 'Release' + end + + result = task.execute + unless result + puts 'Failed to build ParseUI' + exit(1) + end + end + + task :demo_objc do + task = XCTask::BuildTask.new do |t| + t.directory = script_folder + t.workspace = 'Parse.xcworkspace' + + t.scheme = 'ParseUIDemo' + t.sdk = 'iphonesimulator' + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] + t.configuration = 'Debug' + t.additional_options = { "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS" => "YES", + "GCC_GENERATE_TEST_COVERAGE_FILES" => "YES" } + + t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD] + t.formatter = XCTask::BuildFormatter::XCPRETTY + end + + result = task.execute + unless result + puts 'Failed to build ParseUI Demo.' + exit(1) + end + end + + task :demo_swift do + task = XCTask::BuildTask.new do |t| + t.directory = script_folder + t.workspace = 'Parse.xcworkspace' + + t.scheme = 'ParseUIDemo-Swift' + t.sdk = 'iphonesimulator' + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] + t.configuration = 'Debug' + t.additional_options = { "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS" => "YES", + "GCC_GENERATE_TEST_COVERAGE_FILES" => "YES" } + + t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD] + t.formatter = XCTask::BuildFormatter::XCPRETTY + end + + result = task.execute + unless result + puts 'Failed to build iOS ParseUI Swift Demo.' + exit(1) + end + end + end end namespace :package do @@ -159,6 +288,7 @@ namespace :package do package_starter_osx_name = 'ParseStarterProject-OSX.zip' package_starter_tvos_name = 'ParseStarterProject-tvOS.zip' package_starter_watchos_name = 'ParseStarterProject-watchOS.zip' + package_parseui_name = 'ParseUI.zip' task :prepare do `rm -rf #{build_folder} && mkdir -p #{build_folder}` @@ -183,6 +313,14 @@ namespace :package do make_package(release_folder, [ios_framework_path, bolts_path], package_ios_name) + + Rake::Task['build:facebook_utils:ios'].invoke + ios_fb_utils_framework_path = File.join(build_folder, 'iOS', 'ParseFacebookUtilsV4.framework') + make_package(release_folder, [ios_fb_utils_framework_path], 'ParseFacebookUtils-iOS.zip') + + Rake::Task['build:twitter_utils:ios'].invoke + ios_tw_utils_framework_path = File.join(build_folder, 'iOS', 'ParseTwitterUtils.framework') + make_package(release_folder, [ios_tw_utils_framework_path], 'ParseTwitterUtils-iOS.zip') ## Build macOS Framework Rake::Task['build:macos'].invoke @@ -192,13 +330,17 @@ namespace :package do [osx_framework_path, bolts_path], package_macos_name) - ## Build tvOS Framework - Rake::Task['build:tvos'].invoke - bolts_path = File.join(bolts_build_folder, 'tvOS', 'Bolts.framework') - tvos_framework_path = File.join(build_folder, 'Parse.framework') - make_package(release_folder, - [tvos_framework_path, bolts_path], - package_tvos_name) + ## Build tvOS Framework + Rake::Task['build:tvos'].invoke + bolts_path = File.join(bolts_build_folder, 'tvOS', 'Bolts.framework') + tvos_framework_path = File.join(build_folder, 'Parse.framework') + make_package(release_folder, + [tvos_framework_path, bolts_path], + package_tvos_name) + + Rake::Task['build:facebook_utils:tvos'].invoke + tvos_fb_utils_framework_path = File.join(build_folder, 'tvOS', 'ParseFacebookUtilsV4.framework') + make_package(release_folder, [tvos_fb_utils_framework_path], 'ParseFacebookUtils-tvOS.zip') ## Build watchOS Framework Rake::Task['build:watchos'].invoke @@ -207,6 +349,12 @@ namespace :package do make_package(release_folder, [watchos_framework_path, bolts_path], package_watchos_name) + + Rake::Task['build:parseui:framework'].invoke + parseui_framework_path = File.join(build_folder, 'ParseUI.framework') + make_package(release_folder, + [parseui_framework_path], + package_parseui_name) end desc 'Build and package all starter projects for the release' @@ -312,7 +460,7 @@ namespace :test do t.scheme = 'Parse-iOS' t.sdk = 'iphonesimulator' - t.destinations = ["\"platform=iOS Simulator,OS=9.1,name=iPhone 6s\"",] + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] t.configuration = 'Debug' t.additional_options = { "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS" => "YES", "GCC_GENERATE_TEST_COVERAGE_FILES" => "YES" } @@ -348,6 +496,133 @@ namespace :test do end end + namespace :facebook_utils do + desc 'Test iOS FacebookUtils framework.' + task :ios do + task = XCTask::BuildTask.new do |t| + t.directory = script_folder + t.workspace = 'Parse.xcworkspace' + + t.scheme = 'ParseFacebookUtilsV4-iOS' + t.sdk = 'iphonesimulator' + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] + t.configuration = 'Debug' + t.additional_options = { "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS" => "YES", + "GCC_GENERATE_TEST_COVERAGE_FILES" => "YES" } + + t.actions = [XCTask::BuildAction::TEST] + t.formatter = XCTask::BuildFormatter::XCPRETTY + end + + result = task.execute + unless result + puts 'Failed to build iOS FacebookUtils Framework.' + exit(1) + end + end + end + + namespace :twitter_utils do + desc 'Test iOS TwitterUtils framework.' + task :ios do + task = XCTask::BuildTask.new do |t| + t.directory = script_folder + t.workspace = 'Parse.xcworkspace' + + t.scheme = 'ParseTwitterUtils-iOS' + t.sdk = 'iphonesimulator' + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] + t.configuration = 'Debug' + t.additional_options = { "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS" => "YES", + "GCC_GENERATE_TEST_COVERAGE_FILES" => "YES" } + + t.actions = [XCTask::BuildAction::TEST] + t.formatter = XCTask::BuildFormatter::XCPRETTY + end + + result = task.execute + unless result + puts 'Failed to build iOS FacebookUtils Framework.' + exit(1) + end + end + end + + namespace :parseui do + task :all do + Rake::Task['test:parseui:framework'].invoke + Rake::Task['test:parseui:demo_objc'].invoke + Rake::Task['test:parseui:demo_swift'].invoke + end + + task :framework do + task = XCTask::BuildTask.new do |t| + t.directory = script_folder + t.workspace = 'Parse.xcworkspace' + + t.scheme = 'ParseUI' + t.sdk = 'iphonesimulator' + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] + t.configuration = 'Debug' + + t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD] + t.formatter = XCTask::BuildFormatter::XCPRETTY + end + + result = task.execute + unless result + puts 'Failed to build ParseUI' + exit(1) + end + end + + task :demo_objc do + task = XCTask::BuildTask.new do |t| + t.directory = script_folder + t.workspace = 'Parse.xcworkspace' + + t.scheme = 'ParseUIDemo' + t.sdk = 'iphonesimulator' + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] + t.configuration = 'Debug' + t.additional_options = { "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS" => "YES", + "GCC_GENERATE_TEST_COVERAGE_FILES" => "YES" } + + t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD] + t.formatter = XCTask::BuildFormatter::XCPRETTY + end + + result = task.execute + unless result + puts 'Failed to build ParseUI Demo.' + exit(1) + end + end + + task :demo_swift do + task = XCTask::BuildTask.new do |t| + t.directory = script_folder + t.workspace = 'Parse.xcworkspace' + + t.scheme = 'ParseUIDemo-Swift' + t.sdk = 'iphonesimulator' + t.destinations = ["\"platform=iOS Simulator,OS=10.3.1,name=iPhone 6s\"",] + t.configuration = 'Debug' + t.additional_options = { "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS" => "YES", + "GCC_GENERATE_TEST_COVERAGE_FILES" => "YES" } + + t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD] + t.formatter = XCTask::BuildFormatter::XCPRETTY + end + + result = task.execute + unless result + puts 'Failed to build iOS ParseUI Swift Demo.' + exit(1) + end + end + end + desc 'Run Deployment Tests' task :deployment do |_| Rake::Task['package:frameworks'].invoke @@ -414,7 +689,7 @@ namespace :test do t.scheme = scheme t.configuration = 'Debug' - t.destinations = ["\"platform=tvOS Simulator,OS=9.0,name=Apple TV 1080p\"",] + t.destinations = ["\"platform=tvOS Simulator,OS=10.3.1,name=Apple TV 1080p\"",] t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD] t.formatter = XCTask::BuildFormatter::XCPRETTY diff --git a/Scripts/jazzy.sh b/Scripts/jazzy.sh index 1d79d1746..8c4e0c633 100755 --- a/Scripts/jazzy.sh +++ b/Scripts/jazzy.sh @@ -1,7 +1,7 @@ -mkdir -p ./Bolts # Create a temporary bolts folder -cp -R Carthage/Checkouts/Bolts-ObjC/Bolts/**/*.h ./Bolts # Copy bolts +mkdir -p ./Parse/Bolts # Create a temporary bolts folder +cp -R Carthage/Checkouts/Bolts-ObjC/Bolts/**/*.h ./Parse/Bolts # Copy bolts -ver=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Parse/Resources/Parse-iOS.Info.plist` +ver=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Parse/Parse/Resources/Parse-iOS.Info.plist` jazzy \ --objc \ --clean \ @@ -10,11 +10,12 @@ jazzy \ --github_url https://github.com/parse-community/Parse-SDK-iOS-OSX \ --root-url http://parseplatform.org/Parse-SDK-iOS-OSX/api/ \ --module-version ${ver} \ - --xcodebuild-arguments --objc,Parse/Parse.h,--,-x,objective-c,-isysroot,$(xcrun --show-sdk-path --sdk iphonesimulator),-I,$(pwd) \ --theme fullwidth \ --skip-undocumented \ --exclude=./Bolts/* \ --module Parse \ + --umbrella-header Parse/Parse/Parse.h \ + --framework-root Parse \ --output docs/api -rm -rf ./Bolts # cleanup temporary bolts +rm -rf ./Parse/Bolts # cleanup temporary bolts