├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── Phone App ├── AddressFormViewController.swift ├── AppDelegate.swift ├── Beacon.swift ├── BeaconController.swift ├── Commands.swift ├── PayPalClient.swift ├── PaymentKeychain.swift ├── Resources │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ └── Images.xcassets │ │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── appicon-120-1.png │ │ ├── appicon-120.png │ │ ├── appicon-180.png │ │ ├── appicon-58.png │ │ ├── appicon-80.png │ │ └── appicon-87.png ├── Supporting Files │ ├── BridgingHeader.h │ └── Info.plist ├── ViewController.swift └── WatchButton.entitlements ├── Podfile ├── Podfile.lock ├── README.md ├── Screenshots ├── initial_screen.png └── order_successful.png ├── WatchButton.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Glance - WatchButton WatchKit App.xcscheme │ ├── WatchButton WatchKit App.xcscheme │ └── WatchButton.xcscheme ├── WatchButton.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── WatchButton.xcscmblueprint ├── WatchKit App ├── Base.lproj │ └── Interface.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── watch-172.png │ │ ├── watch-196.png │ │ ├── watch-48.png │ │ ├── watch-55.png │ │ ├── watch-58.png │ │ ├── watch-80.png │ │ ├── watch-87.png │ │ └── watch-88.png └── Info.plist └── WatchKit Extension ├── GlanceController.swift ├── Images.xcassets └── README__ignoredByTemplate__ ├── Info.plist ├── InterfaceController.swift ├── PaymentAlert.swift └── WatchButton WatchKit Extension.entitlements /.gitignore: -------------------------------------------------------------------------------- 1 | # AppCode 2 | .idea 3 | 4 | # OS X 5 | .DS_Store 6 | *.swp 7 | 8 | # Xcode 9 | build/ 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | *.xccheckout 20 | profile 21 | *.moved-aside 22 | DerivedData 23 | *.hmap 24 | *.ipa 25 | 26 | # Bundler 27 | .bundle 28 | 29 | # CocoaPods 30 | Pods/ 31 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 0.39.0' 4 | gem 'cocoapods-keys' 5 | gem 'xcpretty' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | RubyInline (3.12.4) 5 | ZenTest (~> 4.3) 6 | ZenTest (4.11.0) 7 | activesupport (4.2.5) 8 | i18n (~> 0.7) 9 | json (~> 1.7, >= 1.7.7) 10 | minitest (~> 5.1) 11 | thread_safe (~> 0.3, >= 0.3.4) 12 | tzinfo (~> 1.1) 13 | claide (0.9.1) 14 | cocoapods (0.39.0) 15 | activesupport (>= 4.0.2) 16 | claide (~> 0.9.1) 17 | cocoapods-core (= 0.39.0) 18 | cocoapods-downloader (~> 0.9.3) 19 | cocoapods-plugins (~> 0.4.2) 20 | cocoapods-search (~> 0.1.0) 21 | cocoapods-stats (~> 0.6.2) 22 | cocoapods-trunk (~> 0.6.4) 23 | cocoapods-try (~> 0.5.1) 24 | colored (~> 1.2) 25 | escape (~> 0.0.4) 26 | molinillo (~> 0.4.0) 27 | nap (~> 1.0) 28 | xcodeproj (~> 0.28.2) 29 | cocoapods-core (0.39.0) 30 | activesupport (>= 4.0.2) 31 | fuzzy_match (~> 2.0.4) 32 | nap (~> 1.0) 33 | cocoapods-downloader (0.9.3) 34 | cocoapods-keys (1.4.0) 35 | osx_keychain 36 | cocoapods-plugins (0.4.2) 37 | nap 38 | cocoapods-search (0.1.0) 39 | cocoapods-stats (0.6.2) 40 | cocoapods-trunk (0.6.4) 41 | nap (>= 0.8, < 2.0) 42 | netrc (= 0.7.8) 43 | cocoapods-try (0.5.1) 44 | colored (1.2) 45 | escape (0.0.4) 46 | fuzzy_match (2.0.4) 47 | i18n (0.7.0) 48 | json (1.8.3) 49 | minitest (5.8.3) 50 | molinillo (0.4.0) 51 | nap (1.0.0) 52 | netrc (0.7.8) 53 | osx_keychain (1.0.1) 54 | RubyInline (~> 3) 55 | thread_safe (0.3.5) 56 | tzinfo (1.2.2) 57 | thread_safe (~> 0.1) 58 | xcodeproj (0.28.2) 59 | activesupport (>= 3) 60 | claide (~> 0.9.1) 61 | colored (~> 1.2) 62 | xcpretty (0.1.10) 63 | 64 | PLATFORMS 65 | ruby 66 | 67 | DEPENDENCIES 68 | cocoapods (~> 0.39.0) 69 | cocoapods-keys 70 | xcpretty 71 | 72 | BUNDLED WITH 73 | 1.10.6 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Contentful GmbH - https://www.contentful.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all pod 2 | 3 | all: 4 | xcodebuild -workspace WatchButton.xcworkspace \ 5 | -scheme WatchButton -sdk iphonesimulator build CODE_SIGN_IDENTITY=- 6 | 7 | pod: 8 | bundle install 9 | bundle exec pod install --no-repo-update 10 | -------------------------------------------------------------------------------- /Phone App/AddressFormViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddressFormViewController.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 10/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Cube 10 | import Form 11 | import UIKit 12 | 13 | class AddressFormViewController: FORMViewController { 14 | static let DefaultAddress = Address(firstName: "YOLO Bert", lastName: "Thiefenthaler", streetName: "Ritterstr.", streetNumber: "23", postalCode: "10249", city: "Berlin", country: "DE") 15 | 16 | required init(coder aDecoder: NSCoder) { 17 | fatalError("Not supported.") 18 | } 19 | 20 | private static func group(fields: [[String:AnyObject]]) -> [String:AnyObject] { 21 | return [ "id": "group-id", "title": "Address", "sections": [ [ "id": "section-0", "fields": fields ] ] ] 22 | } 23 | 24 | private static func addressJSON() -> [[String:AnyObject]] { 25 | let keys = DefaultAddress.toDictionary().keys.sort() { $0 < $1 } 26 | let fields: [[String:AnyObject]] = keys.map { (key) in 27 | return [ "id": key, "title": key, "type": "text" ] 28 | } 29 | 30 | return [ group(fields) ] 31 | } 32 | 33 | override class func initialize() { 34 | FORMDefaultStyle.applyStyle() 35 | } 36 | 37 | init() { 38 | super.init(JSON: self.dynamicType.addressJSON(), andInitialValues: self.dynamicType.DefaultAddress.toDictionary(), disabled: false) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Phone App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Cube 10 | import Keys 11 | import MMWormhole 12 | import WatchConnectivity 13 | import UIKit 14 | 15 | // Change the used sphere.io project here 16 | let SphereIOProject = "ecomhack-demo-67" 17 | 18 | private extension Array { 19 | func randomItem() -> Element { 20 | let index = Int(arc4random_uniform(UInt32(self.count))) 21 | return self[index] 22 | } 23 | } 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate { 27 | private let beaconController = BeaconController() 28 | private let keys = WatchbuttonKeys() 29 | private var sphereClient: SphereIOClient! 30 | private let wormhole = MMWormhole(applicationGroupIdentifier: AppGroupIdentifier, optionalDirectory: DirectoryIdentifier) 31 | 32 | var selectedProduct: [String:AnyObject]? 33 | var window: UIWindow? 34 | 35 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 36 | WCSession.defaultSession().delegate = self 37 | WCSession.defaultSession().activateSession() 38 | 39 | // TODO: WCSession.defaultSession().reachable 40 | 41 | PayPalMobile.initializeWithClientIdsForEnvironments([ PayPalEnvironmentSandbox: WatchbuttonKeys().payPalSandboxClientId()]) 42 | 43 | beaconController.beaconCallback = { (beacon, _) in 44 | self.wormhole.passMessageObject(true, identifier: Reply.BeaconRanged.rawValue) 45 | } 46 | 47 | beaconController.outOfRangeCallback = { 48 | self.wormhole.passMessageObject(false, identifier: Reply.BeaconRanged.rawValue) 49 | } 50 | 51 | beaconController.refresh() 52 | return true 53 | } 54 | 55 | func initializeSphereClient() { 56 | if sphereClient == nil { 57 | sphereClient = SphereIOClient(clientId: keys.sphereIOClientId(), clientSecret: keys.sphereIOClientSecret(), project: SphereIOProject) 58 | } 59 | } 60 | 61 | func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) { 62 | if let command = message[CommandIdentifier] as? String { 63 | handleCommand(command, replyHandler) 64 | } else { 65 | fatalError("Invalid WatchKit extension request :(") 66 | } 67 | 68 | // Keep the phone app running a bit for demonstration purposes 69 | UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler() {} 70 | } 71 | 72 | // MARK: - Helpers 73 | 74 | func fetchSelectedProduct(completion: () -> ()) { 75 | initializeSphereClient() 76 | 77 | if selectedProduct != nil { 78 | completion() 79 | return 80 | } 81 | 82 | sphereClient.fetchProductData() { (result) in 83 | if let value = result.value, results = value["results"] as? [[String:AnyObject]] { 84 | self.selectedProduct = results.randomItem() 85 | completion() 86 | } else { 87 | fatalError("Failed to retrieve products from Sphere.IO") 88 | } 89 | } 90 | } 91 | 92 | private func handleCommand(command: String, _ reply: (([String : AnyObject]) -> Void)) { 93 | initializeSphereClient() 94 | 95 | switch(Command(rawValue: command)!) { 96 | case .GetProduct: 97 | fetchSelectedProduct() { 98 | if let product = self.selectedProduct { 99 | reply([Reply.Product.rawValue: product]) 100 | } 101 | return 102 | } 103 | break 104 | case .MakeOrder: 105 | fetchSelectedProduct() { 106 | self.sphereClient.quickOrder(product: self.selectedProduct!, to:retrieveShippingAddress()) { (result) in 107 | if let order = result.value { 108 | let pp = Product(self.selectedProduct!) 109 | let amount = pp.price["amount"]! 110 | let currency = pp.price["currency"]! 111 | 112 | let client = PayPalClient(clientId: self.keys.payPalSandboxClientId(), clientSecret: self.keys.payPalSandboxClientSecret(), code: retrieveRefreshToken()) 113 | 114 | client.pay(retrievePaymentId(), currency, amount) { (paid) in 115 | reply([Reply.Paid.rawValue: paid]) 116 | 117 | self.wormhole.passMessageObject(paid, identifier: Reply.Paid.rawValue) 118 | self.sphereClient.setPaymentState(paid ? .Paid : .Failed, forOrder: order) { (result) in 119 | //println("Payment state result: \(result)") 120 | 121 | if let order = result.value { 122 | self.sphereClient.setState(.Complete, forOrder: order) { (result) in 123 | print("Ordered successfully.") 124 | } 125 | } else { 126 | fatalError("Failed to set order to complete state.") 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | break 134 | default: 135 | break 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Phone App/Beacon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Beacon.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 10/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | struct Beacon { 10 | let identifier: String 11 | let major: NSNumber 12 | let minor: NSNumber 13 | let name: String 14 | let uuid: String 15 | } 16 | -------------------------------------------------------------------------------- /Phone App/BeaconController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BeaconController.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 10/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import CoreLocation 10 | 11 | typealias BeaconCallback = (beacon: Beacon, accuracy: CLLocationAccuracy) -> Void 12 | typealias OutOfRangeCallback = () -> Void 13 | 14 | class BeaconController: NSObject, CLLocationManagerDelegate { 15 | static let estimoteUUID = "B9407F30-F5F8-466E-AFF9-25556B57FE6D" 16 | lazy var locationManager = CLLocationManager() 17 | 18 | var beaconCallback: BeaconCallback = { (beacon, accuracy) in 19 | NSLog(String(format:"Beacon: %@, Accuracy: %.2fm", beacon.name, accuracy)) 20 | } 21 | var outOfRangeCallback: OutOfRangeCallback = { } 22 | 23 | // TODO: Beacons shouldn't be hard-coded 24 | var beacons = [Beacon(identifier: estimoteUUID, major: 62556, minor: 7826, name: "TestBeacon", uuid: estimoteUUID)] 25 | 26 | var regions: [CLBeaconRegion] = [CLBeaconRegion]() { 27 | didSet { 28 | self.locationManager.delegate = self 29 | self.locationManager.requestAlwaysAuthorization() 30 | 31 | let _ = self.regions.map({ (region) -> Void in self.locationManager.startRangingBeaconsInRegion(region) }) 32 | } 33 | } 34 | 35 | func refresh() { 36 | regions = beacons.map({ (beacon: Beacon) -> CLBeaconRegion in 37 | //NSLog("Will range beacon %@ with major %@, minor %@", beacon.name, beacon.major, beacon.minor) 38 | return CLBeaconRegion(proximityUUID: NSUUID(UUIDString: beacon.uuid)!, major: CLBeaconMajorValue(beacon.major.integerValue), minor: CLBeaconMinorValue(beacon.minor.integerValue), identifier: beacon.identifier) 39 | }) 40 | } 41 | 42 | func stop() { 43 | let _ = regions.map({ (region) -> Void in self.locationManager.stopRangingBeaconsInRegion(region) }) 44 | } 45 | 46 | // MARK: CLLocationManagerDelegate 47 | 48 | func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) { 49 | let beacon = self.beacons[regions.indexOf(region)!] 50 | 51 | let filteredBeacons = beacons.filter({ (beacon: CLBeacon) -> Bool in return beacon.proximity == .Immediate }) 52 | 53 | //NSLog("Ranged beacon %@ as Immediate", beacon.name) 54 | 55 | if (filteredBeacons.count > 0) { 56 | let accuracy = (beacons.first)!.accuracy 57 | beaconCallback(beacon: beacon, accuracy: accuracy) 58 | } else { 59 | outOfRangeCallback() 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Phone App/Commands.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Commands.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public let AppGroupIdentifier = "group.vu0.org.WatchButton" 12 | public let CommandIdentifier = "org.vu0.command" 13 | public let DirectoryIdentifier = "WatchButton" 14 | 15 | public enum Command : String { 16 | case GetProduct = "GetProducts" 17 | case MakeOrder = "MakeOrder" 18 | case Nothing = "Nothing" 19 | } 20 | 21 | public enum Reply : String { 22 | case BeaconRanged = "BeaconRanged" 23 | case Paid = "Paid" 24 | case Product = "Product" 25 | } 26 | -------------------------------------------------------------------------------- /Phone App/PayPalClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PayPalClient.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | 11 | // Somehow using `NSURLAuthenticationChallenge` didn't work against the PayPal API, either 😭 12 | private struct AuthRequest: URLRequestConvertible { 13 | private let clientId: String 14 | private let clientSecret: String 15 | private let code: String 16 | private let grantAttributeName: String 17 | private let grantType: String 18 | 19 | var URLRequest: NSMutableURLRequest { 20 | if let URL = NSURL(string: "https://api.sandbox.paypal.com/v1/oauth2/token") { 21 | let URLRequest = NSMutableURLRequest(URL: URL) 22 | URLRequest.HTTPMethod = Method.POST.rawValue 23 | 24 | let parameters = [ "grant_type": grantType, "response_type": "token", "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", grantAttributeName: code ] 25 | 26 | let auth = String(format: "%@:%@", clientId, clientSecret).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! 27 | let header = auth.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) 28 | URLRequest.setValue("Basic \(header)", forHTTPHeaderField: "Authorization") 29 | 30 | let encoding = Alamofire.ParameterEncoding.URL 31 | return encoding.encode(URLRequest, parameters: parameters).0 32 | } 33 | 34 | fatalError("Broken Authentication URL...") 35 | } 36 | } 37 | 38 | /// Implementation of https://github.com/paypal/PayPal-iOS-SDK/blob/master/docs/future_payments_server.md because we want no server. 😎 39 | public class PayPalClient { 40 | let baseURL = "https://api.sandbox.paypal.com/v1" 41 | let clientId: String 42 | let clientSecret: String 43 | let code: String 44 | let metadataId: String 45 | var refreshToken: String? 46 | var token: String? 47 | 48 | /// For inital OAuth, pass futurePaymentCode and metadataId. When refreshing, pass refreshToken. 49 | public init(clientId: String, clientSecret: String, code: String, metadataId: String = "") { 50 | self.clientId = clientId 51 | self.clientSecret = clientSecret 52 | self.code = code 53 | self.metadataId = metadataId 54 | } 55 | 56 | private func createActualPayment(description: String, _ currency: String, _ amount: String, _ completion: (paymentId: String) -> Void) { 57 | let parameters: [String:AnyObject] = [ "intent":"authorize", "payer":["payment_method":"paypal"], "transactions": [ [ "amount": [ "currency": currency, "total": amount ], "description": description ] ]] 58 | 59 | Alamofire.request(payPalRequest("payments/payment", .POST, parameters)) 60 | .responseJSON { (_, _, result) in 61 | if let JSON = result.value as? [String:AnyObject], transactions = JSON["transactions"] as? [[String:AnyObject]], relatedResources = transactions.first?["related_resources"] as? [[String:AnyObject]], authorization = relatedResources.first?["authorization"] as? [String:AnyObject], id = authorization["id"] as? String { 62 | completion(paymentId: id) 63 | } else { 64 | fatalError("Did not receive ID for payment") 65 | } 66 | } 67 | } 68 | 69 | private func payPalRequest(endpoint: String, _ method: Alamofire.Method, _ parameters: [String: AnyObject]?) -> URLRequestConvertible { 70 | assert(token != nil, "") 71 | 72 | if let token = token, URL = NSURL(string: "\(baseURL)/\(endpoint)") { 73 | let URLRequest = NSMutableURLRequest(URL: URL) 74 | URLRequest.HTTPMethod = method.rawValue 75 | 76 | URLRequest.setValue(metadataId, forHTTPHeaderField: "PayPal-Client-Metadata-Id") 77 | URLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") 78 | 79 | let encoding = Alamofire.ParameterEncoding.JSON 80 | return encoding.encode(URLRequest, parameters: parameters).0 81 | } 82 | 83 | fatalError("Could not create valid request.") 84 | } 85 | 86 | private func requestOAuthToken(completion: () -> ()) { 87 | let grantAttributeName = metadataId == "" ? "refresh_token" : "code" 88 | let grantType = metadataId == "" ? "refresh_token" : "authorization_code" 89 | 90 | Alamofire.request(AuthRequest(clientId: clientId, clientSecret: clientSecret, code: code, grantAttributeName: grantAttributeName, grantType: grantType)) 91 | .responseJSON { (_, _, result) in 92 | if let JSON = result.value as? [String:AnyObject] { 93 | if let refreshToken = JSON["refresh_token"] as? String { 94 | self.refreshToken = refreshToken 95 | } 96 | 97 | if let token = JSON["access_token"] as? String { 98 | self.token = token 99 | completion() 100 | } else { 101 | fatalError("No `access_token` received: \(JSON)") 102 | } 103 | } 104 | } 105 | } 106 | 107 | public func createPayment(description: String, _ currency: String, _ amount: String, completion: (paymentId: String) -> Void) { 108 | requestOAuthToken() { 109 | self.createActualPayment(description, currency, amount, completion) 110 | } 111 | } 112 | 113 | public func pay(paymentId: String, _ currency: String, _ amount: String, completion: (paid: Bool) -> Void) { 114 | requestOAuthToken() { 115 | let URL = "payments/authorization/\(paymentId)/capture" 116 | let parameters: [String: AnyObject] = [ "amount": [ "currency": currency, "total": amount ], "is_final_capture": true] 117 | 118 | Alamofire.request(self.payPalRequest(URL, .POST, parameters)) 119 | .responseJSON { (_, _, result) in 120 | //println(JSON) 121 | 122 | if let JSON = result.value as? [String:AnyObject], _ = JSON["amount"] as? [String:AnyObject] { 123 | completion(paid: true) 124 | } else { 125 | completion(paid: false) 126 | } 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Phone App/PaymentKeychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaymentKeychain.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Cube 10 | import KeychainAccess 11 | 12 | let WBKeychainId = "org.vu0.WatchButton.PayPal.PaymentId" 13 | let WBKeychainRefreshToken = "org.vu0.WatchButton.PayPal.RefreshToken" 14 | let WBKeychainService = "org.vu0.WatchButton" 15 | 16 | func retrieveShippingAddress() -> Address { 17 | return AddressFormViewController.DefaultAddress 18 | } 19 | 20 | func retrieveRefreshToken() -> String { 21 | let keychain = Keychain(service: WBKeychainService) 22 | return keychain[WBKeychainRefreshToken] ?? "" 23 | } 24 | 25 | func retrievePaymentId() -> String { 26 | let keychain = Keychain(service: WBKeychainService) 27 | return keychain[WBKeychainId] ?? "" 28 | } 29 | 30 | func storeRefreshToken(refreshToken: String) { 31 | let keychain = Keychain(service: WBKeychainService) 32 | keychain[WBKeychainRefreshToken] = refreshToken 33 | } 34 | 35 | func storePaymentId(paymentId: String) { 36 | let keychain = Keychain(service: WBKeychainService) 37 | keychain[WBKeychainId] = paymentId 38 | } 39 | -------------------------------------------------------------------------------- /Phone App/Resources/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Phone App/Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Phone App/Resources/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "appicon-58.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "appicon-87.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "appicon-80.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "appicon-120.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "appicon-120-1.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "appicon-180.png", 37 | "scale" : "3x" 38 | } 39 | ], 40 | "info" : { 41 | "version" : 1, 42 | "author" : "xcode" 43 | } 44 | } -------------------------------------------------------------------------------- /Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-120-1.png -------------------------------------------------------------------------------- /Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-120.png -------------------------------------------------------------------------------- /Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-180.png -------------------------------------------------------------------------------- /Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-58.png -------------------------------------------------------------------------------- /Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-80.png -------------------------------------------------------------------------------- /Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Phone App/Resources/Images.xcassets/AppIcon.appiconset/appicon-87.png -------------------------------------------------------------------------------- /Phone App/Supporting Files/BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // BridgingHeader.h 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | #import "PayPalMobile.h" 10 | -------------------------------------------------------------------------------- /Phone App/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ÜberSchnell 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSLocationAlwaysUsageDescription 26 | #yolo 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | NSAppTransportSecurity 42 | 43 | NSAllowsArbitraryLoads 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Phone App/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import Cube 11 | import Keys 12 | import MBProgressHUD 13 | import UIKit 14 | 15 | class ViewController: UIViewController, PayPalFuturePaymentDelegate { 16 | @IBOutlet weak var descriptionLabel: UILabel! 17 | @IBOutlet weak var nameLabel: UILabel! 18 | @IBOutlet weak var priceLabel: UILabel! 19 | @IBOutlet weak var productImageView: UIImageView! 20 | 21 | private var payPalConfiguration: PayPalConfiguration! 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | // Not that pretty -- but that way we localized the Sphere.IO interactions for now 27 | if let delegate = UIApplication.sharedApplication().delegate as? AppDelegate { 28 | delegate.fetchSelectedProduct() { 29 | if let productData = delegate.selectedProduct { 30 | let product = Product(productData) 31 | 32 | self.descriptionLabel.text = product.productDescription 33 | self.nameLabel.text = product.name 34 | 35 | if let amount = product.price["amount"], currency = product.price["currency"] { 36 | self.priceLabel.text = "\(amount) \(currency)" 37 | } 38 | 39 | Alamofire.request(.GET, product.imageUrl).response() { (_, _, data, error) in 40 | if let data = data { 41 | self.productImageView.image = UIImage(data: data) 42 | } 43 | } 44 | } 45 | return 46 | } 47 | } 48 | } 49 | 50 | override func viewWillAppear(animated: Bool) { 51 | super.viewWillAppear(animated) 52 | 53 | PayPalMobile.preconnectWithEnvironment(PayPalEnvironmentSandbox) 54 | } 55 | 56 | // MARK: - Actions 57 | @IBAction private func dismissForm() { 58 | dismissViewControllerAnimated(true, completion: nil) 59 | } 60 | 61 | @IBAction func setUpAddressTapped(sender: UIBarButtonItem) { 62 | let vc = AddressFormViewController() 63 | vc.navigationItem.title = "Personal" 64 | vc.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "dismissForm") 65 | 66 | presentViewController(UINavigationController(rootViewController: vc), animated: true) { } 67 | } 68 | 69 | @IBAction func setUpPaymentTapped(sender: UIBarButtonItem) { 70 | payPalConfiguration = PayPalConfiguration() 71 | 72 | payPalConfiguration.merchantName = "Ultramagnetic Omega Supreme" 73 | payPalConfiguration.merchantPrivacyPolicyURL = NSURL(string:"https://www.omega.supreme.example/privacy") 74 | payPalConfiguration.merchantUserAgreementURL = NSURL(string:"https://www.omega.supreme.example/user_agreement") 75 | 76 | let vc = PayPalFuturePaymentViewController(configuration: payPalConfiguration, delegate: self) 77 | presentViewController(vc, animated: true, completion: nil) 78 | } 79 | 80 | // MARK: - PayPalFuturePaymentDelegate 81 | 82 | func payPalFuturePaymentDidCancel(futurePaymentViewController: PayPalFuturePaymentViewController!) { 83 | dismissViewControllerAnimated(true, completion: nil) 84 | } 85 | 86 | func payPalFuturePaymentViewController(futurePaymentViewController: PayPalFuturePaymentViewController!, didAuthorizeFuturePayment futurePaymentAuthorization: [NSObject : AnyObject]!) { 87 | dismissViewControllerAnimated(true, completion: nil) 88 | MBProgressHUD.showHUDAddedTo(view, animated: true) 89 | 90 | if let futurePaymentAuthorization = futurePaymentAuthorization { 91 | let clientMetadataId = PayPalMobile.clientMetadataID() 92 | 93 | if let response = futurePaymentAuthorization["response"] as? [String:AnyObject], code = response["code"] as? String { 94 | let keys = WatchbuttonKeys() 95 | let client = PayPalClient(clientId: keys.payPalSandboxClientId(), clientSecret: keys.payPalSandboxClientSecret(), code: code, metadataId: clientMetadataId) 96 | 97 | if let delegate = UIApplication.sharedApplication().delegate as? AppDelegate, productData = delegate.selectedProduct { 98 | let product = Product(productData) 99 | 100 | if let amount = product.price["amount"], currency = product.price["currency"] { 101 | client.createPayment(product.name, currency, amount) { (paymentId) in 102 | storePaymentId(paymentId) 103 | storeRefreshToken(client.refreshToken!) 104 | NSLog("Stored payment ID \(paymentId) in keychain.") 105 | 106 | MBProgressHUD.hideAllHUDsForView(self.view, animated: true) 107 | } 108 | 109 | return 110 | } 111 | } 112 | } 113 | } 114 | 115 | MBProgressHUD.hideAllHUDsForView(self.view, animated: true) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Phone App/WatchButton.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.vu0.org.WatchButton 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/contentful/CocoaPodsSpecs' 2 | source 'https://github.com/CocoaPods/Specs' 3 | 4 | plugin 'cocoapods-keys', { 5 | :project => 'WatchButton', 6 | :keys => [ 7 | 'PayPalSandboxClientId', 8 | 'PayPalSandboxClientSecret', 9 | 'SphereIOClientId', 10 | 'SphereIOClientSecret' 11 | ]} 12 | 13 | inhibit_all_warnings! 14 | use_frameworks! 15 | 16 | def shared_pods 17 | 18 | pod 'Cube', :git => 'https://github.com/contentful-labs/Cube.git' 19 | #pod 'Cube', :path => '../Cube' 20 | pod 'MMWormhole' 21 | 22 | end 23 | 24 | link_with 'WatchButton' 25 | 26 | shared_pods 27 | 28 | pod 'Alamofire', '~> 2.0' 29 | #pod 'ContentfulDeliveryAPI', :path => '../contentful-delivery-api' 30 | pod 'Form', :head 31 | pod 'KeychainAccess', :git => 'https://github.com/kishikawakatsumi/KeychainAccess.git' 32 | pod 'MBProgressHUD' 33 | pod 'PayPal-iOS-SDK' 34 | pod 'Result', '>= 0.6-beta.1' 35 | 36 | target 'WatchButton WatchKit Extension', :exclusive => true do 37 | 38 | platform :watchos, '2.0' 39 | 40 | shared_pods 41 | 42 | end 43 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (2.0.2) 3 | - CardIO (5.2.2) 4 | - Cube (0.0.1): 5 | - Alamofire (~> 2.0) 6 | - Result (>= 0.6-beta.1) 7 | - Form (HEAD based on 3.7.2): 8 | - Hex (~> 3.0.0) 9 | - HYP8601 (~> 0.7.2) 10 | - HYPMathParser (~> 0.4.1) 11 | - HYPNorwegianAccountNumber (~> 1.2.1) 12 | - HYPNorwegianSSN (~> 1.10.2) 13 | - NSDictionary-ANDYSafeValue (~> 0.3) 14 | - NSDictionary-HYPNestedAttributes (~> 0.4.1) 15 | - NSJSONSerialization-ANDYJSONFile (~> 1.1) 16 | - NSObject-HYPTesting (~> 1.2) 17 | - NSString-HYPContainsString (~> 0.1) 18 | - NSString-HYPFormula (~> 1.6.2) 19 | - NSString-HYPRelationshipParser (~> 0.4.1) 20 | - NSString-HYPWordExtractor (~> 1.2) 21 | - NSString-ZENInflections (~> 1.2) 22 | - UIButton-ANDYHighlighted (~> 0.2.1) 23 | - UIViewController-HYPKeyboardToolbar (~> 0.1) 24 | - Hex (3.0.0) 25 | - HYP8601 (0.7.2) 26 | - HYPMathParser (0.4.1) 27 | - HYPNorwegianAccountNumber (1.2.1) 28 | - HYPNorwegianSSN (1.10.2) 29 | - KeychainAccess (2.3.2) 30 | - Keys (1.0.0) 31 | - MBProgressHUD (0.9.1) 32 | - MMWormhole (2.0.0): 33 | - MMWormhole/Core (= 2.0.0) 34 | - MMWormhole/Core (2.0.0) 35 | - NSDictionary-ANDYSafeValue (0.3.1) 36 | - NSDictionary-HYPNestedAttributes (0.4.1): 37 | - NSString-HYPRelationshipParser (~> 0.4.1) 38 | - NSJSONSerialization-ANDYJSONFile (1.1) 39 | - NSObject-HYPTesting (1.2) 40 | - NSString-HYPContainsString (0.1) 41 | - NSString-HYPFormula (1.6.2): 42 | - NSString-HYPWordExtractor 43 | - NSString-HYPRelationshipParser (0.4.1) 44 | - NSString-HYPWordExtractor (1.2) 45 | - NSString-ZENInflections (1.2) 46 | - PayPal-iOS-SDK (2.12.8): 47 | - PayPal-iOS-SDK/CardIO (= 2.12.8) 48 | - PayPal-iOS-SDK/Core (= 2.12.8) 49 | - PayPal-iOS-SDK/CardIO (2.12.8): 50 | - CardIO (~> 5.2.2) 51 | - PayPal-iOS-SDK/Core (2.12.8) 52 | - Result (0.6-beta.1) 53 | - UIButton-ANDYHighlighted (0.2.1) 54 | - UIViewController-HYPKeyboardToolbar (0.1) 55 | 56 | DEPENDENCIES: 57 | - Alamofire (~> 2.0) 58 | - Cube (from `https://github.com/contentful-labs/Cube.git`) 59 | - Form (HEAD) 60 | - KeychainAccess (from `https://github.com/kishikawakatsumi/KeychainAccess.git`) 61 | - Keys (from `Pods/CocoaPodsKeys`) 62 | - MBProgressHUD 63 | - MMWormhole 64 | - PayPal-iOS-SDK 65 | - Result (>= 0.6-beta.1) 66 | 67 | EXTERNAL SOURCES: 68 | Cube: 69 | :git: https://github.com/contentful-labs/Cube.git 70 | KeychainAccess: 71 | :git: https://github.com/kishikawakatsumi/KeychainAccess.git 72 | Keys: 73 | :path: Pods/CocoaPodsKeys 74 | 75 | CHECKOUT OPTIONS: 76 | Cube: 77 | :commit: 52b43b721811c2bd992d9fa5f10764b0b0dd24c4 78 | :git: https://github.com/contentful-labs/Cube.git 79 | KeychainAccess: 80 | :commit: 11b935868a5c3af1fe7222d02da7215ccdfc88e4 81 | :git: https://github.com/kishikawakatsumi/KeychainAccess.git 82 | 83 | SPEC CHECKSUMS: 84 | Alamofire: 8edbab00802a752bb5240cc2abe70e7e881488ad 85 | CardIO: 2460db0f4936f3441b08d51e3904b436d77ec8fc 86 | Cube: 84d69281a1432b1fd8443c26b8ca559905037830 87 | Form: 01297c7d084fcda402e95e672e322d9c0cb2bcf6 88 | Hex: ee430da9c4412d9196366ae5327f279021dc03eb 89 | HYP8601: 0118757c1332620748c75e7218e7b6b849686d06 90 | HYPMathParser: 146a4981427a0b46cc9af823b210bfefdf0a7107 91 | HYPNorwegianAccountNumber: b0062d7419d217123dff0ef04f1931ed5f1e3bbf 92 | HYPNorwegianSSN: 1eb54f1488ac24cdd7d0aee34e10d0f826c27fc1 93 | KeychainAccess: 96575483583f4640739a5cc26551803d0d34e0dd 94 | Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f 95 | MBProgressHUD: c47f2c166c126cf2ce36498d80f33e754d4e93ad 96 | MMWormhole: 0cd3fd35a9118b2e2d762b499f54eeaace0be791 97 | NSDictionary-ANDYSafeValue: 2d7adf339b6e302d71fec5f1d71ae00aacda993e 98 | NSDictionary-HYPNestedAttributes: 783d3adcf9f8e9a554004ee225f1bc0cdf0cfdfe 99 | NSJSONSerialization-ANDYJSONFile: 4a92ca174d5ccde13015e853e9eb62440459b478 100 | NSObject-HYPTesting: fadde70c75061b731b313c390e20777aed0a914d 101 | NSString-HYPContainsString: a8147bbb90b9aca02f69d12546b4d2938a5d01dc 102 | NSString-HYPFormula: c1d166886afd1cfd139f24ab01bd63ad8f4cc360 103 | NSString-HYPRelationshipParser: 7fa231131e408f26b3b72a41157aa6620a195b4d 104 | NSString-HYPWordExtractor: 7484da8d2d23e41b24b0bdb218293ab349f9f650 105 | NSString-ZENInflections: 35ae6d02981b1def07bd6dc8e6455e827453bc29 106 | PayPal-iOS-SDK: 66f4f6e2f0d0c31bae81642c1518bb72ac18f1a6 107 | Result: af24e3e57ab81005ec4221a53fd36dc1e7a52869 108 | UIButton-ANDYHighlighted: 3f5e9fb68fb3425bd9fddc030af452d3ac9d1613 109 | UIViewController-HYPKeyboardToolbar: d5cb85447caeaf9e0e7ed575ce3b1576c7d53dd8 110 | 111 | PODFILE CHECKSUM: 4d39f40914ea54777e672d2b2b8aa60996e50302 112 | 113 | COCOAPODS: 0.39.0 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WatchButton 2 | 3 | ![](Screenshots/initial_screen.png) 4 | ![](Screenshots/order_successful.png) 5 | 6 | Software version of the [Amazon Dash Button][2] on the ᴡᴀᴛᴄʜ. Uses iBeacons 7 | to determine the room you are in and contextually offers you to buy a product. 8 | By utilizing the [commmercetools][3], PayPal and [Contentful][4] APIs on the phone, custom backend 9 | code is not needed. 10 | 11 | ## Building 12 | 13 | ```bash 14 | $ make pod 15 | $ make all 16 | ``` 17 | 18 | [1]: http://www.ecomhack.io 19 | [2]: https://www.amazon.com/oc/dash-button 20 | [3]: http://www.commercetools.com/de/ 21 | [4]: https://www.contentful.com 22 | 23 | -------------------------------------------------------------------------------- /Screenshots/initial_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Screenshots/initial_screen.png -------------------------------------------------------------------------------- /Screenshots/order_successful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/Screenshots/order_successful.png -------------------------------------------------------------------------------- /WatchButton.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4E203B455D3ECAC8B1AA8892 /* Pods_WatchButton_WatchKit_Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7B612E7C95BC7692FDF64C9 /* Pods_WatchButton_WatchKit_Extension.framework */; }; 11 | A15AB65C1AFECD32000DEA74 /* BeaconController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15AB65B1AFECD32000DEA74 /* BeaconController.swift */; }; 12 | A15AB65E1AFECD8A000DEA74 /* Beacon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15AB65D1AFECD8A000DEA74 /* Beacon.swift */; }; 13 | A19B89B31AFEEC6C00E15887 /* AddressFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19B89B21AFEEC6C00E15887 /* AddressFormViewController.swift */; }; 14 | A1AAA6A91AFE8950000D4227 /* PayPalClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AAA6A71AFE86CD000D4227 /* PayPalClient.swift */; }; 15 | A1AAA6AB1AFE93A0000D4227 /* PaymentKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AAA6AA1AFE93A0000D4227 /* PaymentKeychain.swift */; }; 16 | A1AAA6AD1AFE97C4000D4227 /* Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AAA6AC1AFE97C4000D4227 /* Commands.swift */; }; 17 | A1AAA6AE1AFE97C4000D4227 /* Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AAA6AC1AFE97C4000D4227 /* Commands.swift */; }; 18 | A1CF8D321AFE1325005EDE51 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1CF8D311AFE1325005EDE51 /* AppDelegate.swift */; }; 19 | A1CF8D341AFE1325005EDE51 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1CF8D331AFE1325005EDE51 /* ViewController.swift */; }; 20 | A1CF8D371AFE1325005EDE51 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1CF8D351AFE1325005EDE51 /* Main.storyboard */; }; 21 | A1CF8D391AFE1325005EDE51 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1CF8D381AFE1325005EDE51 /* Images.xcassets */; }; 22 | A1CF8D3C1AFE1325005EDE51 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = A1CF8D3A1AFE1325005EDE51 /* LaunchScreen.xib */; }; 23 | A1CF8D5A1AFE1332005EDE51 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1CF8D591AFE1332005EDE51 /* InterfaceController.swift */; }; 24 | A1CF8D5C1AFE1332005EDE51 /* GlanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1CF8D5B1AFE1332005EDE51 /* GlanceController.swift */; }; 25 | A1CF8D5E1AFE1332005EDE51 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1CF8D5D1AFE1332005EDE51 /* Images.xcassets */; }; 26 | A1CF8D6A1AFE1332005EDE51 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1CF8D681AFE1332005EDE51 /* Interface.storyboard */; }; 27 | A1CF8D6C1AFE1332005EDE51 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1CF8D6B1AFE1332005EDE51 /* Images.xcassets */; }; 28 | A1ED03471AFEB10B00570757 /* PaymentAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED03461AFEB10B00570757 /* PaymentAlert.swift */; }; 29 | A1F8A7F81B70BA1300D22AC4 /* WatchButton WatchKit Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = A1CF8D551AFE1332005EDE51 /* WatchButton WatchKit Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 30 | A1F8A7FB1B70BA1300D22AC4 /* WatchButton WatchKit App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = A1CF8D611AFE1332005EDE51 /* WatchButton WatchKit App.app */; }; 31 | F6312121D73E165B1EEE3D8D /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFD8A85CEC34E4A816905356 /* Pods.framework */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | A1F8A7F61B70BA1300D22AC4 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = A1CF8D241AFE1325005EDE51 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = A1CF8D541AFE1332005EDE51; 40 | remoteInfo = "WatchButton WatchKit Extension"; 41 | }; 42 | A1F8A7F91B70BA1300D22AC4 /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = A1CF8D241AFE1325005EDE51 /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = A1CF8D601AFE1332005EDE51; 47 | remoteInfo = "WatchButton WatchKit App"; 48 | }; 49 | /* End PBXContainerItemProxy section */ 50 | 51 | /* Begin PBXCopyFilesBuildPhase section */ 52 | A1F8A7FC1B70BA1300D22AC4 /* Embed App Extensions */ = { 53 | isa = PBXCopyFilesBuildPhase; 54 | buildActionMask = 2147483647; 55 | dstPath = ""; 56 | dstSubfolderSpec = 13; 57 | files = ( 58 | A1F8A7F81B70BA1300D22AC4 /* WatchButton WatchKit Extension.appex in Embed App Extensions */, 59 | ); 60 | name = "Embed App Extensions"; 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | A1F8A7FD1B70BA1300D22AC4 /* Embed Watch Content */ = { 64 | isa = PBXCopyFilesBuildPhase; 65 | buildActionMask = 2147483647; 66 | dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; 67 | dstSubfolderSpec = 16; 68 | files = ( 69 | A1F8A7FB1B70BA1300D22AC4 /* WatchButton WatchKit App.app in Embed Watch Content */, 70 | ); 71 | name = "Embed Watch Content"; 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXCopyFilesBuildPhase section */ 75 | 76 | /* Begin PBXFileReference section */ 77 | 4AE19A26D612E6E67E88A766 /* Pods-WatchButton WatchKit Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchButton WatchKit Extension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WatchButton WatchKit Extension/Pods-WatchButton WatchKit Extension.release.xcconfig"; sourceTree = ""; }; 78 | 8C79ADFDE7F13A447C77626D /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 79 | 8E86E131A6CC65020705ACD9 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 80 | A15AB65B1AFECD32000DEA74 /* BeaconController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BeaconController.swift; sourceTree = ""; }; 81 | A15AB65D1AFECD8A000DEA74 /* Beacon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Beacon.swift; sourceTree = ""; }; 82 | A19B89B21AFEEC6C00E15887 /* AddressFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressFormViewController.swift; sourceTree = ""; }; 83 | A1AAA6A71AFE86CD000D4227 /* PayPalClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalClient.swift; sourceTree = ""; }; 84 | A1AAA6AA1AFE93A0000D4227 /* PaymentKeychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentKeychain.swift; sourceTree = ""; }; 85 | A1AAA6AC1AFE97C4000D4227 /* Commands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Commands.swift; sourceTree = ""; }; 86 | A1CF8D2C1AFE1325005EDE51 /* WatchButton.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WatchButton.app; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | A1CF8D301AFE1325005EDE51 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88 | A1CF8D311AFE1325005EDE51 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 89 | A1CF8D331AFE1325005EDE51 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 90 | A1CF8D361AFE1325005EDE51 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 91 | A1CF8D381AFE1325005EDE51 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 92 | A1CF8D3B1AFE1325005EDE51 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 93 | A1CF8D551AFE1332005EDE51 /* WatchButton WatchKit Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "WatchButton WatchKit Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | A1CF8D581AFE1332005EDE51 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 95 | A1CF8D591AFE1332005EDE51 /* InterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = ""; }; 96 | A1CF8D5B1AFE1332005EDE51 /* GlanceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlanceController.swift; sourceTree = ""; }; 97 | A1CF8D5D1AFE1332005EDE51 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 98 | A1CF8D611AFE1332005EDE51 /* WatchButton WatchKit App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WatchButton WatchKit App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | A1CF8D671AFE1332005EDE51 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 100 | A1CF8D691AFE1332005EDE51 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; 101 | A1CF8D6B1AFE1332005EDE51 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 102 | A1E9A40C1AFE742D00C28085 /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; 103 | A1ED03461AFEB10B00570757 /* PaymentAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentAlert.swift; sourceTree = ""; }; 104 | A1ED03481AFEBE6F00570757 /* WatchButton.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = WatchButton.entitlements; path = ../WatchButton.entitlements; sourceTree = ""; }; 105 | A1ED03491AFEBEA500570757 /* WatchButton WatchKit Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "WatchButton WatchKit Extension.entitlements"; sourceTree = ""; }; 106 | B94957861BC3C5A0ED44131E /* Pods-WatchButton WatchKit Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchButton WatchKit Extension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WatchButton WatchKit Extension/Pods-WatchButton WatchKit Extension.debug.xcconfig"; sourceTree = ""; }; 107 | E7B612E7C95BC7692FDF64C9 /* Pods_WatchButton_WatchKit_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WatchButton_WatchKit_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 108 | FFD8A85CEC34E4A816905356 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 109 | /* End PBXFileReference section */ 110 | 111 | /* Begin PBXFrameworksBuildPhase section */ 112 | 083B70BE0987AE1EACD4863A /* Frameworks */ = { 113 | isa = PBXFrameworksBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | A1CF8D291AFE1325005EDE51 /* Frameworks */ = { 120 | isa = PBXFrameworksBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | F6312121D73E165B1EEE3D8D /* Pods.framework in Frameworks */, 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | A1CF8D521AFE1332005EDE51 /* Frameworks */ = { 128 | isa = PBXFrameworksBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | 4E203B455D3ECAC8B1AA8892 /* Pods_WatchButton_WatchKit_Extension.framework in Frameworks */, 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | /* End PBXFrameworksBuildPhase section */ 136 | 137 | /* Begin PBXGroup section */ 138 | 5215D1152283133381C73C7C /* Frameworks */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | FFD8A85CEC34E4A816905356 /* Pods.framework */, 142 | E7B612E7C95BC7692FDF64C9 /* Pods_WatchButton_WatchKit_Extension.framework */, 143 | ); 144 | name = Frameworks; 145 | sourceTree = ""; 146 | }; 147 | A1CF8D231AFE1325005EDE51 = { 148 | isa = PBXGroup; 149 | children = ( 150 | A1CF8D2E1AFE1325005EDE51 /* Phone App */, 151 | A1CF8D651AFE1332005EDE51 /* WatchKit App */, 152 | A1CF8D561AFE1332005EDE51 /* WatchKit Extension */, 153 | 5215D1152283133381C73C7C /* Frameworks */, 154 | A1CF8D2D1AFE1325005EDE51 /* Products */, 155 | CEAF4F522F566795258DD4FE /* Pods */, 156 | ); 157 | sourceTree = ""; 158 | }; 159 | A1CF8D2D1AFE1325005EDE51 /* Products */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | A1CF8D2C1AFE1325005EDE51 /* WatchButton.app */, 163 | A1CF8D551AFE1332005EDE51 /* WatchButton WatchKit Extension.appex */, 164 | A1CF8D611AFE1332005EDE51 /* WatchButton WatchKit App.app */, 165 | ); 166 | name = Products; 167 | sourceTree = ""; 168 | }; 169 | A1CF8D2E1AFE1325005EDE51 /* Phone App */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | A19B89B21AFEEC6C00E15887 /* AddressFormViewController.swift */, 173 | A1CF8D311AFE1325005EDE51 /* AppDelegate.swift */, 174 | A15AB65D1AFECD8A000DEA74 /* Beacon.swift */, 175 | A15AB65B1AFECD32000DEA74 /* BeaconController.swift */, 176 | A1AAA6AC1AFE97C4000D4227 /* Commands.swift */, 177 | A1AAA6AA1AFE93A0000D4227 /* PaymentKeychain.swift */, 178 | A1AAA6A71AFE86CD000D4227 /* PayPalClient.swift */, 179 | A1CF8D331AFE1325005EDE51 /* ViewController.swift */, 180 | A1CF8D771AFE1363005EDE51 /* Resources */, 181 | A1CF8D2F1AFE1325005EDE51 /* Supporting Files */, 182 | ); 183 | path = "Phone App"; 184 | sourceTree = ""; 185 | }; 186 | A1CF8D2F1AFE1325005EDE51 /* Supporting Files */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | A1E9A40C1AFE742D00C28085 /* BridgingHeader.h */, 190 | A1CF8D301AFE1325005EDE51 /* Info.plist */, 191 | A1ED03481AFEBE6F00570757 /* WatchButton.entitlements */, 192 | ); 193 | path = "Supporting Files"; 194 | sourceTree = ""; 195 | }; 196 | A1CF8D561AFE1332005EDE51 /* WatchKit Extension */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | A1CF8D5B1AFE1332005EDE51 /* GlanceController.swift */, 200 | A1CF8D5D1AFE1332005EDE51 /* Images.xcassets */, 201 | A1CF8D581AFE1332005EDE51 /* Info.plist */, 202 | A1CF8D591AFE1332005EDE51 /* InterfaceController.swift */, 203 | A1ED03461AFEB10B00570757 /* PaymentAlert.swift */, 204 | A1ED03491AFEBEA500570757 /* WatchButton WatchKit Extension.entitlements */, 205 | ); 206 | path = "WatchKit Extension"; 207 | sourceTree = ""; 208 | }; 209 | A1CF8D651AFE1332005EDE51 /* WatchKit App */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | A1CF8D6B1AFE1332005EDE51 /* Images.xcassets */, 213 | A1CF8D671AFE1332005EDE51 /* Info.plist */, 214 | A1CF8D681AFE1332005EDE51 /* Interface.storyboard */, 215 | ); 216 | path = "WatchKit App"; 217 | sourceTree = ""; 218 | }; 219 | A1CF8D771AFE1363005EDE51 /* Resources */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | A1CF8D381AFE1325005EDE51 /* Images.xcassets */, 223 | A1CF8D3A1AFE1325005EDE51 /* LaunchScreen.xib */, 224 | A1CF8D351AFE1325005EDE51 /* Main.storyboard */, 225 | ); 226 | path = Resources; 227 | sourceTree = ""; 228 | }; 229 | CEAF4F522F566795258DD4FE /* Pods */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 8E86E131A6CC65020705ACD9 /* Pods.debug.xcconfig */, 233 | 8C79ADFDE7F13A447C77626D /* Pods.release.xcconfig */, 234 | B94957861BC3C5A0ED44131E /* Pods-WatchButton WatchKit Extension.debug.xcconfig */, 235 | 4AE19A26D612E6E67E88A766 /* Pods-WatchButton WatchKit Extension.release.xcconfig */, 236 | ); 237 | name = Pods; 238 | sourceTree = ""; 239 | }; 240 | /* End PBXGroup section */ 241 | 242 | /* Begin PBXNativeTarget section */ 243 | A1CF8D2B1AFE1325005EDE51 /* WatchButton */ = { 244 | isa = PBXNativeTarget; 245 | buildConfigurationList = A1CF8D4B1AFE1325005EDE51 /* Build configuration list for PBXNativeTarget "WatchButton" */; 246 | buildPhases = ( 247 | 9CDB9024CEBBA0A18D26F0FF /* Check Pods Manifest.lock */, 248 | A1CF8D281AFE1325005EDE51 /* Sources */, 249 | A1CF8D291AFE1325005EDE51 /* Frameworks */, 250 | A1CF8D2A1AFE1325005EDE51 /* Resources */, 251 | A1AAA6AF1AFE9D51000D4227 /* Show TODOs and FIXMEs */, 252 | A1F8A7FD1B70BA1300D22AC4 /* Embed Watch Content */, 253 | 1744D74E15042A95715B312F /* Embed Pods Frameworks */, 254 | A07DC42AFE7194D6716B1C0B /* Copy Pods Resources */, 255 | ); 256 | buildRules = ( 257 | ); 258 | dependencies = ( 259 | A1F8A7FA1B70BA1300D22AC4 /* PBXTargetDependency */, 260 | ); 261 | name = WatchButton; 262 | productName = WatchButton; 263 | productReference = A1CF8D2C1AFE1325005EDE51 /* WatchButton.app */; 264 | productType = "com.apple.product-type.application"; 265 | }; 266 | A1CF8D541AFE1332005EDE51 /* WatchButton WatchKit Extension */ = { 267 | isa = PBXNativeTarget; 268 | buildConfigurationList = A1CF8D731AFE1332005EDE51 /* Build configuration list for PBXNativeTarget "WatchButton WatchKit Extension" */; 269 | buildPhases = ( 270 | 851BA33E51AA8A8B8ABE8885 /* Check Pods Manifest.lock */, 271 | A1CF8D511AFE1332005EDE51 /* Sources */, 272 | A1CF8D521AFE1332005EDE51 /* Frameworks */, 273 | A1CF8D531AFE1332005EDE51 /* Resources */, 274 | FC69E518A3D0DB23661D485B /* Embed Pods Frameworks */, 275 | 1E7790BE1866CD6A69CBAB19 /* Copy Pods Resources */, 276 | ); 277 | buildRules = ( 278 | ); 279 | dependencies = ( 280 | ); 281 | name = "WatchButton WatchKit Extension"; 282 | productName = "WatchButton WatchKit Extension"; 283 | productReference = A1CF8D551AFE1332005EDE51 /* WatchButton WatchKit Extension.appex */; 284 | productType = "com.apple.product-type.watchkit2-extension"; 285 | }; 286 | A1CF8D601AFE1332005EDE51 /* WatchButton WatchKit App */ = { 287 | isa = PBXNativeTarget; 288 | buildConfigurationList = A1CF8D701AFE1332005EDE51 /* Build configuration list for PBXNativeTarget "WatchButton WatchKit App" */; 289 | buildPhases = ( 290 | A1CF8D5F1AFE1332005EDE51 /* Resources */, 291 | 083B70BE0987AE1EACD4863A /* Frameworks */, 292 | A1F8A7FC1B70BA1300D22AC4 /* Embed App Extensions */, 293 | ); 294 | buildRules = ( 295 | ); 296 | dependencies = ( 297 | A1F8A7F71B70BA1300D22AC4 /* PBXTargetDependency */, 298 | ); 299 | name = "WatchButton WatchKit App"; 300 | productName = "WatchButton WatchKit App"; 301 | productReference = A1CF8D611AFE1332005EDE51 /* WatchButton WatchKit App.app */; 302 | productType = "com.apple.product-type.application.watchapp2"; 303 | }; 304 | /* End PBXNativeTarget section */ 305 | 306 | /* Begin PBXProject section */ 307 | A1CF8D241AFE1325005EDE51 /* Project object */ = { 308 | isa = PBXProject; 309 | attributes = { 310 | LastSwiftUpdateCheck = 0700; 311 | LastUpgradeCheck = 0700; 312 | ORGANIZATIONNAME = "Boris Bügling"; 313 | TargetAttributes = { 314 | A1CF8D2B1AFE1325005EDE51 = { 315 | CreatedOnToolsVersion = 6.3.2; 316 | DevelopmentTeam = RWJ5E97L7R; 317 | SystemCapabilities = { 318 | com.apple.ApplicationGroups.iOS = { 319 | enabled = 1; 320 | }; 321 | }; 322 | }; 323 | A1CF8D541AFE1332005EDE51 = { 324 | CreatedOnToolsVersion = 6.3.2; 325 | DevelopmentTeam = RWJ5E97L7R; 326 | SystemCapabilities = { 327 | com.apple.ApplicationGroups.iOS = { 328 | enabled = 1; 329 | }; 330 | }; 331 | }; 332 | A1CF8D601AFE1332005EDE51 = { 333 | CreatedOnToolsVersion = 6.3.2; 334 | }; 335 | }; 336 | }; 337 | buildConfigurationList = A1CF8D271AFE1325005EDE51 /* Build configuration list for PBXProject "WatchButton" */; 338 | compatibilityVersion = "Xcode 3.2"; 339 | developmentRegion = English; 340 | hasScannedForEncodings = 0; 341 | knownRegions = ( 342 | en, 343 | Base, 344 | ); 345 | mainGroup = A1CF8D231AFE1325005EDE51; 346 | productRefGroup = A1CF8D2D1AFE1325005EDE51 /* Products */; 347 | projectDirPath = ""; 348 | projectRoot = ""; 349 | targets = ( 350 | A1CF8D2B1AFE1325005EDE51 /* WatchButton */, 351 | A1CF8D541AFE1332005EDE51 /* WatchButton WatchKit Extension */, 352 | A1CF8D601AFE1332005EDE51 /* WatchButton WatchKit App */, 353 | ); 354 | }; 355 | /* End PBXProject section */ 356 | 357 | /* Begin PBXResourcesBuildPhase section */ 358 | A1CF8D2A1AFE1325005EDE51 /* Resources */ = { 359 | isa = PBXResourcesBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | A1CF8D371AFE1325005EDE51 /* Main.storyboard in Resources */, 363 | A1CF8D3C1AFE1325005EDE51 /* LaunchScreen.xib in Resources */, 364 | A1CF8D391AFE1325005EDE51 /* Images.xcassets in Resources */, 365 | ); 366 | runOnlyForDeploymentPostprocessing = 0; 367 | }; 368 | A1CF8D531AFE1332005EDE51 /* Resources */ = { 369 | isa = PBXResourcesBuildPhase; 370 | buildActionMask = 2147483647; 371 | files = ( 372 | A1CF8D5E1AFE1332005EDE51 /* Images.xcassets in Resources */, 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | }; 376 | A1CF8D5F1AFE1332005EDE51 /* Resources */ = { 377 | isa = PBXResourcesBuildPhase; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | A1CF8D6A1AFE1332005EDE51 /* Interface.storyboard in Resources */, 381 | A1CF8D6C1AFE1332005EDE51 /* Images.xcassets in Resources */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | /* End PBXResourcesBuildPhase section */ 386 | 387 | /* Begin PBXShellScriptBuildPhase section */ 388 | 1744D74E15042A95715B312F /* Embed Pods Frameworks */ = { 389 | isa = PBXShellScriptBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | ); 393 | inputPaths = ( 394 | ); 395 | name = "Embed Pods Frameworks"; 396 | outputPaths = ( 397 | ); 398 | runOnlyForDeploymentPostprocessing = 0; 399 | shellPath = /bin/sh; 400 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; 401 | showEnvVarsInLog = 0; 402 | }; 403 | 1E7790BE1866CD6A69CBAB19 /* Copy Pods Resources */ = { 404 | isa = PBXShellScriptBuildPhase; 405 | buildActionMask = 2147483647; 406 | files = ( 407 | ); 408 | inputPaths = ( 409 | ); 410 | name = "Copy Pods Resources"; 411 | outputPaths = ( 412 | ); 413 | runOnlyForDeploymentPostprocessing = 0; 414 | shellPath = /bin/sh; 415 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-WatchButton WatchKit Extension/Pods-WatchButton WatchKit Extension-resources.sh\"\n"; 416 | showEnvVarsInLog = 0; 417 | }; 418 | 851BA33E51AA8A8B8ABE8885 /* Check Pods Manifest.lock */ = { 419 | isa = PBXShellScriptBuildPhase; 420 | buildActionMask = 2147483647; 421 | files = ( 422 | ); 423 | inputPaths = ( 424 | ); 425 | name = "Check Pods Manifest.lock"; 426 | outputPaths = ( 427 | ); 428 | runOnlyForDeploymentPostprocessing = 0; 429 | shellPath = /bin/sh; 430 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 431 | showEnvVarsInLog = 0; 432 | }; 433 | 9CDB9024CEBBA0A18D26F0FF /* Check Pods Manifest.lock */ = { 434 | isa = PBXShellScriptBuildPhase; 435 | buildActionMask = 2147483647; 436 | files = ( 437 | ); 438 | inputPaths = ( 439 | ); 440 | name = "Check Pods Manifest.lock"; 441 | outputPaths = ( 442 | ); 443 | runOnlyForDeploymentPostprocessing = 0; 444 | shellPath = /bin/sh; 445 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 446 | showEnvVarsInLog = 0; 447 | }; 448 | A07DC42AFE7194D6716B1C0B /* Copy Pods Resources */ = { 449 | isa = PBXShellScriptBuildPhase; 450 | buildActionMask = 2147483647; 451 | files = ( 452 | ); 453 | inputPaths = ( 454 | ); 455 | name = "Copy Pods Resources"; 456 | outputPaths = ( 457 | ); 458 | runOnlyForDeploymentPostprocessing = 0; 459 | shellPath = /bin/sh; 460 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; 461 | showEnvVarsInLog = 0; 462 | }; 463 | A1AAA6AF1AFE9D51000D4227 /* Show TODOs and FIXMEs */ = { 464 | isa = PBXShellScriptBuildPhase; 465 | buildActionMask = 2147483647; 466 | files = ( 467 | ); 468 | inputPaths = ( 469 | ); 470 | name = "Show TODOs and FIXMEs"; 471 | outputPaths = ( 472 | ); 473 | runOnlyForDeploymentPostprocessing = 0; 474 | shellPath = /bin/sh; 475 | shellScript = "KEYWORDS=\"TODO:|FIXME:|\\?\\?\\?:|\\!\\!\\!:\"\nfind \"${SRCROOT}\" \\( -name \"*.h\" -or -name \"*.m\" -or -name \"*.mm\" -or -name \"*.swift\" \\) -print0 | xargs -0 egrep --with-filename --line-number --only-matching \"($KEYWORDS).*\\$\" | grep -v '/Pods/' | perl -p -e \"s/($KEYWORDS)/ warning: \\$1/\""; 476 | }; 477 | FC69E518A3D0DB23661D485B /* Embed Pods Frameworks */ = { 478 | isa = PBXShellScriptBuildPhase; 479 | buildActionMask = 2147483647; 480 | files = ( 481 | ); 482 | inputPaths = ( 483 | ); 484 | name = "Embed Pods Frameworks"; 485 | outputPaths = ( 486 | ); 487 | runOnlyForDeploymentPostprocessing = 0; 488 | shellPath = /bin/sh; 489 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-WatchButton WatchKit Extension/Pods-WatchButton WatchKit Extension-frameworks.sh\"\n"; 490 | showEnvVarsInLog = 0; 491 | }; 492 | /* End PBXShellScriptBuildPhase section */ 493 | 494 | /* Begin PBXSourcesBuildPhase section */ 495 | A1CF8D281AFE1325005EDE51 /* Sources */ = { 496 | isa = PBXSourcesBuildPhase; 497 | buildActionMask = 2147483647; 498 | files = ( 499 | A1AAA6AB1AFE93A0000D4227 /* PaymentKeychain.swift in Sources */, 500 | A1AAA6AD1AFE97C4000D4227 /* Commands.swift in Sources */, 501 | A19B89B31AFEEC6C00E15887 /* AddressFormViewController.swift in Sources */, 502 | A1CF8D341AFE1325005EDE51 /* ViewController.swift in Sources */, 503 | A1CF8D321AFE1325005EDE51 /* AppDelegate.swift in Sources */, 504 | A15AB65E1AFECD8A000DEA74 /* Beacon.swift in Sources */, 505 | A15AB65C1AFECD32000DEA74 /* BeaconController.swift in Sources */, 506 | A1AAA6A91AFE8950000D4227 /* PayPalClient.swift in Sources */, 507 | ); 508 | runOnlyForDeploymentPostprocessing = 0; 509 | }; 510 | A1CF8D511AFE1332005EDE51 /* Sources */ = { 511 | isa = PBXSourcesBuildPhase; 512 | buildActionMask = 2147483647; 513 | files = ( 514 | A1CF8D5A1AFE1332005EDE51 /* InterfaceController.swift in Sources */, 515 | A1CF8D5C1AFE1332005EDE51 /* GlanceController.swift in Sources */, 516 | A1ED03471AFEB10B00570757 /* PaymentAlert.swift in Sources */, 517 | A1AAA6AE1AFE97C4000D4227 /* Commands.swift in Sources */, 518 | ); 519 | runOnlyForDeploymentPostprocessing = 0; 520 | }; 521 | /* End PBXSourcesBuildPhase section */ 522 | 523 | /* Begin PBXTargetDependency section */ 524 | A1F8A7F71B70BA1300D22AC4 /* PBXTargetDependency */ = { 525 | isa = PBXTargetDependency; 526 | target = A1CF8D541AFE1332005EDE51 /* WatchButton WatchKit Extension */; 527 | targetProxy = A1F8A7F61B70BA1300D22AC4 /* PBXContainerItemProxy */; 528 | }; 529 | A1F8A7FA1B70BA1300D22AC4 /* PBXTargetDependency */ = { 530 | isa = PBXTargetDependency; 531 | target = A1CF8D601AFE1332005EDE51 /* WatchButton WatchKit App */; 532 | targetProxy = A1F8A7F91B70BA1300D22AC4 /* PBXContainerItemProxy */; 533 | }; 534 | /* End PBXTargetDependency section */ 535 | 536 | /* Begin PBXVariantGroup section */ 537 | A1CF8D351AFE1325005EDE51 /* Main.storyboard */ = { 538 | isa = PBXVariantGroup; 539 | children = ( 540 | A1CF8D361AFE1325005EDE51 /* Base */, 541 | ); 542 | name = Main.storyboard; 543 | path = .; 544 | sourceTree = ""; 545 | }; 546 | A1CF8D3A1AFE1325005EDE51 /* LaunchScreen.xib */ = { 547 | isa = PBXVariantGroup; 548 | children = ( 549 | A1CF8D3B1AFE1325005EDE51 /* Base */, 550 | ); 551 | name = LaunchScreen.xib; 552 | path = .; 553 | sourceTree = ""; 554 | }; 555 | A1CF8D681AFE1332005EDE51 /* Interface.storyboard */ = { 556 | isa = PBXVariantGroup; 557 | children = ( 558 | A1CF8D691AFE1332005EDE51 /* Base */, 559 | ); 560 | name = Interface.storyboard; 561 | path = .; 562 | sourceTree = ""; 563 | }; 564 | /* End PBXVariantGroup section */ 565 | 566 | /* Begin XCBuildConfiguration section */ 567 | A1CF8D491AFE1325005EDE51 /* Debug */ = { 568 | isa = XCBuildConfiguration; 569 | buildSettings = { 570 | ALWAYS_SEARCH_USER_PATHS = NO; 571 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 572 | CLANG_CXX_LIBRARY = "libc++"; 573 | CLANG_ENABLE_MODULES = YES; 574 | CLANG_ENABLE_OBJC_ARC = YES; 575 | CLANG_WARN_BOOL_CONVERSION = YES; 576 | CLANG_WARN_CONSTANT_CONVERSION = YES; 577 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 578 | CLANG_WARN_EMPTY_BODY = YES; 579 | CLANG_WARN_ENUM_CONVERSION = YES; 580 | CLANG_WARN_INT_CONVERSION = YES; 581 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 582 | CLANG_WARN_UNREACHABLE_CODE = YES; 583 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 584 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 585 | COPY_PHASE_STRIP = NO; 586 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 587 | ENABLE_STRICT_OBJC_MSGSEND = YES; 588 | ENABLE_TESTABILITY = YES; 589 | GCC_C_LANGUAGE_STANDARD = gnu99; 590 | GCC_DYNAMIC_NO_PIC = NO; 591 | GCC_NO_COMMON_BLOCKS = YES; 592 | GCC_OPTIMIZATION_LEVEL = 0; 593 | GCC_PREPROCESSOR_DEFINITIONS = ( 594 | "DEBUG=1", 595 | "$(inherited)", 596 | ); 597 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 598 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 599 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 600 | GCC_WARN_UNDECLARED_SELECTOR = YES; 601 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 602 | GCC_WARN_UNUSED_FUNCTION = YES; 603 | GCC_WARN_UNUSED_VARIABLE = YES; 604 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 605 | MTL_ENABLE_DEBUG_INFO = YES; 606 | ONLY_ACTIVE_ARCH = YES; 607 | SDKROOT = iphoneos; 608 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 609 | }; 610 | name = Debug; 611 | }; 612 | A1CF8D4A1AFE1325005EDE51 /* Release */ = { 613 | isa = XCBuildConfiguration; 614 | buildSettings = { 615 | ALWAYS_SEARCH_USER_PATHS = NO; 616 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 617 | CLANG_CXX_LIBRARY = "libc++"; 618 | CLANG_ENABLE_MODULES = YES; 619 | CLANG_ENABLE_OBJC_ARC = YES; 620 | CLANG_WARN_BOOL_CONVERSION = YES; 621 | CLANG_WARN_CONSTANT_CONVERSION = YES; 622 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 623 | CLANG_WARN_EMPTY_BODY = YES; 624 | CLANG_WARN_ENUM_CONVERSION = YES; 625 | CLANG_WARN_INT_CONVERSION = YES; 626 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 627 | CLANG_WARN_UNREACHABLE_CODE = YES; 628 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 629 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 630 | COPY_PHASE_STRIP = NO; 631 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 632 | ENABLE_NS_ASSERTIONS = NO; 633 | ENABLE_STRICT_OBJC_MSGSEND = YES; 634 | GCC_C_LANGUAGE_STANDARD = gnu99; 635 | GCC_NO_COMMON_BLOCKS = YES; 636 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 637 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 638 | GCC_WARN_UNDECLARED_SELECTOR = YES; 639 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 640 | GCC_WARN_UNUSED_FUNCTION = YES; 641 | GCC_WARN_UNUSED_VARIABLE = YES; 642 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 643 | MTL_ENABLE_DEBUG_INFO = NO; 644 | SDKROOT = iphoneos; 645 | VALIDATE_PRODUCT = YES; 646 | }; 647 | name = Release; 648 | }; 649 | A1CF8D4C1AFE1325005EDE51 /* Debug */ = { 650 | isa = XCBuildConfiguration; 651 | baseConfigurationReference = 8E86E131A6CC65020705ACD9 /* Pods.debug.xcconfig */; 652 | buildSettings = { 653 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 654 | CODE_SIGN_ENTITLEMENTS = "Phone App/WatchButton.entitlements"; 655 | CODE_SIGN_IDENTITY = "iPhone Developer"; 656 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 657 | ENABLE_BITCODE = NO; 658 | INFOPLIST_FILE = "Phone App/Supporting Files/Info.plist"; 659 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 660 | PRODUCT_BUNDLE_IDENTIFIER = "org.vu0.$(PRODUCT_NAME:rfc1034identifier)"; 661 | PRODUCT_NAME = "$(TARGET_NAME)"; 662 | PROVISIONING_PROFILE = ""; 663 | SWIFT_OBJC_BRIDGING_HEADER = "Phone App/Supporting Files/BridgingHeader.h"; 664 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/Pods/Headers/Public/PayPal-iOS-SDK"; 665 | }; 666 | name = Debug; 667 | }; 668 | A1CF8D4D1AFE1325005EDE51 /* Release */ = { 669 | isa = XCBuildConfiguration; 670 | baseConfigurationReference = 8C79ADFDE7F13A447C77626D /* Pods.release.xcconfig */; 671 | buildSettings = { 672 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 673 | CODE_SIGN_ENTITLEMENTS = "Phone App/WatchButton.entitlements"; 674 | CODE_SIGN_IDENTITY = "iPhone Developer"; 675 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 676 | ENABLE_BITCODE = NO; 677 | INFOPLIST_FILE = "Phone App/Supporting Files/Info.plist"; 678 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 679 | PRODUCT_BUNDLE_IDENTIFIER = "org.vu0.$(PRODUCT_NAME:rfc1034identifier)"; 680 | PRODUCT_NAME = "$(TARGET_NAME)"; 681 | PROVISIONING_PROFILE = ""; 682 | SWIFT_OBJC_BRIDGING_HEADER = "Phone App/Supporting Files/BridgingHeader.h"; 683 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/Pods/Headers/Public/PayPal-iOS-SDK"; 684 | }; 685 | name = Release; 686 | }; 687 | A1CF8D711AFE1332005EDE51 /* Debug */ = { 688 | isa = XCBuildConfiguration; 689 | buildSettings = { 690 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 691 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 692 | GCC_PREPROCESSOR_DEFINITIONS = ( 693 | "DEBUG=1", 694 | "$(inherited)", 695 | ); 696 | IBSC_MODULE = WatchButton_WatchKit_Extension; 697 | INFOPLIST_FILE = "WatchKit App/Info.plist"; 698 | PRODUCT_BUNDLE_IDENTIFIER = org.vu0.WatchButton.watchkitapp; 699 | PRODUCT_NAME = "$(TARGET_NAME)"; 700 | SDKROOT = watchos; 701 | SKIP_INSTALL = YES; 702 | TARGETED_DEVICE_FAMILY = 4; 703 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 704 | }; 705 | name = Debug; 706 | }; 707 | A1CF8D721AFE1332005EDE51 /* Release */ = { 708 | isa = XCBuildConfiguration; 709 | buildSettings = { 710 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 711 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 712 | IBSC_MODULE = WatchButton_WatchKit_Extension; 713 | INFOPLIST_FILE = "WatchKit App/Info.plist"; 714 | PRODUCT_BUNDLE_IDENTIFIER = org.vu0.WatchButton.watchkitapp; 715 | PRODUCT_NAME = "$(TARGET_NAME)"; 716 | SDKROOT = watchos; 717 | SKIP_INSTALL = YES; 718 | TARGETED_DEVICE_FAMILY = 4; 719 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 720 | }; 721 | name = Release; 722 | }; 723 | A1CF8D741AFE1332005EDE51 /* Debug */ = { 724 | isa = XCBuildConfiguration; 725 | baseConfigurationReference = B94957861BC3C5A0ED44131E /* Pods-WatchButton WatchKit Extension.debug.xcconfig */; 726 | buildSettings = { 727 | CODE_SIGN_ENTITLEMENTS = "WatchKit Extension/WatchButton WatchKit Extension.entitlements"; 728 | CODE_SIGN_IDENTITY = "iPhone Developer"; 729 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 730 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; 731 | GCC_PREPROCESSOR_DEFINITIONS = ( 732 | "DEBUG=1", 733 | "$(inherited)", 734 | ); 735 | INFOPLIST_FILE = "WatchKit Extension/Info.plist"; 736 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 737 | PRODUCT_BUNDLE_IDENTIFIER = org.vu0.WatchButton.watchkitapp.watchkitextension; 738 | PRODUCT_NAME = "${TARGET_NAME}"; 739 | PROVISIONING_PROFILE = ""; 740 | SDKROOT = watchos; 741 | SKIP_INSTALL = YES; 742 | TARGETED_DEVICE_FAMILY = 4; 743 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 744 | }; 745 | name = Debug; 746 | }; 747 | A1CF8D751AFE1332005EDE51 /* Release */ = { 748 | isa = XCBuildConfiguration; 749 | baseConfigurationReference = 4AE19A26D612E6E67E88A766 /* Pods-WatchButton WatchKit Extension.release.xcconfig */; 750 | buildSettings = { 751 | CODE_SIGN_ENTITLEMENTS = "WatchKit Extension/WatchButton WatchKit Extension.entitlements"; 752 | CODE_SIGN_IDENTITY = "iPhone Developer"; 753 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 754 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; 755 | INFOPLIST_FILE = "WatchKit Extension/Info.plist"; 756 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 757 | PRODUCT_BUNDLE_IDENTIFIER = org.vu0.WatchButton.watchkitapp.watchkitextension; 758 | PRODUCT_NAME = "${TARGET_NAME}"; 759 | PROVISIONING_PROFILE = ""; 760 | SDKROOT = watchos; 761 | SKIP_INSTALL = YES; 762 | TARGETED_DEVICE_FAMILY = 4; 763 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 764 | }; 765 | name = Release; 766 | }; 767 | /* End XCBuildConfiguration section */ 768 | 769 | /* Begin XCConfigurationList section */ 770 | A1CF8D271AFE1325005EDE51 /* Build configuration list for PBXProject "WatchButton" */ = { 771 | isa = XCConfigurationList; 772 | buildConfigurations = ( 773 | A1CF8D491AFE1325005EDE51 /* Debug */, 774 | A1CF8D4A1AFE1325005EDE51 /* Release */, 775 | ); 776 | defaultConfigurationIsVisible = 0; 777 | defaultConfigurationName = Release; 778 | }; 779 | A1CF8D4B1AFE1325005EDE51 /* Build configuration list for PBXNativeTarget "WatchButton" */ = { 780 | isa = XCConfigurationList; 781 | buildConfigurations = ( 782 | A1CF8D4C1AFE1325005EDE51 /* Debug */, 783 | A1CF8D4D1AFE1325005EDE51 /* Release */, 784 | ); 785 | defaultConfigurationIsVisible = 0; 786 | defaultConfigurationName = Release; 787 | }; 788 | A1CF8D701AFE1332005EDE51 /* Build configuration list for PBXNativeTarget "WatchButton WatchKit App" */ = { 789 | isa = XCConfigurationList; 790 | buildConfigurations = ( 791 | A1CF8D711AFE1332005EDE51 /* Debug */, 792 | A1CF8D721AFE1332005EDE51 /* Release */, 793 | ); 794 | defaultConfigurationIsVisible = 0; 795 | defaultConfigurationName = Release; 796 | }; 797 | A1CF8D731AFE1332005EDE51 /* Build configuration list for PBXNativeTarget "WatchButton WatchKit Extension" */ = { 798 | isa = XCConfigurationList; 799 | buildConfigurations = ( 800 | A1CF8D741AFE1332005EDE51 /* Debug */, 801 | A1CF8D751AFE1332005EDE51 /* Release */, 802 | ); 803 | defaultConfigurationIsVisible = 0; 804 | defaultConfigurationName = Release; 805 | }; 806 | /* End XCConfigurationList section */ 807 | }; 808 | rootObject = A1CF8D241AFE1325005EDE51 /* Project object */; 809 | } 810 | -------------------------------------------------------------------------------- /WatchButton.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WatchButton.xcodeproj/xcshareddata/xcschemes/Glance - WatchButton WatchKit App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 83 | 87 | 93 | 94 | 95 | 96 | 102 | 103 | 104 | 105 | 106 | 107 | 114 | 118 | 124 | 125 | 126 | 127 | 133 | 134 | 135 | 136 | 138 | 139 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /WatchButton.xcodeproj/xcshareddata/xcschemes/WatchButton WatchKit App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 82 | 86 | 92 | 93 | 94 | 95 | 101 | 102 | 103 | 104 | 105 | 106 | 112 | 116 | 122 | 123 | 124 | 125 | 131 | 132 | 133 | 134 | 136 | 137 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /WatchButton.xcodeproj/xcshareddata/xcschemes/WatchButton.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /WatchButton.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WatchButton.xcworkspace/xcshareddata/WatchButton.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1F11C153B7ACCE97334A74ED7522D4780A4D0750", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "1F11C153B7ACCE97334A74ED7522D4780A4D0750" : 0, 8 | "EB9FF6CC2185ECB3EFA62740E51DAF662F69DD0D" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "A512FC55-DA6E-45AF-B1B7-A65024CB2B87", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "1F11C153B7ACCE97334A74ED7522D4780A4D0750" : "WatchButton\/", 13 | "EB9FF6CC2185ECB3EFA62740E51DAF662F69DD0D" : "Cube\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "WatchButton", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "WatchButton.xcworkspace", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:contentful-labs\/Wunderschnell.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1F11C153B7ACCE97334A74ED7522D4780A4D0750" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:contentful-labs\/Cube.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "EB9FF6CC2185ECB3EFA62740E51DAF662F69DD0D" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /WatchKit App/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "filename" : "watch-48.png", 8 | "role" : "notificationCenter", 9 | "subtype" : "38mm" 10 | }, 11 | { 12 | "size" : "27.5x27.5", 13 | "idiom" : "watch", 14 | "scale" : "2x", 15 | "filename" : "watch-55.png", 16 | "role" : "notificationCenter", 17 | "subtype" : "42mm" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "watch", 22 | "filename" : "watch-58.png", 23 | "role" : "companionSettings", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "size" : "29x29", 28 | "idiom" : "watch", 29 | "filename" : "watch-87.png", 30 | "role" : "companionSettings", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "watch", 36 | "scale" : "2x", 37 | "filename" : "watch-80.png", 38 | "role" : "appLauncher", 39 | "subtype" : "38mm" 40 | }, 41 | { 42 | "size" : "44x44", 43 | "idiom" : "watch", 44 | "scale" : "2x", 45 | "filename" : "watch-88.png", 46 | "role" : "longLook", 47 | "subtype" : "42mm" 48 | }, 49 | { 50 | "size" : "86x86", 51 | "idiom" : "watch", 52 | "scale" : "2x", 53 | "filename" : "watch-172.png", 54 | "role" : "quickLook", 55 | "subtype" : "38mm" 56 | }, 57 | { 58 | "size" : "98x98", 59 | "idiom" : "watch", 60 | "scale" : "2x", 61 | "filename" : "watch-196.png", 62 | "role" : "quickLook", 63 | "subtype" : "42mm" 64 | } 65 | ], 66 | "info" : { 67 | "version" : 1, 68 | "author" : "xcode" 69 | } 70 | } -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-172.png -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-196.png -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-48.png -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-55.png -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-58.png -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-80.png -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-87.png -------------------------------------------------------------------------------- /WatchKit App/Images.xcassets/AppIcon.appiconset/watch-88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/Wunderschnell/65d36dc26d4d5520652b202a79846b48022dd452/WatchKit App/Images.xcassets/AppIcon.appiconset/watch-88.png -------------------------------------------------------------------------------- /WatchKit App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ÜberSchnell 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | UISupportedInterfaceOrientations 26 | 27 | UIInterfaceOrientationPortrait 28 | UIInterfaceOrientationPortraitUpsideDown 29 | 30 | WKCompanionAppBundleIdentifier 31 | org.vu0.WatchButton 32 | WKWatchKitApp 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /WatchKit Extension/GlanceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlanceController.swift 3 | // WatchButton WatchKit Extension 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MMWormhole 11 | import WatchConnectivity 12 | import WatchKit 13 | 14 | class GlanceController: WKInterfaceController { 15 | let wormhole = MMWormhole(applicationGroupIdentifier: AppGroupIdentifier, optionalDirectory: DirectoryIdentifier) 16 | 17 | @IBOutlet weak var infoLabel: WKInterfaceLabel! 18 | 19 | func resetLabel() { 20 | infoLabel.setText("No beacons in range :(") 21 | } 22 | 23 | override func willActivate() { 24 | super.willActivate() 25 | 26 | resetLabel() 27 | wormhole.listenForMessageWithIdentifier(Reply.BeaconRanged.rawValue) { (data) in 28 | if let ranged = data as? Bool { 29 | if ranged { 30 | self.infoLabel.setText("Beacon in range, tap to buy.") 31 | } 32 | return 33 | } 34 | 35 | self.resetLabel() 36 | } 37 | 38 | WCSession.defaultSession().sendMessage([ CommandIdentifier: Command.Nothing.rawValue ], replyHandler: { (_) in 39 | }, errorHandler: nil) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WatchKit Extension/Images.xcassets/README__ignoredByTemplate__: -------------------------------------------------------------------------------- 1 | Did you know that git does not support storing empty directories? 2 | -------------------------------------------------------------------------------- /WatchKit Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ÜberSchnell 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | WKAppBundleIdentifier 30 | org.vu0.WatchButton.watchkitapp 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.watchkit 34 | 35 | RemoteInterfacePrincipalClass 36 | $(PRODUCT_MODULE_NAME).InterfaceController 37 | 38 | 39 | -------------------------------------------------------------------------------- /WatchKit Extension/InterfaceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.swift 3 | // WatchButton WatchKit Extension 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Cube 10 | import Foundation 11 | import MMWormhole 12 | import WatchConnectivity 13 | import WatchKit 14 | 15 | class InterfaceController: WKInterfaceController, WCSessionDelegate { 16 | let wormhole = MMWormhole(applicationGroupIdentifier: AppGroupIdentifier, optionalDirectory: DirectoryIdentifier) 17 | 18 | @IBOutlet weak var productImage: WKInterfaceImage! 19 | @IBOutlet weak var productPrice: WKInterfaceLabel! 20 | 21 | override func willActivate() { 22 | super.willActivate() 23 | 24 | let session = WCSession.defaultSession() 25 | session.delegate = self 26 | session.activateSession() 27 | 28 | session.sendMessage([ CommandIdentifier: Command.GetProduct.rawValue ], replyHandler: { (data) in 29 | let product = Product(data[Reply.Product.rawValue] as! [String:AnyObject]) 30 | 31 | if let amount = product.price["amount"], currency = product.price["currency"] { 32 | self.productPrice.setText("\(amount) \(currency)") 33 | } 34 | 35 | NSURLSession.sharedSession().dataTaskWithRequest(NSURLRequest(URL: NSURL(string: product.imageUrl)!)) { (data, _, _) in 36 | if let data = data { 37 | self.productImage.setImage(UIImage(data: data)) 38 | } 39 | } 40 | }) { (error) in 41 | print(error) 42 | } 43 | } 44 | 45 | @IBAction func tapped() { 46 | wormhole.listenForMessageWithIdentifier(Reply.Paid.rawValue) { (data) in 47 | if let paid = data as? Bool { 48 | self.presentControllerWithName("PaymentAlert", context: paid) 49 | } else { 50 | self.presentControllerWithName("PaymentAlert", context: false) 51 | } 52 | } 53 | 54 | WCSession.defaultSession().sendMessage([ CommandIdentifier: Command.MakeOrder.rawValue ], replyHandler: { (_) in 55 | // Seems like openParentApplication() has a too short timeout for our orders. 56 | }, errorHandler: nil) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /WatchKit Extension/PaymentAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaymentAlert.swift 3 | // WatchButton 4 | // 5 | // Created by Boris Bügling on 09/05/15. 6 | // Copyright (c) 2015 Boris Bügling. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WatchKit 11 | 12 | class PaymentAlert: WKInterfaceController { 13 | @IBOutlet weak var infoLabel: WKInterfaceLabel! 14 | 15 | override func awakeWithContext(context: AnyObject?) { 16 | if let paid = context as? Bool { 17 | if paid { 18 | infoLabel.setText("Ordered successfully! 💸") 19 | return 20 | } 21 | } 22 | 23 | infoLabel.setText("Order failed... 😭") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WatchKit Extension/WatchButton WatchKit Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.vu0.org.WatchButton 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------