feat(Widget): Implement MultiFollowersCountWidget for single Column
This commit is contained in:
parent
e05a8602d5
commit
9eb26d4ed8
|
@ -53,6 +53,9 @@
|
||||||
2A72813F297EC762004138C5 /* WidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A72813E297EC762004138C5 /* WidgetExtension.swift */; };
|
2A72813F297EC762004138C5 /* WidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A72813E297EC762004138C5 /* WidgetExtension.swift */; };
|
||||||
2A76F75C2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */; };
|
2A76F75C2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */; };
|
||||||
2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */; };
|
2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */; };
|
||||||
|
2A86A14629892944007F1062 /* MultiFollowersCountIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A86A14529892944007F1062 /* MultiFollowersCountIntentHandler.swift */; };
|
||||||
|
2A86A14929892B3A007F1062 /* MultiFollowersCountWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A86A14829892B3A007F1062 /* MultiFollowersCountWidget.swift */; };
|
||||||
|
2A86A14B2989326E007F1062 /* MultiFollowersCountWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A86A14A2989326E007F1062 /* MultiFollowersCountWidgetView.swift */; };
|
||||||
2A90A157296EEE500026C155 /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 2A90A156296EEE500026C155 /* MastodonSDKDynamic */; };
|
2A90A157296EEE500026C155 /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 2A90A156296EEE500026C155 /* MastodonSDKDynamic */; };
|
||||||
2AB12E4629362F27006BC925 /* DataSourceFacade+Translate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */; };
|
2AB12E4629362F27006BC925 /* DataSourceFacade+Translate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */; };
|
||||||
2AE202AA297FE10B00F66E55 /* WidgetExtension.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A72812C297EA9D7004138C5 /* WidgetExtension.intentdefinition */; };
|
2AE202AA297FE10B00F66E55 /* WidgetExtension.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A72812C297EA9D7004138C5 /* WidgetExtension.intentdefinition */; };
|
||||||
|
@ -642,6 +645,9 @@
|
||||||
2A72813E297EC762004138C5 /* WidgetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetExtension.swift; sourceTree = "<group>"; };
|
2A72813E297EC762004138C5 /* WidgetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetExtension.swift; sourceTree = "<group>"; };
|
||||||
2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineHeaderViewActionButton.swift; sourceTree = "<group>"; };
|
2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineHeaderViewActionButton.swift; sourceTree = "<group>"; };
|
||||||
2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+NextAccount.swift"; sourceTree = "<group>"; };
|
2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+NextAccount.swift"; sourceTree = "<group>"; };
|
||||||
|
2A86A14529892944007F1062 /* MultiFollowersCountIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiFollowersCountIntentHandler.swift; sourceTree = "<group>"; };
|
||||||
|
2A86A14829892B3A007F1062 /* MultiFollowersCountWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiFollowersCountWidget.swift; sourceTree = "<group>"; };
|
||||||
|
2A86A14A2989326E007F1062 /* MultiFollowersCountWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiFollowersCountWidgetView.swift; sourceTree = "<group>"; };
|
||||||
2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Translate.swift"; sourceTree = "<group>"; };
|
2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Translate.swift"; sourceTree = "<group>"; };
|
||||||
2AE202A9297FDDF500F66E55 /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = "<group>"; };
|
2AE202A9297FDDF500F66E55 /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||||
2AE202AB297FE19100F66E55 /* FollowersCountIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersCountIntentHandler.swift; sourceTree = "<group>"; };
|
2AE202AB297FE19100F66E55 /* FollowersCountIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersCountIntentHandler.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1440,7 +1446,6 @@
|
||||||
2AE202A9297FDDF500F66E55 /* WidgetExtension.entitlements */,
|
2AE202A9297FDDF500F66E55 /* WidgetExtension.entitlements */,
|
||||||
2A72813E297EC762004138C5 /* WidgetExtension.swift */,
|
2A72813E297EC762004138C5 /* WidgetExtension.swift */,
|
||||||
2A728126297EA9D7004138C5 /* WidgetExtensionBundle.swift */,
|
2A728126297EA9D7004138C5 /* WidgetExtensionBundle.swift */,
|
||||||
2A33062C2987DBFA001D4C51 /* FollowersCountHistory.swift */,
|
|
||||||
2A72812C297EA9D7004138C5 /* WidgetExtension.intentdefinition */,
|
2A72812C297EA9D7004138C5 /* WidgetExtension.intentdefinition */,
|
||||||
2A72812D297EA9D8004138C5 /* Assets.xcassets */,
|
2A72812D297EA9D8004138C5 /* Assets.xcassets */,
|
||||||
2A72812F297EA9D8004138C5 /* Info.plist */,
|
2A72812F297EA9D8004138C5 /* Info.plist */,
|
||||||
|
@ -1452,6 +1457,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2A86A14429892709007F1062 /* FollowersCount */,
|
2A86A14429892709007F1062 /* FollowersCount */,
|
||||||
|
2A86A14729892B1B007F1062 /* MultiFollowersCount */,
|
||||||
);
|
);
|
||||||
path = Variants;
|
path = Variants;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1459,12 +1465,22 @@
|
||||||
2A86A14429892709007F1062 /* FollowersCount */ = {
|
2A86A14429892709007F1062 /* FollowersCount */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
2A33062C2987DBFA001D4C51 /* FollowersCountHistory.swift */,
|
||||||
2A72812A297EA9D7004138C5 /* FollowersCountWidget.swift */,
|
2A72812A297EA9D7004138C5 /* FollowersCountWidget.swift */,
|
||||||
2A33AB652982C4AF008A7FB1 /* FollowersCountWidgetView.swift */,
|
2A33AB652982C4AF008A7FB1 /* FollowersCountWidgetView.swift */,
|
||||||
);
|
);
|
||||||
path = FollowersCount;
|
path = FollowersCount;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
2A86A14729892B1B007F1062 /* MultiFollowersCount */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
2A86A14829892B3A007F1062 /* MultiFollowersCountWidget.swift */,
|
||||||
|
2A86A14A2989326E007F1062 /* MultiFollowersCountWidgetView.swift */,
|
||||||
|
);
|
||||||
|
path = MultiFollowersCount;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
2D152A8A25C295B8009AA50C /* Content */ = {
|
2D152A8A25C295B8009AA50C /* Content */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2291,6 +2307,7 @@
|
||||||
children = (
|
children = (
|
||||||
DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */,
|
DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */,
|
||||||
2AE202AB297FE19100F66E55 /* FollowersCountIntentHandler.swift */,
|
2AE202AB297FE19100F66E55 /* FollowersCountIntentHandler.swift */,
|
||||||
|
2A86A14529892944007F1062 /* MultiFollowersCountIntentHandler.swift */,
|
||||||
);
|
);
|
||||||
path = Handler;
|
path = Handler;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3485,10 +3502,12 @@
|
||||||
2A33062D2987DBFA001D4C51 /* FollowersCountHistory.swift in Sources */,
|
2A33062D2987DBFA001D4C51 /* FollowersCountHistory.swift in Sources */,
|
||||||
2A33063829880835001D4C51 /* LineChart.swift in Sources */,
|
2A33063829880835001D4C51 /* LineChart.swift in Sources */,
|
||||||
2A728130297EA9D8004138C5 /* WidgetExtension.intentdefinition in Sources */,
|
2A728130297EA9D8004138C5 /* WidgetExtension.intentdefinition in Sources */,
|
||||||
|
2A86A14B2989326E007F1062 /* MultiFollowersCountWidgetView.swift in Sources */,
|
||||||
2A72813F297EC762004138C5 /* WidgetExtension.swift in Sources */,
|
2A72813F297EC762004138C5 /* WidgetExtension.swift in Sources */,
|
||||||
2A33063A29880835001D4C51 /* LightChart.swift in Sources */,
|
2A33063A29880835001D4C51 /* LightChart.swift in Sources */,
|
||||||
2A33063B29880835001D4C51 /* ChartType.swift in Sources */,
|
2A33063B29880835001D4C51 /* ChartType.swift in Sources */,
|
||||||
2A33063629880835001D4C51 /* Math.swift in Sources */,
|
2A33063629880835001D4C51 /* Math.swift in Sources */,
|
||||||
|
2A86A14929892B3A007F1062 /* MultiFollowersCountWidget.swift in Sources */,
|
||||||
2A33AB662982C4AF008A7FB1 /* FollowersCountWidgetView.swift in Sources */,
|
2A33AB662982C4AF008A7FB1 /* FollowersCountWidgetView.swift in Sources */,
|
||||||
2A728127297EA9D7004138C5 /* WidgetExtensionBundle.swift in Sources */,
|
2A728127297EA9D7004138C5 /* WidgetExtensionBundle.swift in Sources */,
|
||||||
2A72812B297EA9D7004138C5 /* FollowersCountWidget.swift in Sources */,
|
2A72812B297EA9D7004138C5 /* FollowersCountWidget.swift in Sources */,
|
||||||
|
@ -3932,6 +3951,7 @@
|
||||||
2AE202AC297FE19100F66E55 /* FollowersCountIntentHandler.swift in Sources */,
|
2AE202AC297FE19100F66E55 /* FollowersCountIntentHandler.swift in Sources */,
|
||||||
DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */,
|
DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */,
|
||||||
DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */,
|
DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */,
|
||||||
|
2A86A14629892944007F1062 /* MultiFollowersCountIntentHandler.swift in Sources */,
|
||||||
DB8FABCA26AEC7B2008E5AF4 /* IntentHandler.swift in Sources */,
|
DB8FABCA26AEC7B2008E5AF4 /* IntentHandler.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
<key>NSUserActivityTypes</key>
|
<key>NSUserActivityTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>FollowersCountIntent</string>
|
<string>FollowersCountIntent</string>
|
||||||
|
<string>MultiFollowersCountSmallIntent</string>
|
||||||
<string>SendPostIntent</string>
|
<string>SendPostIntent</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIApplicationSceneManifest</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
|
|
@ -30,13 +30,11 @@ class FollowersCountIntentHandler: INExtension, FollowersCountIntentHandling {
|
||||||
.apiService
|
.apiService
|
||||||
.search(query: .init(q: searchTerm), authenticationBox: authenticationBox)
|
.search(query: .init(q: searchTerm), authenticationBox: authenticationBox)
|
||||||
|
|
||||||
debugPrint(results.value.statuses)
|
|
||||||
|
|
||||||
return INObjectCollection(items: results.value.accounts.map { $0.acctWithDomain(localDomain: authenticationBox.domain) as NSString })
|
return INObjectCollection(items: results.value.accounts.map { $0.acctWithDomain(localDomain: authenticationBox.domain) as NSString })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension Mastodon.Entity.Account {
|
extension Mastodon.Entity.Account {
|
||||||
func acctWithDomain(localDomain: String) -> String {
|
func acctWithDomain(localDomain: String) -> String {
|
||||||
guard acct.contains("@") else {
|
guard acct.contains("@") else {
|
||||||
return "\(acct)@\(localDomain)"
|
return "\(acct)@\(localDomain)"
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Intents
|
||||||
|
import MastodonCore
|
||||||
|
import MastodonSDK
|
||||||
|
import MastodonLocalization
|
||||||
|
|
||||||
|
class MultiFollowersCountIntentHandler: INExtension, MultiFollowersCountSmallIntentHandling {
|
||||||
|
func provideAccountsOptionsCollection(for intent: MultiFollowersCountSmallIntent, searchTerm: String?) async throws -> INObjectCollection<NSString> {
|
||||||
|
guard
|
||||||
|
let searchTerm = searchTerm,
|
||||||
|
let authenticationBox = WidgetExtension.appContext
|
||||||
|
.authenticationService
|
||||||
|
.mastodonAuthenticationBoxes
|
||||||
|
.first
|
||||||
|
else {
|
||||||
|
return INObjectCollection(items: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = try await WidgetExtension.appContext
|
||||||
|
.apiService
|
||||||
|
.search(query: .init(q: searchTerm), authenticationBox: authenticationBox)
|
||||||
|
|
||||||
|
return INObjectCollection(items: results.value.accounts.map { $0.acctWithDomain(localDomain: authenticationBox.domain) as NSString })
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@
|
||||||
<key>IntentsSupported</key>
|
<key>IntentsSupported</key>
|
||||||
<array>
|
<array>
|
||||||
<string>FollowersCountIntent</string>
|
<string>FollowersCountIntent</string>
|
||||||
|
<string>MultiFollowersCountSmallIntent</string>
|
||||||
<string>SendPostIntent</string>
|
<string>SendPostIntent</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
|
@ -17,6 +17,8 @@ class IntentHandler: INExtension {
|
||||||
return SendPostIntentHandler()
|
return SendPostIntentHandler()
|
||||||
case is FollowersCountIntent:
|
case is FollowersCountIntent:
|
||||||
return FollowersCountIntentHandler()
|
return FollowersCountIntentHandler()
|
||||||
|
case is MultiFollowersCountSmallIntent:
|
||||||
|
return MultiFollowersCountIntentHandler()
|
||||||
default:
|
default:
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import WidgetKit
|
||||||
|
import SwiftUI
|
||||||
|
import Intents
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
struct MultiFollowersCountWidgetProvider: IntentTimelineProvider {
|
||||||
|
func placeholder(in context: Context) -> MultiFollowersCountEntry {
|
||||||
|
.placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(for configuration: MultiFollowersCountSmallIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> ()) {
|
||||||
|
guard !context.isPreview else {
|
||||||
|
return completion(.placeholder)
|
||||||
|
}
|
||||||
|
loadCurrentEntry(for: configuration, in: context, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(for configuration: MultiFollowersCountSmallIntent, 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: [FollowersEntryAccountable]?
|
||||||
|
let configuration: MultiFollowersCountSmallIntent
|
||||||
|
|
||||||
|
static var placeholder: Self {
|
||||||
|
MultiFollowersCountEntry(
|
||||||
|
date: .now,
|
||||||
|
accounts: [
|
||||||
|
FollowersEntryAccount(
|
||||||
|
followersCount: 99_900,
|
||||||
|
displayNameWithFallback: "Mastodon",
|
||||||
|
acct: "mastodon",
|
||||||
|
avatarImage: UIImage(named: "missingAvatar")!,
|
||||||
|
domain: "mastodon"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
configuration: MultiFollowersCountSmallIntent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var unconfigured: Self {
|
||||||
|
MultiFollowersCountEntry(
|
||||||
|
date: .now,
|
||||||
|
accounts: [],
|
||||||
|
configuration: MultiFollowersCountSmallIntent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MultiFollowersCountWidget: Widget {
|
||||||
|
private var availableFamilies: [WidgetFamily] {
|
||||||
|
return [.systemSmall]
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
IntentConfiguration(kind: "Multiple followers", intent: MultiFollowersCountSmallIntent.self, provider: MultiFollowersCountWidgetProvider()) { entry in
|
||||||
|
MultiFollowersCountWidgetView(entry: entry)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Multiple followers")
|
||||||
|
.description("Show number of followers for multiple accounts.")
|
||||||
|
.supportedFamilies(availableFamilies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension MultiFollowersCountWidgetProvider {
|
||||||
|
func loadCurrentEntry(for configuration: MultiFollowersCountSmallIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> Void) {
|
||||||
|
Task {
|
||||||
|
guard
|
||||||
|
let authBox = WidgetExtension.appContext
|
||||||
|
.authenticationService
|
||||||
|
.mastodonAuthenticationBoxes
|
||||||
|
.first
|
||||||
|
else {
|
||||||
|
return completion(.unconfigured)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let desiredAccounts: [String] = {
|
||||||
|
guard let account = configuration.accounts?.compactMap({ $0 }) else {
|
||||||
|
if let acct = authBox.authenticationRecord.object(in: WidgetExtension.appContext.managedObjectContext)?.user.acct {
|
||||||
|
return [acct]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return account
|
||||||
|
}() else {
|
||||||
|
return completion(.unconfigured)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accounts = [FollowersEntryAccountable]()
|
||||||
|
|
||||||
|
for desiredAccount in desiredAccounts {
|
||||||
|
let resultingAccount = try await WidgetExtension.appContext
|
||||||
|
.apiService
|
||||||
|
.search(query: .init(q: desiredAccount, type: .accounts), authenticationBox: authBox)
|
||||||
|
.value
|
||||||
|
.accounts
|
||||||
|
.first!
|
||||||
|
|
||||||
|
let imageData = try await URLSession.shared.data(from: resultingAccount.avatarImageURLWithFallback(domain: authBox.domain)).0
|
||||||
|
|
||||||
|
accounts.append(FollowersEntryAccount.from(
|
||||||
|
mastodonAccount: resultingAccount,
|
||||||
|
domain: authBox.domain,
|
||||||
|
avatarImage: UIImage(data: imageData) ?? UIImage(named: "missingAvatar")!
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
import MastodonAsset
|
||||||
|
|
||||||
|
struct MultiFollowersCountWidgetView: View {
|
||||||
|
@Environment(\.widgetFamily) var family
|
||||||
|
|
||||||
|
var entry: MultiFollowersCountWidgetProvider.Entry
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let accounts = entry.accounts {
|
||||||
|
switch family {
|
||||||
|
case .systemSmall:
|
||||||
|
viewForSmallWidgetNoChart(accounts)
|
||||||
|
default:
|
||||||
|
Text("Sorry but this Widget family is unsupported.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("Please open Mastodon to log in to an Account.")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.font(.caption)
|
||||||
|
.padding(.all, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func viewForSmallWidgetNoChart(_ accounts: [FollowersEntryAccountable]) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
ForEach(accounts, id: \.acct) { account in
|
||||||
|
HStack {
|
||||||
|
if let avatarImage = account.avatarImage {
|
||||||
|
Image(uiImage: avatarImage)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
.cornerRadius(5)
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(account.followersCount.asAbbreviatedCountString())
|
||||||
|
.font(.title2)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
|
||||||
|
Text("@\(account.acct)")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.leading, 20)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
}
|
||||||
|
}
|
|
@ -184,9 +184,9 @@
|
||||||
<key>INIntentIneligibleForSuggestions</key>
|
<key>INIntentIneligibleForSuggestions</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>INIntentLastParameterTag</key>
|
<key>INIntentLastParameterTag</key>
|
||||||
<integer>5</integer>
|
<integer>6</integer>
|
||||||
<key>INIntentName</key>
|
<key>INIntentName</key>
|
||||||
<string>MultiFollowCountSmall</string>
|
<string>MultiFollowersCountSmall</string>
|
||||||
<key>INIntentParameters</key>
|
<key>INIntentParameters</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -251,17 +251,24 @@
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>INIntentParameterFixedSizeArray</key>
|
<key>INIntentParameterFixedSizeArray</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
|
<key>INIntentParameterMetadata</key>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentParameterMetadataCapitalization</key>
|
||||||
|
<string>Sentences</string>
|
||||||
|
<key>INIntentParameterMetadataDefaultValueID</key>
|
||||||
|
<string>SNXOJo</string>
|
||||||
|
</dict>
|
||||||
<key>INIntentParameterName</key>
|
<key>INIntentParameterName</key>
|
||||||
<string>accounts</string>
|
<string>accounts</string>
|
||||||
<key>INIntentParameterObjectType</key>
|
|
||||||
<string>MultiFollowAccountsSmall</string>
|
|
||||||
<key>INIntentParameterObjectTypeNamespace</key>
|
|
||||||
<string>88xZPY</string>
|
|
||||||
<key>INIntentParameterPromptDialogs</key>
|
<key>INIntentParameterPromptDialogs</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>INIntentParameterPromptDialogCustom</key>
|
<key>INIntentParameterPromptDialogCustom</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>INIntentParameterPromptDialogFormatString</key>
|
||||||
|
<string>Enter username</string>
|
||||||
|
<key>INIntentParameterPromptDialogFormatStringID</key>
|
||||||
|
<string>3d6HSO</string>
|
||||||
<key>INIntentParameterPromptDialogType</key>
|
<key>INIntentParameterPromptDialogType</key>
|
||||||
<string>Configuration</string>
|
<string>Configuration</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -271,13 +278,37 @@
|
||||||
<key>INIntentParameterPromptDialogType</key>
|
<key>INIntentParameterPromptDialogType</key>
|
||||||
<string>Primary</string>
|
<string>Primary</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentParameterPromptDialogCustom</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentParameterPromptDialogFormatString</key>
|
||||||
|
<string>There are ${count} options matching ‘${accounts}’.</string>
|
||||||
|
<key>INIntentParameterPromptDialogFormatStringID</key>
|
||||||
|
<string>3nWfxd</string>
|
||||||
|
<key>INIntentParameterPromptDialogType</key>
|
||||||
|
<string>DisambiguationIntroduction</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentParameterPromptDialogCustom</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentParameterPromptDialogFormatString</key>
|
||||||
|
<string>Just to confirm, you wanted ‘${accounts}’?</string>
|
||||||
|
<key>INIntentParameterPromptDialogFormatStringID</key>
|
||||||
|
<string>IP6ujX</string>
|
||||||
|
<key>INIntentParameterPromptDialogType</key>
|
||||||
|
<string>Confirmation</string>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>INIntentParameterSupportsDynamicEnumeration</key>
|
||||||
|
<true/>
|
||||||
<key>INIntentParameterSupportsMultipleValues</key>
|
<key>INIntentParameterSupportsMultipleValues</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>INIntentParameterSupportsSearch</key>
|
||||||
|
<true/>
|
||||||
<key>INIntentParameterTag</key>
|
<key>INIntentParameterTag</key>
|
||||||
<integer>5</integer>
|
<integer>6</integer>
|
||||||
<key>INIntentParameterType</key>
|
<key>INIntentParameterType</key>
|
||||||
<string>Object</string>
|
<string>String</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>INIntentResponse</key>
|
<key>INIntentResponse</key>
|
||||||
|
@ -295,9 +326,32 @@
|
||||||
<string>failure</string>
|
<string>failure</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>INIntentResponseLastParameterTag</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
<key>INIntentResponseOutput</key>
|
||||||
|
<string>username</string>
|
||||||
|
<key>INIntentResponseParameters</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseParameterDisplayName</key>
|
||||||
|
<string>Username</string>
|
||||||
|
<key>INIntentResponseParameterDisplayNameID</key>
|
||||||
|
<string>7DZrRA</string>
|
||||||
|
<key>INIntentResponseParameterDisplayPriority</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>INIntentResponseParameterName</key>
|
||||||
|
<string>username</string>
|
||||||
|
<key>INIntentResponseParameterSupportsMultipleValues</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentResponseParameterTag</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
<key>INIntentResponseParameterType</key>
|
||||||
|
<string>Object</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<key>INIntentTitle</key>
|
<key>INIntentTitle</key>
|
||||||
<string>Multi Follow Count Small</string>
|
<string>Multi Followers Count Small</string>
|
||||||
<key>INIntentTitleID</key>
|
<key>INIntentTitleID</key>
|
||||||
<string>e0W2wo</string>
|
<string>e0W2wo</string>
|
||||||
<key>INIntentType</key>
|
<key>INIntentType</key>
|
||||||
|
@ -307,86 +361,6 @@
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>INTypes</key>
|
<key>INTypes</key>
|
||||||
<array>
|
<array/>
|
||||||
<dict>
|
|
||||||
<key>INTypeDisplayName</key>
|
|
||||||
<string>Account</string>
|
|
||||||
<key>INTypeDisplayNameID</key>
|
|
||||||
<string>LUrJ3D</string>
|
|
||||||
<key>INTypeLastPropertyTag</key>
|
|
||||||
<integer>101</integer>
|
|
||||||
<key>INTypeName</key>
|
|
||||||
<string>MultiFollowAccountsSmall</string>
|
|
||||||
<key>INTypeProperties</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>INTypePropertyDefault</key>
|
|
||||||
<true/>
|
|
||||||
<key>INTypePropertyDisplayPriority</key>
|
|
||||||
<integer>1</integer>
|
|
||||||
<key>INTypePropertyName</key>
|
|
||||||
<string>identifier</string>
|
|
||||||
<key>INTypePropertyTag</key>
|
|
||||||
<integer>1</integer>
|
|
||||||
<key>INTypePropertyType</key>
|
|
||||||
<string>String</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>INTypePropertyDefault</key>
|
|
||||||
<true/>
|
|
||||||
<key>INTypePropertyDisplayPriority</key>
|
|
||||||
<integer>2</integer>
|
|
||||||
<key>INTypePropertyName</key>
|
|
||||||
<string>displayString</string>
|
|
||||||
<key>INTypePropertyTag</key>
|
|
||||||
<integer>2</integer>
|
|
||||||
<key>INTypePropertyType</key>
|
|
||||||
<string>String</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>INTypePropertyDefault</key>
|
|
||||||
<true/>
|
|
||||||
<key>INTypePropertyDisplayPriority</key>
|
|
||||||
<integer>3</integer>
|
|
||||||
<key>INTypePropertyName</key>
|
|
||||||
<string>pronunciationHint</string>
|
|
||||||
<key>INTypePropertyTag</key>
|
|
||||||
<integer>3</integer>
|
|
||||||
<key>INTypePropertyType</key>
|
|
||||||
<string>String</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>INTypePropertyDefault</key>
|
|
||||||
<true/>
|
|
||||||
<key>INTypePropertyDisplayPriority</key>
|
|
||||||
<integer>4</integer>
|
|
||||||
<key>INTypePropertyName</key>
|
|
||||||
<string>alternativeSpeakableMatches</string>
|
|
||||||
<key>INTypePropertySupportsMultipleValues</key>
|
|
||||||
<true/>
|
|
||||||
<key>INTypePropertyTag</key>
|
|
||||||
<integer>4</integer>
|
|
||||||
<key>INTypePropertyType</key>
|
|
||||||
<string>SpeakableString</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>INTypePropertyDisplayName</key>
|
|
||||||
<string>username</string>
|
|
||||||
<key>INTypePropertyDisplayNameID</key>
|
|
||||||
<string>TQNJZz</string>
|
|
||||||
<key>INTypePropertyDisplayPriority</key>
|
|
||||||
<integer>5</integer>
|
|
||||||
<key>INTypePropertyName</key>
|
|
||||||
<string>property</string>
|
|
||||||
<key>INTypePropertySupportsMultipleValues</key>
|
|
||||||
<true/>
|
|
||||||
<key>INTypePropertyTag</key>
|
|
||||||
<integer>101</integer>
|
|
||||||
<key>INTypePropertyType</key>
|
|
||||||
<string>String</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -7,5 +7,6 @@ import SwiftUI
|
||||||
struct WidgetExtensionBundle: WidgetBundle {
|
struct WidgetExtensionBundle: WidgetBundle {
|
||||||
var body: some Widget {
|
var body: some Widget {
|
||||||
FollowersCountWidget()
|
FollowersCountWidget()
|
||||||
|
MultiFollowersCountWidget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue