// // LocalData.swift // Tusker // // Created by Shadowfacts on 8/18/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import Foundation import Combine class LocalData: ObservableObject { static let shared = LocalData() let defaults: UserDefaults private init() { if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING") { defaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).uitesting")! if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") { accounts = [ UserAccountInfo( id: UUID().uuidString, instanceURL: URL(string: "http://localhost:8080")!, clientID: "client_id", clientSecret: "client_secret", username: "admin", accessToken: "access_token") ] } } else { defaults = UserDefaults() } } private let accountsKey = "accounts" var accounts: [UserAccountInfo] { get { if let array = defaults.array(forKey: accountsKey) as? [[String: String]] { return array.compactMap { (info) in guard let id = info["id"], let instanceURL = info["instanceURL"], let url = URL(string: instanceURL), let clientId = info["clientID"], let secret = info["clientSecret"], let accessToken = info["accessToken"] else { return nil } return UserAccountInfo(id: id, instanceURL: url, clientID: clientId, clientSecret: secret, username: info["username"], accessToken: accessToken) } } else { return [] } } set { objectWillChange.send() let array = newValue.map { (info) -> [String: String] in var res = [ "id": info.id, "instanceURL": info.instanceURL.absoluteString, "clientID": info.clientID, "clientSecret": info.clientSecret, "accessToken": info.accessToken ] if let username = info.username { res["username"] = username } return res } defaults.set(array, forKey: accountsKey) } } private let mostRecentAccountKey = "mostRecentAccount" private var mostRecentAccount: String? { get { return defaults.string(forKey: mostRecentAccountKey) } set { objectWillChange.send() defaults.set(newValue, forKey: mostRecentAccountKey) } } var onboardingComplete: Bool { return !accounts.isEmpty } func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String?, accessToken: String) -> UserAccountInfo { var accounts = self.accounts if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) { accounts.remove(at: index) } let id = UUID().uuidString let info = UserAccountInfo(id: id, instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken) accounts.append(info) self.accounts = accounts return info } func setUsername(for info: UserAccountInfo, username: String) { var info = info info.username = username removeAccount(info) accounts.append(info) } func removeAccount(_ info: UserAccountInfo) { accounts.removeAll(where: { $0.id == info.id }) } func getAccount(id: String) -> UserAccountInfo? { return accounts.first(where: { $0.id == id }) } func getMostRecentAccount() -> UserAccountInfo? { guard onboardingComplete else { return nil } let mostRecent: UserAccountInfo? if let id = mostRecentAccount { mostRecent = accounts.first { $0.id == id } } else { mostRecent = nil } return mostRecent ?? accounts.first! } func setMostRecentAccount(_ account: UserAccountInfo?) { mostRecentAccount = account?.id } } extension LocalData { struct UserAccountInfo: Equatable, Hashable { let id: String let instanceURL: URL let clientID: String let clientSecret: String fileprivate(set) var username: String! let accessToken: String func hash(into hasher: inout Hasher) { hasher.combine(id) } static func ==(lhs: UserAccountInfo, rhs: UserAccountInfo) -> Bool { return lhs.id == rhs.id } } } extension Notification.Name { static let userLoggedOut = Notification.Name("Tusker.userLoggedOut") static let addAccount = Notification.Name("Tusker.addAccount") static let activateAccount = Notification.Name("Tusker.activateAccount") }