2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00

Begin moving filesystem cache responsibility into PersistenceManager

contributes to iOS-319
This commit is contained in:
shannon 2024-11-18 08:38:55 -05:00
parent 11c0deff2d
commit 9ed9c79f25
29 changed files with 188 additions and 184 deletions

View File

@ -66,7 +66,7 @@ final public class SceneCoordinator {
}
let domain = authentication.domain
let userID = authentication.userID
let isSuccess = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: domain, userID: userID)
let isSuccess = AuthenticationServiceProvider.shared.activateUser(userID, inDomain: domain)
guard isSuccess else { return }
self.setup()
@ -94,7 +94,7 @@ final public class SceneCoordinator {
// show notification related content
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return }
guard let authenticationBox = self.authenticationBox else { return }
guard let me = authenticationBox.authentication.account() else { return }
guard let me = authenticationBox.cachedAccount else { return }
let notificationID = String(pushNotification.notificationID)
switch type {

View File

@ -37,7 +37,7 @@ extension UserSection {
case .account(let account, let relationship):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell
guard let me = authenticationBox.authentication.account() else { return cell }
guard let me = authenticationBox.cachedAccount else { return cell }
cell.userView.setButtonState(.loading)
cell.configure(

View File

@ -83,10 +83,8 @@ extension DataSourceFacade {
do {
let account = try await APIService.shared.accountInfo(
domain: domain,
userID: accountID,
authorization: provider.authenticationBox.userAuthorization
).value
provider.authenticationBox
)
provider.coordinator.hideLoading()
@ -103,7 +101,7 @@ extension DataSourceFacade {
) async {
provider.coordinator.showLoading()
guard let me = provider.authenticationBox.authentication.account(),
guard let me = provider.authenticationBox.cachedAccount,
let relationship = try? await APIService.shared.relationship(forAccounts: [account], authenticationBox: provider.authenticationBox).value.first else {
return provider.coordinator.hideLoading()
}

View File

@ -104,7 +104,7 @@ extension AccountListViewModel {
authentication: MastodonAuthentication,
activeAuthentication: MastodonAuthentication
) {
guard let account = authentication.account() else { return }
guard let account = authentication.cachedAccount() else { return }
// avatar
cell.avatarButton.avatarImageView.configure(with: account.avatarImageURL())

View File

@ -144,7 +144,7 @@ extension AccountListViewController: UITableViewDelegate {
case .authentication(let record):
assert(Thread.isMainThread)
Task { @MainActor in
let isActive = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: record.domain, userID: record.userID)
let isActive = AuthenticationServiceProvider.shared.activateUser(record.userID, inDomain: record.domain)
guard isActive else { return }
self.coordinator.setup()
} // end Task

View File

@ -355,7 +355,7 @@ extension HomeTimelineViewController {
let userDoesntFollowPeople: Bool
if let authenticationBox = self?.authenticationBox,
let me = authenticationBox.authentication.account() {
let me = authenticationBox.cachedAccount {
userDoesntFollowPeople = me.followersCount == 0
} else {
userDoesntFollowPeople = true

View File

@ -96,7 +96,7 @@ final class HomeTimelineViewModel: NSObject {
self.authenticationBox = authenticationBox
self.dataController = FeedDataController(context: context, authenticationBox: authenticationBox)
super.init()
self.dataController.records = (try? FileManager.default.cachedHomeTimeline(for: authenticationBox).map {
self.dataController.records = (try? PersistenceManager.shared.cachedTimeline(.homeTimeline(authenticationBox)).map {
MastodonFeed.fromStatus($0, kind: .home)
}) ?? []

View File

@ -189,7 +189,7 @@ extension NotificationView {
notificationTypeIndicatorLabel.reset()
}
if let me = authenticationBox.authentication.account() {
if let me = authenticationBox.cachedAccount {
let isMyself = (author == me)
let isMuting: Bool
let isBlocking: Bool

View File

@ -125,8 +125,7 @@ class MastodonLoginViewController: UIViewController, NeedsDependency {
.authenticated.sink { (domain, account) in
Task { @MainActor in
do {
_ = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: domain, userID: account.id)
FileManager.default.store(account: account, forUserID: MastodonUserIdentifier(domain: domain, userID: account.id))
AuthenticationServiceProvider.shared.activateUser(account.id, inDomain: domain)
self.coordinator.setup()
} catch {

View File

@ -151,8 +151,8 @@ extension MastodonPickServerViewController {
.authenticated
.asyncMap { domain, user -> Result<Bool, Error> in
do {
let result = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: domain, userID: user.id)
return .success(result)
let activated = AuthenticationServiceProvider.shared.activateUser(user.id, inDomain: domain)
return .success(activated)
} catch {
return .failure(error)
}

View File

@ -209,42 +209,9 @@ extension AuthenticationViewModel {
.authentications
.insert(authentication, at: 0)
FileManager.default.store(account: account, forUserID: authentication.userIdentifier())
return response
}
.eraseToAnyPublisher()
}
static func verifyAndSaveAuthentication(
context: AppContext,
domain: String,
clientID: String,
clientSecret: String,
userToken: String
) async throws -> Mastodon.Entity.Account {
let authorization = Mastodon.API.OAuth.Authorization(accessToken: userToken)
let account = try await APIService.shared.accountVerifyCredentials(
domain: domain,
authorization: authorization
)
let authentication = MastodonAuthentication.createFrom(domain: domain,
userID: account.id,
username: account.username,
appAccessToken: userToken, // TODO: swap app token
userAccessToken: userToken,
clientID: clientID,
clientSecret: clientSecret,
accountCreatedAt: account.createdAt)
AuthenticationServiceProvider.shared
.authentications
.insert(authentication, at: 0)
FileManager.default.store(account: account, forUserID: authentication.userIdentifier())
return account
}
}

View File

@ -653,9 +653,8 @@ extension ProfileViewController {
viewModel.profileAboutViewModel.fields = updatedAccount.mastodonFields
}
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox).value {
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox) {
viewModel.me = updatedMe
FileManager.default.store(account: updatedMe, forUserID: viewModel.authenticationBox.authentication.userIdentifier())
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
@ -1097,10 +1096,9 @@ extension ProfileViewController {
} else if viewModel.account == viewModel.me {
// update my profile
Task {
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox).value {
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox) {
viewModel.me = updatedMe
viewModel.account = updatedMe
FileManager.default.store(account: updatedMe, forUserID: viewModel.authenticationBox.authentication.userIdentifier())
}
viewModel.isUpdating = false

View File

@ -181,7 +181,6 @@ class ProfileViewModel: NSObject {
let authorization = Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken)
return APIService.shared.accountVerifyCredentials(domain: domain, authorization: authorization)
.tryMap { response in
FileManager.default.store(account: response.value, forUserID: mastodonAuthentication.userIdentifier())
return response
}.eraseToAnyPublisher()
}
@ -233,8 +232,6 @@ extension ProfileViewModel {
query: query,
authorization: authorization
)
FileManager.default.store(account: response.value, forUserID: authenticationBox.authentication.userIdentifier())
NotificationCenter.default.post(name: .userFetched, object: nil)
return response

View File

@ -86,7 +86,7 @@ class MainTabBarController: UITabBarController {
homeTimelineViewController.viewModel = HomeTimelineViewModel(context: context, authenticationBox: authenticationBox)
searchViewController.viewModel = SearchViewModel(context: context, authenticationBox: authenticationBox)
if let account = authenticationBox.authentication.account() {
if let account = authenticationBox.cachedAccount {
meProfileViewController.viewModel = ProfileViewModel(context: context, authenticationBox: authenticationBox, account: account, relationship: nil, me: account)
}
}
@ -112,7 +112,7 @@ extension MainTabBarController {
willSet {
if let profileView = (newValue as? UINavigationController)?.topViewController as? ProfileViewController{
guard let authenticationBox,
let account = authenticationBox.authentication.account() else { return }
let account = authenticationBox.cachedAccount else { return }
profileView.viewModel = ProfileViewModel(context: self.context, authenticationBox: authenticationBox, account: account, relationship: nil, me: account)
}
}
@ -204,7 +204,7 @@ extension MainTabBarController {
.sink { [weak self] _ in
guard let self,
let authenticationBox,
let account = authenticationBox.authentication.account() else { return }
let account = authenticationBox.cachedAccount else { return }
self.avatarURL = account.avatarImageURL()
@ -381,7 +381,6 @@ extension MainTabBarController {
Task { @MainActor in
let profileResponse = try await APIService.shared.authenticatedUserInfo(authenticationBox: authenticationBox)
FileManager.default.store(account: profileResponse.value, forUserID: authenticationBox.authentication.userIdentifier())
}
}
}

View File

@ -76,7 +76,7 @@ extension SidebarViewModel {
let imageURL: URL?
switch item {
case .me:
let account = self.authenticationBox?.authentication.account()
let account = self.authenticationBox?.authentication.cachedAccount()
imageURL = account?.avatarImageURL()
case .home, .search, .compose, .notifications:
// no custom avatar for other tabs
@ -134,7 +134,7 @@ extension SidebarViewModel {
}
.store(in: &cell.disposeBag)
case .me:
guard let account = self.authenticationBox?.authentication.account() else { return }
guard let account = self.authenticationBox?.authentication.cachedAccount() else { return }
let currentUserDisplayName = account.displayNameWithFallback
cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName)

View File

@ -44,7 +44,7 @@ extension SearchResultSection {
case .account(let account, let relationship):
let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as! UserTableViewCell
guard let me = authenticationBox.authentication.account() else { return cell }
guard let me = authenticationBox.cachedAccount else { return cell }
cell.userView.setButtonState(.loading)
cell.configure(

View File

@ -139,7 +139,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
switch (profile, statusID) {
case (profile, nil):
Task {
guard let me = authenticationBox.authentication.account() else { return }
guard let me = authenticationBox.cachedAccount else { return }
guard let account = try await APIService.shared.search(
query: .init(q: incomingURL.absoluteString, type: .accounts, resolve: true),
@ -208,9 +208,8 @@ extension SceneDelegate {
return false
}
let _isActive = try? await AuthenticationServiceProvider.shared.activeMastodonUser(
domain: authentication.domain,
userID: authentication.userID
let _isActive = AuthenticationServiceProvider.shared.activateUser(authentication.userID,
inDomain: authentication.domain
)
guard _isActive == true else {
@ -283,7 +282,7 @@ extension SceneDelegate {
Task {
do {
guard let me = authenticationBox.authentication.account() else { return }
guard let me = authenticationBox.cachedAccount else { return }
guard let account = try await APIService.shared.search(
query: .init(q: components[1], type: .accounts, resolve: true),

View File

@ -16,7 +16,7 @@ extension Account {
@MainActor
static func fetch() async throws -> [Account] {
let accounts = AuthenticationServiceProvider.shared.authentications.compactMap { mastodonAuthentication -> Account? in
guard let authenticatedAccount = mastodonAuthentication.account() else {
guard let authenticatedAccount = mastodonAuthentication.cachedAccount() else {
return nil
}
let account = Account(

View File

@ -30,6 +30,11 @@ public struct MastodonAuthenticationBox: UserIdentifier {
public init(authentication: MastodonAuthentication) {
self.authentication = authentication
}
@MainActor
public var cachedAccount: Mastodon.Entity.Account? {
return authentication.cachedAccount()
}
}
public class MastodonAccountInMemoryCache {

View File

@ -101,13 +101,16 @@ public class AuthenticationServiceProvider: ObservableObject {
authentications.removeAll(where: { $0 == authentication })
}
func activateAuthentication(in domain: String, for userID: String) {
public func activateUser(_ userID: String, inDomain domain: String) -> Bool {
var found = false
authentications = authentications.map { authentication in
guard authentication.domain == domain, authentication.userID == userID else {
return authentication
}
found = true
return authentication.updating(activatedAt: Date())
}
return found
}
func getAuthentication(in domain: String, for userID: String) -> MastodonAuthentication? {
@ -141,16 +144,6 @@ public extension AuthenticationServiceProvider {
}
}
func activeMastodonUser(domain: String, userID: String) async throws -> Bool {
var isActive = false
AuthenticationServiceProvider.shared.activateAuthentication(in: domain, for: userID)
isActive = true
return isActive
}
func signOutMastodonUser(authentication: MastodonAuthentication) async throws {
try await AuthenticationServiceProvider.shared.delete(authentication: authentication)
_ = try await APIService.shared.cancelSubscription(domain: authentication.domain, authorization: authentication.authorization)
@ -230,11 +223,7 @@ public extension AuthenticationServiceProvider {
// it when we need it to display on the home timeline.
// We need this (also) for the Account-list, but it might be the wrong place. App Startup might be more appropriate
for authentication in authentications {
guard let account = try? await APIService.shared.accountInfo(domain: authentication.domain,
userID: authentication.userID,
authorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken)).value else { continue }
FileManager.default.store(account: account, forUserID: authentication.userIdentifier())
guard let account = try? await APIService.shared.accountInfo(MastodonAuthenticationBox(authentication: authentication)) else { continue }
}
NotificationCenter.default.post(name: .userFetched, object: nil)

View File

@ -136,14 +136,9 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier {
)
}
public func account() -> Mastodon.Entity.Account? {
let account = FileManager
.default
.accounts(for: self.userIdentifier())
.first(where: { $0.id == userID })
return account
@MainActor
public func cachedAccount() -> Mastodon.Entity.Account? {
return PersistenceManager.shared.cachedAccount(for: self)
}
public func userIdentifier() -> MastodonUserIdentifier {

View File

@ -1,57 +0,0 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
import Foundation
import MastodonSDK
public extension FileManager {
func store(account: Mastodon.Entity.Account, forUserID userID: UserIdentifier) {
var accounts = accounts(for: userID)
if let index = accounts.firstIndex(of: account) {
accounts.remove(at: index)
}
accounts.append(account)
storeJSON(accounts, userID: userID)
}
func accounts(for userId: UserIdentifier) -> [Mastodon.Entity.Account] {
guard let sharedDirectory else { assert(false); return [] }
let accountPath = Persistence.accounts(userId).filepath(baseURL: sharedDirectory)
guard let data = try? Data(contentsOf: accountPath) else { return [] }
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .iso8601
do {
let accounts = try jsonDecoder.decode([Mastodon.Entity.Account].self, from: data)
assert(accounts.count > 0)
return accounts
} catch {
return []
}
}
}
private extension FileManager {
private func storeJSON(_ encodable: Encodable, userID: UserIdentifier) {
guard let sharedDirectory else { return }
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .iso8601
do {
let data = try jsonEncoder.encode(encodable)
let accountsPath = Persistence.accounts( userID).filepath(baseURL: sharedDirectory)
try data.write(to: accountsPath)
} catch {
debugPrint(error.localizedDescription)
}
}
}

View File

@ -7,6 +7,7 @@
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
@MainActor
public class PersistenceManager {
@ -37,7 +38,125 @@ public class PersistenceManager {
.store(in: &disposeBag)
}
func newTaskContext() -> NSManagedObjectContext {
public func newTaskContext() -> NSManagedObjectContext {
return coreDataStack.newTaskContext()
}
public func cachedTimeline(_ timeline: Persistence) throws -> [MastodonStatus] {
return try FileManager.default.cached(timeline: timeline).map(MastodonStatus.fromEntity)
}
public func cachedAccount(for authentication: MastodonAuthentication) -> Mastodon.Entity.Account? {
let account = FileManager
.default
.accounts(for: authentication.userIdentifier())
.first(where: { $0.id == authentication.userID })
return account
}
public func cacheAccount(_ account: Mastodon.Entity.Account, for authenticationBox: MastodonAuthenticationBox) {
FileManager.default.store(account: account, forUserID: authenticationBox.authentication.userIdentifier())
}
}
private extension FileManager {
static let cacheItemsLimit: Int = 100 // max number of items to cache
func cached<T: Decodable>(timeline: Persistence) throws -> [T] {
guard let cachesDirectory else { return [] }
let filePath = timeline.filepath(baseURL: cachesDirectory)
guard let data = try? Data(contentsOf: filePath) else { return [] }
do {
let items = try JSONDecoder().decode([T].self, from: data)
return items
} catch {
return []
}
}
func cache<T: Encodable>(_ items: [T], timeline: Persistence) {
guard let cachesDirectory else { return }
let processableItems: [T]
if items.count > Self.cacheItemsLimit {
processableItems = items.dropLast(items.count - Self.cacheItemsLimit)
} else {
processableItems = items
}
do {
let data = try JSONEncoder().encode(processableItems)
let filePath = timeline.filepath(baseURL: cachesDirectory)
try data.write(to: filePath)
} catch {
debugPrint(error.localizedDescription)
}
}
func invalidate(timeline: Persistence) {
guard let cachesDirectory else { return }
let filePath = timeline.filepath(baseURL: cachesDirectory)
try? removeItem(at: filePath)
}
}
private extension FileManager {
func store(account: Mastodon.Entity.Account, forUserID userID: UserIdentifier) {
var accounts = accounts(for: userID)
if let index = accounts.firstIndex(of: account) {
accounts.remove(at: index)
}
accounts.append(account)
storeJSON(accounts, userID: userID)
}
func accounts(for userId: UserIdentifier) -> [Mastodon.Entity.Account] {
guard let sharedDirectory else { assert(false); return [] }
let accountPath = Persistence.accounts(userId).filepath(baseURL: sharedDirectory)
guard let data = try? Data(contentsOf: accountPath) else { return [] }
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .iso8601
do {
let accounts = try jsonDecoder.decode([Mastodon.Entity.Account].self, from: data)
assert(accounts.count > 0)
return accounts
} catch {
return []
}
}
}
private extension FileManager {
private func storeJSON(_ encodable: Encodable, userID: UserIdentifier) {
guard let sharedDirectory else { return }
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .iso8601
do {
let data = try jsonEncoder.encode(encodable)
let accountsPath = Persistence.accounts( userID).filepath(baseURL: sharedDirectory)
try data.write(to: accountsPath)
} catch {
debugPrint(error.localizedDescription)
}
}
}

View File

@ -14,27 +14,23 @@ import MastodonSDK
extension APIService {
public func authenticatedUserInfo(
authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
try await accountInfo(
) async throws -> Mastodon.Entity.Account {
let authenticated = try await accountInfo(authenticationBox)
return authenticated
}
public func accountInfo(_ authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Entity.Account {
let account = try await Mastodon.API.Account.accountInfo(
session: session,
domain: authenticationBox.domain,
userID: authenticationBox.userID,
authorization: authenticationBox.userAuthorization
)
}
public func accountInfo(
domain: String,
userID: Mastodon.Entity.Account.ID,
authorization: Mastodon.API.OAuth.Authorization
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
let response = try await Mastodon.API.Account.accountInfo(
session: session,
domain: domain,
userID: userID,
authorization: authorization
).singleOutput()
).singleOutput().value
return response
PersistenceManager.shared.cacheAccount(account, for: authenticationBox)
return account
}
}

View File

@ -94,7 +94,7 @@ extension NotificationService {
var items: [UIApplicationShortcutItem] = []
for authentication in AuthenticationServiceProvider.shared.authentications {
guard let account = authentication.account() else { continue }
guard let account = authentication.cachedAccount() else { continue }
let accessToken = authentication.userAccessToken
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken)
guard count > 0 else { continue }

View File

@ -156,7 +156,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
self.visibility = {
// default private when user locked
var visibility: Mastodon.Entity.Status.Visibility = {
guard let author = authenticationBox.authentication.account() else {
guard let author = authenticationBox.cachedAccount else {
return .public
}
return author.locked ? .private : .public
@ -195,7 +195,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
switch destination {
case .reply(let record):
let status = record.entity
let author = authenticationBox.authentication.account()
let author = authenticationBox.cachedAccount
var mentionAccts: [String] = []
if author?.id != status.account.id {
@ -306,7 +306,7 @@ extension ComposeContentViewModel {
// bind author
$authenticationBox
.sink { [weak self] authenticationBox in
guard let self, let account = authenticationBox.authentication.account() else { return }
guard let self, let account = authenticationBox.cachedAccount else { return }
self.avatarURL = account.avatarImageURL()
@ -575,7 +575,7 @@ extension ComposeContentViewModel {
public func statusPublisher() throws -> StatusPublisher {
guard authenticationBox.authentication.account() != nil else {
guard authenticationBox.cachedAccount != nil else {
throw AppError.badAuthentication
}
@ -624,7 +624,7 @@ extension ComposeContentViewModel {
guard case let .editStatus(status, _) = composeContext else { return nil }
// author
guard let author = authenticationBox.authentication.account() else {
guard let author = authenticationBox.cachedAccount else {
throw AppError.badAuthentication
}

View File

@ -88,7 +88,7 @@ extension StatusView {
private func configureHeader(status: MastodonStatus) {
if status.entity.reblogged == true,
let authenticationBox = viewModel.authenticationBox,
let account = authenticationBox.authentication.account() {
let account = authenticationBox.cachedAccount {
let name = account.displayNameWithFallback
let emojis = account.emojis

View File

@ -71,12 +71,12 @@ struct FollowersCountWidget: Widget {
private extension FollowersCountWidgetProvider {
func loadCurrentEntry(for configuration: FollowersCountIntent, in context: Context, completion: @escaping (FollowersCountEntry) -> Void) {
Task {
Task { @MainActor in
await AuthenticationServiceProvider.shared.prepareForUse()
AuthenticationServiceProvider.shared.prepareForUse()
guard
let authBox = await AuthenticationServiceProvider.shared.activeAuthentication
let authBox = AuthenticationServiceProvider.shared.activeAuthentication
else {
guard !context.isPreview else {
return completion(.placeholder)
@ -85,7 +85,7 @@ private extension FollowersCountWidgetProvider {
}
guard
let desiredAccount = configuration.account ?? authBox.authentication.account()?.acctWithDomain
let desiredAccount = configuration.account ?? authBox.cachedAccount?.acctWithDomain
else {
return completion(.unconfigured)
}

View File

@ -71,12 +71,12 @@ struct MultiFollowersCountWidget: Widget {
private extension MultiFollowersCountWidgetProvider {
func loadCurrentEntry(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> Void) {
Task {
Task { @MainActor in
await AuthenticationServiceProvider.shared.prepareForUse()
AuthenticationServiceProvider.shared.prepareForUse()
guard
let authBox = await AuthenticationServiceProvider.shared.activeAuthentication
let authBox = AuthenticationServiceProvider.shared.activeAuthentication
else {
guard !context.isPreview else {
return completion(.placeholder)
@ -88,7 +88,7 @@ private extension MultiFollowersCountWidgetProvider {
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
desiredAccounts = configuredAccounts
} else if let currentlyLoggedInAccount = authBox.authentication.account()?.acctWithDomain {
} else if let currentlyLoggedInAccount = authBox.cachedAccount?.acctWithDomain {
desiredAccounts = [currentlyLoggedInAccount]
} else {
return completion(.unconfigured)