diff --git a/Packages/ComposeUI/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/Packages/ComposeUI/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/Packages/ComposeUI/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Packages/UserAccounts/.gitignore b/Packages/UserAccounts/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/UserAccounts/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/UserAccounts/Package.resolved b/Packages/UserAccounts/Package.resolved new file mode 100644 index 00000000..a9ae5ab1 --- /dev/null +++ b/Packages/UserAccounts/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-url", + "kind" : "remoteSourceControl", + "location" : "https://github.com/karwa/swift-url.git", + "state" : { + "branch" : "main", + "revision" : "220f6ab9d8a7e0742f85eb9f21b745942e001ae6" + } + } + ], + "version" : 2 +} diff --git a/Packages/UserAccounts/Package.swift b/Packages/UserAccounts/Package.swift new file mode 100644 index 00000000..cc7656a8 --- /dev/null +++ b/Packages/UserAccounts/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 5.7 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "UserAccounts", + platforms: [ + .iOS(.v15), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "UserAccounts", + targets: ["UserAccounts"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(path: "../Pachyderm"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "UserAccounts", + dependencies: ["Pachyderm"]), + .testTarget( + name: "UserAccountsTests", + dependencies: ["UserAccounts"]), + ] +) diff --git a/Packages/UserAccounts/README.md b/Packages/UserAccounts/README.md new file mode 100644 index 00000000..c2ecdca8 --- /dev/null +++ b/Packages/UserAccounts/README.md @@ -0,0 +1,3 @@ +# UserAccounts + +A description of this package. diff --git a/Packages/UserAccounts/Sources/UserAccounts/UserAccountInfo.swift b/Packages/UserAccounts/Sources/UserAccounts/UserAccountInfo.swift new file mode 100644 index 00000000..2fac2f8a --- /dev/null +++ b/Packages/UserAccounts/Sources/UserAccounts/UserAccountInfo.swift @@ -0,0 +1,80 @@ +// +// UserAccountInfo.swift +// UserAccounts +// +// Created by Shadowfacts on 3/5/23. +// + +import Foundation +import CryptoKit + +public struct UserAccountInfo: Equatable, Hashable { + public let id: String + public let instanceURL: URL + public let clientID: String + public let clientSecret: String + public private(set) var username: String! + public let accessToken: String + + fileprivate static let tempAccountID = "temp" + + static func id(instanceURL: URL, username: String?) -> String { + // We hash the instance host and username to form the account ID + // so that account IDs will match across devices, allowing for data syncing and handoff. + var hasher = SHA256() + hasher.update(data: instanceURL.host!.data(using: .utf8)!) + if let username { + hasher.update(data: username.data(using: .utf8)!) + } + return Data(hasher.finalize()).base64EncodedString() + } + + /// Only to be used for temporary MastodonController needed to fetch own account info and create final UserAccountInfo with real username + public init(tempInstanceURL instanceURL: URL, clientID: String, clientSecret: String, accessToken: String) { + self.id = UserAccountInfo.tempAccountID + self.instanceURL = instanceURL + self.clientID = clientID + self.clientSecret = clientSecret + self.accessToken = accessToken + } + + init(instanceURL: URL, clientID: String, clientSecret: String, username: String? = nil, accessToken: String) { + self.id = UserAccountInfo.id(instanceURL: instanceURL, username: username) + self.instanceURL = instanceURL + self.clientID = clientID + self.clientSecret = clientSecret + self.username = username + self.accessToken = accessToken + } + + init?(userDefaultsDict dict: [String: String]) { + guard let id = dict["id"], + let instanceURL = dict["instanceURL"], + let url = URL(string: instanceURL), + let clientID = dict["clientID"], + let secret = dict["clientSecret"], + let accessToken = dict["accessToken"] else { + return nil + } + self.id = id + self.instanceURL = url + self.clientID = clientID + self.clientSecret = secret + self.username = dict["username"] + self.accessToken = accessToken + } + + /// A filename-safe string for this account + public var persistenceKey: String { + // slashes are not allowed in the persistent store coordinator name + id.replacingOccurrences(of: "/", with: "_") + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + + public static func ==(lhs: UserAccountInfo, rhs: UserAccountInfo) -> Bool { + return lhs.id == rhs.id + } +} diff --git a/Tusker/LocalData.swift b/Packages/UserAccounts/Sources/UserAccounts/UserAccountsManager.swift similarity index 55% rename from Tusker/LocalData.swift rename to Packages/UserAccounts/Sources/UserAccounts/UserAccountsManager.swift index 4da80d77..e69973ae 100644 --- a/Tusker/LocalData.swift +++ b/Packages/UserAccounts/Sources/UserAccounts/UserAccountsManager.swift @@ -1,18 +1,16 @@ // -// LocalData.swift -// Tusker +// UserAccountsManager.swift +// UserAccounts // -// Created by Shadowfacts on 8/18/18. -// Copyright © 2018 Shadowfacts. All rights reserved. +// Created by Shadowfacts on 3/5/23. // import Foundation import Combine -import CryptoKit -class LocalData: ObservableObject { +public class UserAccountsManager: ObservableObject { - static let shared = LocalData() + public static let shared = UserAccountsManager() let defaults: UserDefaults @@ -38,7 +36,7 @@ class LocalData: ObservableObject { } private let accountsKey = "accounts" - private(set) var accounts: [UserAccountInfo] { + public private(set) var accounts: [UserAccountInfo] { get { if let array = defaults.array(forKey: accountsKey) as? [[String: String]] { return array.compactMap(UserAccountInfo.init(userDefaultsDict:)) @@ -66,7 +64,7 @@ class LocalData: ObservableObject { } private let mostRecentAccountKey = "mostRecentAccount" - private(set) var mostRecentAccountID: String? { + public private(set) var mostRecentAccountID: String? { get { return defaults.string(forKey: mostRecentAccountKey) } @@ -109,13 +107,13 @@ class LocalData: ObservableObject { usesAccountIDHashes = true } - // MARK: - Account Management + // MARK: Account Management - var onboardingComplete: Bool { + public var onboardingComplete: Bool { return !accounts.isEmpty } - func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String?, accessToken: String) -> UserAccountInfo { + 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) @@ -126,15 +124,15 @@ class LocalData: ObservableObject { return info } - func removeAccount(_ info: UserAccountInfo) { + public func removeAccount(_ info: UserAccountInfo) { accounts.removeAll(where: { $0.id == info.id }) } - func getAccount(id: String) -> UserAccountInfo? { + public func getAccount(id: String) -> UserAccountInfo? { return accounts.first(where: { $0.id == id }) } - func getMostRecentAccount() -> UserAccountInfo? { + public func getMostRecentAccount() -> UserAccountInfo? { guard onboardingComplete else { return nil } let mostRecent: UserAccountInfo? if let id = mostRecentAccountID { @@ -145,86 +143,13 @@ class LocalData: ObservableObject { return mostRecent ?? accounts.first! } - func setMostRecentAccount(_ account: UserAccountInfo?) { + public func setMostRecentAccount(_ account: UserAccountInfo?) { mostRecentAccountID = account?.id } } -extension LocalData { - struct UserAccountInfo: Equatable, Hashable { - let id: String - let instanceURL: URL - let clientID: String - let clientSecret: String - private(set) var username: String! - let accessToken: String - - fileprivate static let tempAccountID = "temp" - - fileprivate static func id(instanceURL: URL, username: String?) -> String { - // We hash the instance host and username to form the account ID - // so that account IDs will match across devices, allowing for data syncing and handoff. - var hasher = SHA256() - hasher.update(data: instanceURL.host!.data(using: .utf8)!) - if let username { - hasher.update(data: username.data(using: .utf8)!) - } - return Data(hasher.finalize()).base64EncodedString() - } - - /// Only to be used for temporary MastodonController needed to fetch own account info and create final UserAccountInfo with real username - init(tempInstanceURL instanceURL: URL, clientID: String, clientSecret: String, accessToken: String) { - self.id = UserAccountInfo.tempAccountID - self.instanceURL = instanceURL - self.clientID = clientID - self.clientSecret = clientSecret - self.accessToken = accessToken - } - - fileprivate init(instanceURL: URL, clientID: String, clientSecret: String, username: String? = nil, accessToken: String) { - self.id = UserAccountInfo.id(instanceURL: instanceURL, username: username) - self.instanceURL = instanceURL - self.clientID = clientID - self.clientSecret = clientSecret - self.username = username - self.accessToken = accessToken - } - - fileprivate init?(userDefaultsDict dict: [String: String]) { - guard let id = dict["id"], - let instanceURL = dict["instanceURL"], - let url = URL(string: instanceURL), - let clientID = dict["clientID"], - let secret = dict["clientSecret"], - let accessToken = dict["accessToken"] else { - return nil - } - self.id = id - self.instanceURL = url - self.clientID = clientID - self.clientSecret = secret - self.username = dict["username"] - self.accessToken = accessToken - } - - /// A filename-safe string for this account - var persistenceKey: String { - // slashes are not allowed in the persistent store coordinator name - id.replacingOccurrences(of: "/", with: "_") - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } - - static func ==(lhs: UserAccountInfo, rhs: UserAccountInfo) -> Bool { - return lhs.id == rhs.id - } - } -} - -extension Notification.Name { +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") diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index d7f04697..cd4275d7 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -142,7 +142,6 @@ D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; }; D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */; }; D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */; }; - D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */; }; D651C5B42915B00400236EF6 /* ProfileFieldsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */; }; @@ -268,6 +267,7 @@ D6ADB6F028ED1F25009924AB /* CachedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6EF28ED1F25009924AB /* CachedImageView.swift */; }; D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; }; D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; }; + D6B0026E29B5248800C70BE2 /* UserAccounts in Frameworks */ = {isa = PBXBuildFile; productRef = D6B0026D29B5248800C70BE2 /* UserAccounts */; }; D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */; }; D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */; }; D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */; }; @@ -559,7 +559,6 @@ D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = ""; }; D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestNotificationTableViewCell.swift; sourceTree = ""; }; D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowRequestNotificationTableViewCell.xib; sourceTree = ""; }; - D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = ""; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadDictionary.swift; sourceTree = ""; }; D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldsView.swift; sourceTree = ""; }; @@ -688,6 +687,7 @@ D6ADB6EF28ED1F25009924AB /* CachedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedImageView.swift; sourceTree = ""; }; D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = ""; }; D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = ""; }; + D6B0026C29B5245400C70BE2 /* UserAccounts */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = UserAccounts; path = Packages/UserAccounts; sourceTree = ""; }; D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerViewController.swift; sourceTree = ""; }; D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionsListViewController.swift; sourceTree = ""; }; D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewController.swift; sourceTree = ""; }; @@ -813,6 +813,7 @@ files = ( D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */, D659F35E2953A212002D944A /* TTTKit in Frameworks */, + D6B0026E29B5248800C70BE2 /* UserAccounts in Frameworks */, D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */, D60088EF2980D8B5005B4D00 /* StoreKit.framework in Frameworks */, D6552367289870790048A653 /* ScreenCorners in Frameworks */, @@ -1537,6 +1538,7 @@ D674A50727F910F300BA03AC /* Pachyderm */, D6BEA243291A0C83002F4D01 /* Duckable */, D68A76F22953915C001DA1B3 /* TTTKit */, + D6B0026C29B5245400C70BE2 /* UserAccounts */, D6D4DDCE212518A000E1C4BB /* Tusker */, D6D4DDE3212518A200E1C4BB /* TuskerTests */, D6D4DDEE212518A200E1C4BB /* TuskerUITests */, @@ -1573,7 +1575,6 @@ D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */, D61F75BC293D099600C0B37F /* Lazy.swift */, D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */, - D64D0AAC2128D88B005A6F37 /* LocalData.swift */, D61DC84528F498F200B82C6E /* Logging.swift */, D6B81F432560390300F6E31D /* MenuController.swift */, D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */, @@ -1741,6 +1742,7 @@ D63CC701290EC0B8000E19DE /* Sentry */, D6BEA244291A0EDE002F4D01 /* Duckable */, D659F35D2953A212002D944A /* TTTKit */, + D6B0026D29B5248800C70BE2 /* UserAccounts */, ); productName = Tusker; productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */; @@ -2147,7 +2149,6 @@ D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */, D61F759029353B4300C0B37F /* FileManager+Size.swift in Sources */, 0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */, - D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */, D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */, D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */, D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */, @@ -2966,6 +2967,10 @@ isa = XCSwiftPackageProductDependency; productName = Pachyderm; }; + D6B0026D29B5248800C70BE2 /* UserAccounts */ = { + isa = XCSwiftPackageProductDependency; + productName = UserAccounts; + }; D6BEA244291A0EDE002F4D01 /* Duckable */ = { isa = XCSwiftPackageProductDependency; productName = Duckable; diff --git a/Tusker/API/LogoutService.swift b/Tusker/API/LogoutService.swift index 42b76255..e5837ee8 100644 --- a/Tusker/API/LogoutService.swift +++ b/Tusker/API/LogoutService.swift @@ -7,13 +7,14 @@ // import Foundation +import UserAccounts @MainActor class LogoutService { - let accountInfo: LocalData.UserAccountInfo + let accountInfo: UserAccountInfo private let mastodonController: MastodonController - init(accountInfo: LocalData.UserAccountInfo) { + init(accountInfo: UserAccountInfo) { self.accountInfo = accountInfo self.mastodonController = MastodonController.getForAccount(accountInfo) } @@ -23,7 +24,7 @@ class LogoutService { try? await self.mastodonController.client.revokeAccessToken() } MastodonController.removeForAccount(accountInfo) - LocalData.shared.removeAccount(accountInfo) + UserAccountsManager.shared.removeAccount(accountInfo) let psc = mastodonController.persistentContainer.persistentStoreCoordinator for store in psc.persistentStores { guard let url = store.url else { diff --git a/Tusker/API/MastodonController.swift b/Tusker/API/MastodonController.swift index a2b8e60a..489118fd 100644 --- a/Tusker/API/MastodonController.swift +++ b/Tusker/API/MastodonController.swift @@ -9,15 +9,16 @@ import Foundation import Pachyderm import Combine +import UserAccounts class MastodonController: ObservableObject { - static private(set) var all = [LocalData.UserAccountInfo: MastodonController]() + static private(set) var all = [UserAccountInfo: MastodonController]() @available(*, message: "do something less dumb") static var first: MastodonController { all.first!.value } - static func getForAccount(_ account: LocalData.UserAccountInfo) -> MastodonController { + static func getForAccount(_ account: UserAccountInfo) -> MastodonController { if let controller = all[account] { return controller } else { @@ -31,7 +32,7 @@ class MastodonController: ObservableObject { } } - static func removeForAccount(_ account: LocalData.UserAccountInfo) { + static func removeForAccount(_ account: UserAccountInfo) { all.removeValue(forKey: account) } @@ -43,7 +44,7 @@ class MastodonController: ObservableObject { private(set) nonisolated lazy var persistentContainer = MastodonCachePersistentStore(for: accountInfo, transient: transient) let instanceURL: URL - var accountInfo: LocalData.UserAccountInfo? + var accountInfo: UserAccountInfo? var accountPreferences: AccountPreferences! let client: Client! diff --git a/Tusker/AppDelegate.swift b/Tusker/AppDelegate.swift index 5ffd44e7..d4141592 100644 --- a/Tusker/AppDelegate.swift +++ b/Tusker/AppDelegate.swift @@ -10,6 +10,7 @@ import UIKit import CoreData import OSLog import Sentry +import UserAccounts let stateRestorationLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "StateRestoration") @@ -32,7 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { if let oldSavedData = SavedDataManager.load() { do { for account in oldSavedData.accountIDs { - guard let account = LocalData.shared.getAccount(id: account) else { + guard let account = UserAccountsManager.shared.getAccount(id: account) else { continue } let controller = MastodonController.getForAccount(account) diff --git a/Tusker/CoreData/AccountPreferences.swift b/Tusker/CoreData/AccountPreferences.swift index 53f990d1..c8c0b388 100644 --- a/Tusker/CoreData/AccountPreferences.swift +++ b/Tusker/CoreData/AccountPreferences.swift @@ -9,11 +9,12 @@ import Foundation import CoreData import Pachyderm +import UserAccounts @objc(AccountPreferences) public final class AccountPreferences: NSManagedObject { - @nonobjc class func fetchRequest(account: LocalData.UserAccountInfo) -> NSFetchRequest { + @nonobjc class func fetchRequest(account: UserAccountInfo) -> NSFetchRequest { let req = NSFetchRequest(entityName: "AccountPreferences") req.predicate = NSPredicate(format: "accountID = %@", account.id) req.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)] @@ -27,7 +28,7 @@ public final class AccountPreferences: NSManagedObject { @LazilyDecoding(from: \AccountPreferences.pinnedTimelinesData, fallback: AccountPreferences.defaultPinnedTimelines) var pinnedTimelines: [PinnedTimeline] - static func `default`(account: LocalData.UserAccountInfo, context: NSManagedObjectContext) -> AccountPreferences { + static func `default`(account: UserAccountInfo, context: NSManagedObjectContext) -> AccountPreferences { let prefs = AccountPreferences(context: context) prefs.accountID = account.id prefs.createdAt = Date() diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 1c6b78d7..05e4560f 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -13,12 +13,13 @@ import Combine import OSLog import Sentry import CloudKit +import UserAccounts fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentStore") class MastodonCachePersistentStore: NSPersistentCloudKitContainer { - private let accountInfo: LocalData.UserAccountInfo? + private let accountInfo: UserAccountInfo? private static let managedObjectModel: NSManagedObjectModel = { let url = Bundle.main.url(forResource: "Tusker", withExtension: "momd")! @@ -51,7 +52,7 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer { let accountSubject = PassthroughSubject() let relationshipSubject = PassthroughSubject() - init(for accountInfo: LocalData.UserAccountInfo?, transient: Bool = false) { + init(for accountInfo: UserAccountInfo?, transient: Bool = false) { self.accountInfo = accountInfo let group = DispatchGroup() diff --git a/Tusker/CoreData/SavedHashtag.swift b/Tusker/CoreData/SavedHashtag.swift index 265513b9..f206d456 100644 --- a/Tusker/CoreData/SavedHashtag.swift +++ b/Tusker/CoreData/SavedHashtag.swift @@ -10,6 +10,7 @@ import Foundation import CoreData import Pachyderm import WebURLFoundationExtras +import UserAccounts @objc(SavedHashtag) public final class SavedHashtag: NSManagedObject { @@ -18,13 +19,13 @@ public final class SavedHashtag: NSManagedObject { return NSFetchRequest(entityName: "SavedHashtag") } - @nonobjc class func fetchRequest(account: LocalData.UserAccountInfo) -> NSFetchRequest { + @nonobjc class func fetchRequest(account: UserAccountInfo) -> NSFetchRequest { let req = NSFetchRequest(entityName: "SavedHashtag") req.predicate = NSPredicate(format: "accountID = %@", account.id) return req } - @nonobjc class func fetchRequest(name: String, account: LocalData.UserAccountInfo) -> NSFetchRequest { + @nonobjc class func fetchRequest(name: String, account: UserAccountInfo) -> NSFetchRequest { let req = NSFetchRequest(entityName: "SavedHashtag") req.predicate = NSPredicate(format: "name LIKE[cd] %@ AND accountID = %@", name, account.id) return req @@ -37,7 +38,7 @@ public final class SavedHashtag: NSManagedObject { } extension SavedHashtag { - convenience init(hashtag: Hashtag, account: LocalData.UserAccountInfo, context: NSManagedObjectContext) { + convenience init(hashtag: Hashtag, account: UserAccountInfo, context: NSManagedObjectContext) { self.init(context: context) self.accountID = account.id self.name = hashtag.name diff --git a/Tusker/CoreData/SavedInstance.swift b/Tusker/CoreData/SavedInstance.swift index f2c612e8..d6e938b7 100644 --- a/Tusker/CoreData/SavedInstance.swift +++ b/Tusker/CoreData/SavedInstance.swift @@ -8,6 +8,7 @@ import Foundation import CoreData +import UserAccounts @objc(SavedInstance) public final class SavedInstance: NSManagedObject { @@ -16,13 +17,13 @@ public final class SavedInstance: NSManagedObject { return NSFetchRequest(entityName: "SavedInstance") } - @nonobjc class func fetchRequest(account: LocalData.UserAccountInfo) -> NSFetchRequest { + @nonobjc class func fetchRequest(account: UserAccountInfo) -> NSFetchRequest { let req = NSFetchRequest(entityName: "SavedInstance") req.predicate = NSPredicate(format: "accountID = %@", account.id) return req } - @nonobjc class func fetchRequest(url: URL, account: LocalData.UserAccountInfo) -> NSFetchRequest { + @nonobjc class func fetchRequest(url: URL, account: UserAccountInfo) -> NSFetchRequest { let req = NSFetchRequest(entityName: "SavedInstance") req.predicate = NSPredicate(format: "url = %@ AND accountID = %@", url as NSURL, account.id) return req @@ -34,7 +35,7 @@ public final class SavedInstance: NSManagedObject { } extension SavedInstance { - convenience init(url: URL, account: LocalData.UserAccountInfo, context: NSManagedObjectContext) { + convenience init(url: URL, account: UserAccountInfo, context: NSManagedObjectContext) { self.init(context: context) self.accountID = account.id self.url = url diff --git a/Tusker/CoreData/TimelinePosition.swift b/Tusker/CoreData/TimelinePosition.swift index 444bae4a..99d164a6 100644 --- a/Tusker/CoreData/TimelinePosition.swift +++ b/Tusker/CoreData/TimelinePosition.swift @@ -9,11 +9,12 @@ import Foundation import CoreData import Pachyderm +import UserAccounts @objc(TimelinePosition) public final class TimelinePosition: NSManagedObject { - @nonobjc class func fetchRequest(timeline: Timeline, account: LocalData.UserAccountInfo) -> NSFetchRequest { + @nonobjc class func fetchRequest(timeline: Timeline, account: UserAccountInfo) -> NSFetchRequest { let req = NSFetchRequest(entityName: "TimelinePosition") req.predicate = NSPredicate(format: "accountID = %@ AND timelineKind = %@", account.id, toTimelineKind(timeline)) req.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)] @@ -34,7 +35,7 @@ public final class TimelinePosition: NSManagedObject { set { timelineKind = toTimelineKind(newValue) } } - convenience init(timeline: Timeline, account: LocalData.UserAccountInfo, context: NSManagedObjectContext) { + convenience init(timeline: Timeline, account: UserAccountInfo, context: NSManagedObjectContext) { self.init(context: context) self.timeline = timeline self.accountID = account.id diff --git a/Tusker/Scenes/AuxiliarySceneDelegate.swift b/Tusker/Scenes/AuxiliarySceneDelegate.swift index 211f7c63..7480865f 100644 --- a/Tusker/Scenes/AuxiliarySceneDelegate.swift +++ b/Tusker/Scenes/AuxiliarySceneDelegate.swift @@ -8,6 +8,7 @@ import UIKit import Pachyderm +import UserAccounts class AuxiliarySceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate { @@ -31,11 +32,11 @@ class AuxiliarySceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDel } launchActivity = activity - let account: LocalData.UserAccountInfo + let account: UserAccountInfo if let activityAccount = UserActivityManager.getAccount(from: activity) { account = activityAccount - } else if let mostRecent = LocalData.shared.getMostRecentAccount() { + } else if let mostRecent = UserAccountsManager.shared.getMostRecentAccount() { account = mostRecent } else { // without an account, we can't do anything so we just destroy the scene diff --git a/Tusker/Scenes/ComposeSceneDelegate.swift b/Tusker/Scenes/ComposeSceneDelegate.swift index 61f2de1c..c97a36b9 100644 --- a/Tusker/Scenes/ComposeSceneDelegate.swift +++ b/Tusker/Scenes/ComposeSceneDelegate.swift @@ -8,6 +8,7 @@ import UIKit import Combine +import UserAccounts class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate { @@ -22,12 +23,12 @@ class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDeleg return } - guard LocalData.shared.onboardingComplete else { + guard UserAccountsManager.shared.onboardingComplete else { UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil) return } - let account: LocalData.UserAccountInfo + let account: UserAccountInfo let controller: MastodonController let draft: Draft? @@ -36,7 +37,7 @@ class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDeleg account = activityAccount } else { // todo: this potentially changes the account for the draft, should show the same warning to user as in the drafts selection screen - account = LocalData.shared.getMostRecentAccount()! + account = UserAccountsManager.shared.getMostRecentAccount()! } controller = MastodonController.getForAccount(account) @@ -49,7 +50,7 @@ class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDeleg draft = nil } } else { - account = LocalData.shared.getMostRecentAccount()! + account = UserAccountsManager.shared.getMostRecentAccount()! controller = MastodonController.getForAccount(account) draft = nil } diff --git a/Tusker/Scenes/MainSceneDelegate.swift b/Tusker/Scenes/MainSceneDelegate.swift index 7ca55ae0..8b6f5547 100644 --- a/Tusker/Scenes/MainSceneDelegate.swift +++ b/Tusker/Scenes/MainSceneDelegate.swift @@ -11,6 +11,7 @@ import Pachyderm import MessageUI import CoreData import Duckable +import UserAccounts class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate { @@ -161,13 +162,13 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate func showAppOrOnboardingUI(session: UISceneSession? = nil) { let session = session ?? window!.windowScene!.session - if LocalData.shared.onboardingComplete { - let account: LocalData.UserAccountInfo + if UserAccountsManager.shared.onboardingComplete { + let account: UserAccountInfo if let activity = launchActivity, let activityAccount = UserActivityManager.getAccount(from: activity) { account = activityAccount } else { - account = LocalData.shared.getMostRecentAccount()! + account = UserAccountsManager.shared.getMostRecentAccount()! } if session.mastodonController == nil { @@ -194,9 +195,9 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate } } - func activateAccount(_ account: LocalData.UserAccountInfo, animated: Bool) { - let oldMostRecentAccount = LocalData.shared.mostRecentAccountID - LocalData.shared.setMostRecentAccount(account) + func activateAccount(_ account: UserAccountInfo, animated: Bool) { + let oldMostRecentAccount = UserAccountsManager.shared.mostRecentAccountID + UserAccountsManager.shared.setMostRecentAccount(account) window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account) // iPadOS shows the title below the App Name @@ -212,8 +213,8 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate if let container = window?.rootViewController as? AccountSwitchingContainerViewController { let direction: AccountSwitchingContainerViewController.AnimationDirection if animated, - let oldIndex = LocalData.shared.accounts.firstIndex(where: { $0.id == oldMostRecentAccount }), - let newIndex = LocalData.shared.accounts.firstIndex(of: account) { + let oldIndex = UserAccountsManager.shared.accounts.firstIndex(where: { $0.id == oldMostRecentAccount }), + let newIndex = UserAccountsManager.shared.accounts.firstIndex(of: account) { direction = newIndex > oldIndex ? .upwards : .downwards } else { direction = .none @@ -229,8 +230,8 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate return } LogoutService(accountInfo: account).run() - if LocalData.shared.onboardingComplete { - activateAccount(LocalData.shared.accounts.first!, animated: false) + if UserAccountsManager.shared.onboardingComplete { + activateAccount(UserAccountsManager.shared.accounts.first!, animated: false) } else { window!.rootViewController = createOnboardingUI() } @@ -269,7 +270,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate } extension MainSceneDelegate: OnboardingViewControllerDelegate { - func didFinishOnboarding(account: LocalData.UserAccountInfo) { + func didFinishOnboarding(account: UserAccountInfo) { activateAccount(account, animated: false) } } diff --git a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift index 408e10ce..0104cfab 100644 --- a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift +++ b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import UserAccounts protocol FastAccountSwitcherViewControllerDelegate: AnyObject { func fastAccountSwitcherAddToViewHierarchy(_ fastAccountSwitcher: FastAccountSwitcherViewController) @@ -139,9 +140,9 @@ class FastAccountSwitcherViewController: UIViewController { addAccountPlaceholder ] - for account in LocalData.shared.accounts { + for account in UserAccountsManager.shared.accounts { let accountView = FastSwitchingAccountView(account: account, orientation: itemOrientation) - accountView.isCurrent = account.id == LocalData.shared.mostRecentAccountID + accountView.isCurrent = account.id == UserAccountsManager.shared.mostRecentAccountID accountsStack.addArrangedSubview(accountView) accountViews.append(accountView) } @@ -168,9 +169,9 @@ class FastAccountSwitcherViewController: UIViewController { (self.view.window!.windowScene!.delegate as! MainSceneDelegate).showAddAccount() } } else { - let account = LocalData.shared.accounts[newIndex - 1] + let account = UserAccountsManager.shared.accounts[newIndex - 1] - if account.id != LocalData.shared.mostRecentAccountID { + if account.id != UserAccountsManager.shared.mostRecentAccountID { if hapticFeedback { selectionChangedFeedbackGenerator?.selectionChanged() } diff --git a/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift b/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift index e5930cf7..7e04fbc3 100644 --- a/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift +++ b/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift @@ -7,6 +7,7 @@ // import UIKit +import UserAccounts class FastSwitchingAccountView: UIView { @@ -49,7 +50,7 @@ class FastSwitchingAccountView: UIView { private var avatarRequest: ImageCache.Request? - init(account: LocalData.UserAccountInfo, orientation: FastAccountSwitcherViewController.ItemOrientation) { + init(account: UserAccountInfo, orientation: FastAccountSwitcherViewController.ItemOrientation) { self.orientation = orientation super.init(frame: .zero) commonInit() @@ -121,7 +122,7 @@ class FastSwitchingAccountView: UIView { isAccessibilityElement = true } - private func setupAccount(account: LocalData.UserAccountInfo) { + private func setupAccount(account: UserAccountInfo) { usernameLabel.text = account.username instanceLabel.text = account.instanceURL.host! let controller = MastodonController.getForAccount(account) diff --git a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift index eb35c7ea..29a0b767 100644 --- a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift +++ b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift @@ -8,6 +8,7 @@ import UIKit import ScreenCorners +import UserAccounts class AccountSwitchingContainerViewController: UIViewController { @@ -16,7 +17,7 @@ class AccountSwitchingContainerViewController: UIViewController { private var userActivities: [String: NSUserActivity] = [:] - init(root: TuskerRootViewController, for account: LocalData.UserAccountInfo) { + init(root: TuskerRootViewController, for account: UserAccountInfo) { self.currentAccountID = account.id self.root = root @@ -33,7 +34,7 @@ class AccountSwitchingContainerViewController: UIViewController { embedChild(root) } - func setRoot(_ newRoot: TuskerRootViewController, for account: LocalData.UserAccountInfo, animating direction: AnimationDirection) { + func setRoot(_ newRoot: TuskerRootViewController, for account: UserAccountInfo, animating direction: AnimationDirection) { let oldRoot = self.root if direction == .none { oldRoot.removeViewAndController() diff --git a/Tusker/Screens/Main/MainSidebarMyProfileCollectionViewCell.swift b/Tusker/Screens/Main/MainSidebarMyProfileCollectionViewCell.swift index dd7e5961..0de63d85 100644 --- a/Tusker/Screens/Main/MainSidebarMyProfileCollectionViewCell.swift +++ b/Tusker/Screens/Main/MainSidebarMyProfileCollectionViewCell.swift @@ -7,6 +7,7 @@ // import UIKit +import UserAccounts class MainSidebarMyProfileCollectionViewCell: UICollectionViewListCell { @@ -35,7 +36,7 @@ class MainSidebarMyProfileCollectionViewCell: UICollectionViewListCell { fatalError("init(coder:) has not been implemented") } - func updateUI(item: MainSidebarViewController.Item, account: LocalData.UserAccountInfo) async { + func updateUI(item: MainSidebarViewController.Item, account: UserAccountInfo) async { var config = defaultContentConfiguration() config.text = item.title config.image = UIImage(systemName: item.imageName!) diff --git a/Tusker/Screens/Onboarding/OnboardingViewController.swift b/Tusker/Screens/Onboarding/OnboardingViewController.swift index f3331181..402c783c 100644 --- a/Tusker/Screens/Onboarding/OnboardingViewController.swift +++ b/Tusker/Screens/Onboarding/OnboardingViewController.swift @@ -10,10 +10,11 @@ import UIKit import AuthenticationServices import Pachyderm import OSLog +import UserAccounts protocol OnboardingViewControllerDelegate { @MainActor - func didFinishOnboarding(account: LocalData.UserAccountInfo) + func didFinishOnboarding(account: UserAccountInfo) } private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "OnboardingViewController") @@ -145,7 +146,7 @@ class OnboardingViewController: UINavigationController { } // construct a temporary UserAccountInfo instance for the MastodonController to use to fetch its own account - let tempAccountInfo = LocalData.UserAccountInfo(tempInstanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, accessToken: accessToken) + let tempAccountInfo = UserAccountInfo(tempInstanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, accessToken: accessToken) mastodonController.accountInfo = tempAccountInfo updateStatus("Checking Credentials") @@ -158,7 +159,7 @@ class OnboardingViewController: UINavigationController { throw Error.gettingOwnAccount(error) } - let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: ownAccount.username, accessToken: accessToken) + let accountInfo = UserAccountsManager.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: ownAccount.username, accessToken: accessToken) mastodonController.accountInfo = accountInfo self.onboardingDelegate?.didFinishOnboarding(account: accountInfo) diff --git a/Tusker/Screens/Preferences/AdvancedPrefsView.swift b/Tusker/Screens/Preferences/AdvancedPrefsView.swift index d5aed245..d698f14c 100644 --- a/Tusker/Screens/Preferences/AdvancedPrefsView.swift +++ b/Tusker/Screens/Preferences/AdvancedPrefsView.swift @@ -9,6 +9,7 @@ import SwiftUI import Pachyderm import CoreData import CloudKit +import UserAccounts struct AdvancedPrefsView : View { @ObservedObject var preferences = Preferences.shared @@ -30,7 +31,7 @@ struct AdvancedPrefsView : View { var formattingFooter: some View { var s: AttributedString = "This option is only supported with Pleroma and some compatible Mastodon instances (such as Glitch).\n" - if let account = LocalData.shared.getMostRecentAccount() { + if let account = UserAccountsManager.shared.getMostRecentAccount() { let mastodonController = MastodonController.getForAccount(account) // shouldn't need to load the instance here, because loading it is kicked off my the scene delegate if !mastodonController.instanceFeatures.probablySupportsMarkdown { @@ -135,7 +136,7 @@ struct AdvancedPrefsView : View { ].map { $0.getDiskSizeInBytes() ?? 0 }.reduce(0, +) - mastodonCacheSize = LocalData.shared.accounts.map { + mastodonCacheSize = UserAccountsManager.shared.accounts.map { let descriptions = MastodonController.getForAccount($0).persistentContainer.persistentStoreDescriptions return descriptions.map { guard let url = $0.url else { @@ -148,7 +149,7 @@ struct AdvancedPrefsView : View { } private func clearCache() { - for account in LocalData.shared.accounts { + for account in UserAccountsManager.shared.accounts { let controller = MastodonController.getForAccount(account) let container = controller.persistentContainer do { @@ -178,7 +179,7 @@ struct AdvancedPrefsView : View { } private func resetUI() { - let mostRecent = LocalData.shared.getMostRecentAccount()! + let mostRecent = UserAccountsManager.shared.getMostRecentAccount()! NotificationCenter.default.post(name: .activateAccount, object: nil, userInfo: ["account": mostRecent]) } } diff --git a/Tusker/Screens/Preferences/LocalAccountAvatarView.swift b/Tusker/Screens/Preferences/LocalAccountAvatarView.swift index 6d17804e..b586e528 100644 --- a/Tusker/Screens/Preferences/LocalAccountAvatarView.swift +++ b/Tusker/Screens/Preferences/LocalAccountAvatarView.swift @@ -7,9 +7,10 @@ // import SwiftUI +import UserAccounts struct LocalAccountAvatarView: View { - let localAccountInfo: LocalData.UserAccountInfo + let localAccountInfo: UserAccountInfo @State var avatarImage: UIImage? = nil @ObservedObject var preferences = Preferences.shared diff --git a/Tusker/Screens/Preferences/PreferencesNavigationController.swift b/Tusker/Screens/Preferences/PreferencesNavigationController.swift index 5514a635..a36aee6d 100644 --- a/Tusker/Screens/Preferences/PreferencesNavigationController.swift +++ b/Tusker/Screens/Preferences/PreferencesNavigationController.swift @@ -8,6 +8,7 @@ import UIKit import SwiftUI +import UserAccounts class PreferencesNavigationController: UINavigationController { @@ -64,7 +65,7 @@ class PreferencesNavigationController: UINavigationController { guard let windowScene = self.view.window?.windowScene else { return } - let account = notification.userInfo!["account"] as! LocalData.UserAccountInfo + let account = notification.userInfo!["account"] as! UserAccountInfo if let sceneDelegate = windowScene.delegate as? MainSceneDelegate { isSwitchingAccounts = true dismiss(animated: true) { // dismiss preferences @@ -85,8 +86,8 @@ class PreferencesNavigationController: UINavigationController { sceneDelegate.logoutCurrent() } } else { - LogoutService(accountInfo: LocalData.shared.getMostRecentAccount()!).run() - let accountID = LocalData.shared.getMostRecentAccount()?.id + LogoutService(accountInfo: UserAccountsManager.shared.getMostRecentAccount()!).run() + let accountID = UserAccountsManager.shared.getMostRecentAccount()?.id UIApplication.shared.requestSceneSessionActivation(nil, userActivity: UserActivityManager.mainSceneActivity(accountID: accountID), options: nil) UIApplication.shared.requestSceneSessionDestruction(windowScene.session, options: nil) } @@ -95,7 +96,7 @@ class PreferencesNavigationController: UINavigationController { } extension PreferencesNavigationController: OnboardingViewControllerDelegate { - func didFinishOnboarding(account: LocalData.UserAccountInfo) { + func didFinishOnboarding(account: UserAccountInfo) { guard let windowScene = self.view.window?.windowScene else { return } diff --git a/Tusker/Screens/Preferences/PreferencesView.swift b/Tusker/Screens/Preferences/PreferencesView.swift index 049002e8..6ee2f61a 100644 --- a/Tusker/Screens/Preferences/PreferencesView.swift +++ b/Tusker/Screens/Preferences/PreferencesView.swift @@ -6,11 +6,12 @@ // import SwiftUI +import UserAccounts struct PreferencesView: View { let mastodonController: MastodonController - @ObservedObject private var localData = LocalData.shared + @ObservedObject private var userAccounts = UserAccountsManager.shared @State private var showingLogoutConfirmation = false init(mastodonController: MastodonController) { @@ -31,7 +32,7 @@ struct PreferencesView: View { private var accountsSection: some View { Section { - ForEach(localData.accounts, id: \.accessToken) { (account) in + ForEach(userAccounts.accounts, id: \.accessToken) { (account) in Button(action: { NotificationCenter.default.post(name: .activateAccount, object: nil, userInfo: ["account": account]) }) { @@ -58,12 +59,12 @@ struct PreferencesView: View { }.onDelete { (indices: IndexSet) in var indices = indices var logoutFromCurrent = false - if let index = indices.first(where: { localData.accounts[$0] == mastodonController.accountInfo! }) { + if let index = indices.first(where: { userAccounts.accounts[$0] == mastodonController.accountInfo! }) { logoutFromCurrent = true indices.remove(index) } - indices.forEach { LogoutService(accountInfo: localData.accounts[$0]).run() } + indices.forEach { LogoutService(accountInfo: userAccounts.accounts[$0]).run() } if logoutFromCurrent { self.logoutPressed() diff --git a/Tusker/Shortcuts/UserActivityManager.swift b/Tusker/Shortcuts/UserActivityManager.swift index 48fd88f1..eaa3eff0 100644 --- a/Tusker/Shortcuts/UserActivityManager.swift +++ b/Tusker/Shortcuts/UserActivityManager.swift @@ -10,6 +10,7 @@ import UIKit import Intents import Pachyderm import OSLog +import UserAccounts private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "UserActivityManager") @@ -32,11 +33,11 @@ class UserActivityManager { scene.session.mastodonController! } - static func getAccount(from activity: NSUserActivity) -> LocalData.UserAccountInfo? { + static func getAccount(from activity: NSUserActivity) -> UserAccountInfo? { guard let id = activity.userInfo?["accountID"] as? String else { return nil } - return LocalData.shared.getAccount(id: id) + return UserAccountsManager.shared.getAccount(id: id) } // MARK: - Main Scene