diff --git a/README.md b/README.md index 2e49be1..4614a1e 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ For devices running watchOS 5 or higher. ![Show project and targets list](Screenshots/Show_project_and_targets_list.png) 1. Select the **General** tab: ![Change bundle identifier 1](Screenshots/Change_bundle_identifier1.png) - 1. For each of the 3 **targets** replece *kuglee* in the **Bundle Identifier** field with the name of your developer account. (The name of your Apple ID without the *@xxxx.com*.) + 1. For each of the 3 **targets** replece *k0042n* in the **Bundle Identifier** field with the name of your developer account. (The name of your Apple ID without the *@xxxx.com*.) ![Change bundle identifier 2](Screenshots/Change_bundle_identifier2.png) 1. Change the project's team: 1. Select the **Signing & Capabilities** tab: @@ -52,7 +52,7 @@ For devices running watchOS 5 or higher. ![Change team 2](Screenshots/Change_team2.png) 1. Manually replace bundle identifiers: 1. Select **Xcode** menu -> **Find** -> **Find and Replace in Project…**. - 1. In the **Text** field type *kuglee*. + 1. In the **Text** field type *k0042n*. 1. In the **With** field type the name of your developer account. (The name of your Apple ID without the @xxxx.com.) 1. Click the **Replace All** button. ![Change bundle identifier 3](Screenshots/Change_bundle_identifier3.png) diff --git a/TermiWatch WatchKit App/Base.lproj/Interface.storyboard b/TermiWatch WatchKit App/Base.lproj/Interface.storyboard index 0f1f189..1ce74d9 100644 --- a/TermiWatch WatchKit App/Base.lproj/Interface.storyboard +++ b/TermiWatch WatchKit App/Base.lproj/Interface.storyboard @@ -1,12 +1,10 @@ - - - - + + - - + + @@ -107,7 +105,36 @@ + + + diff --git a/TermiWatch WatchKit App/Info.plist b/TermiWatch WatchKit App/Info.plist index af0cd8c..56e90c1 100644 --- a/TermiWatch WatchKit App/Info.plist +++ b/TermiWatch WatchKit App/Info.plist @@ -26,7 +26,7 @@ UIInterfaceOrientationPortraitUpsideDown WKCompanionAppBundleIdentifier - com.kuglee.TermiWatch + com.k0042n.TermiWatch WKWatchKitApp diff --git a/TermiWatch WatchKit Extension/EventKitUtils.swift b/TermiWatch WatchKit Extension/EventKitUtils.swift new file mode 100644 index 0000000..8cf47cf --- /dev/null +++ b/TermiWatch WatchKit Extension/EventKitUtils.swift @@ -0,0 +1,30 @@ +import Foundation +import EventKit +import PromiseKit + +func eventRequestAccess(eventStore: EKEventStore) { + eventStore.requestAccess(to: .event) { granted, error in + // Handle the response to the request. + } +} + +func fetchTopEvent(eventStore: EKEventStore, calendar: Calendar) -> String { + // Create the start date components + let now = Date() + + // Create the end date components. + let tomorrow = Date().addingTimeInterval(60 * 60 * 24) + + // Create the predicate from the event store's instance method. + var predicate: NSPredicate? = nil + predicate = eventStore.predicateForEvents(withStart: now, end: tomorrow, calendars: nil) + + // Fetch all events that match the predicate. + var events: [EKEvent]? = nil + if let aPredicate = predicate { + events = eventStore.events(matching: aPredicate) + } + + let topEventTitle = events?.first?.title ?? "No more events" + return topEventTitle +} diff --git a/TermiWatch WatchKit Extension/Info.plist b/TermiWatch WatchKit Extension/Info.plist index b958b89..d8912dd 100644 --- a/TermiWatch WatchKit Extension/Info.plist +++ b/TermiWatch WatchKit Extension/Info.plist @@ -25,7 +25,7 @@ NSExtensionAttributes WKAppBundleIdentifier - com.kuglee.TermiWatch.watchkitapp + com.k0042n.TermiWatch.watchkitapp NSExtensionPointIdentifier com.apple.watchkit @@ -34,6 +34,8 @@ Your health records will be used to display activity data. NSLocationWhenInUseUsageDescription Your location data will be used to display weather data. + NSCalendarsUsageDescription + Your calendar data will be used to display event OpenWeatherMapAPIKey 8d30dae09830ab2917caface6d6cf1c5 WKExtensionDelegateClassName diff --git a/TermiWatch WatchKit Extension/InterfaceController.swift b/TermiWatch WatchKit Extension/InterfaceController.swift index 965a64e..5ccd704 100644 --- a/TermiWatch WatchKit Extension/InterfaceController.swift +++ b/TermiWatch WatchKit Extension/InterfaceController.swift @@ -1,10 +1,12 @@ import Foundation import HealthKit +import EventKit import PMKCoreLocation import PMKHealthKit import PromiseKit import Swizzle import WatchKit +import WatchConnectivity // MARK: - UIKit stubs @@ -172,14 +174,28 @@ func batteryIndicatorString(percent: UInt) -> String { } class InterfaceController: WKInterfaceController { + @IBOutlet weak var userHostLabelNow: WKInterfaceLabel! @IBOutlet var batteryLabel: WKInterfaceLabel! + @IBOutlet weak var batteryGroup: WKInterfaceGroup! @IBOutlet var activityLabel: WKInterfaceLabel! + @IBOutlet weak var activityGroup: WKInterfaceGroup! @IBOutlet var stepsLabel: WKInterfaceLabel! + @IBOutlet weak var stepsGroup: WKInterfaceGroup! @IBOutlet var heartRateLabel: WKInterfaceLabel! + @IBOutlet weak var heartRateGroup: WKInterfaceGroup! @IBOutlet var temperatureLabel: WKInterfaceLabel! + @IBOutlet weak var temperatureGroup: WKInterfaceGroup! + @IBOutlet var calendarLabel: WKInterfaceLabel! + @IBOutlet weak var calendarGroup: WKInterfaceGroup! + @IBOutlet weak var userHostLabel: WKInterfaceLabel! + + let session = WCSession.default override func awake(withContext context: Any?) { super.awake(withContext: context) + + session.delegate = self + session.activate() // MARK: - Temperature @@ -204,7 +220,34 @@ class InterfaceController: WKInterfaceController { }.catch { print("Error:", $0) } - + + // MARK: - Calendar + + let eventStore = EKEventStore() + + let ekStatus = EKEventStore.authorizationStatus(for: .event) + switch ekStatus { + case EKAuthorizationStatus.notDetermined: + eventRequestAccess(eventStore: eventStore) + self.calendarLabel.setText("notDetermined") + case EKAuthorizationStatus.authorized: + let calendar = Calendar.current + self.calendarLabel.setText(fetchTopEvent(eventStore: eventStore, calendar: calendar)) + case EKAuthorizationStatus.restricted, EKAuthorizationStatus.denied: + self.calendarLabel.setText("restricted") + default: + self.calendarLabel.setText("error") + } + + NotificationCenter.default.addObserver( + forName: .EKEventStoreChanged, + object: eventStore, + queue: nil + ) { [weak self] notification in + let calendar = Calendar.current + self?.calendarLabel.setText(fetchTopEvent(eventStore: eventStore, calendar: calendar)) + } + // MARK: - Health let healthStore = HKHealthStore() @@ -301,3 +344,29 @@ class InterfaceController: WKInterfaceController { hideTimeOnce() } } + +extension InterfaceController: WCSessionDelegate { + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + + } + + func session(_ session: WCSession, didReceiveMessage message: [String : Any]) { + let username = message["username"] as? String ?? "user" + let hostname = message["hostname"] as? String ?? "watch" + let showTemperature = message["temperature"] as? Bool ?? true + let showBattery = message["battery"] as? Bool ?? true + let showActivity = message["activity"] as? Bool ?? true + let showSteps = message["steps"] as? Bool ?? true + let showHeartRate = message["heart-rate"] as? Bool ?? true + let showCalendar = message["calendar"] as? Bool ?? false + + self.userHostLabelNow.setText(username + "@" + hostname + ":~ $ now") + self.userHostLabel.setText(username + "@" + hostname + ":~ $") + self.temperatureGroup.setHidden(!showTemperature) + self.batteryGroup.setHidden(!showBattery) + self.activityGroup.setHidden(!showActivity) + self.stepsGroup.setHidden(!showSteps) + self.heartRateGroup.setHidden(!showHeartRate) + self.calendarGroup.setHidden(!showCalendar) + } +} diff --git a/TermiWatch.xcodeproj/project.pbxproj b/TermiWatch.xcodeproj/project.pbxproj index 659a0f7..55bb7b2 100644 --- a/TermiWatch.xcodeproj/project.pbxproj +++ b/TermiWatch.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 5DD84FF8245F172E00E780A4 /* Swizzle in Frameworks */ = {isa = PBXBuildFile; productRef = 5DD84FF7245F172E00E780A4 /* Swizzle */; }; 5DD84FFB245F176400E780A4 /* PMKCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = 5DD84FFA245F176400E780A4 /* PMKCoreLocation */; }; 5DD84FFD245F176800E780A4 /* PMKHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5DD84FFC245F176800E780A4 /* PMKHealthKit */; }; + B639AD6C26BDE85200895D4F /* EventKitUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B639AD6B26BDE85200895D4F /* EventKitUtils.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -100,6 +101,7 @@ 5DD84FE4245F12D800E780A4 /* TermiWatch WatchKit Extension.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "TermiWatch WatchKit Extension.entitlements"; sourceTree = ""; }; 5DD84FE8245F132B00E780A4 /* TermiWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TermiWatch.entitlements; sourceTree = ""; }; 5DD84FE9245F134E00E780A4 /* SF-Mono-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-Regular.otf"; sourceTree = ""; }; + B639AD6B26BDE85200895D4F /* EventKitUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventKitUtils.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -185,6 +187,7 @@ 5DD84FE1245F12D700E780A4 /* TemperatureNotifier.swift */, 5DD84FD1245F118500E780A4 /* Assets.xcassets */, 5DD84FD3245F118500E780A4 /* Info.plist */, + B639AD6B26BDE85200895D4F /* EventKitUtils.swift */, ); path = "TermiWatch WatchKit Extension"; sourceTree = ""; @@ -361,6 +364,7 @@ 5DD84FE7245F12D800E780A4 /* BatteryInfoNotifier.swift in Sources */, 5DD84FE6245F12D800E780A4 /* HealthKitUtils.swift in Sources */, 5DD84FCE245F118400E780A4 /* InterfaceController.swift in Sources */, + B639AD6C26BDE85200895D4F /* EventKitUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -527,14 +531,14 @@ ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CODE_SIGN_ENTITLEMENTS = "TermiWatch WatchKit Extension/TermiWatch WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = C89KNBM28S; + DEVELOPMENT_TEAM = 59BBS8YWDG; INFOPLIST_FILE = "TermiWatch WatchKit Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.kuglee.TermiWatch.watchkitapp.watchkitextension; + PRODUCT_BUNDLE_IDENTIFIER = com.k0042n.TermiWatch.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -550,14 +554,14 @@ ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CODE_SIGN_ENTITLEMENTS = "TermiWatch WatchKit Extension/TermiWatch WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = C89KNBM28S; + DEVELOPMENT_TEAM = 59BBS8YWDG; INFOPLIST_FILE = "TermiWatch WatchKit Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.kuglee.TermiWatch.watchkitapp.watchkitextension; + PRODUCT_BUNDLE_IDENTIFIER = com.k0042n.TermiWatch.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -573,10 +577,10 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = C89KNBM28S; + DEVELOPMENT_TEAM = 59BBS8YWDG; IBSC_MODULE = TermiWatch_WatchKit_Extension; INFOPLIST_FILE = "TermiWatch WatchKit App/Info.plist"; - PRODUCT_BUNDLE_IDENTIFIER = com.kuglee.TermiWatch.watchkitapp; + PRODUCT_BUNDLE_IDENTIFIER = com.k0042n.TermiWatch.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -592,10 +596,10 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = C89KNBM28S; + DEVELOPMENT_TEAM = 59BBS8YWDG; IBSC_MODULE = TermiWatch_WatchKit_Extension; INFOPLIST_FILE = "TermiWatch WatchKit App/Info.plist"; - PRODUCT_BUNDLE_IDENTIFIER = com.kuglee.TermiWatch.watchkitapp; + PRODUCT_BUNDLE_IDENTIFIER = com.k0042n.TermiWatch.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -611,14 +615,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = TermiWatch/TermiWatch.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = C89KNBM28S; + DEVELOPMENT_TEAM = 59BBS8YWDG; INFOPLIST_FILE = TermiWatch/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.kuglee.TermiWatch; + PRODUCT_BUNDLE_IDENTIFIER = com.k0042n.TermiWatch; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -638,7 +642,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.kuglee.TermiWatch; + PRODUCT_BUNDLE_IDENTIFIER = com.k0042n.TermiWatch; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/TermiWatch/Base.lproj/LaunchScreen.storyboard b/TermiWatch/Base.lproj/LaunchScreen.storyboard index bfa3612..1abc00d 100644 --- a/TermiWatch/Base.lproj/LaunchScreen.storyboard +++ b/TermiWatch/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,9 @@ - - + + + - + + @@ -11,10 +13,10 @@ - + - + diff --git a/TermiWatch/Base.lproj/Main.storyboard b/TermiWatch/Base.lproj/Main.storyboard index a551298..e0918c9 100644 --- a/TermiWatch/Base.lproj/Main.storyboard +++ b/TermiWatch/Base.lproj/Main.storyboard @@ -1,12 +1,11 @@ - - - - + + - + + @@ -15,14 +14,302 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TermiWatch/Info.plist b/TermiWatch/Info.plist index 363c693..60aaa9d 100644 --- a/TermiWatch/Info.plist +++ b/TermiWatch/Info.plist @@ -26,6 +26,8 @@ Your health records will be used to display activity data. NSLocationWhenInUseUsageDescription Your location data will be used to display weather data. + NSCalendarsUsageDescription + Your calendar data will be used to display event UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/TermiWatch/ViewController.swift b/TermiWatch/ViewController.swift index 826418b..2af497f 100644 --- a/TermiWatch/ViewController.swift +++ b/TermiWatch/ViewController.swift @@ -1,9 +1,11 @@ import CoreLocation import HealthKit +import EventKit import PMKCoreLocation import PMKHealthKit import PromiseKit import UIKit +import WatchConnectivity let hkDataTypesOfInterest = Set([ HKObjectType.activitySummaryType(), @@ -15,8 +17,29 @@ let hkDataTypesOfInterest = Set([ ]) class ViewController: UIViewController { + @IBOutlet weak var usernameTextfield: UITextField! + @IBOutlet weak var hostnameTextfield: UITextField! + @IBOutlet weak var temperatureSwitch: UISwitch! + @IBOutlet weak var batterySwitch: UISwitch! + @IBOutlet weak var activitySwitch: UISwitch! + @IBOutlet weak var stepSwitch: UISwitch! + @IBOutlet weak var hrSwitch: UISwitch! + @IBOutlet weak var calendarSwitch: UISwitch! + @IBOutlet weak var warningLabel: UILabel! + + var session: WCSession? + override func viewDidLoad() { super.viewDidLoad() + + self.warningLabel.isHidden = true + + createWCSession() + + setDefaultSwitchesStatus() + + let dismissalTap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + view.addGestureRecognizer(dismissalTap) firstly { CLLocationManager.requestAuthorization() @@ -25,5 +48,75 @@ class ViewController: UIViewController { }.catch { print("Error:", $0) } + + EKEventStore().requestAccess(to: .event) { granted, error in + // Handle the response to the request. + } + } + + func createWCSession() { + if WCSession.isSupported() { + session = WCSession.default + session?.delegate = self + session?.activate() + } + } + + func setDefaultSwitchesStatus() { + self.temperatureSwitch.setOn(true, animated: false) + self.batterySwitch.setOn(true, animated: false) + self.activitySwitch.setOn(true, animated: false) + self.stepSwitch.setOn(true, animated: false) + self.hrSwitch.setOn(true, animated: false) + self.calendarSwitch.setOn(false, animated: false) + } + + @IBAction func updateConfigToWatch(_ sender: UIButton) { + let username: String = self.usernameTextfield.text! + let hostname: String = self.hostnameTextfield.text! + if !isInputLengthValid(username: username, hostname: hostname) { return } + + let data = ["username": username, + "hostname": hostname, + "temperature": self.temperatureSwitch.isOn, + "battery": self.batterySwitch.isOn, + "activity": self.activitySwitch.isOn, + "steps": self.stepSwitch.isOn, + "heart-rate": self.hrSwitch.isOn, + "calendar": self.calendarSwitch.isOn] as [String : Any] + + if let validSession = self.session, validSession.isReachable { + validSession.sendMessage(data, replyHandler: nil, errorHandler: nil) + } + } + + func isInputLengthValid(username: String, hostname: String) -> Bool { + self.warningLabel.isHidden = true + if (username.count + hostname.count > 9) { + self.warningLabel.text = "Max total Characters for username and hostname: 9" + self.warningLabel.isHidden = false + return false + } + + return true + } + + @objc func dismissKeyboard() { + // Causes the view (or one of its embedded text fields) to resign the first responder status. + view.endEditing(true) + } +} + +extension ViewController: WCSessionDelegate { + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + + } + + func sessionDidBecomeInactive(_ session: WCSession) { + + } + + func sessionDidDeactivate(_ session: WCSession) { + } }