Tusker/Tusker/LocalData.swift
Shadowfacts 465aedd43f
Make account info username optional
Onboarding view controller needs to set the account info object on the
mastodon controller before calling getOwnAccount since getOwnAccount
will upsert the user's account into the persistent container, which
requires the account info to exist to create a unique-per-account
identifier.
2020-04-12 11:14:10 -04:00

158 lines
5.2 KiB
Swift

//
// 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")
}