Skip to content

Commit f40aeb9

Browse files
committed
Add followers count widget
1 parent 1578896 commit f40aeb9

5 files changed

Lines changed: 137 additions & 0 deletions

File tree

IceCubesApp.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
9F7788F02BE78E77004E6BEF /* Timeline in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788EF2BE78E77004E6BEF /* Timeline */; };
9090
9F7D93942980063100EE6B7A /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7D93932980063100EE6B7A /* AppAccount */; };
9191
9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */; };
92+
9F8B92122BF77DBE003D37A2 /* AccountWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8B920E2BF77DB4003D37A2 /* AccountWidgetConfiguration.swift */; };
93+
9F8B92132BF77DBE003D37A2 /* AccountWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8B92102BF77DBB003D37A2 /* AccountWidget.swift */; };
94+
9F8B92162BF77F0B003D37A2 /* AccountWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8B92142BF77F05003D37A2 /* AccountWidgetView.swift */; };
9295
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */; };
9396
9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; };
9497
9FAD858E29743F7400496AB1 /* (null) in Resources */ = {isa = PBXBuildFile; };
@@ -275,6 +278,9 @@
275278
9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFilterEntity.swift; sourceTree = "<group>"; };
276279
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = "<group>"; };
277280
9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = "<group>"; };
281+
9F8B920E2BF77DB4003D37A2 /* AccountWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWidgetConfiguration.swift; sourceTree = "<group>"; };
282+
9F8B92102BF77DBB003D37A2 /* AccountWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWidget.swift; sourceTree = "<group>"; };
283+
9F8B92142BF77F05003D37A2 /* AccountWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWidgetView.swift; sourceTree = "<group>"; };
278284
9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
279285
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
280286
9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
@@ -497,6 +503,7 @@
497503
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */ = {
498504
isa = PBXGroup;
499505
children = (
506+
9F8B920D2BF77DA8003D37A2 /* AccountWidget */,
500507
9F5BE6202BF48FB20074387E /* ListsWidget */,
501508
9FF2FB6B2BE8AE78001560CE /* MentionWidget */,
502509
9FF2FB642BE7F7FA001560CE /* Shared */,
@@ -510,6 +517,16 @@
510517
path = IceCubesAppWidgetsExtension;
511518
sourceTree = "<group>";
512519
};
520+
9F8B920D2BF77DA8003D37A2 /* AccountWidget */ = {
521+
isa = PBXGroup;
522+
children = (
523+
9F8B920E2BF77DB4003D37A2 /* AccountWidgetConfiguration.swift */,
524+
9F8B92102BF77DBB003D37A2 /* AccountWidget.swift */,
525+
9F8B92142BF77F05003D37A2 /* AccountWidgetView.swift */,
526+
);
527+
path = AccountWidget;
528+
sourceTree = "<group>";
529+
};
513530
9FA0D2AC29921C1F008A143B /* Embeds */ = {
514531
isa = PBXGroup;
515532
children = (
@@ -1011,6 +1028,9 @@
10111028
9F5BE6272BF492CF0074387E /* ListEntity.swift in Sources */,
10121029
9FF2FB622BE7F5D5001560CE /* HashtagPostsWidget.swift in Sources */,
10131030
9FF2FB712BE8AEA0001560CE /* MentionWidget.swift in Sources */,
1031+
9F8B92162BF77F0B003D37A2 /* AccountWidgetView.swift in Sources */,
1032+
9F8B92122BF77DBE003D37A2 /* AccountWidgetConfiguration.swift in Sources */,
1033+
9F8B92132BF77DBE003D37A2 /* AccountWidget.swift in Sources */,
10141034
9F7788EA2BE65585004E6BEF /* AppAccountEntity.swift in Sources */,
10151035
9FF2FB6A2BE7F84E001560CE /* SharedUtils.swift in Sources */,
10161036
9F7788CE2BE652B1004E6BEF /* LatestPostsWidget.swift in Sources */,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import DesignSystem
2+
import Models
3+
import Network
4+
import SwiftUI
5+
import Timeline
6+
import WidgetKit
7+
8+
struct AccountWidgetProvider: AppIntentTimelineProvider {
9+
func placeholder(in _: Context) -> AccountWidgetEntry {
10+
.init(date: Date(), account: .placeholder(), avatar: nil)
11+
}
12+
13+
func snapshot(for configuration: AccountWidgetConfiguration, in context: Context) async -> AccountWidgetEntry {
14+
let account = await fetchAccount(configuration: configuration)
15+
return .init(date: Date(), account: account, avatar: nil)
16+
}
17+
18+
func timeline(for configuration: AccountWidgetConfiguration, in context: Context) async -> Timeline<AccountWidgetEntry> {
19+
let account = await fetchAccount(configuration: configuration)
20+
let images = try? await loadImages(urls: [account.avatar])
21+
return .init(entries: [.init(date: Date(), account: account, avatar: images?.first?.value)],
22+
policy: .atEnd)
23+
}
24+
25+
private func fetchAccount(configuration: AccountWidgetConfiguration) async -> Account {
26+
let client = Client(server: configuration.account.account.server,
27+
oauthToken: configuration.account.account.oauthToken)
28+
do {
29+
let account: Account = try await client.get(endpoint: Accounts.verifyCredentials)
30+
return account
31+
} catch {
32+
return .placeholder()
33+
}
34+
}
35+
}
36+
37+
struct AccountWidgetEntry: TimelineEntry {
38+
let date: Date
39+
let account: Account
40+
let avatar: UIImage?
41+
}
42+
43+
struct AccountWidget: Widget {
44+
let kind: String = "AccountWidget"
45+
46+
var body: some WidgetConfiguration {
47+
AppIntentConfiguration(kind: kind,
48+
intent: AccountWidgetConfiguration.self,
49+
provider: AccountWidgetProvider())
50+
{ entry in
51+
AccountWidgetView(entry: entry)
52+
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
53+
}
54+
.configurationDisplayName("Account")
55+
.description("Show information about your Mastodon account")
56+
.supportedFamilies([.systemSmall])
57+
}
58+
}
59+
60+
#Preview(as: .systemSmall) {
61+
AccountWidget()
62+
} timeline: {
63+
AccountWidgetEntry(date: Date(), account: .placeholder(), avatar: nil)
64+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import AppIntents
2+
import WidgetKit
3+
4+
struct AccountWidgetConfiguration: WidgetConfigurationIntent {
5+
static let title: LocalizedStringResource = "Configuration"
6+
static let description = IntentDescription("Choose the account for this widget")
7+
8+
@Parameter(title: "Account")
9+
var account: AppAccountEntity
10+
}
11+
12+
extension AccountWidgetConfiguration {
13+
static var previewAccount: AccountWidgetConfiguration {
14+
let intent = AccountWidgetConfiguration()
15+
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
16+
return intent
17+
}
18+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import DesignSystem
2+
import Models
3+
import Network
4+
import SwiftUI
5+
import Timeline
6+
import WidgetKit
7+
8+
struct AccountWidgetView: View {
9+
var entry: AccountWidgetProvider.Entry
10+
11+
@Environment(\.widgetFamily) var family
12+
@Environment(\.redactionReasons) var redacted
13+
14+
15+
var body: some View {
16+
VStack(alignment: .center, spacing: 4) {
17+
if let avatar = entry.avatar {
18+
Image(uiImage: avatar)
19+
.resizable()
20+
.frame(width: 64, height: 64)
21+
.clipShape(Circle())
22+
Text("\(entry.account.followersCount ?? 0)")
23+
.font(.title)
24+
.fontDesign(.rounded)
25+
.fontWeight(.bold)
26+
.monospacedDigit()
27+
Text("Followers")
28+
.font(.headline)
29+
.foregroundStyle(.secondary)
30+
}
31+
}
32+
.frame(maxWidth: .infinity)
33+
}
34+
}

IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ struct IceCubesAppWidgetsExtensionBundle: WidgetBundle {
88
HashtagPostsWidget()
99
ListsPostWidget()
1010
MentionsWidget()
11+
AccountWidget()
1112
}
1213
}

0 commit comments

Comments
 (0)