2023-01-31 14:37:49 +01:00
|
|
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
|
|
|
|
|
|
|
import WidgetKit
|
|
|
|
import SwiftUI
|
|
|
|
import Intents
|
|
|
|
import MastodonSDK
|
2023-12-31 16:44:54 +01:00
|
|
|
import MastodonCore
|
2023-02-06 19:57:33 +01:00
|
|
|
import MastodonLocalization
|
2023-01-31 14:37:49 +01:00
|
|
|
|
|
|
|
struct MultiFollowersCountWidgetProvider: IntentTimelineProvider {
|
|
|
|
func placeholder(in context: Context) -> MultiFollowersCountEntry {
|
|
|
|
.placeholder
|
|
|
|
}
|
|
|
|
|
2023-01-31 14:55:59 +01:00
|
|
|
func getSnapshot(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> ()) {
|
2023-01-31 14:37:49 +01:00
|
|
|
loadCurrentEntry(for: configuration, in: context, completion: completion)
|
|
|
|
}
|
|
|
|
|
2023-01-31 14:55:59 +01:00
|
|
|
func getTimeline(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (Timeline<MultiFollowersCountEntry>) -> ()) {
|
2023-01-31 14:37:49 +01:00
|
|
|
loadCurrentEntry(for: configuration, in: context) { entry in
|
|
|
|
completion(Timeline(entries: [entry], policy: .after(.now)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct MultiFollowersCountEntry: TimelineEntry {
|
|
|
|
let date: Date
|
2023-02-06 13:51:58 +01:00
|
|
|
let accounts: [MultiFollowersEntryAccountable]?
|
2023-01-31 14:55:59 +01:00
|
|
|
let configuration: MultiFollowersCountIntent
|
2023-01-31 14:37:49 +01:00
|
|
|
|
|
|
|
static var placeholder: Self {
|
|
|
|
MultiFollowersCountEntry(
|
|
|
|
date: .now,
|
|
|
|
accounts: [
|
2023-02-06 13:51:58 +01:00
|
|
|
MultiFollowersEntryAccount(
|
2023-01-31 14:37:49 +01:00
|
|
|
followersCount: 99_900,
|
|
|
|
displayNameWithFallback: "Mastodon",
|
|
|
|
acct: "mastodon",
|
|
|
|
avatarImage: UIImage(named: "missingAvatar")!,
|
|
|
|
domain: "mastodon"
|
|
|
|
)
|
|
|
|
],
|
2023-01-31 14:55:59 +01:00
|
|
|
configuration: MultiFollowersCountIntent()
|
2023-01-31 14:37:49 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
static var unconfigured: Self {
|
|
|
|
MultiFollowersCountEntry(
|
|
|
|
date: .now,
|
2023-02-06 13:51:58 +01:00
|
|
|
accounts: nil,
|
2023-01-31 14:55:59 +01:00
|
|
|
configuration: MultiFollowersCountIntent()
|
2023-01-31 14:37:49 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct MultiFollowersCountWidget: Widget {
|
|
|
|
private var availableFamilies: [WidgetFamily] {
|
2023-01-31 14:54:05 +01:00
|
|
|
return [.systemSmall, .systemMedium]
|
2023-01-31 14:37:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var body: some WidgetConfiguration {
|
2023-01-31 14:55:59 +01:00
|
|
|
IntentConfiguration(kind: "Multiple followers", intent: MultiFollowersCountIntent.self, provider: MultiFollowersCountWidgetProvider()) { entry in
|
2023-01-31 14:37:49 +01:00
|
|
|
MultiFollowersCountWidgetView(entry: entry)
|
|
|
|
}
|
2023-02-07 09:33:47 +01:00
|
|
|
.configurationDisplayName(L10n.Widget.MultipleFollowers.configurationDisplayName)
|
|
|
|
.description(L10n.Widget.MultipleFollowers.configurationDescription)
|
2023-01-31 14:37:49 +01:00
|
|
|
.supportedFamilies(availableFamilies)
|
2024-02-20 16:34:40 +01:00
|
|
|
.contentMarginsDisabled() // Disable excessive margins (only effective for iOS >= 17.0
|
2023-01-31 14:37:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension MultiFollowersCountWidgetProvider {
|
2023-01-31 14:55:59 +01:00
|
|
|
func loadCurrentEntry(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> Void) {
|
2023-01-31 14:37:49 +01:00
|
|
|
Task {
|
2023-12-31 16:44:54 +01:00
|
|
|
|
2024-10-30 19:38:21 +01:00
|
|
|
await AuthenticationServiceProvider.shared.prepareForUse()
|
2023-12-31 16:44:54 +01:00
|
|
|
|
2023-01-31 14:37:49 +01:00
|
|
|
guard
|
|
|
|
let authBox = WidgetExtension.appContext
|
|
|
|
.authenticationService
|
|
|
|
.mastodonAuthenticationBoxes
|
|
|
|
.first
|
|
|
|
else {
|
2023-02-06 11:46:46 +01:00
|
|
|
guard !context.isPreview else {
|
|
|
|
return completion(.placeholder)
|
|
|
|
}
|
2023-01-31 14:37:49 +01:00
|
|
|
return completion(.unconfigured)
|
|
|
|
}
|
|
|
|
|
2023-02-07 09:07:37 +01:00
|
|
|
let desiredAccounts: [String]
|
|
|
|
|
|
|
|
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
|
|
|
|
desiredAccounts = configuredAccounts
|
2024-01-02 23:09:50 +01:00
|
|
|
} else if let currentlyLoggedInAccount = authBox.authentication.account()?.acctWithDomain {
|
2023-02-07 09:07:37 +01:00
|
|
|
desiredAccounts = [currentlyLoggedInAccount]
|
|
|
|
} else {
|
2023-01-31 14:37:49 +01:00
|
|
|
return completion(.unconfigured)
|
|
|
|
}
|
2023-02-07 09:07:37 +01:00
|
|
|
|
2023-02-06 13:51:58 +01:00
|
|
|
var accounts = [MultiFollowersEntryAccountable]()
|
2023-01-31 14:37:49 +01:00
|
|
|
|
|
|
|
for desiredAccount in desiredAccounts {
|
2023-02-07 09:07:37 +01:00
|
|
|
guard
|
|
|
|
let resultingAccount = try await WidgetExtension.appContext
|
|
|
|
.apiService
|
|
|
|
.search(query: .init(q: desiredAccount, type: .accounts), authenticationBox: authBox)
|
|
|
|
.value
|
|
|
|
.accounts
|
2023-02-07 12:36:13 +01:00
|
|
|
.first(where: { $0.acctWithDomainIfMissing(authBox.domain) == desiredAccount })
|
2023-02-07 09:07:37 +01:00
|
|
|
else {
|
|
|
|
continue
|
|
|
|
}
|
2023-01-31 14:37:49 +01:00
|
|
|
|
|
|
|
let imageData = try await URLSession.shared.data(from: resultingAccount.avatarImageURLWithFallback(domain: authBox.domain)).0
|
|
|
|
|
2023-02-06 13:51:58 +01:00
|
|
|
accounts.append(MultiFollowersEntryAccount.from(
|
2023-01-31 14:37:49 +01:00
|
|
|
mastodonAccount: resultingAccount,
|
|
|
|
domain: authBox.domain,
|
|
|
|
avatarImage: UIImage(data: imageData) ?? UIImage(named: "missingAvatar")!
|
|
|
|
))
|
|
|
|
}
|
2023-02-06 13:51:58 +01:00
|
|
|
|
|
|
|
if context.isPreview {
|
|
|
|
accounts.append(
|
|
|
|
MultiFollowersEntryAccount(
|
|
|
|
followersCount: 1_200,
|
2023-02-06 19:57:33 +01:00
|
|
|
displayNameWithFallback: L10n.Widget.MultipleFollowers.MockUser.displayName,
|
|
|
|
acct: L10n.Widget.MultipleFollowers.MockUser.accountName,
|
2023-02-06 13:51:58 +01:00
|
|
|
avatarImage: UIImage(named: "missingAvatar")!,
|
|
|
|
domain: authBox.domain
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2023-01-31 14:37:49 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|