Tusker/Packages/UserAccounts/Sources/UserAccounts/UserAccountsManager.swift

156 lines
5.4 KiB
Swift

//
// UserAccountsManager.swift
// UserAccounts
//
// Created by Shadowfacts on 3/5/23.
//
import Foundation
import Combine
public class UserAccountsManager: ObservableObject {
public static let shared = UserAccountsManager()
let defaults: UserDefaults
private init() {
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING") {
defaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).uitesting")!
defaults.removePersistentDomain(forName: "\(Bundle.main.bundleIdentifier!).uitesting")
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") {
accounts = [
UserAccountInfo(
instanceURL: URL(string: "http://localhost:8080")!,
clientID: "client_id",
clientSecret: "client_secret",
username: "admin",
accessToken: "access_token")
]
}
} else {
defaults = UserDefaults(suiteName: "group.space.vaccor.Tusker")!
}
migrateAccountIDsIfNecessary()
}
private let accountsKey = "accounts"
public private(set) var accounts: [UserAccountInfo] {
get {
if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
return array.compactMap(UserAccountInfo.init(userDefaultsDict:))
} else {
return []
}
}
set {
objectWillChange.send()
let array = newValue.map(\.userDefaultsDict)
defaults.set(array, forKey: accountsKey)
}
}
private let mostRecentAccountKey = "mostRecentAccount"
public private(set) var mostRecentAccountID: String? {
get {
return defaults.string(forKey: mostRecentAccountKey)
}
set {
objectWillChange.send()
defaults.set(newValue, forKey: mostRecentAccountKey)
}
}
private let usesAccountIDHashesKey = "usesAccountIDHashes"
private var usesAccountIDHashes: Bool {
get {
return defaults.bool(forKey: usesAccountIDHashesKey)
}
set {
return defaults.set(newValue, forKey: usesAccountIDHashesKey)
}
}
private func migrateAccountIDsIfNecessary() {
if usesAccountIDHashes {
return
}
if let mostRecentAccount = getMostRecentAccount() {
let hashedMostRecentID = UserAccountInfo.id(instanceURL: mostRecentAccount.instanceURL, username: mostRecentAccount.username)
mostRecentAccountID = hashedMostRecentID
}
if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
accounts = array.compactMap {
guard let urlString = $0["instanceURL"],
let url = URL(string: urlString),
let username = $0["username"] else {
return nil
}
var dict = $0
dict["id"] = UserAccountInfo.id(instanceURL: url, username: username)
return UserAccountInfo(userDefaultsDict: dict)
}
}
usesAccountIDHashes = true
}
// MARK: Account Management
public var onboardingComplete: Bool {
return !accounts.isEmpty
}
public 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 info = UserAccountInfo(instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken)
accounts.append(info)
self.accounts = accounts
return info
}
public func removeAccount(_ info: UserAccountInfo) {
accounts.removeAll(where: { $0.id == info.id })
}
public func getAccount(id: String) -> UserAccountInfo? {
return accounts.first(where: { $0.id == id })
}
public func getMostRecentAccount() -> UserAccountInfo? {
guard onboardingComplete else { return nil }
let mostRecent: UserAccountInfo?
if let id = mostRecentAccountID {
mostRecent = accounts.first { $0.id == id }
} else {
mostRecent = nil
}
return mostRecent ?? accounts.first!
}
public func setMostRecentAccount(_ account: UserAccountInfo?) {
mostRecentAccountID = account?.id
}
public func updateServerPreferences(_ account: UserAccountInfo, defaultLanguage: String?, defaultVisibility: String?, defaultFederation: Bool?) {
guard let index = accounts.firstIndex(where: { $0.id == account.id }) else {
return
}
var account = account
account.serverDefaultLanguage = defaultLanguage
account.serverDefaultVisibility = defaultVisibility
account.serverDefaultFederation = defaultFederation
accounts[index] = account
}
}
public extension Notification.Name {
static let userLoggedOut = Notification.Name("Tusker.userLoggedOut")
static let addAccount = Notification.Name("Tusker.addAccount")
static let activateAccount = Notification.Name("Tusker.activateAccount")
}