├── .github
└── workflows
│ └── ios.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── DocumentationAssets
├── button-email-state.jpeg
└── button-light-style.jpeg
├── Examples
├── Examples.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── GroceryExpress.xcscheme
└── GroceryExpress
│ ├── AddressSelectionViewController.swift
│ ├── AddressUpdateViewController.swift
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon.png
│ │ ├── icon_20pt.png
│ │ ├── icon_20pt@2x-1.png
│ │ ├── icon_20pt@2x.png
│ │ ├── icon_20pt@3x.png
│ │ ├── icon_29pt.png
│ │ ├── icon_29pt@2x-1.png
│ │ ├── icon_29pt@2x.png
│ │ ├── icon_29pt@3x.png
│ │ ├── icon_40pt.png
│ │ ├── icon_40pt@2x-1.png
│ │ ├── icon_40pt@2x.png
│ │ ├── icon_40pt@3x.png
│ │ ├── icon_60pt@2x.png
│ │ ├── icon_60pt@3x.png
│ │ ├── icon_76pt.png
│ │ ├── icon_76pt@2x.png
│ │ └── icon_83.5@2x.png
│ ├── Contents.json
│ ├── Icons
│ │ ├── Contents.json
│ │ ├── cart.imageset
│ │ │ ├── Contents.json
│ │ │ └── cart.pdf
│ │ ├── chevron_right.imageset
│ │ │ ├── Contents.json
│ │ │ └── chevron_right.pdf
│ │ └── menu.imageset
│ │ │ ├── Contents.json
│ │ │ └── menu.pdf
│ └── Mock UI
│ │ ├── Contents.json
│ │ ├── calendar_connection_image.imageset
│ │ ├── Contents.json
│ │ ├── Main value prop@1x.png
│ │ ├── Main value prop@2x.png
│ │ └── Main value prop@3x.png
│ │ ├── cart_ui.imageset
│ │ ├── Contents.json
│ │ ├── Grocery_shopping cart@1x.png
│ │ ├── Grocery_shopping cart@2x.png
│ │ └── Grocery_shopping cart@3x.png
│ │ ├── location_connection_image.imageset
│ │ ├── Contents.json
│ │ ├── fruit-1509588@1x.jpeg
│ │ ├── fruit-1509588@2x.jpeg
│ │ └── fruit-1509588@3x.png
│ │ └── store_ui.imageset
│ │ ├── Contents.json
│ │ ├── Grocery flow@1x.png
│ │ ├── Grocery flow@2x.png
│ │ └── Grocery flow@3x.png
│ ├── Base.lproj
│ └── LaunchScreen.storyboard
│ ├── ConnectionCredentials.swift
│ ├── ConnectionViewController.swift
│ ├── DisplayViewController.swift
│ ├── Info.plist
│ ├── Main.storyboard
│ ├── Settings.swift
│ ├── SettingsViewController.swift
│ ├── TokenRequest.swift
│ ├── UIImageView+NetworkLoading.swift
│ └── UIView+Helpers.swift
├── IFTTT SDK.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── IFTTTConnectSDK.xcscheme
│ └── SDKHostApp.xcscheme
├── IFTTT SDK
├── API.swift
├── ASWebServiceAuthentication.swift
├── AboutViewController.swift
├── Analytics+DataStructures.swift
├── Analytics.swift
├── AnalyticsNetworkController.swift
├── Array+Helpers.swift
├── Assets.swift
├── AuthenticationSessionPresentationContextProvider.swift
├── Bundle+Helpers.swift
├── CLCircularRegion+Parsing.swift
├── Color.swift
├── ConnectButton+AnimationState.swift
├── ConnectButton+CheckmarkView.swift
├── ConnectButton+Constants.swift
├── ConnectButton+Interaction.swift
├── ConnectButton+LabelAnimator.swift
├── ConnectButton+LabelValue.swift
├── ConnectButton+ProgressBar.swift
├── ConnectButton+Service.swift
├── ConnectButton+Style.swift
├── ConnectButton+SwitchControl.swift
├── ConnectButton+Transition.swift
├── ConnectButton.swift
├── ConnectButtonController+Logging.swift
├── ConnectButtonController+Public.swift
├── ConnectButtonController.swift
├── Connection+Location.swift
├── Connection+Parsing.swift
├── Connection+Request.swift
├── Connection+Storage.swift
├── Connection.swift
├── ConnectionActivationFlow.swift
├── ConnectionConfiguration.swift
├── ConnectionCredentialProvider.swift
├── ConnectionDeeplinkAction.swift
├── ConnectionNetworkController.swift
├── ConnectionNetworkError.swift
├── ConnectionRedirectHandler.swift
├── ConnectionVerificationSession.swift
├── ConnectionsMonitor.swift
├── ConnectionsRegistry.swift
├── ConnectionsSynchronizer.swift
├── ConstraintsMaker.swift
├── EmailValidator.swift
├── EventPublisher.swift
├── Font.swift
├── IFTTT_SDK.h
├── ImageCache.swift
├── ImageDownloader.swift
├── ImageViewNetworkController.swift
├── Info.plist
├── JSON.swift
├── JSONNetworkController.swift
├── Keychain.swift
├── LegalTermsText.swift
├── Library.swift
├── Links.swift
├── LocalizedStrings.swift
├── LocationEvent.swift
├── LocationEventReporter.swift
├── LocationEventStore.swift
├── LocationLibrary.swift
├── LocationMonitor.swift
├── LocationService.swift
├── NativeServices.swift
├── Notification+Redirect.swift
├── PassthroughView.swift
├── PermissionsRequestor.swift
├── PillButton.swift
├── PillView.swift
├── ProgressBarController.swift
├── Reachability.swift
├── RegionEvent.swift
├── RegionEventsRegistry.swift
├── RegionsMonitor.swift
├── Resources
│ ├── Assets.xcassets
│ │ ├── About page
│ │ │ ├── Contents.json
│ │ │ ├── Value props
│ │ │ │ ├── Contents.json
│ │ │ │ ├── ifttt_about_connect.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── about_connect.pdf
│ │ │ │ ├── ifttt_about_control.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── about_control.pdf
│ │ │ │ ├── ifttt_about_security.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── about_security.pdf
│ │ │ │ └── ifttt_about_unplug.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── about_unplug.pdf
│ │ │ ├── ifttt_about_close.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── about_close.pdf
│ │ │ ├── ifttt_about_connect_arrow.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── connect_arrow.pdf
│ │ │ └── ifttt_about_download_on_app_store.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── about_download_on_app_store.pdf
│ │ ├── Contents.json
│ │ └── ifttt_email_confirm.imageset
│ │ │ ├── Contents.json
│ │ │ └── email_confirm.pdf
│ ├── Localizable.strings
│ ├── Localizable_cs.strings
│ ├── Localizable_da.strings
│ ├── Localizable_de.strings
│ ├── Localizable_en-GB.strings
│ ├── Localizable_en-US.strings
│ ├── Localizable_es-419.strings
│ ├── Localizable_es.strings
│ ├── Localizable_fi.strings
│ ├── Localizable_fr-CA.strings
│ ├── Localizable_fr.strings
│ ├── Localizable_it.strings
│ ├── Localizable_ja.strings
│ ├── Localizable_ko.strings
│ ├── Localizable_nb.strings
│ ├── Localizable_nl.strings
│ ├── Localizable_pl.strings
│ ├── Localizable_pt-BR.strings
│ ├── Localizable_pt-PT.strings
│ ├── Localizable_ru.strings
│ ├── Localizable_sv.strings
│ ├── Localizable_zh-Hans.strings
│ └── Localizable_zh-Hant.strings
├── Result+Queries.swift
├── SFWebServiceAuthentication.swift
├── SelectGestureRecognizer.swift
├── Selectable.swift
├── ServiceIconsNetworkController.swift
├── Set+Helpers.swift
├── SignInWithAppleAuthentication.swift
├── SynchronizationManager.swift
├── SynchronizationScheduler.swift
├── SynchronizationSubscriber.swift
├── SynchronizationTriggerEvent.swift
├── UIKit+ConvenienceInit.swift
├── URLComponents+email.swift
├── URLRequest+CommonValues.swift
├── URLSession+JSONTask.swift
├── User.swift
└── WebServiceAuthentication.swift
├── IFTTTConnectSDK.podspec
├── LICENSE
├── Location.md
├── Package.swift
├── README.md
├── SDKHostApp
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── LaunchScreen.storyboard
├── ContentView.swift
├── Info.plist
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── SDKHostApp.entitlements
└── SceneDelegate.swift
├── SDKHostAppTests
├── ArrayHelpersTests.swift
├── CLRegion+Parsing_spec.swift
├── Connection_ParsingTests.swift
├── ConnectionsRegistryTests.swift
├── EventPublisherTests.swift
├── IFTTT SDKTests-Bridging-Header.h
├── Info.plist
├── LocationEventReporterTests.swift
├── LocationEventStoreTests.swift
├── LocationServiceTests.swift
├── RegionEventsRegistryTests.swift
├── RegionsMonitorTests.swift
├── String_EmailDataDetectorTests.swift
├── SynchronizationSchedulerTests.swift
├── fetch_connection_response.json
└── fetch_multiple_feature_fields_connection_response.json
└── SDKHostAppUITests
├── Info.plist
└── SDKHostAppUITests.swift
/.github/workflows/ios.yml:
--------------------------------------------------------------------------------
1 | name: Build and run tests
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | name: Build and Test SDKHostApp scheme using any available iPhone simulator
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 | - name: Build and Test
18 | env:
19 | scheme: ${{ 'SDKHostApp' }}
20 | run: |
21 | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
22 |
23 | if [ $scheme = default ]; then scheme=$(cat default); fi
24 |
25 | # Determine file to build: .xcworkspace or .xcodeproj
26 | if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
27 |
28 | # Clean up whitespace
29 | file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
30 |
31 | # Find first available simulator
32 | device_name=$(xcrun simctl list devices available | grep "iPhone" | head -n 1 | sed -E 's/^[[:space:]]*([^()]+)[[:space:]]*\(.*$/\1/' | awk '{$1=$1; print}')
33 |
34 | if [ -z "$device_name" ]; then
35 | echo "❌ Failed to find a valid iOS device."
36 | exit 1
37 | fi
38 |
39 | echo "📱 Using device: $device_name"
40 |
41 | # Build and run the tests
42 | xcodebuild test \
43 | -scheme "$scheme" \
44 | -"$filetype_parameter" "$file_to_build" \
45 | -destination "platform=iOS Simulator,name=$device_name"
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | # OSX
6 | .DS_Store
7 |
8 | ## Build generated
9 | build/
10 | DerivedData/
11 |
12 | ## Various settings
13 | *.pbxuser
14 | !default.pbxuser
15 | *.mode1v3
16 | !default.mode1v3
17 | *.mode2v3
18 | !default.mode2v3
19 | *.perspectivev3
20 | !default.perspectivev3
21 | xcuserdata/
22 |
23 | ## Other
24 | *.moved-aside
25 | *.xccheckout
26 | *.xcscmblueprint
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 | *.ipa
31 | *.dSYM.zip
32 | *.dSYM
33 |
34 | ## Playgrounds
35 | timeline.xctimeline
36 | playground.xcworkspace
37 |
38 | # Swift Package Manager
39 | #
40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
41 | # Packages/
42 | # Package.pins
43 | # Package.resolved
44 | .build/
45 |
46 | # CocoaPods
47 | #
48 | # We recommend against adding the Pods directory to your .gitignore. However
49 | # you should judge for yourself, the pros and cons are mentioned at:
50 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
51 | #
52 | # Pods/
53 |
54 | # Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 | # Carthage/Checkouts
58 |
59 | Carthage/Build
60 |
61 | # fastlane
62 | #
63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
64 | # screenshots whenever they are needed.
65 | # For more information about the recommended setup visit:
66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
67 |
68 | fastlane/report.xml
69 | fastlane/Preview.html
70 | fastlane/screenshots/**/*.png
71 | fastlane/test_output
72 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | xcode_project: IFTTT SDK.xcodeproj
3 | xcode_scheme: SDKHostApp
4 | osx_image: xcode12.2
5 | xcode_destination: platform=iOS Simulator,OS=14.2,name=iPhone 11
6 |
--------------------------------------------------------------------------------
/DocumentationAssets/button-email-state.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/DocumentationAssets/button-email-state.jpeg
--------------------------------------------------------------------------------
/DocumentationAssets/button-light-style.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/DocumentationAssets/button-light-style.jpeg
--------------------------------------------------------------------------------
/Examples/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/Examples.xcodeproj/xcshareddata/xcschemes/GroceryExpress.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SDK Example
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 | import IFTTTConnectSDK
10 | import CoreLocation
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | static let connectionRedirectURL = URL(string: "groceryexpress://connect_callback")!
18 |
19 | private let connectionRedirectHandler = ConnectionRedirectHandler(redirectURL: AppDelegate.connectionRedirectURL)
20 |
21 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
22 | ConnectButtonController.synchronizationLoggingEnabled = true
23 | ConnectButtonController.analyticsEnabled = true
24 | ConnectButtonController.initialize(options: .init(enableSDKBackgroundProcess: true, showPermissionsPrompts: true))
25 | if ConnectionCredentials(settings: .init()).isLoggedIn {
26 | ConnectButtonController.activate(connections: [DisplayInformation.locationConnection.connectionId])
27 | } else {
28 | ConnectButtonController.deactivate()
29 | }
30 |
31 | ConnectButtonController.setBackgroundProcessClosures {
32 | print("Background process started!")
33 | } expirationHandler: {
34 | print("Background process expired!")
35 | }
36 |
37 | ConnectButtonController.setLocationEventReportedClosure { events in
38 | print(events)
39 | }
40 |
41 | return true
42 | }
43 |
44 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
45 | if connectionRedirectHandler.handleApplicationRedirect(url: url, options: options) {
46 | // This is an IFTTT SDK redirect, it will take over from here
47 | return true
48 | } else {
49 | // This is unrelated to the IFTTT SDK
50 | return false
51 | }
52 | }
53 |
54 | func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
55 | ConnectButtonController.performFetchWithCompletionHandler { (result) in
56 | completionHandler(result)
57 | }
58 | }
59 |
60 | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
61 | ConnectButtonController.didReceiveSilentRemoteNotification { (result) in
62 | completionHandler(result)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "icon_20pt@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "icon_20pt@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "icon_29pt@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "icon_29pt@3x.png",
25 | "scale" : "3x"
26 | },
27 | {
28 | "size" : "40x40",
29 | "idiom" : "iphone",
30 | "filename" : "icon_40pt@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "icon_40pt@3x.png",
37 | "scale" : "3x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "icon_60pt@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "icon_60pt@3x.png",
49 | "scale" : "3x"
50 | },
51 | {
52 | "size" : "20x20",
53 | "idiom" : "ipad",
54 | "filename" : "icon_20pt.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "icon_20pt@2x-1.png",
61 | "scale" : "2x"
62 | },
63 | {
64 | "size" : "29x29",
65 | "idiom" : "ipad",
66 | "filename" : "icon_29pt.png",
67 | "scale" : "1x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "icon_29pt@2x-1.png",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "40x40",
77 | "idiom" : "ipad",
78 | "filename" : "icon_40pt.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "icon_40pt@2x-1.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "76x76",
89 | "idiom" : "ipad",
90 | "filename" : "icon_76pt.png",
91 | "scale" : "1x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "icon_76pt@2x.png",
97 | "scale" : "2x"
98 | },
99 | {
100 | "size" : "83.5x83.5",
101 | "idiom" : "ipad",
102 | "filename" : "icon_83.5@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "1024x1024",
107 | "idiom" : "ios-marketing",
108 | "filename" : "Icon.png",
109 | "scale" : "1x"
110 | }
111 | ],
112 | "info" : {
113 | "version" : 1,
114 | "author" : "xcode"
115 | }
116 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Icons/cart.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "cart.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Icons/cart.imageset/cart.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Icons/cart.imageset/cart.pdf
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Icons/chevron_right.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "chevron_right.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Icons/chevron_right.imageset/chevron_right.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Icons/chevron_right.imageset/chevron_right.pdf
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Icons/menu.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "menu.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Icons/menu.imageset/menu.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Icons/menu.imageset/menu.pdf
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/calendar_connection_image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Main value prop@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Main value prop@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Main value prop@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "original"
25 | }
26 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/calendar_connection_image.imageset/Main value prop@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/calendar_connection_image.imageset/Main value prop@1x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/calendar_connection_image.imageset/Main value prop@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/calendar_connection_image.imageset/Main value prop@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/calendar_connection_image.imageset/Main value prop@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/calendar_connection_image.imageset/Main value prop@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/cart_ui.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Grocery_shopping cart@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Grocery_shopping cart@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Grocery_shopping cart@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "original"
25 | }
26 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/cart_ui.imageset/Grocery_shopping cart@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/cart_ui.imageset/Grocery_shopping cart@1x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/cart_ui.imageset/Grocery_shopping cart@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/cart_ui.imageset/Grocery_shopping cart@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/cart_ui.imageset/Grocery_shopping cart@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/cart_ui.imageset/Grocery_shopping cart@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/location_connection_image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "fruit-1509588@1x.jpeg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "fruit-1509588@2x.jpeg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "fruit-1509588@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/location_connection_image.imageset/fruit-1509588@1x.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/location_connection_image.imageset/fruit-1509588@1x.jpeg
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/location_connection_image.imageset/fruit-1509588@2x.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/location_connection_image.imageset/fruit-1509588@2x.jpeg
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/location_connection_image.imageset/fruit-1509588@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/location_connection_image.imageset/fruit-1509588@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/store_ui.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Grocery flow@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Grocery flow@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Grocery flow@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "original"
25 | }
26 | }
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/store_ui.imageset/Grocery flow@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/store_ui.imageset/Grocery flow@1x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/store_ui.imageset/Grocery flow@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/store_ui.imageset/Grocery flow@2x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Assets.xcassets/Mock UI/store_ui.imageset/Grocery flow@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/Examples/GroceryExpress/Assets.xcassets/Mock UI/store_ui.imageset/Grocery flow@3x.png
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/DisplayViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DisplayViewController.swift
3 | // Grocery Express
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | struct DisplayInformation {
12 | let connectionId: String
13 | let hideOpaqueOverlay: Bool
14 | let headerImage: UIImage
15 | let subtitleText: String
16 | let showLocationUpdateWithSkipConfig: Bool
17 |
18 | static let calendarConnection = DisplayInformation(connectionId: "fWj4fxYg",
19 | hideOpaqueOverlay: true,
20 | headerImage: #imageLiteral(resourceName: "calendar_connection_image"),
21 | subtitleText: "Delivered when you're at home",
22 | showLocationUpdateWithSkipConfig: false)
23 |
24 | static let locationConnection = DisplayInformation(connectionId: "pWisyzm7",
25 | hideOpaqueOverlay: false,
26 | headerImage: #imageLiteral(resourceName: "location_connection_image"),
27 | subtitleText: "Delivered when you're at a location",
28 | showLocationUpdateWithSkipConfig: true)
29 | }
30 |
31 | class DisplayViewController: UIViewController {
32 | @IBOutlet weak var calendarConnectionView: UIView!
33 | @IBOutlet weak var locationConnectionView: UIView!
34 |
35 | private let calendarConnectionGestureRecognizer = UITapGestureRecognizer()
36 | private let locationConnectionGestureRecognizer = UITapGestureRecognizer()
37 |
38 | override func viewDidLoad() {
39 | super.viewDidLoad()
40 | calendarConnectionView.addGestureRecognizer(calendarConnectionGestureRecognizer)
41 | locationConnectionView.addGestureRecognizer(locationConnectionGestureRecognizer)
42 |
43 | calendarConnectionGestureRecognizer.addTarget(self, action: #selector(calendarConnectionViewTapped))
44 | locationConnectionGestureRecognizer.addTarget(self, action: #selector(locationConnectionViewTapped))
45 | }
46 |
47 | @objc func calendarConnectionViewTapped(gestureRecognizer: UIGestureRecognizer) {
48 | navigationController?.pushViewController(ConnectionViewController.instantiate(with: .calendarConnection), animated: true)
49 | }
50 |
51 | @objc func locationConnectionViewTapped(gestureRecognizer: UIGestureRecognizer) {
52 | navigationController?.pushViewController(ConnectionViewController.instantiate(with: .locationConnection), animated: true)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BGTaskSchedulerPermittedIdentifiers
6 |
7 | com.ifttt.ifttt.synchronization_scheduler
8 |
9 | CFBundleDevelopmentRegion
10 | $(DEVELOPMENT_LANGUAGE)
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | Grocery Express
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleTypeRole
27 | Viewer
28 | CFBundleURLName
29 | com.ifttt.sdk.example
30 | CFBundleURLSchemes
31 |
32 | groceryexpress
33 |
34 |
35 |
36 | CFBundleVersion
37 | $(CURRENT_PROJECT_VERSION)
38 | LSApplicationQueriesSchemes
39 |
40 | ifttt-handoff-v1
41 | message
42 | googlegmail
43 | ms-outlook
44 | ymail
45 | airmail
46 | readdle-spark
47 |
48 | LSRequiresIPhoneOS
49 |
50 | NSLocationAlwaysAndWhenInUseUsageDescription
51 | Grocery Express needs your location to be able to update your Connections.
52 | NSLocationWhenInUseUsageDescription
53 | Grocery Express needs your location to be able to update your Connections.
54 | NSLocationAlwaysUsageDescription
55 | Grocery Express needs your location to be able to update your Connections.
56 | UIBackgroundModes
57 |
58 | location
59 | processing
60 |
61 | UILaunchStoryboardName
62 | LaunchScreen
63 | UIMainStoryboardFile
64 | Main
65 | UIRequiredDeviceCapabilities
66 |
67 | armv7
68 |
69 | UISupportedInterfaceOrientations
70 |
71 | UIInterfaceOrientationPortrait
72 |
73 | UISupportedInterfaceOrientations~ipad
74 |
75 | UIInterfaceOrientationPortrait
76 | UIInterfaceOrientationPortraitUpsideDown
77 | UIInterfaceOrientationLandscapeLeft
78 | UIInterfaceOrientationLandscapeRight
79 |
80 | ITSAppUsesNonExemptEncryption
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/Settings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Settings.swift
3 | // SDK Example
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import IFTTTConnectSDK
10 |
11 | /// Access current app settings
12 | struct Settings {
13 |
14 | /// The current grocery express user email
15 | var email: String
16 |
17 | /// Signals that we should always use the new user connection flow
18 | var forcesNewUserFlow: Bool
19 |
20 | /// Signals that we should always use the connection fetching flow
21 | var fetchConnectionFlow: Bool
22 |
23 | /// Signals that we want to skip the connection configuration in the IFTTT app or the web flow.
24 | var skipConnectionConfiguration: Bool
25 |
26 | /// Determines whether or not geofences are enabled for the location connection
27 | var geofenceEnabled: Bool
28 |
29 | /// The overriding locale to use in updating the Connect Button flow with
30 | var locale: Locale
31 |
32 | /// Gets the current settings
33 | init() {
34 | let defaults = UserDefaults.standard
35 |
36 | let localeId: String
37 | let defaultLocaleId = Locale.current.identifier
38 |
39 | if let settings = defaults.dictionary(forKey: Keys.settings) {
40 | email = settings[Keys.email] as? String ?? ""
41 | forcesNewUserFlow = settings[Keys.forcesNewUserFlow] as? Bool ?? false
42 | fetchConnectionFlow = settings[Keys.fetchConnectionFlow] as? Bool ?? false
43 | skipConnectionConfiguration = settings[Keys.skipConnectionConfiguration] as? Bool ?? false
44 | geofenceEnabled = settings[Keys.geofencesEnabled] as? Bool ?? false
45 | localeId = settings[Keys.localeIdentifier] as? String ?? defaultLocaleId
46 | } else {
47 | email = ""
48 | forcesNewUserFlow = false
49 | fetchConnectionFlow = false
50 | skipConnectionConfiguration = false
51 | geofenceEnabled = false
52 | localeId = defaultLocaleId
53 | }
54 | locale = Locale(identifier: localeId)
55 | }
56 |
57 | /// Saves these settings to disk
58 | func save() {
59 | let settings: [String : Any] = [
60 | Keys.email : email,
61 | Keys.forcesNewUserFlow : forcesNewUserFlow,
62 | Keys.fetchConnectionFlow : fetchConnectionFlow,
63 | Keys.skipConnectionConfiguration: skipConnectionConfiguration,
64 | Keys.geofencesEnabled: geofenceEnabled,
65 | Keys.localeIdentifier: locale.identifier
66 | ]
67 | UserDefaults.standard.set(settings, forKey: Keys.settings)
68 | }
69 |
70 | private struct Keys {
71 | static let settings = "settings"
72 | static let email = "email"
73 | static let forcesNewUserFlow = "forces_new_user_flow"
74 | static let fetchConnectionFlow = "fetch_connection_flow"
75 | static let skipConnectionConfiguration = "skip_connection_configuration"
76 | static let geofencesEnabled = "geofences_enabled"
77 | static let localeIdentifier = "locale_identifier"
78 | static let isDarkStyle = "is_dark_style"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/TokenRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenRequest.swift
3 | // Grocery Express
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Request the IFTTT service token for the current user
11 | /// If the user has already connected Grocery Token, this will grant the service token
12 | struct TokenRequest {
13 | private let credentials: ConnectionCredentials
14 | private let urlRequest: URLRequest
15 |
16 | /// Make the request
17 | ///
18 | /// - Parameter completion: Returns the updated credentials
19 | func start(_ completion: ((ConnectionCredentials) -> Void)? = nil) {
20 | let credentials = self.credentials
21 | let task = URLSession.shared.dataTask(with: urlRequest) { (data, _, _) in
22 | if let data = data,
23 | let response = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??),
24 | let token = response?["user_token"] as? String {
25 |
26 | credentials.loginUser(with: token)
27 | }
28 | DispatchQueue.main.async {
29 | completion?(credentials)
30 | }
31 | }
32 | task.resume()
33 | }
34 |
35 | /// Creates a `TokenRequest`
36 | ///
37 | /// - Parameter credentials: The credentials used to fetch the token
38 | init(credentials: ConnectionCredentials) {
39 | self.credentials = credentials
40 | self.urlRequest = TokenRequest.tokenURLRequest(for: credentials)
41 | }
42 |
43 | private static func tokenURLRequest(for credentials: ConnectionCredentials) -> URLRequest {
44 | var components = URLComponents(string: "https://grocery-express.ifttt.com/api/user_token")!
45 | components.queryItems = [URLQueryItem(name: "code", value: credentials.oauthCode)]
46 |
47 | // For the Grocery Express service we use the email as the oauth code
48 | // We need to manually encode `+` characters in a user's e-mail because `+` is a valid character that represents a space in a url query. E-mail's with spaces are not valid.
49 | let percentEncodedQuery = components.percentEncodedQuery?.addingPercentEncoding(withAllowedCharacters: .emailEncodingPassthrough)
50 | components.percentEncodedQuery = percentEncodedQuery
51 |
52 | var request = URLRequest(url: components.url!)
53 | request.httpMethod = "POST"
54 |
55 | return request
56 | }
57 | }
58 |
59 | private extension CharacterSet {
60 |
61 | /// This allows '+' character to passthrough for sending an email address as a url parameter.
62 | static let emailEncodingPassthrough = CharacterSet(charactersIn: "+").inverted
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/Examples/GroceryExpress/UIView+Helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Helpers.swift
3 | // Grocery Express
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | extension UIView {
12 | func fillConstraint(view: UIView, attribute: NSLayoutConstraint.Attribute, constant: CGFloat = 0.0) -> NSLayoutConstraint {
13 | return .init(item: view,
14 | attribute: attribute,
15 | relatedBy: .equal,
16 | toItem: self,
17 | attribute: attribute,
18 | multiplier: 1.0,
19 | constant: constant)
20 | }
21 |
22 | func fillConstraints(view: UIView, constant: CGFloat = 0.0) -> [NSLayoutConstraint] {
23 | let attributes: [NSLayoutConstraint.Attribute] = [.leading, .top, .bottom, .trailing]
24 | return attributes.map {
25 | .init(item: view,
26 | attribute: $0,
27 | relatedBy: .equal,
28 | toItem: self,
29 | attribute: $0,
30 | multiplier: 1.0,
31 | constant: constant)
32 | }
33 | }
34 |
35 | private func sizeConstraint(attribute: NSLayoutConstraint.Attribute, constant: CGFloat) -> NSLayoutConstraint {
36 | return NSLayoutConstraint(item: self,
37 | attribute: attribute,
38 | relatedBy: .equal,
39 | toItem: nil,
40 | attribute: .notAnAttribute,
41 | multiplier: 1,
42 | constant: constant)
43 | }
44 |
45 | func sizeConstraints(size: CGSize) -> [NSLayoutConstraint] {
46 | return [
47 | sizeConstraint(attribute: .height, constant: size.height),
48 | sizeConstraint(attribute: .width, constant: size.width)
49 | ]
50 | }
51 | }
52 |
53 | extension UIStackView {
54 | /// Removes all arranged subviews of a UIStackView.
55 | func removeAllArrangedSubviews() {
56 | arrangedSubviews.forEach {
57 | removeArrangedSubview($0)
58 | $0.removeFromSuperview()
59 | }
60 |
61 | layoutIfNeeded()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/IFTTT SDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/IFTTT SDK.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/IFTTT SDK.xcodeproj/xcshareddata/xcschemes/IFTTTConnectSDK.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/IFTTT SDK/ASWebServiceAuthentication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASWebServiceAuthentication.swift
3 | // IFTTTConnectSDK
4 | //
5 | // Copyright © 2021 IFTTT. All rights reserved.
6 | //
7 |
8 | import AuthenticationServices
9 |
10 | /// Wraps an `ASWebAuthenticationSession`. Used to authenticate web services on iOS 12 and up.
11 | @available(iOS 12.0, *)
12 | final class ASWebServiceAuthentication: WebServiceAuthentication {
13 |
14 | /// The backing `ASWebAuthenticationSession`.
15 | private var session: ASWebAuthenticationSession?
16 |
17 | /// We must hold a reference to the session context provider so it's not deallocated.
18 | /// Only used with `ASWebAuthenticationSession` in iOS 13 and up.
19 | private var authenticationSessionContextPresentationProvider: AuthenticationSessionContextPresentationProvider?
20 |
21 | /// Creates an instance of ASWebServiceAuthentication.
22 | /// - Parameters
23 | /// - authenticationSessionContextProvider: An optional instance of `AuthenticationSessionContextPresentationProvider` used in configuring the web service authentication object. Optional for iOS 12 but required for iOS 13 and up.
24 | init(authenticationSessionContextPresentationProvider: AuthenticationSessionContextPresentationProvider?) {
25 | self.authenticationSessionContextPresentationProvider = authenticationSessionContextPresentationProvider
26 | }
27 |
28 | @discardableResult
29 | override func start(with parameters: Parameters, completionHandler: @escaping (Result) -> Void) -> Bool {
30 | let asWebAuthenticationSession = ASWebAuthenticationSession(url: parameters.url,
31 | callbackURLScheme: parameters.callbackURLScheme) { url, error in
32 | guard let url = url else {
33 | guard let error = error as? ASWebAuthenticationSessionError else {
34 | completionHandler(.failure(.unknown))
35 | return
36 | }
37 | switch error.code {
38 | case .canceledLogin:
39 | completionHandler(.failure(.userCanceled))
40 | default:
41 | completionHandler(.failure(.unknown))
42 | }
43 | return
44 | }
45 | completionHandler(.success(url))
46 | }
47 |
48 | if #available(iOS 13.0, *) {
49 | asWebAuthenticationSession.presentationContextProvider = authenticationSessionContextPresentationProvider
50 | asWebAuthenticationSession.prefersEphemeralWebBrowserSession = parameters.prefersEphemeralWebBrowserSession
51 | }
52 | self.session = asWebAuthenticationSession
53 | return asWebAuthenticationSession.start()
54 | }
55 |
56 | override func cancel() {
57 | session?.cancel()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/IFTTT SDK/Array+Helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Helpers.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import CoreLocation
10 |
11 | extension Array where Element: CLRegion {
12 | /// Determines the regions to return closest to the parameter coordinates. Only returns the top `count` locations.
13 | ///
14 | /// - Parameters:
15 | /// - coordinates: The coordinates of the location to use in determining the regions to be returned.
16 | /// - count: The amount of regions to return.
17 | /// - Returns: The `count` closest regions to the parameter location.
18 | func closestRegions(to coordinates: CLLocationCoordinate2D, count: Int) -> [CLRegion] {
19 | let location = CLLocation(latitude: coordinates.latitude,
20 | longitude: coordinates.longitude)
21 | let regionsClosestToLocation = compactMap { $0 as? CLCircularRegion }
22 | .sorted { (region1, region2) -> Bool in
23 | let region1Location = CLLocation(latitude: region1.center.latitude, longitude: region1.center.longitude)
24 | let region2Location = CLLocation(latitude: region2.center.latitude, longitude: region2.center.longitude)
25 | return location.distance(from: region1Location) < location.distance(from: region2Location)
26 | }
27 | let topClosest = regionsClosestToLocation.keepFirst(count)
28 | return topClosest
29 | }
30 | }
31 |
32 | extension Array {
33 | func keepFirst(_ n: Int) -> Array {
34 | let slice = prefix(n)
35 | return Array(slice)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/IFTTT SDK/Assets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Assets.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | struct Assets {
11 | struct Button {
12 | static let emailConfirm = UIImage.iftttAsset(named: "ifttt_email_confirm")
13 | }
14 | struct About {
15 | static let connectArrow = UIImage.iftttAsset(named: "ifttt_about_connect_arrow")
16 | static let connect = UIImage.iftttAsset(named: "ifttt_about_connect")
17 | static let control = UIImage.iftttAsset(named: "ifttt_about_control")
18 | static let security = UIImage.iftttAsset(named: "ifttt_about_security")
19 | static let manage = UIImage.iftttAsset(named: "ifttt_about_unplug")
20 | static let close = UIImage.iftttAsset(named: "ifttt_about_close")
21 | static let downloadOnAppStore = UIImage.iftttAsset(named: "ifttt_about_download_on_app_store")
22 | }
23 | }
24 |
25 | private extension UIImage {
26 | static func iftttAsset(named: String) -> UIImage? {
27 | return UIImage(named: named, in: Bundle.sdk, compatibleWith: nil)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthenticationSessionContextProvider.swift
3 | // IFTTTConnectSDK
4 | //
5 | // Copyright © 2021 IFTTT. All rights reserved.
6 | //
7 |
8 | import AuthenticationServices
9 |
10 | /// A class that conforms to `ASWebAuthenticationPresentationContextProviding`.
11 | class AuthenticationSessionContextPresentationProvider: NSObject, ASWebAuthenticationPresentationContextProviding, ASAuthorizationControllerPresentationContextProviding {
12 | /// The window context that the presentation of the authentication should take place in.
13 | private let presentationContext: UIWindow
14 |
15 | /// Creates an instance of `AuthenticationSessionContextProvider`.
16 | ///
17 | /// - Parameters:
18 | /// - presentationContext: The `UIWindow` instance to use in conforming to `ASWebAuthenticationPresentationContextProviding`.
19 | init(presentationContext: UIWindow) {
20 | self.presentationContext = presentationContext
21 | super.init()
22 | }
23 |
24 | @available(iOS 12.0, *)
25 | func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
26 | return presentationContext
27 | }
28 |
29 | @available(iOS 13.0, *)
30 | func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
31 | return presentationContext
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/IFTTT SDK/Bundle+Helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Helpers.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Bundle {
11 | private static let ResourceName = "IFTTTConnectSDK"
12 | private static let LocalizedStringResourceName = "IFTTTConnectSDK-Localizations"
13 | private static let BundleExtensionName = "bundle"
14 |
15 | /**
16 | Defines the bundle for the SDK. The bundle for the app could be different from the bundle for the SDK which means that we might need to use the SDK bundle to get assets and other information.
17 | */
18 | static var sdk: Bundle {
19 | #if SWIFT_PACKAGE
20 | let connectButtonBundle = Bundle.module
21 | #else
22 | let connectButtonBundle = Bundle(for: ConnectButton.self)
23 | #endif
24 |
25 | guard let urlForBundle = connectButtonBundle.url(forResource: ResourceName, withExtension: BundleExtensionName),
26 | let bundle = Bundle(url: urlForBundle) else {
27 | // If we're unable to generate the bundle indicated by `ResourceName`, fall back to returning the bundle for the `ConnectButton` instead.
28 | return connectButtonBundle
29 | }
30 |
31 | return bundle
32 | }
33 |
34 | /**
35 | Defines the bundle for the localized strings for the SDK. The bundle for the app could be different from the bundle for the SDK which means that we might need to use the localized string bundle to get assets and other information.
36 | */
37 | static var localizedStrings: Bundle {
38 | #if SWIFT_PACKAGE
39 | let connectButtonBundle = Bundle.module
40 | #else
41 | let connectButtonBundle = Bundle(for: ConnectButton.self)
42 | #endif
43 |
44 | guard let urlForBundle = connectButtonBundle.url(forResource: LocalizedStringResourceName, withExtension: BundleExtensionName),
45 | let bundle = Bundle(url: urlForBundle) else {
46 | // If we're unable to generate the bundle indicated by `ResourceName`, fall back to returning the bundle for the `ConnectButton` instead.
47 | return connectButtonBundle
48 | }
49 |
50 | return bundle
51 | }
52 |
53 | /// Determines whether or not background location is enabled as a capability in the app's info dictionary.
54 | var backgroundLocationEnabled: Bool {
55 | guard let backgroundModes = infoDictionary?["UIBackgroundModes"] as? [String] else { return false }
56 | return backgroundModes.contains("location")
57 | }
58 |
59 | /// Determines whether or not background processing is enabled as a capability in the app's info dictionary.
60 | var backgroundProcessingEnabled: Bool {
61 | guard let backgroundModes = infoDictionary?["UIBackgroundModes"] as? [String] else { return false }
62 | return backgroundModes.contains("processing")
63 | }
64 |
65 | /// Determines whether or not the target's info plist contains the IFTTT background processing task identifier
66 | var containsIFTTTBackgroundProcessingIdentifier: Bool {
67 | guard let backgroundTaskIdentifiers = infoDictionary?["BGTaskSchedulerPermittedIdentifiers"] as? [String] else { return false}
68 |
69 | return backgroundTaskIdentifiers.contains(SynchronizationScheduler.BackgroundProcessIdentifier)
70 | }
71 |
72 | var appName: String? {
73 | return object(forInfoDictionaryKey: "CFBundleName") as? String
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/IFTTT SDK/CLCircularRegion+Parsing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLCircularRegion+Parsing.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import CoreLocation
10 |
11 | struct Constants {
12 | static let IFTTTRegionPrefix = "ifttt"
13 | }
14 |
15 | extension CLRegion {
16 | /// Determines whether or not the region is an region associated with an IFTTT connection.
17 | var isIFTTTRegion: Bool {
18 | return identifier.lowercased().starts(with: Constants.IFTTTRegionPrefix)
19 | }
20 | }
21 |
22 | extension CLCircularRegion {
23 | private static let locationManager = CLLocationManager()
24 |
25 | func toUserDefaultsJSON() -> JSON {
26 | return [
27 | "latitude": center.latitude,
28 | "longitude": center.longitude,
29 | "radius": radius,
30 | "identifier": identifier
31 | ]
32 | }
33 |
34 | convenience init?(parser: Parser) {
35 | guard let latitude = parser["latitude"].double,
36 | let longitude = parser["longitude"].double,
37 | let radius = parser["radius"].double,
38 | let identifier = parser["identifier"].string else {
39 | return nil
40 | }
41 |
42 | let center = CLLocationCoordinate2D(latitude: latitude as CLLocationDegrees,
43 | longitude: longitude as CLLocationDegrees)
44 |
45 | self.init(center: center,
46 | radius: radius as CLLocationDistance,
47 | identifier: identifier)
48 | }
49 |
50 | convenience init?(defaultFieldParser: Parser, triggerId: String) {
51 | let locationParser = defaultFieldParser["default_value"]
52 | self.init(parser: locationParser, triggerId: triggerId)
53 | }
54 |
55 | convenience init?(parser: Parser, triggerId: String) {
56 | guard let latitude = parser["lat"].double,
57 | let longitude = parser["lng"].double,
58 | var radius = parser["radius"].double else {
59 | return nil
60 | }
61 |
62 | radius = min(radius, CLCircularRegion.locationManager.maximumRegionMonitoringDistance)
63 |
64 | let center = CLLocationCoordinate2D(latitude: latitude as CLLocationDegrees, longitude: longitude as CLLocationDegrees)
65 | let identifier = triggerId.addIFTTTPrefix()
66 |
67 | self.init(center: center,
68 | radius: radius as CLLocationDistance,
69 | identifier: identifier)
70 | }
71 |
72 | convenience init?(json: JSON, triggerId: String) {
73 | let parser = Parser(content: json)
74 | let locationParser = parser["value"]
75 | self.init(parser: locationParser, triggerId: triggerId)
76 | }
77 | }
78 |
79 | extension String {
80 | func addIFTTTPrefix() -> String {
81 | return "\(Constants.IFTTTRegionPrefix)_\(self)"
82 | }
83 |
84 | func stripIFTTTPrefix() -> String {
85 | let splitString = split(separator: "_")
86 | if splitString.count == 2 && splitString.first?.lowercased() == "ifttt" {
87 | return String(splitString[1])
88 | } else {
89 | return self
90 | }
91 | }
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectButton+Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectButton+Constants.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | public typealias VoidClosure = () -> Void
11 |
12 | extension ConnectButton {
13 |
14 | // Layout constants
15 | struct Layout {
16 | static let height: CGFloat = 72
17 | static let maximumWidth = 5.0 * height
18 | static let knobInset: CGFloat = borderWidth
19 | static let knobDiameter = height - 2 * knobInset
20 | static let checkmarkDiameter: CGFloat = 42
21 | static let checkmarkLength: CGFloat = 14
22 | static let serviceIconDiameter = 0.5 * knobDiameter
23 |
24 | /// The thickness of the border around the the connect button.
25 | static let borderWidth: CGFloat = 4
26 |
27 | /// The amount by which the email field is offset from the center
28 | static let emailFieldOffset: CGFloat = 4
29 | static let buttonFooterSpacing: CGFloat = 15
30 | }
31 |
32 | struct Color {
33 | static let blue = UIColor(hex: 0x0099FF)
34 | static let lightGrey = UIColor(hex: 0xCCCCCC)
35 | static let mediumGrey = UIColor(hex: 0x666666)
36 | static let grey = UIColor(hex: 0x414141)
37 | static let border = UIColor(white: 1, alpha: 0.32)
38 | static let almostBlack = UIColor(hex: 0x222222)
39 |
40 | static func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
41 | if #available(iOS 13, *) {
42 | return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
43 | if UITraitCollection.userInterfaceStyle == .dark {
44 | return dark
45 | } else {
46 | return light
47 | }
48 | }
49 | } else {
50 | return light
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectButton+LabelValue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectButton+LabelValue.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension ConnectButton {
11 |
12 | /// Wraps various ways a `UILabel`'s text can be set.
13 | enum LabelValue: Equatable {
14 |
15 | /// The label has no text.
16 | case none
17 |
18 | /// The text of the label.
19 | case text(String)
20 |
21 | /// The attributed text of the label.
22 | case attributed(NSAttributedString)
23 |
24 | /// The text color of the label.
25 | case textColor(UIColor)
26 |
27 | /// Updates the label with the value of the enum.
28 | ///
29 | /// - Parameter label: The label to update the text on.
30 | func update(label: UILabel) {
31 | switch self {
32 | case .none:
33 | label.text = nil
34 | label.attributedText = nil
35 | case .text(let text):
36 | label.text = text
37 | case .attributed(let text):
38 | label.attributedText = text
39 | case .textColor(let color):
40 | label.textColor = color
41 | }
42 | }
43 |
44 | /// Whether there is text provided.
45 | var isEmpty: Bool {
46 | if case .none = self {
47 | return true
48 | }
49 | return false
50 | }
51 |
52 | static func ==(lhs: LabelValue, rhs: LabelValue) -> Bool {
53 | switch (lhs, rhs) {
54 | case (.none, .none):
55 | return true
56 | case (.text(let lhs), .text(let rhs)):
57 | return lhs == rhs
58 | case (.attributed(let lhs), .attributed(let rhs)):
59 | return lhs == rhs
60 | case (.textColor(let lhs), .textColor(let rhs)):
61 | return lhs == rhs
62 | default:
63 | return false
64 | }
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectButton+ProgressBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectButton+ProgressBar.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension ConnectButton {
11 |
12 | /// A `PassthroughView` subclass that represents the progress bar of the connect button.
13 | final class ProgressBar: PassthroughView {
14 |
15 | /// The value of how much of the progress bar has completed.
16 | var fractionComplete: CGFloat = 0 {
17 | didSet {
18 | update()
19 | }
20 | }
21 |
22 | private let track = UIView()
23 | private let bar = PassthroughView()
24 |
25 | private var defaultColor: UIColor {
26 | return Color.dynamicColor(
27 | light: Color.grey,
28 | dark: Color.lightGrey
29 | )
30 | }
31 |
32 | /// Configures the progress bar background with the optionally provided `Service`.
33 | ///
34 | /// - Parameter service: An optional `Service` to set the backgrund color to.
35 | func configure(with service: Service?) {
36 | bar.backgroundColor = service?.brandColor.contrasting() ?? defaultColor
37 | }
38 |
39 | private func update() {
40 | bar.transform = CGAffineTransform(translationX: (1 - fractionComplete) * -bounds.width, y: 0)
41 | track.layer.cornerRadius = 0.5 * bounds.height // Progress bar should match rounded corners of connect button
42 | }
43 |
44 | override func layoutSubviews() {
45 | super.layoutSubviews()
46 | update()
47 | }
48 |
49 | /// Creates a `ProgressBar`.
50 | init() {
51 | super.init(frame: .zero)
52 |
53 | // The track ensures that the progress bar stays within its intended bounds
54 |
55 | track.clipsToBounds = true
56 |
57 | addSubview(track)
58 | track.constrain.edges(to: layoutMarginsGuide)
59 |
60 | track.addSubview(bar)
61 | bar.constrain.edges(to: track)
62 |
63 | layoutMargins = .zero
64 | }
65 |
66 | @available(*, unavailable)
67 | required init?(coder aDecoder: NSCoder) {
68 | fatalError("init(coder:) has not been implemented")
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectButton+Service.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectButton+Service.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension ConnectButton {
11 |
12 | /// Wraps various information for configuring the connect button based on the service it is connecting.
13 | struct Service {
14 |
15 | /// An optional icon url to use on the button.
16 | let iconURL: URL?
17 |
18 | /// The color associated with the service.
19 | let brandColor: UIColor
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectButton+Style.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectButton+Style.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension ConnectButton {
11 |
12 | /// Adjusts the button for a white or black background
13 | ///
14 | /// - light: Style the button for a white background
15 | public enum Style {
16 |
17 | /// Style the button for a white background
18 | case light
19 |
20 | /// Style the button for a dark background
21 | case dark
22 |
23 | /// Style the button for with dynamic colors:
24 | /// When the user device is on light mode, the style used is equivalent to `light`.
25 | /// When on dark mode, the style is equivalent to `dark`.
26 | ///
27 | /// On iOS versions before 13.0, this style is the same as `light`.
28 | case dynamic
29 |
30 | struct Font {
31 | static let connect = UIFont(name: "AvenirNext-Bold", size: 22)!
32 | static let email = UIFont(name: "AvenirNext-DemiBold", size: 18)!
33 | }
34 |
35 | /// The color to use for the footer based on the style.
36 | var footerColor: UIColor {
37 | return UIColor(hex: 0x999999)
38 | }
39 |
40 | /// The color to use for the button background based on the style
41 | var buttonBackgroundColor: UIColor {
42 | return colors(light: Color.almostBlack, dark: .white)
43 | }
44 |
45 | /// The color to use for the button text based on the style
46 | var textColor: UIColor {
47 | return colors(light: .white, dark: .black)
48 | }
49 |
50 | private func colors(light: UIColor, dark: UIColor) -> UIColor {
51 | switch self {
52 | case .light:
53 | return light
54 | case .dark:
55 | return dark
56 | case .dynamic:
57 | return Color.dynamicColor(light: light, dark: dark)
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectButton+Transition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectButton+Transition.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension ConnectButton {
11 |
12 | /// Groups button State and footer value into a single state transition
13 | struct Transition {
14 |
15 | /// An optional `AnimationState` to transition to.
16 | let state: AnimationState?
17 |
18 | /// An optional `LabelValue` to update the footer to.
19 | let footerValue: LabelValue?
20 |
21 | /// How long the transition should take.
22 | let duration: TimeInterval
23 |
24 | /// Creates a `Transition` for the connect button.
25 | ///
26 | /// - Parameters:
27 | /// - state: An `AnimationState` to transition the connect button to.
28 | /// - duration: How long the transition should take. Defaults to 0.4 seconds.
29 | /// - Returns: A configured `Transition`.
30 | static func buttonState(_ state: AnimationState, duration: TimeInterval = 0.4) -> Transition {
31 | return Transition(state: state, footerValue: nil, duration: duration)
32 | }
33 |
34 | /// Creates a `Transition` for the connect button.
35 | ///
36 | /// - Parameters:
37 | /// - state: An `AnimationState` to transition the connect button to.
38 | /// - footerValue: A `LabelValue` to update the footer to.
39 | /// - duration: How long the transition should take. Defaults to 0.4 seconds.
40 | /// - Returns: A configured `Transition`.
41 | static func buttonState(_ state: AnimationState, footerValue: LabelValue, duration: TimeInterval = 0.4) -> Transition {
42 | return Transition(state: state, footerValue: footerValue, duration: duration)
43 | }
44 |
45 | /// Creates a footer `Transition` for the connect button.
46 | ///
47 | /// - Parameters:
48 | /// - value: A `LabelValue` to update the footer to.
49 | /// - duration: How long the transition should take. Defaults to 0.4 seconds.
50 | /// - Returns: A configured `Transition`.
51 | static func footerValue(_ value: LabelValue, duration: TimeInterval = 0.4) -> Transition {
52 | return Transition(state: nil, footerValue: value, duration: duration)
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectButtonController+Logging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectButtonController+Logging.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension ConnectButtonController {
11 | static func log(handler: ((String) -> Void)?,
12 | isEnabled: Bool,
13 | event: String,
14 | domain: String) {
15 | guard isEnabled else { return }
16 | if let handler = handler {
17 | handler(event)
18 | } else {
19 | NSLog("[ConnectSDK/\(domain)] \(event)")
20 | }
21 | }
22 |
23 | /// Logs localization events to the console only if `localizationLoggingEnabled` is `true`.
24 | ///
25 | /// - Parameters:
26 | /// - event: A string corresponding to the event to log.
27 | static func localizationLog(_ event: String) {
28 | log(handler: localizationLoggingHandler,
29 | isEnabled: localizationLoggingEnabled,
30 | event: event,
31 | domain: "Localization")
32 | }
33 |
34 | /// Logs synchronization events to the console only if `synchronizationLoggingEnabled` is `true`.
35 | ///
36 | /// - Parameters:
37 | /// - event: A string corresponding to the event to log.
38 | static func synchronizationLog(_ event: String) {
39 | log(handler: synchnronizationLoggingHandler,
40 | isEnabled: synchronizationLoggingEnabled,
41 | event: event,
42 | domain: "Synchronization")
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/IFTTT SDK/Connection+Location.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Connection+Location.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import CoreLocation
10 |
11 | extension Connection.ConnectionStorage {
12 | var hasLocationTriggers: Bool {
13 | let closure: (Trigger) -> Bool = {
14 | switch $0 {
15 | case .location:
16 | return true
17 | }
18 | }
19 |
20 | return allTriggers.contains(where: closure) || activeUserTriggers.contains(where: closure)
21 | }
22 | var locationRegions: [CLCircularRegion] {
23 | return activeUserTriggers.map { (trigger) -> CLCircularRegion in
24 | switch trigger {
25 | case .location(let region):
26 | return region
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/IFTTT SDK/Connection+Request.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Connection+Request.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Connection {
11 |
12 | /// Handles network requests related to the `Connection`.
13 | struct Request {
14 |
15 | /// The HTTP request method options.
16 | enum Method: String {
17 |
18 | /// The HTTP GET method.
19 | case GET = "GET"
20 |
21 | /// The HTTP POST method.
22 | case POST = "POST"
23 | }
24 |
25 | /// The `Request`'s `URLRequest` that task are completed on.
26 | public let urlRequest: URLRequest
27 |
28 | /// A `Request` configured to get a `Connection` with the provided identifier.
29 | ///
30 | /// - Parameters:
31 | /// - id: The identifier of the `Connection`.
32 | /// - credentialProvider: An object that handle providing credentials for a request.
33 | /// - Returns: A `Request` configured to get the `Connection`.
34 | public static func fetchConnection(for id: String, credentialProvider: ConnectionCredentialProvider) -> Request {
35 | return Request(path: "/connections/\(id)", method: .GET, credentialProvider: credentialProvider)
36 | }
37 |
38 | /// A disconnection `Request` for a `Connection` with the provided identifier.
39 | ///
40 | /// - Parameters:
41 | /// - id: The identifier of the `Connection`.
42 | /// - credentialProvider: An object that handle providing credentials for a request.
43 | /// - Returns: A `Request` configured to disconnect the `Connection`.
44 | public static func disconnectConnection(with id: String, credentialProvider: ConnectionCredentialProvider) -> Request {
45 | return Request(path: "/connections/\(id)/disable", method: .POST, credentialProvider: credentialProvider)
46 | }
47 |
48 | private init(path: String, method: Method, credentialProvider: ConnectionCredentialProvider) {
49 | let url = API.base.appendingPathComponent(path)
50 |
51 | var request = URLRequest(url: url)
52 | request.httpMethod = method.rawValue
53 |
54 | if let userToken = credentialProvider.userToken, userToken.isEmpty == false {
55 | request.addIftttServiceToken(userToken)
56 | }
57 |
58 | if let inviteCode = credentialProvider.inviteCode, inviteCode.isEmpty == false {
59 | request.addIftttInviteCode(inviteCode)
60 | }
61 | request.addVersionTracking()
62 |
63 | self.urlRequest = request
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/IFTTT SDK/Connection+Storage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Connection+Storage.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Connection {
11 | enum NativeServiceDescription: String, CaseIterable {
12 | case location
13 | }
14 |
15 | struct ConnectionStorage: Hashable {
16 | let id: String
17 | var status: Status
18 | let activeUserTriggers: Set
19 | let allTriggers: Set
20 |
21 | var enabledNativeServiceMap: [NativeServiceDescription: Bool]
22 |
23 | init(id: String,
24 | status: Status,
25 | activeUserTriggers: Set,
26 | allTriggers: Set,
27 | enabledNativeServiceMap: [NativeServiceDescription: Bool]) {
28 | self.id = id
29 | self.status = status
30 | self.activeUserTriggers = activeUserTriggers
31 | self.allTriggers = allTriggers
32 | self.enabledNativeServiceMap = enabledNativeServiceMap
33 | }
34 |
35 | init(id: String,
36 | status: Status,
37 | activeUserTriggers: Set,
38 | allTriggers: Set) {
39 | self.id = id
40 | self.status = status
41 | self.activeUserTriggers = activeUserTriggers
42 | self.allTriggers = allTriggers
43 | self.enabledNativeServiceMap = NativeServiceDescription.allCases.reduce(into: [:], { (dict, description) in
44 | dict[description] = true
45 | })
46 | }
47 |
48 | func hash(into hasher: inout Hasher) {
49 | hasher.combine(id)
50 | }
51 |
52 | static func == (lhs: Connection.ConnectionStorage, rhs: Connection.ConnectionStorage) -> Bool {
53 | return lhs.id == rhs.id
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectionConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectionConfiguration.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Encapsulates the information needed to authenticate a `Connection`'s services.
11 | public struct ConnectionConfiguration {
12 |
13 | /// The identifier for the `Connection`.
14 | public let connectionId: String
15 |
16 | /// The `Connection` for authentication.
17 | public let connection: Connection?
18 |
19 | /// A `String` provided as the suggested user's email address.
20 | public let suggestedUserEmail: String
21 |
22 | /// A `CredentialProvider` conforming object for providing credentials.
23 | public let credentialProvider: ConnectionCredentialProvider
24 |
25 | /// The `URL` that is used for authentication redirects.
26 | public let redirectURL: URL
27 |
28 | /// An flag that allows users to skip configuration of the connection when it's activated either in the IFTTT app or the web flow
29 | public let skipConnectionConfiguration: Bool
30 |
31 | /// Creates a new `ConnectionConfiguration`.
32 | ///
33 | /// - Parameters:
34 | /// - connectionId: The connection identifier to fetch the `Connection` for authentication.
35 | /// - suggestedUserEmail: A `String` with a an email for the user.
36 | /// - credentialProvider: A `CredentialProvider` conforming object for providing credentials.
37 | /// - redirectURL: The `URL` that is used for connection activation redirects.
38 | /// - skipConnectionConfiguration: A `Bool` that is used to skip the configuration of the Connection when it's activated in either the IFTTT app or the web flow. Defaults to `false`.
39 | public init(connectionId: String,
40 | suggestedUserEmail: String,
41 | credentialProvider: ConnectionCredentialProvider,
42 | redirectURL: URL,
43 | skipConnectionConfiguration: Bool = false) {
44 | self.connectionId = connectionId
45 | self.connection = nil
46 | self.suggestedUserEmail = suggestedUserEmail
47 | self.credentialProvider = credentialProvider
48 | self.redirectURL = redirectURL
49 | self.skipConnectionConfiguration = skipConnectionConfiguration
50 | }
51 |
52 | /// Creates a new `ConnectionConfiguration`.
53 | ///
54 | /// - Parameters:
55 | /// - connection: The `Connection` for authentication.
56 | /// - suggestedUserEmail: A `String` with a an email for the user.
57 | /// - credentialProvider: A `CredentialProvider` conforming object for providing credentials.
58 | /// - redirectURL: The `URL` that is used for connection activation redirects.
59 | /// - skipConnectionConfiguration: A `Bool` that is used to skip the configuration of the Connection when it's activated in either the IFTTT app or the web flow. Defaults to `false`.
60 | public init(connection: Connection,
61 | suggestedUserEmail: String,
62 | credentialProvider: ConnectionCredentialProvider,
63 | redirectURL: URL,
64 | skipConnectionConfiguration: Bool = false) {
65 | self.connectionId = connection.id
66 | self.connection = connection
67 | self.suggestedUserEmail = suggestedUserEmail
68 | self.credentialProvider = credentialProvider
69 | self.redirectURL = redirectURL
70 | self.skipConnectionConfiguration = skipConnectionConfiguration
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectionCredentialProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectionCredentialProvider.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A protocol that defines APIs for providing credentials used during the service authentication process for an `Connection`.
11 | public protocol ConnectionCredentialProvider {
12 |
13 | /// The OAuth code for your user on your service. This is used to skip a step for connecting to your own service during the Connect Button activation flow. We require this value to provide the best possible user experience.
14 | var oauthCode: String { get }
15 |
16 | /// This is the IFTTT user token for your service. This token allows you to get IFTTT user data related to only your service. For example, include this token to get the enabled status of Connections for your user. It is also the same token that is used to make trigger, query, and action requests for Connections on behave of the user. You should get this token from a communication between your servers and ours using your `IFTTT-Service-Key`. Never include this key in your app binary, rather create on endpoint on your own server to access the user's IFTTT service token.
17 | /// Additionally, the callback from ConnectButtonController returns the user token when a connection is made.
18 | /// You should support both method, in the case that your user has already connected your service to IFTTT.
19 | var userToken: String? { get }
20 |
21 | /// This value is only required if your service is not published. You can find it on https://platform.ifttt.com on the Service tab in General under invite URL. If your service is published, return nil.
22 | var inviteCode: String? { get }
23 | }
24 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectionDeeplinkAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectionDeeplinkAction.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | enum ConnectionDeeplinkAction: String {
11 | case view = "view"
12 | case edit = "edit"
13 | case activation = "activation"
14 |
15 | static var isIftttAppAvailable: Bool {
16 | return UIApplication.shared.canOpenURL(URL(string: API.iftttAppScheme)!)
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectionNetworkError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectionNetworkError.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// An error occurred, preventing the network controller from completing network requests.
11 | public enum NetworkControllerError: Error {
12 |
13 | /// The total retry attempts were exhausted.
14 | case exhaustedRetryAttempts
15 |
16 | /// We got back some invalid response that can't be dealt with
17 | case invalidResponse
18 |
19 | /// Response parameters did not match what we expected. This should never happen. Verify that the latest SDK is being used.
20 | case unknownResponse
21 |
22 | /// The network request got cancelled. This could happen by nil'ing out a network request or by explicitly canceling a network request.
23 | case cancelled
24 |
25 | /// The network request resulted in a authentication error
26 | case authenticationFailure
27 |
28 | /// Could not decode image data.
29 | case invalidImageData
30 |
31 | /// Some generic networking error occurred
32 | case genericError(Error)
33 |
34 | }
35 |
36 | /// An error occurred, preventing the network controller from completing `Connection` network requests.
37 | public enum ConnectionNetworkError: Error {
38 |
39 | /// Some generic networking error occurred
40 | case genericError(Error)
41 |
42 | /// Response parameters did not match what we expected. This should never happen. Verify you are using the latest SDK.
43 | case unknownResponse
44 |
45 | init(networkControllerError: NetworkControllerError) {
46 | switch networkControllerError {
47 | case .genericError(let error):
48 | self = .genericError(error)
49 | default:
50 | self = .unknownResponse
51 | }
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/IFTTT SDK/ConnectionRedirectHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectionRedirectHandler.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /// A class to handle redirections of `URL`s recieved as a part of the `Connection` activation process.
11 | public final class ConnectionRedirectHandler {
12 |
13 | private let redirectURL: URL
14 |
15 | /// An `AuthenticationRedirectHandler` configured to handle a `URL`.
16 | ///
17 | /// - Parameter redirectURL: A `URL` that is used as the redirect sent on `Connection` activation.
18 | public init(redirectURL: URL) {
19 | self.redirectURL = redirectURL
20 | }
21 |
22 | /// Handles redirects during a `Connection` activation.
23 | ///
24 | /// Generally, this is used to handle url redirects the app recieves in `func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool` in the `AppDelgate`.
25 | /// - Example: `AuthenticationRedirectHandler.handleApplicationRedirect(url: url, options: options)`.
26 | ///
27 | /// - Parameters:
28 | /// - url: The `URL` resource to open.
29 | /// - options: A dictionary of `URL` handling options. For information about the possible keys in this dictionary, see UIApplicationOpenURLOptionsKey.
30 | /// - Returns: True if this is an IFTTT SDK redirect. False for any other `URL`.
31 | public func handleApplicationRedirect(url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
32 |
33 | // Checks if the scheme matches the SDK redirect.
34 | if url.scheme == redirectURL.scheme {
35 | NotificationCenter.default.post(name: .connectionRedirect, object: url)
36 | return true
37 | }
38 |
39 | return false
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/IFTTT SDK/EmailValidator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmailValidator.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Determines the validity of an email address.
11 | struct EmailValidator {
12 |
13 | /// Validates the parameter string to make sure it's a valid email address.
14 | ///
15 | /// - Parameters:
16 | /// - text: The email address to validate.
17 | /// - Returns: A boolean value that determines whether or not the parameter text is a valid email address or not.
18 | func validate(with text: String) -> Bool {
19 | // An empty string is not a valid email
20 | if text.isEmpty { return false }
21 |
22 | // Use NSDataDetector to determine whether or not the text is valid
23 | guard let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
24 | return false
25 | }
26 |
27 | // Remove any spaces in the text
28 | let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
29 | let range = NSMakeRange(0, NSString(string: trimmedText).length)
30 | let allMatches = dataDetector.matches(in: trimmedText,
31 | options: [],
32 | range: range)
33 |
34 | if allMatches.count == 1, allMatches.first?.url?.absoluteString.contains("mailto:") == true {
35 | return true
36 | }
37 | return false
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/IFTTT SDK/EventPublisher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventPublisher.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Defines an generic threadsafe publisher-subscriber construct allowing multiple subscribers to be
11 | /// notified of events from the publisher.
12 | final class EventPublisher {
13 | /// A typealias to refer to the closure that's executed on each subscriber.
14 | typealias SubscriberClosure = (Type) -> Void
15 |
16 | /// A typealias to refer to a subscriber.
17 | private typealias Subscriber = (TimeInterval, SubscriberClosure)
18 |
19 | /// The `DispatchQueue` used to access the subscriberMap on.
20 | private let subscriberMapDispatchQueue: DispatchQueue
21 |
22 | /// The `DispatchQueue` used to publish events on
23 | private let publisherDispatchQueue: DispatchQueue
24 |
25 | // Don't access this Dictionary directly outside of `addSubscriber(...)` or `removeSubscriber(...)`. Maps aren't threadsafe and therefore we can't use the subscripting directly.
26 | private var subscriberMap = [UUID: Subscriber]()
27 |
28 | /// Creates an instance of `EventPublisher`.
29 | ///
30 | /// - Parameters:
31 | /// - queue: The queue to use in notifying subscribers of any published events.
32 | init(subscriberMapDispatchQueue: DispatchQueue = DispatchQueue(label: "com.ifttt.subscriber_map.lock"),
33 | publisherDispatchQueue: DispatchQueue = DispatchQueue(label: "com.ifttt.publisher", attributes: .concurrent)) {
34 | self.subscriberMapDispatchQueue = subscriberMapDispatchQueue
35 | self.publisherDispatchQueue = publisherDispatchQueue
36 | }
37 |
38 | /// Publishes an event to subscribers. Subscribers get notified of events in descending order of time they were added.
39 | ///
40 | /// - Parameter:
41 | /// - object: The event that is to be delivered to subscribers.
42 | func onNext(_ object: Type) {
43 | subscriberMapDispatchQueue.sync { [weak self] in
44 | /// Sort the subscribers by time they were added. This means that the oldest subscriber gets notified of an event first.
45 | self?.subscriberMap.values.sorted { $0.0 < $1.0 }
46 | .map { $1 }
47 | .forEach { closure in
48 | self?.publisherDispatchQueue.async {
49 | closure(object)
50 | }
51 | }
52 | }
53 | }
54 |
55 | /// Adds a subscriber to be notified of events.
56 | ///
57 | /// - Parameters:
58 | /// - subscriber: A closure that gets called when there's a new published event.
59 | /// - Returns: A `UUID` token that can be used to remove a subscriber using `removeSubscriber`.
60 | @discardableResult
61 | func addSubscriber(_ subscriber: @escaping SubscriberClosure) -> UUID {
62 | return subscriberMapDispatchQueue.sync { [weak self] in
63 | let id = UUID()
64 | self?.subscriberMap[id] = (Date().timeIntervalSince1970, subscriber)
65 | return id
66 | }
67 | }
68 |
69 | /// Removes a subscriber from the subscriber map.
70 | ///
71 | /// - Parameters:
72 | /// - identifier: The token corresponding to the subscriber that is to be removed.
73 | func removeSubscriber(_ identifier: UUID) {
74 | subscriberMapDispatchQueue.sync { [weak self] in
75 | self?.subscriberMap[identifier] = nil
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/IFTTT SDK/IFTTT_SDK.h:
--------------------------------------------------------------------------------
1 | //
2 | // IFTTT_SDK.h
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | #import
9 |
10 | //! Project version number for IFTTT_SDK.
11 | FOUNDATION_EXPORT double IFTTT_SDKVersionNumber;
12 |
13 | //! Project version string for IFTTT_SDK.
14 | FOUNDATION_EXPORT const unsigned char IFTTT_SDKVersionString[];
15 |
16 | // All swift so nothing to see here ✌️
17 |
--------------------------------------------------------------------------------
/IFTTT SDK/ImageCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageCache.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | struct ImageCache {
11 |
12 | struct Capacity {
13 | private static let MB = 1024 * 1024 // One MegaByte
14 |
15 | static let inMemory = 4 * MB
16 | static let onDisk = 20 * MB
17 | }
18 |
19 | let urlCache: URLCache
20 |
21 | init(urlCache: URLCache = URLCache(memoryCapacity: Capacity.inMemory,
22 | diskCapacity: Capacity.onDisk,
23 | diskPath: nil)) {
24 | self.urlCache = urlCache
25 | }
26 |
27 | func store(imageData: Data, response: URLResponse, for url: URL) {
28 | let cachedResponse = CachedURLResponse(response: response, data: imageData)
29 | urlCache.storeCachedResponse(cachedResponse, for: URLRequest(url: url))
30 | }
31 |
32 | func image(for url: URL) -> UIImage? {
33 | guard let cachedResponse = urlCache.cachedResponse(for: URLRequest(url: url)) else {
34 | return nil
35 | }
36 | return UIImage(data: cachedResponse.data)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/IFTTT SDK/ImageDownloader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageDownloader.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | struct ImageDownloader {
11 |
12 | let urlSession: URLSession
13 |
14 | let cache: ImageCache
15 |
16 | init(urlSession: URLSession = URLSession(configuration: .default), cache: ImageCache = ImageCache()) {
17 | self.urlSession = urlSession
18 | self.cache = cache
19 | }
20 |
21 | @discardableResult
22 | func downloadImage(url: URL, _ completion: @escaping (Result) -> Void) -> URLSessionDataTask? {
23 | if let image = cache.image(for: url) {
24 | completion(.success(image))
25 | return nil
26 | }
27 | let task = urlSession.dataTask(with: url) { (imageData, response, error) in
28 | if let imageData = imageData, let response = response, let image = UIImage(data: imageData) {
29 | self.cache.store(imageData: imageData, response: response, for: url)
30 | DispatchQueue.main.async {
31 | completion(.success(image))
32 | }
33 | } else {
34 | DispatchQueue.main.async {
35 | if let networkError = error {
36 | completion(.failure(.genericError(networkError)))
37 | } else {
38 | completion(.failure(.invalidImageData))
39 | }
40 | }
41 | }
42 | }
43 | task.resume()
44 | return task
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/IFTTT SDK/ImageViewNetworkController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageViewNetworkController.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | protocol ImageViewNetworkController {
11 | func setImage(with url: URL?, for imageView: UIImageView)
12 | }
13 |
--------------------------------------------------------------------------------
/IFTTT SDK/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/IFTTT SDK/LegalTermsText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LegalText.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Factory for building `NSAttributedString` with links for Terms of Use and our Privacy Policy
11 | struct LegalTermsText {
12 |
13 | /// Creates a string with links to IFTTT's terms of service and privacy policy
14 | /// Prefix text is added before the links in the format "[prefix text] Terms_of_Use and Privacy_Policy"
15 | ///
16 | /// - Parameters:
17 | /// - prefix: The prefix before the links
18 | /// - activateLinks: Adds the link attribute, making the links active. Set to false if handling interaction in a custom maner. Default value is true.
19 | /// - Returns: The constructed `NSAttributedString`
20 | static func string(withPrefix prefix: String, activateLinks: Bool = true, attributes: [NSAttributedString.Key : Any]) -> NSAttributedString {
21 | let text = NSMutableAttributedString(string: prefix, attributes: attributes)
22 |
23 | text.addLink(text: "about.legal.link".localized,
24 | to: Links.privacyAndTerms,
25 | activateLinks: activateLinks,
26 | attributes: attributes)
27 |
28 | return NSAttributedString(attributedString: text)
29 | }
30 | }
31 |
32 | private extension NSMutableAttributedString {
33 |
34 | /// Adds a link to a url for a text snippet
35 | ///
36 | /// - Parameters:
37 | /// - text: The text that have a link added to.
38 | /// - activateLinks: Adds the link attribute to the string
39 | /// - url: The URL to link to
40 | func addLink(text: String, to url: URL, activateLinks: Bool, attributes: [NSAttributedString.Key : Any]) {
41 | let range = mutableString.range(of: text)
42 | assert(range.location != NSNotFound, "This should never happen but if it does, the `text` may include some special characters.")
43 | if range.location != NSNotFound {
44 | if activateLinks {
45 | addAttribute(.link, value: url.absoluteString, range: range)
46 | }
47 | addAttributes(attributes, range: range)
48 | addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/IFTTT SDK/Library.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Library.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | enum LibraryAccess: CustomDebugStringConvertible {
11 | case denied, restricted, notDetermined, authorized
12 |
13 | var canBeAuthorized: Bool {
14 | switch self {
15 | case .denied, .restricted: return false
16 | default: return true
17 | }
18 | }
19 |
20 | var debugDescription: String {
21 | switch self {
22 | case .authorized: return "Authorized"
23 | case .denied: return "Denied"
24 | case .notDetermined: return "Not determined"
25 | case .restricted: return "Restricted"
26 | }
27 | }
28 | }
29 |
30 | protocol Library {
31 | /// Maps the access authorization status of this native library to the `LibraryAccess` generalized type
32 | var access: LibraryAccess { get }
33 |
34 | /// Checks current access to this native library. If it's `notDetermined` then it requests access immediately.
35 | ///
36 | /// - Parameter completion: This will be called immediately if `access != .notDetermined` else it will prompt the user for access.
37 | func requestAccess(_ completion: @escaping (LibraryAccess) -> Void)
38 | }
39 |
--------------------------------------------------------------------------------
/IFTTT SDK/Links.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Links.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Defines links to IFTTT properties
11 | struct Links {
12 |
13 | /// The IFTTT privacy & terms of use page
14 | static let privacyAndTerms = URL(string: "https://ifttt.com/terms")!
15 | }
16 |
--------------------------------------------------------------------------------
/IFTTT SDK/LocalizedStrings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalizedStrings.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | func localized(with arguments: CVarArg ...) -> String {
12 | return String(format: localized, locale: nil, arguments: arguments)
13 | }
14 | var localized: String {
15 | // Use the ConnectButtonController's locale to change the string that's returned
16 | let locale = ConnectButtonController.locale
17 | let bundle = Bundle.localizedStrings
18 | let localeIdentifier = locale.identifier
19 |
20 | // String files are suffixed with the locale identifier. Try to grab the strings file and check to see if it exists.
21 | let table = "Localizable_\(localeIdentifier)"
22 |
23 | if let tablePath = bundle.path(forResource: table, ofType: "strings"),
24 | FileManager.default.fileExists(atPath: tablePath) {
25 | return NSLocalizedString(self,
26 | tableName: table,
27 | bundle: bundle,
28 | value: "",
29 | comment: "")
30 | }
31 | else if let languageCode = locale.languageCode,
32 | let languageTableFallbackPath = bundle.path(forResource: "Localizable_\(languageCode)", ofType: "strings"),
33 | FileManager.default.fileExists(atPath: languageTableFallbackPath) {
34 | ConnectButtonController.localizationLog("The key \(self) wasn't found in a strings file with name \(table) in the the bundle. Will try to fallback to Localizable_\(languageCode).strings file. Try reinstalling the SDK and then perform a clean/rebuild")
35 | return NSLocalizedString(self, tableName: "Localizable_\(languageCode)", bundle: bundle, value: "", comment: "")
36 | } else {
37 | ConnectButtonController.localizationLog("The key \(self) wasn't found in a strings file with name \(table) in the the bundle. Will try to fallback to Localizable.strings file. Try reinstalling the SDK and then perform a clean/rebuild")
38 | return NSLocalizedString(self, bundle: bundle, value: "", comment: "")
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/IFTTT SDK/LocationEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationEvent.swift
3 | // IFTTTConnectSDK
4 | //
5 | // Created by Siddharth Sathyam on 10/25/21.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Descibes a closure that is invoked whenever the SDK generates a `[LocationEvent]`
11 | public typealias LocationEventsClosure = ([LocationEvent]) -> Void
12 |
13 | /// Describes the kinds of region events that can be reported.
14 | public enum RegionEventKind: String {
15 | /// The user entered the region.
16 | case entry = "entry"
17 |
18 | /// The user exited the region.
19 | case exit = "exit"
20 | }
21 |
22 | /// Describes the reasons why an event was not uploaded.
23 | public enum EventUploadError: Error {
24 |
25 | /// The total number of events to be uploaded exceeds a sanity threshold.
26 | case crossedSanityThreshold
27 |
28 | /// A network error ocurred in uploading the event.
29 | case networkError
30 | }
31 |
32 | /// Describes all of the possible events in the Location monitoring flow.
33 | public enum LocationEvent: Equatable {
34 |
35 | /// The location event was recorded by the SDK.
36 | ///
37 | /// - Parameters:
38 | /// - `region`: The details of the region that was recorded.
39 | case reported(region: RegionEvent)
40 |
41 | /// The SDK attempted to upload a region event.
42 | ///
43 | /// - Parameters:
44 | /// - `region`: The details of the region that was attempted to be uploaded.
45 | /// - `delay`: The time in seconds between reporting the event and an attempted upload.
46 | case uploadAttempted(region: RegionEvent, delay: TimeInterval)
47 |
48 | /// The SDK successfully uploaded the region event.
49 | ///
50 | /// - Parameters:
51 | /// - `region`: The details of the region that was successfully uploaded.
52 | /// - `delay`: The time in seconds between reporting the event and a successful upload.
53 | case uploadSuccessful(region: RegionEvent, delay: TimeInterval)
54 |
55 | /// The SDK failed to upload the region event.
56 | ///
57 | /// - Parameters:
58 | /// - `region`: The details of the region that was successfully uploaded.
59 | /// - `error`: The `EventUploadError` that occurred.
60 | /// - `delay`: The time in seconds between attempting the event upload and error in completing the upload.
61 | case uploadFailed(region: RegionEvent, error: EventUploadError, delay: TimeInterval)
62 | }
63 |
--------------------------------------------------------------------------------
/IFTTT SDK/LocationLibrary.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationLibrarh.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import CoreLocation
10 |
11 | final class LocationLibrary: NSObject, Library, CLLocationManagerDelegate {
12 | var access: LibraryAccess {
13 | switch CLLocationManager.authorizationStatus() {
14 | case .authorizedAlways, .authorizedWhenInUse:
15 | return .authorized
16 | case .notDetermined:
17 | return .notDetermined
18 | case .restricted:
19 | return .restricted
20 | case .denied:
21 | return .denied
22 | @unknown default:
23 | return .notDetermined
24 | }
25 | }
26 |
27 | private let locationManager: CLLocationManager
28 | private var completion: ((LibraryAccess) -> Void)?
29 |
30 | init(locationManager: CLLocationManager) {
31 | self.locationManager = locationManager
32 | super.init()
33 | locationManager.delegate = self
34 | }
35 |
36 | func requestAccess(_ completion: @escaping (LibraryAccess) -> Void) {
37 | if access != .authorized {
38 | locationManager.requestAlwaysAuthorization()
39 | } else {
40 | completion(access)
41 | }
42 | }
43 |
44 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
45 | completion?(access)
46 | }
47 |
48 | deinit {
49 | locationManager.delegate = nil
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/IFTTT SDK/LocationMonitor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationMonitor.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import CoreLocation
10 |
11 | /// A core protocol to expose CoreLocation's location monitoring.
12 | protocol LocationMonitor {
13 | /// Starts the monitoring
14 | func startMonitor()
15 |
16 | /// Stops the monitoring
17 | func stopMonitor()
18 |
19 | /// Starts or stops the monitoring based on the user's location authorization status.
20 | /// By default, this method starts the monitor if the user has given Always authorization for location and stops it otherwise.
21 | ///
22 | /// - Parameters:
23 | /// - status: The user's permission level for location.
24 | func updateMonitoring(with status: CLAuthorizationStatus)
25 | }
26 |
--------------------------------------------------------------------------------
/IFTTT SDK/Notification+Redirect.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Notification.Name+Redirect.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Notification.Name {
11 |
12 | /// A `Notification.Name` used to post notifications when the app recieves a redirect request for a `Connection` activation.
13 | static let connectionRedirect = Notification.Name("ifttt.connection.redirect")
14 | }
15 |
--------------------------------------------------------------------------------
/IFTTT SDK/PassthroughView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PassthroughView.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class PassthroughView: UIView {
11 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
12 | let view = super.hitTest(point, with: event)
13 | return view == self ? nil : view
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/IFTTT SDK/PermissionsRequestor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PermissionsRequestor.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 | import CoreLocation
10 |
11 | /// Handles requesting device permissions
12 | final class PermissionsRequestor {
13 | var showPermissionsPrompts = true
14 |
15 | private let locationManager = CLLocationManager()
16 |
17 | private let queue: OperationQueue = {
18 | let queue = OperationQueue()
19 | queue.maxConcurrentOperationCount = 1
20 | return queue
21 | }()
22 |
23 | private let registry: ConnectionsRegistry
24 |
25 | init(registry: ConnectionsRegistry) {
26 | self.registry = registry
27 | }
28 |
29 | func processUpdate(with connections: Set) {
30 | if !canUpdate() { return }
31 |
32 | let operations = connections.reduce(.init()) { (currSet, connections) -> Set in
33 | return currSet.union(connections.allTriggers)
34 | }
35 | .compactMap { permission -> Library in
36 | switch permission {
37 | case .location:
38 | return LocationLibrary(locationManager: locationManager)
39 | }
40 | }.map { BlockOperation(library: $0) }
41 |
42 | queue.addOperations(operations, waitUntilFinished: false)
43 | }
44 |
45 | private func canUpdate() -> Bool {
46 | return !registry.getConnections().isEmpty
47 | && UserAuthenticatedRequestCredentialProvider.standard.userToken != nil
48 | && UIApplication.shared.applicationState == .active
49 | && showPermissionsPrompts
50 | }
51 | }
52 |
53 | private extension BlockOperation {
54 | convenience init(library: Library) {
55 | self.init {
56 | let semaphore = DispatchSemaphore(value: 0)
57 | DispatchQueue.main.async {
58 | library.requestAccess { _ in
59 | semaphore.signal()
60 | }
61 | }
62 | semaphore.wait()
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/IFTTT SDK/PillButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PillButton.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class PillButton: PillView {
11 |
12 | let label = UILabel()
13 |
14 | let imageView = UIImageView()
15 |
16 | func onSelect(_ body: @escaping VoidClosure) {
17 | assert(selectable == nil, "PillButton may have a single select handler")
18 | selectable = Selectable(self, onSelect: body)
19 | selectable?.performHighlight = { [weak self] _, isHighlighted in
20 | self?.label.alpha = isHighlighted ? 0.8 : 1
21 | self?.imageView.alpha = isHighlighted ? 0.8 : 1
22 | self?.isHighlighted = isHighlighted
23 | }
24 | }
25 |
26 | private var selectable: Selectable?
27 |
28 | override var intrinsicContentSize: CGSize {
29 | if imageView.image != nil {
30 | var size = imageView.intrinsicContentSize
31 | size.height += 20
32 | size.width += 20
33 | return size
34 | } else {
35 | return super.intrinsicContentSize
36 | }
37 | }
38 |
39 | init(_ image: UIImage?, _ configure: ((PillButton) -> Void)? = nil) {
40 | super.init()
41 |
42 | addSubview(imageView)
43 | imageView.constrain.center(in: self)
44 |
45 | imageView.image = image
46 |
47 | configure?(self)
48 | }
49 |
50 | init(_ text: String, _ configure: ((PillButton) -> Void)? = nil) {
51 | super.init()
52 |
53 | label.text = text
54 | label.textAlignment = .center
55 |
56 | layoutMargins = UIEdgeInsets(top: 12, left: 32, bottom: 12, right: 32)
57 | addSubview(label)
58 | label.constrain.edges(to: layoutMarginsGuide)
59 |
60 | configure?(self)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/IFTTT SDK/RegionEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RegionEvent.swift
3 | // IFTTTConnectSDK
4 | //
5 | // Copyright © 2021 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A record of a region entry/exit event that occurs in the SDK.
11 | public struct RegionEvent: Hashable {
12 | private struct Key {
13 | static let RecordId = "record_id"
14 | static let RegionType = "region_type"
15 | static let OccurredAt = "occurred_at"
16 | static let EventType = "event_type"
17 | static let ChannelId = "channel_id"
18 | static let TriggerSubscriptionId = "trigger_subscription_id"
19 | static let InstallationId = "installation_id"
20 | }
21 |
22 | private struct Constants {
23 | static let Geo = "geo"
24 | static let LocationServiceId = 941030000.description
25 | }
26 |
27 | /// The kind of event this is.
28 | public let kind: RegionEventKind
29 |
30 | /// The id of the event.
31 | let recordId: UUID
32 |
33 | /// The timestamp this event occurred at.
34 | let occurredAt: Date
35 |
36 | /// The trigger subscription id that this event corresponds to.
37 | let triggerSubscriptionId: String
38 |
39 | /// Creates an instance of `RegionEvent`.
40 | ///
41 | /// - Parameters:
42 | /// - recordId: The id of the recorded event.
43 | /// - kind: The kind of region event.
44 | /// - ocurredAt: The timestamp this event occurred at.
45 | /// - triggerSubscriptionId: The trigger subscription id that this event corresponds to.
46 | init(recordId: UUID = UUID(),
47 | kind: RegionEventKind,
48 | occurredAt: Date = Date(),
49 | triggerSubscriptionId: String) {
50 | self.recordId = recordId
51 | self.kind = kind
52 | self.occurredAt = occurredAt
53 | self.triggerSubscriptionId = triggerSubscriptionId
54 | }
55 |
56 | /// Creates an optional instance of `RegionEvent`.
57 | ///
58 | /// - Parameters:
59 | /// - json: The `JSON` that should be used in creating the `RegionEvent`
60 | init?(json: JSON) {
61 | let parser = Parser(content: json)
62 | guard let recordId = parser[Key.RecordId].uuid,
63 | let ocurredAtString = parser[Key.OccurredAt].string,
64 | let ocurredAtDate = APIDateFormatter.satellite.date(from: ocurredAtString),
65 | let eventType = parser[Key.EventType].representation(of: RegionEventKind.self),
66 | let triggerSubscriptionId = parser[Key.TriggerSubscriptionId].string else {
67 | return nil
68 | }
69 | self.recordId = recordId
70 | self.occurredAt = ocurredAtDate
71 | self.kind = eventType
72 | self.triggerSubscriptionId = triggerSubscriptionId
73 | }
74 |
75 | func toJSON(stripPrefix: Bool) -> JSON {
76 | return [
77 | Key.RecordId: recordId.uuidString,
78 | Key.RegionType: Constants.Geo,
79 | Key.OccurredAt: APIDateFormatter.satellite.string(from: occurredAt),
80 | Key.EventType: kind.rawValue,
81 | Key.ChannelId: Constants.LocationServiceId,
82 | Key.TriggerSubscriptionId: stripPrefix ? triggerSubscriptionId.stripIFTTTPrefix(): triggerSubscriptionId,
83 | Key.InstallationId: API.anonymousId
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/IFTTT SDK/RegionEventsRegistry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RegionEventsRegistry.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | struct APIDateFormatter {
11 | static let satellite: DateFormatter = {
12 | let f = DateFormatter()
13 | f.locale = Locale(identifier: "en_US_POSIX")
14 | f.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
15 | f.timeZone = TimeZone(secondsFromGMT: 0)
16 | return f
17 | }()
18 | }
19 |
20 | /// Stores region events information to be able to use in synchronizations.
21 | final class RegionEventsRegistry {
22 | /// Gets all the region events stored in the registry.
23 | func getRegionEvents() -> [RegionEvent] {
24 | guard let array = StorageHelpers.regionEvents else { return [] }
25 | return array.compactMap { $0 as? JSON }.compactMap { RegionEvent(json: $0) }
26 | }
27 |
28 | /// Adds a region event to the registry.
29 | ///
30 | /// - Parameters:
31 | /// - event: The event to add to the registry.
32 | func add(_ event: RegionEvent) {
33 | var array = StorageHelpers.regionEvents
34 |
35 | let storage = event.toJSON(stripPrefix: false)
36 | if array != nil {
37 | array?.append(storage)
38 | } else {
39 | array = [storage]
40 | }
41 |
42 | StorageHelpers.regionEvents = array
43 | }
44 |
45 | /// Removes an array of region events from the registry.
46 | ///
47 | /// - Parameters:
48 | /// - events: The events to remove from the registry.
49 | func remove(_ events: [RegionEvent]) {
50 | var array = StorageHelpers.regionEvents
51 | array?.removeAll(where: { (value) -> Bool in
52 | guard let eventJSON = value as? JSON,
53 | let event = RegionEvent(json: eventJSON) else { return false }
54 | return events.contains {
55 | $0.triggerSubscriptionId == event.triggerSubscriptionId &&
56 | $0.recordId == event.recordId
57 | }
58 | })
59 |
60 | StorageHelpers.regionEvents = array
61 | }
62 |
63 | /// Removes all region events from the registry.
64 | func removeAll() {
65 | StorageHelpers.regionEvents = nil
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_connect.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "about_connect.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_connect.imageset/about_connect.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_connect.imageset/about_connect.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_control.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "about_control.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_control.imageset/about_control.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_control.imageset/about_control.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_security.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "about_security.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_security.imageset/about_security.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_security.imageset/about_security.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_unplug.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "about_unplug.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_unplug.imageset/about_unplug.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/About page/Value props/ifttt_about_unplug.imageset/about_unplug.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_close.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "about_close.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_close.imageset/about_close.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_close.imageset/about_close.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_connect_arrow.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "connect_arrow.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_connect_arrow.imageset/connect_arrow.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_connect_arrow.imageset/connect_arrow.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_download_on_app_store.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "about_download_on_app_store.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_download_on_app_store.imageset/about_download_on_app_store.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/About page/ifttt_about_download_on_app_store.imageset/about_download_on_app_store.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/ifttt_email_confirm.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "email_confirm.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Assets.xcassets/ifttt_email_confirm.imageset/email_confirm.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IFTTT/ConnectSDK-iOS/bf1b3a707ad18a9a1096e4a4889a00f569176abd/IFTTT SDK/Resources/Assets.xcassets/ifttt_email_confirm.imageset/email_confirm.pdf
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "common.cancel" = "Cancel";
9 |
10 | "button.state.loading" = "Loading...";
11 | "button.state.connect" = "Connect %@";
12 | "button.state.verifying" = "Verifying...";
13 | "button.state.creating_account" = "Creating account...";
14 | "button.state.sign_in" = "Going to %@...";
15 | "button.state.sign_in.brief" = "Sign in";
16 | "button.state.saving_configuration" = "Saving settings...";
17 | "button.state.connecting" = "Connecting...";
18 | "button.state.connected" = "Connected";
19 | "button.state.disconnect" = "Slide to disconnect";
20 | "button.state.disconnecting" = "Disconnecting...";
21 | "button.state.disconnected" = "Disconnected";
22 |
23 | "button.email.placeholder" = "Your email";
24 |
25 | "button.footer.works_with" = "WORKS WITH";
26 | "button.footer.email.invalid" = "Enter valid email";
27 | "button.footer.loading.failed.prefix" = "Make sure your phone has internet connection.";
28 | "button.footer.loading.failed.postfix" = "Retry";
29 | "button.footer.email.prefix" = "Secured with IFTTT. Learn More";
30 | "button.footer.email.postfix" = "Learn more";
31 | "button.footer.accountCreation" = "New IFTTT account for %@";
32 |
33 | "about.title_full" = "IFTTT connects %@ to %@";
34 | "about.connect" = "Unlock new features when your favorite things work together";
35 | "about.control" = "Control which services access your data and how they use it";
36 | "about.security" = "Protect and monitor your data";
37 | "about.manage" = "Unplug any time";
38 | "about.connection-deep-link" = "Manage";
39 | "about.legal.full" = "By using this connection, you agree to our Privacy & Terms";
40 | "about.legal.prefix" = "By using this connection you agree to our ";
41 | "about.legal.link" = "Privacy & Terms";
42 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_cs.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Načítání…";
9 | "button.footer.works_with" = "FUNGUJE S";
10 | "button.state.verifying" = "Ověřování...";
11 | "button.state.creating_account" = "Vytváření účtu...";
12 | "button.footer.accountCreation" = "Nový účet IFTTT pro %@";
13 | "button.state.sign_in" = "Přechod na %@...";
14 | "button.state.sign_in.brief" = "Přihlásit se";
15 | "button.state.saving_configuration" = "Ukládání nastavení...";
16 | "button.state.connected" = "Připojeno";
17 | "button.state.disconnect" = "Posunutím odpojit";
18 | "button.state.disconnecting" = "Odpojování...";
19 | "button.state.disconnected" = "Odpojeno";
20 | "button.email.placeholder" = "Váš e-mail";
21 | "button.footer.email.invalid" = "Zadejte platný e-mail";
22 | "button.footer.loading.failed.postfix" = "Zkusit znovu";
23 | "button.state.connect" = "Připojit %@";
24 | "button.state.connecting" = "Připojování";
25 | "button.footer.email.prefix" = "Zabezpečeno pomocí IFTTT. Další informace";
26 | "about.title_full" = "IFTTT propojuje %@ s %@";
27 | "about.connect" = "Získejte nové funkce, kdy budou vaše oblíbené věci spolupracovat";
28 | "about.control" = "Ovládejte, které služby mají přístup k vašim údajům a jak je využívají";
29 | "about.security" = "Chraňte a monitorujte své údaje";
30 | "about.manage" = "Můžete kdykoli odpojit";
31 | "about.legal.full" = "Využíváním tohoto propojení vyjadřujete souhlas Ochrana soukromí a smluvní podmínky";
32 | "button.footer.loading.failed.prefix" = "Zkontrolujte, že má váš telefon přístup k internetu";
33 | "about.connection-deep-link" = "Spravovat";
34 | "about.legal.link" = "Ochrana soukromí a smluvní podmínky";
35 | "button.footer.email.postfix" = "Další informace";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_da.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Indlæser...";
9 | "button.footer.works_with" = "VIRKER MED";
10 | "button.state.verifying" = "Bekræfter...";
11 | "button.state.creating_account" = "Opretter konto...";
12 | "button.footer.accountCreation" = "Ny IFTTT-konto til %@";
13 | "button.state.sign_in" = "Går til %@...";
14 | "button.state.sign_in.brief" = "Log på";
15 | "button.state.saving_configuration" = "Gemmer indstillinger...";
16 | "button.state.connected" = "Tilsluttede ";
17 | "button.state.disconnect" = "Skub for afbryde forbindelsen";
18 | "button.state.disconnecting" = "Afbryder forbindelsen...";
19 | "button.state.disconnected" = "Forbindelsen er afbrudt.";
20 | "button.email.placeholder" = "Din e-mail";
21 | "button.footer.email.invalid" = "Indtast gyldig e-mail";
22 | "button.footer.loading.failed.postfix" = "Nyt forsøg";
23 | "button.state.connect" = "Opret forbindelse til %@";
24 | "button.state.connecting" = "Tilslutning";
25 | "button.footer.email.prefix" = "Sikret med IFTTT. Få flere oplysninger";
26 | "about.title_full" = "IFTTT opretter forbindelse mellem %@ og %@";
27 | "about.connect" = "Lås op for nye funktioner, når dine foretrukne ting fungerer sammen";
28 | "about.control" = "Styr, hvilke tjenester der har adgang til dine data, og hvordan de bruger dem";
29 | "about.security" = "Beskyt og overvåg dine data";
30 | "about.manage" = "Du kan afbryde forbindelsen når som helst";
31 | "about.legal.full" = "Ved at bruge denne forbindelse accepterer du vores Fortrolighedspolitik og vilkår.";
32 | "button.footer.loading.failed.prefix" = "Sørg for, at din telefon har internetforbindelse";
33 | "about.connection-deep-link" = "Administrer";
34 | "about.legal.link" = "Fortrolighedspolitik og vilkår";
35 | "button.footer.email.postfix" = "Få flere oplysninger";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_de.strings:
--------------------------------------------------------------------------------
1 | "button.state.loading" = "Laden…";
2 | "button.footer.works_with" = "FUNKTIONIERT MIT";
3 | "button.state.verifying" = "Wird bestätigt ...";
4 | "button.state.creating_account" = "Konto wird erstellt ...";
5 | "button.footer.accountCreation" = "Neues IFTTT-Konto für %@";
6 | "button.state.sign_in" = "Navigieren zu %@ ...";
7 | "button.state.sign_in.brief" = "Anmelden";
8 | "button.state.saving_configuration" = "Einstellungen werden gespeichert ...";
9 | "button.state.connected" = "verbunden";
10 | "button.state.disconnect" = "Zum Trennen wischen";
11 | "button.state.disconnecting" = "Wird getrennt ...";
12 | "button.state.disconnected" = "Verbindung getrennt";
13 | "button.email.placeholder" = "Ihre E-Mail-Adresse";
14 | "button.footer.email.invalid" = "Geben Sie eine gültige E-Mail-Adresse ein.";
15 | "button.footer.loading.failed.postfix" = "Wiederholen";
16 | "button.state.connect" = "Verbinden %@";
17 | "button.state.connecting" = "Wird verbunden";
18 | "button.footer.email.prefix" = "Mit IFTTT gesichert Weitere Informationen";
19 | "about.title_full" = "IFTTT verbindet %@ mit %@.";
20 | "about.connect" = "Schalten Sie neue Funktion frei, indem Sie Ihre Lieblingsapps zusammenarbeiten lassen.";
21 | "about.control" = "Steuern Sie, welche Dienste auf Ihre Daten zugreifen und wie sie diese verwenden.";
22 | "about.security" = "Schützen und überwachen Sie Ihre Daten.";
23 | "about.manage" = "Trennen jederzeit möglich";
24 | "about.legal.full" = "Durch die Nutzung dieser Verbindung stimmen Sie unseren Datenschutzbestimmungen zu.";
25 | "button.footer.loading.failed.prefix" = "Stellen Sie sicher, dass Ihr Telefon über eine Internetverbindung verfügt.";
26 | "about.connection-deep-link" = "Verwalten";
27 | "about.legal.link" = "Datenschutzbestimmungen";
28 | "button.footer.email.postfix" = "Weitere Informationen";
29 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_en-GB.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Loading...";
9 | "button.footer.works_with" = "WORKS WITH";
10 | "button.state.verifying" = "Verifying...";
11 | "button.state.creating_account" = "Creating account...";
12 | "button.footer.accountCreation" = "New IFTTT account for %@";
13 | "button.state.sign_in" = "Going to %@...";
14 | "button.state.sign_in.brief" = "Sign in";
15 | "button.state.saving_configuration" = "Saving settings...";
16 | "button.state.connected" = "Connected ";
17 | "button.state.disconnect" = "Slide to disconnect";
18 | "button.state.disconnecting" = "Disconnecting...";
19 | "button.state.disconnected" = "Disconnected";
20 | "button.email.placeholder" = "Your email";
21 | "button.footer.email.invalid" = "Enter valid email";
22 | "button.footer.loading.failed.postfix" = "Retry";
23 | "button.state.connect" = "Connect %@";
24 | "button.state.connecting" = "Connecting...";
25 | "button.footer.email.prefix" = "Secured with IFTTT. Learn More";
26 | "about.title_full" = "IFTTT connects %@ to %@";
27 | "about.connect" = "Unlock new features when your favourite things work together";
28 | "about.control" = "Control which services access your data and how they use it";
29 | "about.security" = "Protect and monitor your data";
30 | "about.manage" = "Unplug any time";
31 | "about.legal.full" = "By using this connection, you agree to our Privacy & Terms";
32 | "button.footer.loading.failed.prefix" = "Make sure your phone has internet connection";
33 | "about.connection-deep-link" = "Manage";
34 | "about.legal.link" = "Privacy & Terms";
35 | "button.footer.email.postfix" = "Learn more";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_en-US.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "common.cancel" = "Cancel";
9 |
10 | "button.state.loading" = "Loading...";
11 | "button.state.connect" = "Connect %@";
12 | "button.state.verifying" = "Verifying...";
13 | "button.state.creating_account" = "Creating account...";
14 | "button.state.sign_in" = "Going to %@...";
15 | "button.state.sign_in.brief" = "Sign in";
16 | "button.state.saving_configuration" = "Saving settings...";
17 | "button.state.connecting" = "Connecting...";
18 | "button.state.connected" = "Connected";
19 | "button.state.disconnect" = "Slide to disconnect";
20 | "button.state.disconnecting" = "Disconnecting...";
21 | "button.state.disconnected" = "Disconnected";
22 |
23 | "button.email.placeholder" = "Your email";
24 |
25 | "button.footer.works_with" = "WORKS WITH";
26 | "button.footer.email.invalid" = "Enter valid email";
27 | "button.footer.loading.failed.prefix" = "Make sure your phone has internet connection.";
28 | "button.footer.loading.failed.postfix" = "Retry";
29 | "button.footer.email.prefix" = "Secured with IFTTT. Learn More";
30 | "button.footer.email.postfix" = "Learn more";
31 | "button.footer.accountCreation" = "New IFTTT account for %@";
32 |
33 | "about.title_full" = "IFTTT connects %@ to %@";
34 | "about.connect" = "Unlock new features when your favorite things work together";
35 | "about.control" = "Control which services access your data and how they use it";
36 | "about.security" = "Protect and monitor your data";
37 | "about.manage" = "Unplug any time";
38 | "about.connection-deep-link" = "Manage";
39 | "about.legal.full" = "By using this connection, you agree to our Privacy & Terms";
40 | "about.legal.prefix" = "By using this connection you agree to our ";
41 | "about.legal.link" = "Privacy & Terms";
42 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_es-419.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Cargando...";
9 | "button.footer.works_with" = "FUNCIONA CON";
10 | "button.state.verifying" = "Verificando...";
11 | "button.state.creating_account" = "Creando cuenta...";
12 | "button.footer.accountCreation" = "Nueva cuenta IFTTT para %@";
13 | "button.state.sign_in" = "Yendo a %@...";
14 | "button.state.sign_in.brief" = "Iniciar sesión";
15 | "button.state.saving_configuration" = "Guardando configuración...";
16 | "button.state.connected" = "Se conectó con";
17 | "button.state.disconnect" = "Deslice para desconectar";
18 | "button.state.disconnecting" = "Desconectando...";
19 | "button.state.disconnected" = "Desconectado";
20 | "button.email.placeholder" = "Su correo electrónico";
21 | "button.footer.email.invalid" = "Ingrese un correo electrónico válido";
22 | "button.footer.loading.failed.postfix" = "Intentar nuevamente";
23 | "button.state.connect" = "Conectar %@";
24 | "button.state.connecting" = "Conectando";
25 | "button.footer.email.prefix" = "Protegido con IFTTT. Más información";
26 | "about.title_full" = "IFTTT conecta %@ con %@";
27 | "about.connect" = "Desbloquee nuevas funciones cuando sus cosas favoritas funcionan juntas";
28 | "about.control" = "Controle qué servicios acceden a sus datos y cómo los usan";
29 | "about.security" = "Proteja y monitoree sus datos";
30 | "about.manage" = "Desconéctese en cualquier momento";
31 | "about.legal.full" = "Al usar esta conexión, acepta nuestros Términos y privacidad";
32 | "button.footer.loading.failed.prefix" = "Asegúrese de que su teléfono tenga conexión a internet";
33 | "about.connection-deep-link" = "Administrar";
34 | "about.legal.link" = "Términos y privacidad";
35 | "button.footer.email.postfix" = "Más información";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_es.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Cargando...";
9 | "button.footer.works_with" = "FUNCIONA CON";
10 | "button.state.verifying" = "Verificando...";
11 | "button.state.creating_account" = "Creando cuenta...";
12 | "button.footer.accountCreation" = "Nueva cuenta IFTTT para %@";
13 | "button.state.sign_in" = "Yendo a %@ ...";
14 | "button.state.sign_in.brief" = "Iniciar sesión";
15 | "button.state.saving_configuration" = "Guardando ajustes ...";
16 | "button.state.connected" = "Conectado";
17 | "button.state.disconnect" = "Desliza para desconectar";
18 | "button.state.disconnecting" = "Desconectando...";
19 | "button.state.disconnected" = "Desconectado";
20 | "button.email.placeholder" = "Tu correo electrónico";
21 | "button.footer.email.invalid" = "Introduce un correo electrónico válido";
22 | "button.footer.loading.failed.postfix" = "Reintentar";
23 | "button.state.connect" = "Conectar %@";
24 | "button.state.connecting" = "Conectando";
25 | "button.footer.email.prefix" = "Asegurado con IFTTT. Más información";
26 | "about.title_full" = "IFTTT conecta %@ con %@";
27 | "about.connect" = "Desbloquea nuevas funciones cuando tus dispositivos favoritos trabajen juntos";
28 | "about.control" = "Controla qué servicios acceden a tus datos y cómo los usan";
29 | "about.security" = "Protege y supervisa tus datos";
30 | "about.manage" = "Desconecta en cualquier momento";
31 | "about.legal.full" = "Al usar esta conexión, aceptas nuestros Privacidad y términos";
32 | "button.footer.loading.failed.prefix" = "Asegúrate de que tu teléfono tenga conexión a Internet";
33 | "about.connection-deep-link" = "Gestionar";
34 | "about.legal.link" = "Privacidad y términos";
35 | "button.footer.email.postfix" = "Más información";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_fi.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Ladataan...";
9 | "button.footer.works_with" = "TOIMII SEURAAVIEN KANSSA:";
10 | "button.state.verifying" = "Vahvistetaan...";
11 | "button.state.creating_account" = "Tiliä luodaan...";
12 | "button.footer.accountCreation" = "Uusi IFTTT-tili käyttäjälle %@";
13 | "button.state.sign_in" = "Siirrytään kohteeseen %@...";
14 | "button.state.sign_in.brief" = "Kirjaudu";
15 | "button.state.saving_configuration" = "Asetuksia tallennetaan...";
16 | "button.state.connected" = "Yhdistetty: ";
17 | "button.state.disconnect" = "Katkaise yhteys liu'uttamalla";
18 | "button.state.disconnecting" = "Yhteys katkaistaan...";
19 | "button.state.disconnected" = "Yhteys katkaistu";
20 | "button.email.placeholder" = "Sähköpostiosoitteesi";
21 | "button.footer.email.invalid" = "Anna kelvollinen sähköpostiosoite";
22 | "button.footer.loading.failed.postfix" = "Yritä uudelleen";
23 | "button.state.connect" = "Yhdistä %@";
24 | "button.state.connecting" = "Yhdistetään";
25 | "button.footer.email.prefix" = "IFTTT-suojattu. Lisätietoja";
26 | "about.title_full" = "IFTTT yhdistää kohteen %@ kohteeseen %@";
27 | "about.connect" = "Kun lempiasiasi toimivat yhdessä, voit hyödyntää uusia ominaisuuksia";
28 | "about.control" = "Määritä, mitkä palvelut voivat käyttää tietojasi ja miten";
29 | "about.security" = "Suojaa ja tarkastele tietojasi";
30 | "about.manage" = "Poista käytöstä koska tahansa";
31 | "about.legal.full" = "Käyttämällä tätä yhteyttä hyväksyt Yksityisyys ja ehdot";
32 | "button.footer.loading.failed.prefix" = "Varmista, että puhelimesi on yhteydessä internetiin";
33 | "about.connection-deep-link" = "Hallinnoi";
34 | "about.legal.link" = "Yksityisyys ja ehdot";
35 | "button.footer.email.postfix" = "Lisätietoja";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_fr-CA.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Chargement...";
9 | "button.footer.works_with" = "FONCTIONNE AVEC";
10 | "button.state.verifying" = "Vérification...";
11 | "button.state.creating_account" = "Création du compte...";
12 | "button.footer.accountCreation" = "Nouveau compte IFTTT pour %@";
13 | "button.state.sign_in" = "Aller à %@...";
14 | "button.state.sign_in.brief" = "Inscrivez-vous";
15 | "button.state.saving_configuration" = "Enregistrement des paramètres...";
16 | "button.state.connected" = "Connecté ";
17 | "button.state.disconnect" = "Faites glisser pour vous déconnecter";
18 | "button.state.disconnecting" = "Déconnexion...";
19 | "button.state.disconnected" = "Déconnecté";
20 | "button.email.placeholder" = "Votre courriel";
21 | "button.footer.email.invalid" = "Saisir une adresse courriel valide";
22 | "button.footer.loading.failed.postfix" = "Réessayer";
23 | "button.state.connect" = "Connecter %@";
24 | "button.state.connecting" = "Connexion";
25 | "button.footer.email.prefix" = "Sécurisé avec IFTTT. En savoir plus";
26 | "about.title_full" = "IFTTT connecte %@ à %@";
27 | "about.connect" = "Déverrouillez de nouvelles fonctionnalités lorsque vos choses préférées fonctionnent ensemble";
28 | "about.control" = "Contrôlez quels services accèdent à vos données et comment ils les utilisent";
29 | "about.security" = "Protégez et surveillez vos données";
30 | "about.manage" = "Débranchez à tout moment";
31 | "about.legal.full" = "En utilisant cette connexion, vous acceptez nos Confidentialité et modalités";
32 | "button.footer.loading.failed.prefix" = "Assurez-vous que votre téléphone dispose d'une connexion Internet";
33 | "about.connection-deep-link" = "Gérer";
34 | "about.legal.link" = "Confidentialité et modalités";
35 | "button.footer.email.postfix" = "En savoir plus";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_fr.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Chargement...";
9 | "button.footer.works_with" = "FONCTIONNE AVEC";
10 | "button.state.verifying" = "Vérification...";
11 | "button.state.creating_account" = "Création du compte...";
12 | "button.footer.accountCreation" = "Nouveau compte IFTTT pour %@";
13 | "button.state.sign_in" = "Déplacement vers %@...";
14 | "button.state.sign_in.brief" = "Connexion";
15 | "button.state.saving_configuration" = "Enregistrement des paramètres...";
16 | "button.state.connected" = "Connecté";
17 | "button.state.disconnect" = "Faites glisser pour vous déconnecter";
18 | "button.state.disconnecting" = "Déconnexion...";
19 | "button.state.disconnected" = "Déconnecté";
20 | "button.email.placeholder" = "Votre e-mail";
21 | "button.footer.email.invalid" = "Entrez une adresse e-mail valide";
22 | "button.footer.loading.failed.postfix" = "Réessayer";
23 | "button.state.connect" = "Connecter %@";
24 | "button.state.connecting" = "Connexion";
25 | "button.footer.email.prefix" = "Sécurisé avec IFTTT. En savoir plus";
26 | "about.title_full" = "IFTTT connecte %@ à %@";
27 | "about.connect" = "Déverrouillez de nouvelles fonctionnalités lorsque vos objets préférés fonctionnent ensemble";
28 | "about.control" = "Contrôlez quels services accèdent à vos données et comment ils les utilisent";
29 | "about.security" = "Protégez et surveillez vos données";
30 | "about.manage" = "Débranchez à tout moment";
31 | "about.legal.full" = "En utilisant cette connexion, vous acceptez nos Confidentialité et conditions";
32 | "button.footer.loading.failed.prefix" = "Assurez-vous que votre téléphone dispose d'une connexion Internet";
33 | "about.connection-deep-link" = "Gérer";
34 | "about.legal.link" = "Confidentialité et conditions";
35 | "button.footer.email.postfix" = "En savoir plus";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_it.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Caricamento...";
9 | "button.footer.works_with" = "FUNZIONA CON";
10 | "button.state.verifying" = "Verifica in corso...";
11 | "button.state.creating_account" = "Creazione dell'account...";
12 | "button.footer.accountCreation" = "Nuovo account IFTTT per %@";
13 | "button.state.sign_in" = "Spostamento verso %@...";
14 | "button.state.sign_in.brief" = "Accedi";
15 | "button.state.saving_configuration" = "Salvataggio delle impostazioni in corso...";
16 | "button.state.connected" = "Collegato a";
17 | "button.state.disconnect" = "Scorri per scollegare";
18 | "button.state.disconnecting" = "Scollegamento in corso...";
19 | "button.state.disconnected" = "Scollegato";
20 | "button.email.placeholder" = "La tua email";
21 | "button.footer.email.invalid" = "Inserisci un indirizzo e-mail valido";
22 | "button.footer.loading.failed.postfix" = "Riprova";
23 | "button.state.connect" = "Collega %@";
24 | "button.state.connecting" = "Connessione";
25 | "button.footer.email.prefix" = "Protetto con IFTTT. Per saperne di più";
26 | "about.title_full" = "IFTTT si collega a %@ per %@";
27 | "about.connect" = "Sblocca nuove funzioni quando i tuoi elementi preferiti funzionano assieme";
28 | "about.control" = "Controlla quali servizi accedono ai tuoi dati e come li utilizzano";
29 | "about.security" = "Proteggi e monitora i tuoi dati";
30 | "about.manage" = "Scollegati quando vuoi";
31 | "about.legal.full" = "Utilizzando questo collegamento, l'utente accetta la nostra Informativa sulla privacy e termini";
32 | "button.footer.loading.failed.prefix" = "Assicurati che il tuo telefono sia collegato a Internet";
33 | "about.connection-deep-link" = "Gestisci";
34 | "about.legal.link" = "Informativa sulla privacy e termini";
35 | "button.footer.email.postfix" = "Per saperne di più";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_ja.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "読み込み中…";
9 | "button.footer.works_with" = "以下と連携";
10 | "button.state.verifying" = "確認中...";
11 | "button.state.creating_account" = "アカウントを作成中...";
12 | "button.footer.accountCreation" = "%@の新規アカウント";
13 | "button.state.sign_in" = "%@に移動中...";
14 | "button.state.sign_in.brief" = "サインイン";
15 | "button.state.saving_configuration" = "設定を保存中...";
16 | "button.state.connected" = "接続済み";
17 | "button.state.disconnect" = "スライドして切断";
18 | "button.state.disconnecting" = "接続解除中...";
19 | "button.state.disconnected" = "切断済み";
20 | "button.email.placeholder" = "電子メール";
21 | "button.footer.email.invalid" = "有効な電子メールの入力";
22 | "button.footer.loading.failed.postfix" = "再試行";
23 | "button.state.connect" = "%@に接続";
24 | "button.state.connecting" = "接続中";
25 | "button.footer.email.prefix" = "IFTTTで保護 詳細を確認する";
26 | "about.title_full" = "IFTTTは%@を%@に接続します";
27 | "about.connect" = "お気に入りのものを連携すると、新しい機能を利用できます";
28 | "about.control" = "データにアクセスするサービスとその使用方法を制御します";
29 | "about.security" = "データを保護し、監視します";
30 | "about.manage" = "いつでも停止可能";
31 | "about.legal.full" = "この接続を使用すると、プライバシーと利用規約に同意したことになります";
32 | "button.footer.loading.failed.prefix" = "スマートフォンがインターネットに接続されていることを確認してください";
33 | "about.connection-deep-link" = "管理";
34 | "about.legal.link" = "プライバシーと利用規約";
35 | "button.footer.email.postfix" = "詳細を確認する";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_ko.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "로드 중...";
9 | "button.footer.works_with" = "함께 사용 가능";
10 | "button.state.verifying" = "확인 중...";
11 | "button.state.creating_account" = "계정을 만드는 중...";
12 | "button.footer.accountCreation" = "%@을(를) 위한 새 IFTTT 계정";
13 | "button.state.sign_in" = "%@(으)로 이동하는 중...";
14 | "button.state.sign_in.brief" = "로그인";
15 | "button.state.saving_configuration" = "설정 저장 중...";
16 | "button.state.connected" = "연결된 ";
17 | "button.state.disconnect" = "옆으로 밀어 연결 해제";
18 | "button.state.disconnecting" = "연결을 해제하는 중...";
19 | "button.state.disconnected" = "연결 해제됨";
20 | "button.email.placeholder" = "사용자 이메일";
21 | "button.footer.email.invalid" = "올바른 이메일 입력";
22 | "button.footer.loading.failed.postfix" = "재시도";
23 | "button.state.connect" = "%@에 연결";
24 | "button.state.connecting" = "연결 중";
25 | "button.footer.email.prefix" = "IFTTT로 보안 처리되었습니다. 자세히 알아보기";
26 | "about.title_full" = "IFTTT는 %@을(를) %@에 연결합니다.";
27 | "about.connect" = "즐겨 사용하는 장치를 함께 사용할 때 새 기능 잠금 해제";
28 | "about.control" = "데이터에 액세스하는 서비스와 사용하는 방법 제어";
29 | "about.security" = "데이터 보호 및 모니터링";
30 | "about.manage" = "언제든지 플러그 분리";
31 | "about.legal.full" = "이 연결 사용 시 당사의 개인정보 개인정보 및 약관";
32 | "button.footer.loading.failed.prefix" = "전화가 인터넷에 연결되었는지 확인";
33 | "about.connection-deep-link" = "관리";
34 | "about.legal.link" = "개인정보 및 약관";
35 | "button.footer.email.postfix" = "자세히 알아보기";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_nb.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Laster inn ...";
9 | "button.footer.works_with" = "FUNGERER MED";
10 | "button.state.verifying" = "Kontrollerer ...";
11 | "button.state.creating_account" = "Oppretter konto ...";
12 | "button.footer.accountCreation" = "Ny IFTTT-konto for %@";
13 | "button.state.sign_in" = "Går til %@ ...";
14 | "button.state.sign_in.brief" = "Logg på";
15 | "button.state.saving_configuration" = "Lagrer innstillinger ...";
16 | "button.state.connected" = "Tilkoblet";
17 | "button.state.disconnect" = "Skyv for å koble fra";
18 | "button.state.disconnecting" = "Kobler fra ...";
19 | "button.state.disconnected" = "Frakoblet";
20 | "button.email.placeholder" = "E-postadressen din";
21 | "button.footer.email.invalid" = "Angi en gyldig e-postadresse";
22 | "button.footer.loading.failed.postfix" = "Prøv igjen";
23 | "button.state.connect" = "Koble til %@";
24 | "button.state.connecting" = "Kobler til";
25 | "button.footer.email.prefix" = "Sikret med IFTTT. Finn ut mer";
26 | "about.title_full" = "IFTTT kobler %@ til %@";
27 | "about.connect" = "Lås opp nye funksjoner når favorittingene dine samarbeider";
28 | "about.control" = "Kontroller hvilke tjenester som har tilgang til dataene dine og hvordan de bruker dem";
29 | "about.security" = "Beskytt og overvåk dataene dine";
30 | "about.manage" = "Koble ut når som helst";
31 | "about.legal.full" = "Ved å bruke denne koblingen, godtar du våre retningslinjer for Personvern og vilkår";
32 | "button.footer.loading.failed.prefix" = "Kontroller at telefonen har tilkobling til Internett";
33 | "about.connection-deep-link" = "Administrer";
34 | "about.legal.link" = "Personvern og vilkår";
35 | "button.footer.email.postfix" = "Finn ut mer";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_nl.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Bezig met laden...";
9 | "button.footer.works_with" = "WERKT MET";
10 | "button.state.verifying" = "Bezig met controleren...";
11 | "button.state.creating_account" = "Account maken...";
12 | "button.footer.accountCreation" = "Nieuw IFTTT-account voor %@";
13 | "button.state.sign_in" = "Gaat %@ naar...";
14 | "button.state.sign_in.brief" = "Aanmelden";
15 | "button.state.saving_configuration" = "Instellingen opslaan...";
16 | "button.state.connected" = "Verbonden";
17 | "button.state.disconnect" = "Schuiven om verbinding te verbreken";
18 | "button.state.disconnecting" = "Verbinding verbreken...";
19 | "button.state.disconnected" = "Verbinding verbroken";
20 | "button.email.placeholder" = "Uw e-mail";
21 | "button.footer.email.invalid" = "Vul een geldig e-mailadres in";
22 | "button.footer.loading.failed.postfix" = "Opnieuw proberen";
23 | "button.state.connect" = "Verbinden met %@";
24 | "button.state.connecting" = "Verbinding maken";
25 | "button.footer.email.prefix" = "Beveiligd met IFTTT. Meer informatie";
26 | "about.title_full" = "IFTTT verbindt %@ met %@";
27 | "about.connect" = "Ontgrendel nieuwe functies wanneer uw favoriete dingen samenwerken";
28 | "about.control" = "Bepaal welke services toegang hebben tot uw gegevens en hoe ze deze gebruiken";
29 | "about.security" = "Uw gegevens beschermen en controleren";
30 | "about.manage" = "Op elk gewenst moment loskoppelen";
31 | "about.legal.full" = "Door deze verbinding te gebruiken, gaat u akkoord met onze Privacy en voorwaarden";
32 | "button.footer.loading.failed.prefix" = "Zorg dat uw telefoon een internetverbinding heeft";
33 | "about.connection-deep-link" = "Beheren";
34 | "about.legal.link" = "Privacy en voorwaarden";
35 | "button.footer.email.postfix" = "Meer informatie";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_pl.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Wczytywanie...";
9 | "button.footer.works_with" = "DZIAŁA Z";
10 | "button.state.verifying" = "Weryfikacja...";
11 | "button.state.creating_account" = "Tworzenie konta...";
12 | "button.footer.accountCreation" = "Nowe konto IFTTT dla %@";
13 | "button.state.sign_in" = "Przechodzenie do %@...";
14 | "button.state.sign_in.brief" = "Zaloguj się";
15 | "button.state.saving_configuration" = "Zapisywanie ustawień...";
16 | "button.state.connected" = "Połączony ";
17 | "button.state.disconnect" = "Przesuń, aby rozłączyć";
18 | "button.state.disconnecting" = "Rozłączanie...";
19 | "button.state.disconnected" = "Rozłączono";
20 | "button.email.placeholder" = "Twój e-mail";
21 | "button.footer.email.invalid" = "Wprowadź prawidłowy e-mail";
22 | "button.footer.loading.failed.postfix" = "Spróbuj ponownie";
23 | "button.state.connect" = "Połącz %@";
24 | "button.state.connecting" = "Łączenie";
25 | "button.footer.email.prefix" = "Zabezpieczone za pomocą IFTTT. Dowiedz się więcej";
26 | "about.title_full" = "IFTTT łączy %@ z %@";
27 | "about.connect" = "Odkrywaj nowe funkcje, gdy ulubione elementy współpracują ze sobą";
28 | "about.control" = "Kontroluj, które usługi mogą mieć dostęp do Twoich danych, i jak mogą je wykorzystywać";
29 | "about.security" = "Monitoruj i chroń swoje dane";
30 | "about.manage" = "Możesz odłączyć w dowolnym momencie";
31 | "about.legal.full" = "Korzystanie z tego połączenia jest równoznaczne z wyrażeniem zgody na Warunki i Polityka prywatności";
32 | "button.footer.loading.failed.prefix" = "Upewnij się, że Twój telefon ma połączenie z Internetem";
33 | "about.connection-deep-link" = "Zarządzaj";
34 | "about.legal.link" = "Warunki i Polityka prywatności";
35 | "button.footer.email.postfix" = "Dowiedz się więcej";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_pt-BR.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Carregando...";
9 | "button.footer.works_with" = "FUNCIONA COM";
10 | "button.state.verifying" = "Verificando...";
11 | "button.state.creating_account" = "Criando conta...";
12 | "button.footer.accountCreation" = "Nova conta do IFTTT para %@";
13 | "button.state.sign_in" = "Indo para %@...";
14 | "button.state.sign_in.brief" = "Entrar";
15 | "button.state.saving_configuration" = "Salvando configurações...";
16 | "button.state.connected" = "Conectado";
17 | "button.state.disconnect" = "Deslize para desconectar";
18 | "button.state.disconnecting" = "Desconectando...";
19 | "button.state.disconnected" = "Desconectado";
20 | "button.email.placeholder" = "Seu e-mail";
21 | "button.footer.email.invalid" = "Insira um e-mail válido";
22 | "button.footer.loading.failed.postfix" = "Tentar novamente";
23 | "button.state.connect" = "Conectar %@";
24 | "button.state.connecting" = "Conectando";
25 | "button.footer.email.prefix" = "Seguro com o IFTTT. Saiba mais";
26 | "about.title_full" = "O IFTTT conecta %@ a %@";
27 | "about.connect" = "Libere novos recursos quando seus produtos favoritos funcionam em conjunto";
28 | "about.control" = "Controle quais serviços acessam seus dados e como eles os usam";
29 | "about.security" = "Proteja e monitore seus dados";
30 | "about.manage" = "Desconecte a qualquer momento";
31 | "about.legal.full" = "Ao usar esta conexão, você concorda com nossa Privacidade e nossos Termos";
32 | "button.footer.loading.failed.prefix" = "Verifique se o telefone tem conexão com a Internet";
33 | "about.connection-deep-link" = "Gerenciar";
34 | "about.legal.link" = "Privacidade e nossos Termos";
35 | "button.footer.email.postfix" = "Saiba mais";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_pt-PT.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "A carregar";
9 | "button.footer.works_with" = "FUNCIONA COM";
10 | "button.state.verifying" = "A verificar...";
11 | "button.state.creating_account" = "A criar conta...";
12 | "button.footer.accountCreation" = "Nova conta IFTTT para %@";
13 | "button.state.sign_in" = "A ir para %@...";
14 | "button.state.sign_in.brief" = "Iniciar sessão";
15 | "button.state.saving_configuration" = "A guardar definições...";
16 | "button.state.connected" = " ligado";
17 | "button.state.disconnect" = "Deslize para desligar";
18 | "button.state.disconnecting" = "A desligar...";
19 | "button.state.disconnected" = "Desligado";
20 | "button.email.placeholder" = "O seu e-mail";
21 | "button.footer.email.invalid" = "Introduza um e-mail válido";
22 | "button.footer.loading.failed.postfix" = "Repetir";
23 | "button.state.connect" = "Estabelecer ligação a %@";
24 | "button.state.connecting" = "A ligar";
25 | "button.footer.email.prefix" = "Protegido com IFTTT. Mais informações";
26 | "about.title_full" = "IFTTT liga %@ para %@";
27 | "about.connect" = "Desbloqueie novas funcionalidades quando os seus equipamentos favoritos trabalham em conjunto";
28 | "about.control" = "Controle que serviços acedem aos seus dados e como utilizá-los";
29 | "about.security" = "Proteja e monitorize os seus dados";
30 | "about.manage" = "Desligue em qualquer altura";
31 | "about.legal.full" = "A utilizar esta ligação, concorda com os nossos Termos e Privacidade";
32 | "button.footer.loading.failed.prefix" = "Certifique-se de que o seu telefone tem ligação à Internet";
33 | "about.connection-deep-link" = "Gerir";
34 | "about.legal.link" = "Termos e Privacidade";
35 | "button.footer.email.postfix" = "Mais informações";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_ru.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Загрузка...";
9 | "button.footer.works_with" = "РАБОТАЕТ С";
10 | "button.state.verifying" = "Проверка...";
11 | "button.state.creating_account" = "Создание учетной записи...";
12 | "button.footer.accountCreation" = "Новая учетная запись IFTTT для %@";
13 | "button.state.sign_in" = "Переход к %@...";
14 | "button.state.sign_in.brief" = "Войти";
15 | "button.state.saving_configuration" = "Сохранение настроек...";
16 | "button.state.connected" = "Подключено: ";
17 | "button.state.disconnect" = "Проведите для отключения";
18 | "button.state.disconnecting" = "Отключение...";
19 | "button.state.disconnected" = "Отключено";
20 | "button.email.placeholder" = "Ваш адрес электронной почты";
21 | "button.footer.email.invalid" = "Введите действительный адрес";
22 | "button.footer.loading.failed.postfix" = "Повторить попытку";
23 | "button.state.connect" = "Подключить %@";
24 | "button.state.connecting" = "Подключение";
25 | "button.footer.email.prefix" = "Под защитой IFTTT. Узнайте больше";
26 | "about.title_full" = "IFTTT подключает %@ к %@";
27 | "about.connect" = "Откройте новые возможности благодаря взаимодействию ваших любимых вещей";
28 | "about.control" = "Контролируйте доступ различных служб к вашим данным и их использование";
29 | "about.security" = "Защищайте и отслеживайте свои данные";
30 | "about.manage" = "Вы можете отключиться в любой момент!";
31 | "about.legal.full" = "Используя это подключение, вы принимаете наши Политику конфиденциальности и Условия";
32 | "button.footer.loading.failed.prefix" = "Убедитесь, что телефон подключен к Интернету";
33 | "about.connection-deep-link" = "Управление";
34 | "about.legal.link" = "конфиденциальности и Условия";
35 | "button.footer.email.postfix" = "Узнайте больше";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_sv.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "Läser in ...";
9 | "button.footer.works_with" = "FUNGERAR MED";
10 | "button.state.verifying" = "Verifierar…";
11 | "button.state.creating_account" = "Skapar konto…";
12 | "button.footer.accountCreation" = "Nytt IFTTT-konto för %@";
13 | "button.state.sign_in" = "Går till %@…";
14 | "button.state.sign_in.brief" = "Logga in";
15 | "button.state.saving_configuration" = "Spara inställningar…";
16 | "button.state.connected" = "Ansluten till ";
17 | "button.state.disconnect" = "Skjut för att koppla från";
18 | "button.state.disconnecting" = "Kopplar från…";
19 | "button.state.disconnected" = "Frånkopplad";
20 | "button.email.placeholder" = "Din e-postadress";
21 | "button.footer.email.invalid" = "Ange en giltig e-postadress";
22 | "button.footer.loading.failed.postfix" = "Försök igen";
23 | "button.state.connect" = "Anslut %@";
24 | "button.state.connecting" = "Ansluter";
25 | "button.footer.email.prefix" = "Skyddad med IFTTT. Läs mer";
26 | "about.title_full" = "IFTTT ansluter %@ till %@";
27 | "about.connect" = "Upptäck nya funktioner när dina favoritsaker arbetar tillsammans";
28 | "about.control" = "Styr vilka tjänster som kommer åt dina uppgifter och hur de använder dem";
29 | "about.security" = "Skydda och övervaka dina uppgifter";
30 | "about.manage" = "Koppla ur när som helst";
31 | "about.legal.full" = "Genom att använda den här anslutningen accepterar du vår Sekretesspolicy och våra villkor";
32 | "button.footer.loading.failed.prefix" = "Kontrollera att din telefon har en Internetanslutning";
33 | "about.connection-deep-link" = "Hantera";
34 | "about.legal.link" = "Sekretesspolicy och våra villkor";
35 | "button.footer.email.postfix" = "Läs mer";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_zh-Hans.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "正在加载...";
9 | "button.footer.works_with" = "使用";
10 | "button.state.verifying" = "正在验证...";
11 | "button.state.creating_account" = "正在创建帐户...";
12 | "button.footer.accountCreation" = "为 %@ 新建 IFTTT 帐户";
13 | "button.state.sign_in" = "正在转到 %@...";
14 | "button.state.sign_in.brief" = "登录";
15 | "button.state.saving_configuration" = "正在保存设置...";
16 | "button.state.connected" = "已连接";
17 | "button.state.disconnect" = "滑动即可断开";
18 | "button.state.disconnecting" = "正在断开连接...";
19 | "button.state.disconnected" = "已断开连接";
20 | "button.email.placeholder" = "您的邮箱";
21 | "button.footer.email.invalid" = "输入有效的邮箱";
22 | "button.footer.loading.failed.postfix" = "重试";
23 | "button.state.connect" = "连接 %@";
24 | "button.state.connecting" = "正在连接";
25 | "button.footer.email.prefix" = "IFTTT 提供保护。了解更多";
26 | "about.title_full" = "IFTTT 将 %@ 连接至 %@";
27 | "about.connect" = "常用工具齐登场,助力解锁新功能。";
28 | "about.control" = "控制可访问您数据的服务,并管理其使用方式";
29 | "about.security" = "保护和监控您的数据";
30 | "about.manage" = "可随时断电";
31 | "about.legal.full" = "使用此连接,即表示您同意我们的隐私条款";
32 | "button.footer.loading.failed.prefix" = "确保您的手机已连接互联网";
33 | "about.connection-deep-link" = "管理";
34 | "about.legal.link" = "隐私条款";
35 | "button.footer.email.postfix" = "了解更多";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Resources/Localizable_zh-Hant.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | IFTTT SDK
4 |
5 | Copyright © 2018 IFTTT. All rights reserved.
6 | */
7 |
8 | "button.state.loading" = "正在載入...";
9 | "button.footer.works_with" = "搭配使用";
10 | "button.state.verifying" = "驗證中...";
11 | "button.state.creating_account" = "正在建立帳戶...";
12 | "button.footer.accountCreation" = "%@ 的 IFTTT 新帳戶";
13 | "button.state.sign_in" = "正在前往 %@...";
14 | "button.state.sign_in.brief" = "登入";
15 | "button.state.saving_configuration" = "正在儲存設定...";
16 | "button.state.connected" = "已連線";
17 | "button.state.disconnect" = "滑動以中斷連線";
18 | "button.state.disconnecting" = "正在中斷連線...";
19 | "button.state.disconnected" = "已中斷連線";
20 | "button.email.placeholder" = "您的電子郵件";
21 | "button.footer.email.invalid" = "輸入有效的電子郵件";
22 | "button.footer.loading.failed.postfix" = "重試";
23 | "button.state.connect" = "連線 %@";
24 | "button.state.connecting" = "正在連線";
25 | "button.footer.email.prefix" = "透過 IFTTT 保護。了解更多";
26 | "about.title_full" = "IFTTT 會將 %@ 連線至 %@";
27 | "about.connect" = "讓您喜愛的事物彼此合作,就能發掘出新功能";
28 | "about.control" = "控制哪些服務可存取您的資料,以及使用資料的方式";
29 | "about.security" = "保護並監控您的資料";
30 | "about.manage" = "隨時斷開";
31 | "about.legal.full" = "使用此連線,即表示您同意我們的隱私權與條款";
32 | "button.footer.loading.failed.prefix" = "請確定您的手機有網際網路連線";
33 | "about.connection-deep-link" = "管理";
34 | "about.legal.link" = "隱私權與條款";
35 | "button.footer.email.postfix" = "了解更多";
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Result+Queries.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Result.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | #if swift(<5.0)
9 | /// An object to model success and failure states from an API.
10 | public enum Result {
11 | /// The operation was successful. The passed associated value is the result that was returned from the API.
12 | case success(ValueType)
13 |
14 | /// The operation failed. The passed associated value is the error that was encountered.
15 | case failure(ErrorType)
16 |
17 | /// An alias for `ValueType`.
18 | typealias Success = ValueType
19 |
20 | /// An alias for `ErrorType`.
21 | typealias Failure = ErrorType
22 | }
23 | #endif
24 |
25 | extension Result {
26 | /// The associated value for `success`es. Returns `nil` on `failure`.
27 | var value: Success? {
28 | switch self {
29 | case let .success(value):
30 | return value
31 | case .failure:
32 | return nil
33 | }
34 | }
35 |
36 | /// The associated error for `failure`s. Returns `nil` on `success`.
37 | var error: Failure? {
38 | switch self {
39 | case .success:
40 | return nil
41 | case.failure(let error):
42 | return error
43 | }
44 | }
45 |
46 | /// Whether the receiver is the `.success` case.
47 | var isSuccess: Bool {
48 | switch self {
49 | case .success:
50 | return true
51 | case .failure:
52 | return false
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/IFTTT SDK/SFWebServiceAuthentication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SFWebServiceAuthentication.swift
3 | // IFTTTConnectSDK
4 | //
5 | // Copyright © 2021 IFTTT. All rights reserved.
6 | //
7 |
8 | import SafariServices
9 |
10 | /// Wraps an `SFAuthenticationSession`. Used to authenticate web services up to (not including) iOS 12.
11 | @available(iOS 11.0, *)
12 | @available(iOS, deprecated: 12, obsoleted: 13, message: "API is deprecated in iOS 12 and obsoleted in iOS 13")
13 | final class SFWebService: WebServiceAuthentication {
14 |
15 | /// The backing `SFAuthenticationSession` object.
16 | private var session: SFAuthenticationSession?
17 |
18 | @discardableResult
19 | override func start(with parameters: Parameters, completionHandler: @escaping (Result) -> Void) -> Bool {
20 | let sfAuthenticationSession = SFAuthenticationSession(url: parameters.url,
21 | callbackURLScheme: parameters.callbackURLScheme) { url, error in
22 | guard error == nil, let url = url else {
23 | completionHandler(.failure(.userCanceled))
24 | return
25 | }
26 | completionHandler(.success(url))
27 | }
28 | self.session = sfAuthenticationSession
29 | return sfAuthenticationSession.start()
30 | }
31 |
32 | override func cancel() {
33 | session?.cancel()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/IFTTT SDK/Selectable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Selectable.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class Selectable: NSObject, UIGestureRecognizerDelegate {
11 | var isEnabled: Bool {
12 | get { return gesture.isEnabled }
13 | set { gesture.isEnabled = newValue }
14 | }
15 |
16 | /// Customize the behavior when the Selectable view is highlighted
17 | var performHighlight: SelectGestureRecognizer.HighlightHandler? {
18 | get {
19 | return gesture.performHighlight
20 | }
21 | set {
22 | gesture.performHighlight = newValue
23 | }
24 | }
25 |
26 | private let gesture = SelectGestureRecognizer()
27 | private var onSelect: VoidClosure
28 |
29 | init(_ view: UIView, onSelect: @escaping VoidClosure) {
30 | self.onSelect = onSelect
31 |
32 | super.init()
33 |
34 | gesture.addTarget(self, action: #selector(handleSelect(_:)))
35 | gesture.delaysTouchesBegan = true
36 | gesture.delegate = self
37 |
38 | view.addGestureRecognizer(gesture)
39 | view.isUserInteractionEnabled = true
40 | }
41 |
42 | @objc private func handleSelect(_ gesture: UIGestureRecognizer) {
43 | if gesture.state == .ended {
44 | onSelect()
45 | }
46 | }
47 |
48 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
49 | return true
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/IFTTT SDK/ServiceIconsNetworkController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceIconsNetworkController.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Implementation of `ImageViewNetworkController` to download service icons
11 | class ServiceIconsNetworkController: ImageViewNetworkController {
12 | private let downloader = ImageDownloader()
13 |
14 | /// Prefetch and cache service icon images for this `Connection`.
15 | /// This will be it very unlikely that an image is delayed and visually "pops in".
16 | func prefetchImages(for connection: Connection) {
17 | connection.services.forEach {
18 | downloader.downloadImage(url: $0.templateIconURL, { _ in})
19 | }
20 | }
21 |
22 | private var currentRequests = [UIImageView : URLSessionDataTask]()
23 |
24 | func setImage(with url: URL?, for imageView: UIImageView) {
25 | if let existingTask = currentRequests[imageView] {
26 | if existingTask.originalRequest?.url == url {
27 | return // This is a duplicate request
28 | } else {
29 | // The image url was changed
30 | existingTask.cancel()
31 | currentRequests[imageView] = nil
32 | }
33 | }
34 |
35 | imageView.image = nil
36 | if let url = url {
37 | let task = downloader.downloadImage(url: url) { (result) in
38 | switch result {
39 | case .success(let image):
40 | imageView.image = image
41 | case .failure:
42 | // Images that fail to load are left blank
43 | // Since we attempt to precatch images, this is actually the second try
44 | break
45 | }
46 | }
47 | currentRequests[imageView] = task
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/IFTTT SDK/Set+Helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Set+Helpers.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Set {
11 | func map(transform: (Element) -> U) -> Set {
12 | return Set(lazy.map(transform))
13 | }
14 |
15 | func compactMap(_ transform: (Element) -> U?) -> Set {
16 | return Set(lazy.compactMap(transform))
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/IFTTT SDK/SynchronizationTriggerEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SynchronizationSubscriber.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Describes a closure that's invoked after a sync subscriber finishes its task
11 | ///
12 | /// - Parameters:
13 | /// - backgroundFetchResult: An instance of `UIBackgroundFetchResult` which describes ahat the result of the fetch result is.
14 | /// - authenticationFailure: A bool which determines whether or not the sync operation resulted in an authentication failure.
15 | typealias BackgroundFetchClosure = (UIBackgroundFetchResult, Bool) -> Void
16 |
17 | /// Defines an event which should trigger a background synchronization
18 | struct SynchronizationTriggerEvent {
19 |
20 | /// The source of the synchronization trigger event
21 | let source: SynchronizationSource
22 |
23 | /// The associated background fetch completion handler. This relates to background handler for push notifications.
24 | let completionHandler: BackgroundFetchClosure?
25 | }
26 |
--------------------------------------------------------------------------------
/IFTTT SDK/UIKit+ConvenienceInit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIKit+ConvenienceInit.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UILabel {
11 | convenience init(_ text: String? = nil, _ configure: ((UILabel) -> Void)? = nil) {
12 | self.init()
13 | self.text = text
14 | self.adjustsFontForContentSizeCategory = true
15 | configure?(self)
16 | }
17 | convenience init(_ attributedText: NSAttributedString?, _ configure: ((UILabel) -> Void)? = nil) {
18 | self.init()
19 | self.attributedText = attributedText
20 | self.adjustsFontForContentSizeCategory = true
21 | configure?(self)
22 | }
23 | }
24 |
25 | extension UIStackView {
26 | convenience init(_ views: [UIView] = [], _ configure: ((UIStackView) -> Void)) {
27 | self.init(arrangedSubviews: views)
28 | configure(self)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/IFTTT SDK/URLComponents+email.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLComponents+email.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension URLComponents {
11 | func fixingEmailEncoding() -> URLComponents {
12 | var components = self
13 | components.fixEmailEncoding()
14 | return components
15 | }
16 |
17 | mutating func fixEmailEncoding() {
18 | // We need to manually encode `+` characters in a user's e-mail because `+` is a valid character that represents a space in a url query. E-mail's with spaces are not valid.
19 | let updatedQuery = percentEncodedQuery?.addingPercentEncoding(withAllowedCharacters: .emailEncodingPassthrough)
20 | self.percentEncodedQuery = updatedQuery
21 | }
22 | }
23 |
24 | private extension CharacterSet {
25 |
26 | /// This allows '+' character to passthrough for sending an email address as a url parameter.
27 | static let emailEncodingPassthrough = CharacterSet(charactersIn: "+").inverted
28 | }
29 |
--------------------------------------------------------------------------------
/IFTTT SDK/URLRequest+CommonValues.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLRequest+CommonValues.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension URLRequest {
11 |
12 | struct HeaderFields {
13 | static let inviteCode = "IFTTT-Invite-Code"
14 | static let sdkVersion = "IFTTT-SDK-Version"
15 | static let sdkPlatform = "IFTTT-SDK-Platform"
16 | static let sdkAnonymousId = "IFTTT-SDK-Anonymous-Id"
17 | }
18 |
19 | mutating func addIftttServiceToken(_ token: String) {
20 | let tokenString = "Bearer \(token)"
21 | addValue(tokenString, forHTTPHeaderField: "Authorization")
22 | }
23 |
24 | mutating func addIftttInviteCode(_ code: String) {
25 | addValue(code, forHTTPHeaderField: HeaderFields.inviteCode)
26 | }
27 |
28 | mutating func addVersionTracking() {
29 | setValue(API.sdkVersion, forHTTPHeaderField: HeaderFields.sdkVersion)
30 | setValue(API.sdkPlatform, forHTTPHeaderField: HeaderFields.sdkPlatform)
31 | setValue(API.anonymousId, forHTTPHeaderField: HeaderFields.sdkAnonymousId)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/IFTTT SDK/URLSession+JSONTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSession+JSONTask.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | extension URLSession {
11 | static let connectionURLSession: URLSession = {
12 | let configuration = URLSessionConfiguration.ephemeral
13 | configuration.httpAdditionalHeaders = ["Accept" : "application/json"]
14 | return URLSession(configuration: configuration)
15 | }()
16 |
17 | func jsonTask(with urlRequest: URLRequest, _ completion: @escaping (Parser, HTTPURLResponse?, Error?) -> Void) -> URLSessionDataTask {
18 | return dataTask(with: urlRequest) { (data, response, error) in
19 | DispatchQueue.main.async {
20 | completion(Parser(content: data), response as? HTTPURLResponse, error)
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/IFTTT SDK/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | struct User {
11 | enum Id {
12 | case username(String), email(String)
13 |
14 | var value: String {
15 | switch self {
16 | case .username(let username):
17 | return username
18 | case .email(let email):
19 | return email
20 | }
21 | }
22 | }
23 |
24 | /// We know something about a user when they begin a connect button flow.
25 | /// This type tells the `ConnectionNetworkController` how to identify the user's IFTTT account.
26 | /// If we have a token, there must be an associate account. If we only have an email, they could be new to IFTTT.
27 | /// The `ConnectionNetworkController` resolves this to a `User` instance.
28 | ///
29 | /// - token: The user is already logged in to IFTTT and we have an IFTTT service user token
30 | /// - email: The user is not logged in but we know their email address
31 | enum LookupMethod {
32 | case token(String), email(String)
33 | }
34 |
35 | let id: User.Id
36 | let isExistingUser: Bool
37 | }
38 |
39 | extension String {
40 |
41 | /// Checks whether a string contains a e-mail address.
42 | ///
43 | /// - Returns: A `Bool` on whether a e-mail address is present.
44 | /// - Throws: If a `NSDataDetector` can not be created.
45 | var isValidEmail: Bool {
46 | let types: NSTextCheckingResult.CheckingType = [.link]
47 | guard let detector = try? NSDataDetector(types: types.rawValue) else {
48 |
49 | // Returning true because the data detectored failed to initalized and we can not validate the e-mail. Erroring on the side of the e-mail being valid in this case.
50 | return true
51 | }
52 |
53 | let range = NSRange(location: 0, length: count)
54 | let matches = detector.matches(in: self, options: [], range: range)
55 |
56 | guard matches.count == 1, let result = matches.first, range == result.range else { return false }
57 | return result.url?.scheme == "mailto"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/IFTTT SDK/WebServiceAuthentication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebServiceAuthentication.swift
3 | // IFTTT SDK
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import AuthenticationServices
9 |
10 | /// Describes the error reason for a failed `WebServiceAuthentication`
11 | ///
12 | /// - userCanceled: The user cancelled the authentication session.
13 | /// - failed: The authentication failed for some reason.
14 | /// - invalidResponse: The response returned by the web service was invalid.
15 | /// - notHandled: The error wasn't handled by the web service.
16 | /// - presentationContextInvalid: The presentation content provided was invalid.
17 | /// - unknown: Some unknown error ocurred when authenticating with the web service.
18 | enum AuthenticationError: Error {
19 | case userCanceled
20 | case failed
21 | case invalidResponse
22 | case notHandled
23 | case presentationContextInvalid
24 | case unknown
25 | }
26 |
27 | /// Describes a generic method of authenticating with a service
28 | protocol ServiceAuthentication {
29 | /// The parameters to pass to authenticate with the service
30 | associatedtype Parameters
31 | /// The object type on a successful service authentication
32 | associatedtype Completion
33 | /// THe error type on a non-successful service authentication
34 | associatedtype ErrorType: Error
35 |
36 | /// Describes a closure that gets invoked on success/failure of a service authentication
37 | typealias AuthenticationSessionClosure = ((Result) -> Void)
38 |
39 | /// Starts the service authentication.
40 | /// - Parameters:
41 | /// - parameters: An instance of `Parameters` which can be used in authenticating against the service.
42 | /// - completionHandler: The closure to invoke on a success or failure response from the service.
43 | /// - Returns: A `Bool` value as to whether or not the service authentication was started or not.
44 | func start(with parameters: Parameters, completionHandler: @escaping AuthenticationSessionClosure) -> Bool
45 |
46 | /// Cancels the service authentication.
47 | func cancel()
48 | }
49 |
50 | /// A basic OAuth web service authentication object.
51 | class WebServiceAuthentication: ServiceAuthentication {
52 |
53 | /// The parameters used when authenticating against a web service
54 | struct WebServiceAuthenticationParameters {
55 | /// The URL to use in authenticating the service
56 | let url: URL
57 | /// The callback url scheme which the service uses to pass back any data
58 | let callbackURLScheme: String?
59 | /// Determines whether or not the session should be ephemeral or not. Not all service authentication types support this
60 | let prefersEphemeralWebBrowserSession: Bool
61 | }
62 |
63 | typealias Parameters = WebServiceAuthenticationParameters
64 | typealias Completion = URL
65 | typealias ErrorType = AuthenticationError
66 |
67 | @discardableResult
68 | func start(with parameters: Parameters, completionHandler: @escaping AuthenticationSessionClosure) -> Bool {
69 | fatalError("This class must be subclassed.")
70 | }
71 |
72 | func cancel() {
73 | fatalError("This class must be subclassed.")
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/IFTTTConnectSDK.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = "IFTTTConnectSDK"
3 | spec.version = "2.8.0"
4 | spec.summary = "Allows your users to activate programmable IFTTT Connections directly in your app."
5 | spec.description = <<-DESC
6 | - Easily authenticate your services to IFTTT through the Connect Button
7 | - Configure the Connect Button through code or through interface builder with IBDesignable
8 | - Configure the ConnectButtonController to handle the Connection activation flow
9 | DESC
10 | spec.homepage = "https://github.com/IFTTT/ConnectSDK-iOS"
11 | spec.license = { :type => "MIT", :file => "LICENSE" }
12 | spec.author = { "Siddharth Sathyam" => "siddharth@ifttt.com" }
13 | spec.platform = :ios, "10.0"
14 | spec.swift_version = "5.0"
15 | spec.source = { :git => "https://github.com/IFTTT/ConnectSDK-iOS.git", :tag => "#{spec.version}" }
16 | spec.source_files = "IFTTT SDK/**/*.swift"
17 | spec.resource_bundles = {
18 | 'IFTTTConnectSDK' => ['IFTTT SDK/Resources/Assets.xcassets'],
19 | 'IFTTTConnectSDK-Localizations' => ['IFTTT SDK/Resources/*.strings']
20 | }
21 | end
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 IFTTT
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "ConnectSDK-iOS",
6 | platforms: [
7 | .iOS(.v10)
8 | ],
9 | products: [
10 | .library(name: "IFTTTConnectSDK", targets: ["IFTTTConnectSDK"])
11 | ],
12 | targets: [
13 | .target(
14 | name: "IFTTTConnectSDK",
15 | path: "IFTTT SDK",
16 | exclude: ["Info.plist"],
17 | resources: [.process("Resources")]
18 | ),
19 | .testTarget(
20 | name: "SDKHostAppTests",
21 | dependencies: ["IFTTTConnectSDK"],
22 | path: "SDKHostAppTests",
23 | exclude: ["Info.plist"],
24 | resources: [.process("fetch_connection_response.json")]
25 | )
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/SDKHostApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SDKHostApp
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
13 | // Override point for customization after application launch.
14 | return true
15 | }
16 |
17 | // MARK: UISceneSession Lifecycle
18 |
19 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
20 | // Called when a new scene session is being created.
21 | // Use this method to select a configuration to create the new scene with.
22 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
23 | }
24 |
25 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
26 | // Called when the user discards a scene session.
27 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
28 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
29 | }
30 |
31 |
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/SDKHostApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SDKHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SDKHostApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SDKHostApp/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/SDKHostApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // SDKHostApp
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ContentView: View {
11 | var body: some View {
12 | Text("Hello, world!")
13 | .padding()
14 | }
15 | }
16 |
17 | struct ContentView_Previews: PreviewProvider {
18 | static var previews: some View {
19 | ContentView()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SDKHostApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 |
37 |
38 |
39 |
40 | UIApplicationSupportsIndirectInputEvents
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UISupportedInterfaceOrientations~ipad
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationPortraitUpsideDown
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/SDKHostApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SDKHostApp/SDKHostApp.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | keychain-access-groups
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SDKHostApp/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // SDKHostApp
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import UIKit
9 | import SwiftUI
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 |
21 | // Create the SwiftUI view that provides the window contents.
22 | let contentView = ContentView()
23 |
24 | // Use a UIHostingController as window root view controller.
25 | if let windowScene = scene as? UIWindowScene {
26 | let window = UIWindow(windowScene: windowScene)
27 | window.rootViewController = UIHostingController(rootView: contentView)
28 | self.window = window
29 | window.makeKeyAndVisible()
30 | }
31 | }
32 |
33 | func sceneDidDisconnect(_ scene: UIScene) {
34 | // Called as the scene is being released by the system.
35 | // This occurs shortly after the scene enters the background, or when its session is discarded.
36 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
37 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
38 | }
39 |
40 | func sceneDidBecomeActive(_ scene: UIScene) {
41 | // Called when the scene has moved from an inactive state to an active state.
42 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
43 | }
44 |
45 | func sceneWillResignActive(_ scene: UIScene) {
46 | // Called when the scene will move from an active state to an inactive state.
47 | // This may occur due to temporary interruptions (ex. an incoming phone call).
48 | }
49 |
50 | func sceneWillEnterForeground(_ scene: UIScene) {
51 | // Called as the scene transitions from the background to the foreground.
52 | // Use this method to undo the changes made on entering the background.
53 | }
54 |
55 | func sceneDidEnterBackground(_ scene: UIScene) {
56 | // Called as the scene transitions from the foreground to the background.
57 | // Use this method to save data, release shared resources, and store enough scene-specific state information
58 | // to restore the scene back to its current state.
59 | }
60 |
61 |
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/SDKHostAppTests/ArrayHelpersTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayHelpersTests.swift
3 | // SDKHostAppTests
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import XCTest
9 | import CoreLocation
10 |
11 | @testable import IFTTTConnectSDK
12 |
13 | class ArrayHelpersTests: XCTestCase {
14 | func testClosestRegionsToLocation() throws {
15 | let regions = LocationTestHelpers.generateRegions(withStartCoordinate: LocationTestHelpers.IFTTTCenterCoordinate,
16 | count: 50,
17 | radius: 100)
18 | let iftttLocation = LocationTestHelpers.IFTTTCenterCoordinate
19 |
20 | let twentyClosestRegions = regions
21 | .shuffled()
22 | .closestRegions(to: iftttLocation,
23 | count: 20)
24 |
25 | XCTAssert(regions.keepFirst(20) == twentyClosestRegions)
26 |
27 | let thirtyClosestRegions = regions
28 | .shuffled()
29 | .closestRegions(to: iftttLocation,
30 | count: 30)
31 | XCTAssert(regions.keepFirst(30) == thirtyClosestRegions)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SDKHostAppTests/CLRegion+Parsing_spec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLRegion+Parsing_spec.swift
3 | // IFTTT SDKTests
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import XCTest
9 | import CoreLocation
10 |
11 | @testable import IFTTTConnectSDK
12 |
13 | class CLRegion_Parsing_spec: XCTestCase {
14 |
15 | func testIFTTTRegion() {
16 | let iftttRegion = CLCircularRegion(center: .init(latitude: 0.0, longitude: 0.0), radius: 100, identifier: "ifttt_somecoolidentifier")
17 | let nonIftttRegion = CLCircularRegion(center: .init(latitude: 10.0, longitude: 10.0), radius: 100, identifier: "somecoolnoniftttidentifier")
18 |
19 | assert(iftttRegion.isIFTTTRegion)
20 | assert(!nonIftttRegion.isIFTTTRegion)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SDKHostAppTests/EventPublisherTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventPublisherTests.swift
3 | // IFTTT SDKTests
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import XCTest
9 |
10 | @testable import IFTTTConnectSDK
11 |
12 | class EventPublisherTests: XCTestCase {
13 | var publisher: EventPublisher!
14 |
15 | override func setUp() {
16 | publisher = EventPublisher()
17 | }
18 |
19 | func testAddSubscriber() {
20 | let addSubscriberExpectation = expectation(description: "verify_added_subscriber_gets_invoked_with_value")
21 | addSubscriberExpectation.assertForOverFulfill = true
22 | publisher.addSubscriber { value in
23 | addSubscriberExpectation.fulfill()
24 | }
25 | publisher.onNext(1)
26 | wait(for: [addSubscriberExpectation], timeout: 1.0)
27 | }
28 |
29 | func testPublish() {
30 | let expectationCount = 100
31 | let expectations = (0..
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SDKHostAppTests/String_EmailDataDetectorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+EmailDataDetectorTests.swift
3 | // IFTTT SDKTests
4 | //
5 | // Copyright © 2019 IFTTT. All rights reserved.
6 | //
7 |
8 | import XCTest
9 | @testable import IFTTTConnectSDK
10 |
11 | class StringEmailDataDetectorTests: XCTestCase {
12 | let emailString = "hello@ifttt.com"
13 | let nonEmailString = "Ten minutes left to execute our plan. Where’s everyone else? Playing Spiderman."
14 | let blockMixedText = "Ten minutes left to execute our plan. Where’s everyone else? Playing Spiderman. Reach out to hello@ifttt.com"
15 | let anotherBlockMixedText = "hello@ifttt.com Ten minutes left to execute our plan. Where’s everyone else? Playing Spiderman."
16 | let multipleEmails = "hello@ifttt.com, feedback@ifttt.com"
17 | let nonTraditionalEmailAddress = "hello+mike.amu_dsen@ifttt.com"
18 |
19 | func testIsEmail() {
20 | XCTAssertEqual(emailString.isValidEmail, true)
21 | XCTAssertEqual(nonEmailString.isValidEmail, false)
22 | XCTAssertEqual(blockMixedText.isValidEmail, false)
23 | XCTAssertEqual(anotherBlockMixedText.isValidEmail, false)
24 | XCTAssertEqual(multipleEmails.isValidEmail, false)
25 | XCTAssertEqual(nonTraditionalEmailAddress.isValidEmail, true)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/SDKHostAppTests/SynchronizationSchedulerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SynchronizationSchedulerTests.swift
3 | // SDKHostAppTests
4 | //
5 | // Copyright © 2021 IFTTT. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import XCTest
10 |
11 | @testable import IFTTTConnectSDK
12 |
13 | class SynchronizationSchedulerTests: XCTestCase {
14 | private var syncScheduler: SynchronizationScheduler!
15 | private var eventPublisher: EventPublisher!
16 |
17 | override func setUp() {
18 | eventPublisher = .init()
19 | syncScheduler = .init(manager: .init(subscribers: []), triggers: eventPublisher)
20 | }
21 |
22 | override func tearDown() {
23 | eventPublisher = nil
24 | syncScheduler = nil
25 | }
26 |
27 | func test_start() {
28 | syncScheduler.stop()
29 | syncScheduler.start(lifecycleSynchronizationOptions: .all)
30 |
31 | XCTAssertNotNil(syncScheduler.subscriberToken)
32 | XCTAssertTrue(!syncScheduler.applicationLifecycleNotificationCenterTokens.isEmpty)
33 | XCTAssertTrue(!syncScheduler.sdkGeneratedNotificationCenterTokens.isEmpty)
34 | }
35 |
36 | func test_stop() {
37 | syncScheduler.start(lifecycleSynchronizationOptions: .all)
38 | syncScheduler.stop()
39 |
40 | XCTAssertNil(syncScheduler.subscriberToken)
41 | XCTAssertTrue(syncScheduler.applicationLifecycleNotificationCenterTokens.isEmpty)
42 | XCTAssertTrue(syncScheduler.sdkGeneratedNotificationCenterTokens.isEmpty)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SDKHostAppUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SDKHostAppUITests/SDKHostAppUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SDKHostAppUITests.swift
3 | // SDKHostAppUITests
4 | //
5 | // Copyright © 2020 IFTTT. All rights reserved.
6 | //
7 |
8 | import XCTest
9 |
10 | class SDKHostAppUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use recording to get started writing UI tests.
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | func testLaunchPerformance() throws {
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTApplicationLaunchMetric()]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------