diff --git a/FitKit/Assets.xcassets/AppIcon.appiconset/Contents.json b/FitKit/Assets.xcassets/AppIcon.appiconset/Contents.json
index d8db8d6..c3bd516 100644
--- a/FitKit/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/FitKit/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -31,13 +31,15 @@
"scale" : "3x"
},
{
- "idiom" : "iphone",
"size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-120.png",
"scale" : "2x"
},
{
- "idiom" : "iphone",
"size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-180.png",
"scale" : "3x"
},
{
diff --git a/FitKit/Assets.xcassets/AppIcon.appiconset/icon-120.png b/FitKit/Assets.xcassets/AppIcon.appiconset/icon-120.png
new file mode 100644
index 0000000..ae6b3b9
Binary files /dev/null and b/FitKit/Assets.xcassets/AppIcon.appiconset/icon-120.png differ
diff --git a/FitKit/Assets.xcassets/AppIcon.appiconset/icon-180.png b/FitKit/Assets.xcassets/AppIcon.appiconset/icon-180.png
new file mode 100644
index 0000000..07f1998
Binary files /dev/null and b/FitKit/Assets.xcassets/AppIcon.appiconset/icon-180.png differ
diff --git a/FitKit/Assets.xcassets/Contents.json b/FitKit/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/FitKit/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/FitKit/Base.lproj/Main.storyboard b/FitKit/Base.lproj/Main.storyboard
index 2ba3971..8ac3174 100644
--- a/FitKit/Base.lproj/Main.storyboard
+++ b/FitKit/Base.lproj/Main.storyboard
@@ -27,14 +27,14 @@
-
-
+
+
@@ -44,7 +44,7 @@
-
+
diff --git a/FitKit/FitbitClient.swift b/FitKit/FitbitClient.swift
index 12e75e2..7f638e9 100644
--- a/FitKit/FitbitClient.swift
+++ b/FitKit/FitbitClient.swift
@@ -15,6 +15,7 @@ import OAuthSwiftAlamofire
class FitbitClient {
private enum FitKitRequestError: Error {
case unableToParseResults
+ case invalidAuthorization
}
private var oauthClient: OAuth2Swift
@@ -24,6 +25,7 @@ class FitbitClient {
private let authorizeUrl = "https://www.fitbit.com/oauth2/authorize"
private let accessTokenUrl = "https://api.fitbit.com/oauth2/token"
private var keychainWrapper: KeychainWrapper
+ private let keychainCredKey = "credential"
init() {
// Init inner client
@@ -44,10 +46,11 @@ class FitbitClient {
self.keychainWrapper = KeychainWrapper.standard
// Attempt to load tokens
- self.loadStoredTokens()
+ self.maybeLoadStoredCredential()
}
- func loadStoredTokens() {
+ /// Attempts to load and use stored credentials
+ func maybeLoadStoredCredential() {
if let credential = self.readCredential() {
NSLog("Using credential from Keychain")
self.oauthClient.client.credential.oauthToken = credential.oauthToken
@@ -55,8 +58,11 @@ class FitbitClient {
}
}
+ /// Attempts to read OAuthSwiftCredential from the keychain
+ ///
+ /// - Returns: If a valid credential is found, it will be returned
func readCredential() -> OAuthSwiftCredential? {
- if let credential = self.keychainWrapper.object(forKey: "credential") as? OAuthSwiftCredential {
+ if let credential = self.keychainWrapper.object(forKey: keychainCredKey) as? OAuthSwiftCredential {
NSLog("Found credential in keychain")
return credential
}
@@ -64,10 +70,21 @@ class FitbitClient {
return nil
}
+ /// Persists an OAuthSwiftCredential into the keychain
+ ///
+ /// - Parameter credential: credential to be persisted
+ /// - Returns: TRUE if persisted successfully
private func storeCredential(_ credential: OAuthSwiftCredential) -> Bool {
- return self.keychainWrapper.set(credential, forKey: "credential")
+ return self.keychainWrapper.set(credential, forKey: keychainCredKey)
}
-
+
+ /// Clears credential token and expiration
+ func clearCredential() {
+ self.oauthClient.client.credential.oauthToken = ""
+ self.oauthClient.client.credential.oauthTokenExpiresAt = nil
+ self.keychainWrapper.removeObject(forKey: keychainCredKey)
+ }
+
/// Check if the current client is already authorized
///
/// - Returns: True if the current client can make requests
@@ -90,12 +107,13 @@ class FitbitClient {
/// - viewController: the source controller to create the new Safari view
/// - success: Callback to be executed on success
/// - failure: Callback to be executed on failure
- func authorize(viewController: UIViewController, success: @escaping () -> Void, failure: @escaping () -> Void) {
+ func authorize(viewController: UIViewController, callback: @escaping (String?, Error?) -> Void) {
// Set webview handler
self.oauthClient.authorizeURLHandler = SafariURLHandler(
viewController: viewController,
oauthSwift: self.oauthClient
)
+
// Authorize
let _ = self.oauthClient.authorize(
withCallbackURL: URL(string: self.callbackUrl)!,
@@ -108,19 +126,18 @@ class FitbitClient {
let scope = parameters["scope"] as? String
else {
NSLog("Invalid authorization response")
- failure()
+ callback(nil, FitKitRequestError.invalidAuthorization)
return
}
NSLog("Authorized scope: \(scope)")
NSLog("Succesfully authenticated with credentials \(credential.oauthToken)")
let _ = self.storeCredential(credential)
- success()
+ callback(scope, nil)
},
failure: {
error in
- // TODO: Maybe return the error
- NSLog(error.localizedDescription)
- failure()
+ NSLog("Error authorizing \(error.localizedDescription)")
+ callback(nil, error)
}
)
}
diff --git a/FitKit/ViewController.swift b/FitKit/ViewController.swift
index 6687dcf..c1a06bf 100644
--- a/FitKit/ViewController.swift
+++ b/FitKit/ViewController.swift
@@ -23,7 +23,7 @@ class ViewController: UIViewController {
}
override func viewDidAppear(_ animated: Bool) {
- authorize()
+ ensureAuthorized(callback: self.updateView)
}
override func didReceiveMemoryWarning() {
@@ -34,35 +34,53 @@ class ViewController: UIViewController {
fileprivate func updateAuthorizedLabel() {
if self.client.isAuthorized() {
self.loggedIn.text = "Logged In"
+ //self.loginButton.currentTitle = "Log Out"
} else {
self.loggedIn.text = "Not Logged In"
+ //self.loginButton.currentTitle = "Log In"
}
}
- fileprivate func authorize() {
- HealthKitHelper.authorizeHealthKit() {
- result, error in
- // TODO: Do something with the result and error
- if !self.client.isAuthorized() {
- self.client.authorize(viewController: self, success: self.authorized, failure: self.unauthroized)
- } else {
- // TODO: this should be made significantly more clear
- self.displayWeight()
+ /// Ensure that the client is authorized and then continue
+ fileprivate func ensureAuthorized(callback: @escaping (Bool) -> Void) {
+ // If already authroized, we can short circuit
+ if self.client.isAuthorized() {
+ callback(true)
+ return
+ }
+ // Authorize the client
+ self.client.authorize(viewController: self) {
+ scope, error in
+ if error != nil {
+ NSLog("Error encountered when attempting authorization: \(error.debugDescription)")
+ }
+ guard let scope = scope else {
+ NSLog("Did not retreive any authorized scope")
+ callback(false)
+ return
+ }
+ NSLog("Authorized for scope: \(scope). Requesting HealthKit access")
+ HealthKitHelper.authorizeHealthKit() {
+ result, error in
+ // TODO: Do something with the result and error
+ if error != nil {
+ callback(false)
+ } else {
+ callback(true)
+ }
}
}
}
- fileprivate func authorized() {
- NSLog("We have Fitbit auth!!!")
- updateAuthorizedLabel()
- self.displayWeight()
- }
-
- fileprivate func unauthroized() {
- NSLog("No auth. Booo!!!")
+ fileprivate func updateView(authSuccess: Bool) {
+ self.updateAuthorizedLabel()
+ if authSuccess {
+ self.syncWeights()
+ }
}
- func displayWeight() {
+ /// Synchronizes weights from Fitbit to HealthKit
+ func syncWeights() {
let today = Date()
let lastMonth = Calendar.current.date(byAdding: .day, value: -31, to: today)!
self.client.getWeight(start: lastMonth, end: today) {
@@ -70,6 +88,7 @@ class ViewController: UIViewController {
fbWeights, error in
if let error = error {
NSLog("Unable to get weights: \(error)")
+ self.outputText.text = "Error getting weights"
return
}