mastodon-ios/WidgetExtension/Variants/MultiFollowersCount/MultiFollowersCountWidget.s...

173 lines
6.3 KiB
Swift

// Copyright © 2023 Mastodon gGmbH. All rights reserved.
import WidgetKit
import SwiftUI
import Intents
import MastodonSDK
import MastodonCore
import MastodonLocalization
struct MultiFollowersCountWidgetProvider: IntentTimelineProvider {
func placeholder(in context: Context) -> MultiFollowersCountEntry {
.placeholder
}
func getSnapshot(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> ()) {
loadCurrentEntry(for: configuration, in: context, completion: completion)
}
func getTimeline(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (Timeline<MultiFollowersCountEntry>) -> ()) {
loadCurrentEntry(for: configuration, in: context) { entry in
completion(Timeline(entries: [entry], policy: .after(.now)))
}
}
}
struct MultiFollowersCountEntry: TimelineEntry {
let date: Date
let accounts: [MultiFollowersEntryAccountable]?
let configuration: MultiFollowersCountIntent
static var placeholder: Self {
MultiFollowersCountEntry(
date: .now,
accounts: [
MultiFollowersEntryAccount(
followersCount: 99_900,
displayNameWithFallback: "Mastodon",
acct: "mastodon",
avatarImage: UIImage(named: "missingAvatar")!,
domain: "mastodon"
)
],
configuration: MultiFollowersCountIntent()
)
}
static var unconfigured: Self {
MultiFollowersCountEntry(
date: .now,
accounts: nil,
configuration: MultiFollowersCountIntent()
)
}
}
struct MultiFollowersCountWidget: Widget {
private var availableFamilies: [WidgetFamily] {
return [.systemSmall, .systemMedium]
}
var body: some WidgetConfiguration {
IntentConfiguration(kind: "Multiple followers", intent: MultiFollowersCountIntent.self, provider: MultiFollowersCountWidgetProvider()) { entry in
MultiFollowersCountWidgetView(entry: entry)
}
.configurationDisplayName(L10n.Widget.MultipleFollowers.configurationDisplayName)
.description(L10n.Widget.MultipleFollowers.configurationDescription)
.supportedFamilies(availableFamilies)
.contentMarginsDisabled() // Disable excessive margins (only effective for iOS >= 17.0
}
}
private extension MultiFollowersCountWidgetProvider {
func loadCurrentEntry(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> Void) {
Task {
AuthenticationServiceProvider.shared.restore()
guard
let authBox = WidgetExtension.appContext
.authenticationService
.mastodonAuthenticationBoxes
.first
else {
guard !context.isPreview else {
return completion(.placeholder)
}
return completion(.unconfigured)
}
let desiredAccounts: [String]
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
desiredAccounts = configuredAccounts
} else if let currentlyLoggedInAccount = authBox.authentication.user(
in: WidgetExtension.appContext.managedObjectContext
)?.acctWithDomain {
desiredAccounts = [currentlyLoggedInAccount]
} else {
return completion(.unconfigured)
}
var accounts = [MultiFollowersEntryAccountable]()
for desiredAccount in desiredAccounts {
guard
let resultingAccount = try await WidgetExtension.appContext
.apiService
.search(query: .init(q: desiredAccount, type: .accounts), authenticationBox: authBox)
.value
.accounts
.first(where: { $0.acctWithDomainIfMissing(authBox.domain) == desiredAccount })
else {
continue
}
let imageData = try await URLSession.shared.data(from: resultingAccount.avatarImageURLWithFallback(domain: authBox.domain)).0
accounts.append(MultiFollowersEntryAccount.from(
mastodonAccount: resultingAccount,
domain: authBox.domain,
avatarImage: UIImage(data: imageData) ?? UIImage(named: "missingAvatar")!
))
}
if context.isPreview {
accounts.append(
MultiFollowersEntryAccount(
followersCount: 1_200,
displayNameWithFallback: L10n.Widget.MultipleFollowers.MockUser.displayName,
acct: L10n.Widget.MultipleFollowers.MockUser.accountName,
avatarImage: UIImage(named: "missingAvatar")!,
domain: authBox.domain
)
)
}
let entry = MultiFollowersCountEntry(
date: Date(),
accounts: accounts,
configuration: configuration
)
completion(entry)
}
}
}
protocol MultiFollowersEntryAccountable {
var followersCount: Int { get }
var displayNameWithFallback: String { get }
var acct: String { get }
var avatarImage: UIImage { get }
var domain: String { get }
}
struct MultiFollowersEntryAccount: MultiFollowersEntryAccountable {
let followersCount: Int
let displayNameWithFallback: String
let acct: String
let avatarImage: UIImage
let domain: String
static func from(mastodonAccount: Mastodon.Entity.Account, domain: String, avatarImage: UIImage) -> Self {
MultiFollowersEntryAccount(
followersCount: mastodonAccount.followersCount,
displayNameWithFallback: mastodonAccount.displayNameWithFallback,
acct: mastodonAccount.acct,
avatarImage: avatarImage,
domain: domain
)
}
}