Add lists widget

This commit is contained in:
Thomas Ricouard 2024-05-15 09:27:35 +02:00
parent e3d4e693d2
commit 13d721912b
5 changed files with 172 additions and 0 deletions

View file

@ -57,6 +57,9 @@
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4A48182976B21900A1A038 /* ProfileTab.swift */; };
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; };
9F55C6902955993C00F94077 /* Explore in Frameworks */ = {isa = PBXBuildFile; productRef = 9F55C68F2955993C00F94077 /* Explore */; };
9F5BE6272BF492CF0074387E /* ListEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5BE6252BF48FE10074387E /* ListEntity.swift */; };
9F5BE6282BF492D10074387E /* ListsWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5BE6232BF48FC40074387E /* ListsWidgetConfiguration.swift */; };
9F5BE6292BF492D40074387E /* ListsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5BE6212BF48FBA0074387E /* ListsWidget.swift */; };
9F5E581929545BE700A53960 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5E581829545BE700A53960 /* Env */; };
9F6028562B3F36AE00476078 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6028552B3F36AE00476078 /* AppView.swift */; };
9F6028582B3F3B7600476078 /* ToolbarTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6028572B3F3B7600476078 /* ToolbarTab.swift */; };
@ -245,6 +248,9 @@
9F4A48182976B21900A1A038 /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; };
9F55C68C2955968700F94077 /* ExploreTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreTab.swift; sourceTree = "<group>"; };
9F55C68E295598F900F94077 /* Explore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Explore; path = Packages/Explore; sourceTree = "<group>"; };
9F5BE6212BF48FBA0074387E /* ListsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsWidget.swift; sourceTree = "<group>"; };
9F5BE6232BF48FC40074387E /* ListsWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsWidgetConfiguration.swift; sourceTree = "<group>"; };
9F5BE6252BF48FE10074387E /* ListEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListEntity.swift; sourceTree = "<group>"; };
9F5E581729545B5500A53960 /* Env */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Env; path = Packages/Env; sourceTree = "<group>"; };
9F6028552B3F36AE00476078 /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = "<group>"; };
9F6028572B3F3B7600476078 /* ToolbarTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarTab.swift; sourceTree = "<group>"; };
@ -433,6 +439,7 @@
9F37BDDA2BE36E22007F28AD /* PostIntent.swift */,
9F37BDDE2BE37C35007F28AD /* TabIntent.swift */,
9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */,
9F5BE6252BF48FE10074387E /* ListEntity.swift */,
);
path = IceCubesAppIntents;
sourceTree = "<group>";
@ -461,6 +468,15 @@
path = Resources;
sourceTree = "<group>";
};
9F5BE6202BF48FB20074387E /* ListsWidget */ = {
isa = PBXGroup;
children = (
9F5BE6212BF48FBA0074387E /* ListsWidget.swift */,
9F5BE6232BF48FC40074387E /* ListsWidgetConfiguration.swift */,
);
path = ListsWidget;
sourceTree = "<group>";
};
9F654BF0299AC46200D27FA5 /* Report */ = {
isa = PBXGroup;
children = (
@ -481,6 +497,7 @@
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */ = {
isa = PBXGroup;
children = (
9F5BE6202BF48FB20074387E /* ListsWidget */,
9FF2FB6B2BE8AE78001560CE /* MentionWidget */,
9FF2FB642BE7F7FA001560CE /* Shared */,
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */,
@ -991,17 +1008,20 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9F5BE6272BF492CF0074387E /* ListEntity.swift in Sources */,
9FF2FB622BE7F5D5001560CE /* HashtagPostsWidget.swift in Sources */,
9FF2FB712BE8AEA0001560CE /* MentionWidget.swift in Sources */,
9F7788EA2BE65585004E6BEF /* AppAccountEntity.swift in Sources */,
9FF2FB6A2BE7F84E001560CE /* SharedUtils.swift in Sources */,
9F7788CE2BE652B1004E6BEF /* LatestPostsWidget.swift in Sources */,
9F7788EE2BE78D7B004E6BEF /* TimelineFilterEntity.swift in Sources */,
9F5BE6292BF492D40074387E /* ListsWidget.swift in Sources */,
9F7788CC2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift in Sources */,
9FF2FB702BE8AE9D001560CE /* MentionWidgetConfiguration.swift in Sources */,
9F7788D02BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift in Sources */,
9FF2FB672BE7F816001560CE /* PostsWidgetView.swift in Sources */,
9FF2FB632BE7F5D9001560CE /* HashtagPostsWidgetConfiguration.swift in Sources */,
9F5BE6282BF492D10074387E /* ListsWidgetConfiguration.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -0,0 +1,55 @@
import AppAccount
import AppIntents
import Env
import Foundation
import Models
import Network
import Timeline
public struct ListEntity: Identifiable, AppEntity {
public var id: String { list.id }
public let list: Models.List
public static let defaultQuery = DefaultListEntityQuery()
public static let typeDisplayRepresentation: TypeDisplayRepresentation = "List"
public var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(list.title)")
}
}
public struct DefaultListEntityQuery: EntityQuery {
public init() {}
@IntentParameterDependency<ListsWidgetConfiguration>(
\.$account
)
var account
public func entities(for _: [ListEntity.ID]) async throws -> [ListEntity] {
await fetchLists().map{ .init(list: $0 )}
}
public func suggestedEntities() async throws -> [ListEntity] {
await fetchLists().map{ .init(list: $0 )}
}
public func defaultResult() async -> ListEntity? {
nil
}
private func fetchLists() async -> [Models.List] {
guard let account = account?.account.account else {
return []
}
let client = Client(server: account.server, oauthToken: account.oauthToken)
do {
let lists: [Models.List] = try await client.get(endpoint: Lists.lists)
return lists
} catch {
return []
}
}
}

View file

@ -6,6 +6,7 @@ struct IceCubesAppWidgetsExtensionBundle: WidgetBundle {
var body: some Widget {
LatestPostsWidget()
HashtagPostsWidget()
ListsPostWidget()
MentionsWidget()
}
}

View file

@ -0,0 +1,75 @@
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct ListsWidgetProvider: AppIntentTimelineProvider {
func placeholder(in _: Context) -> PostsWidgetEntry {
.init(date: Date(),
title: "List name",
statuses: [.placeholder()],
images: [:])
}
func snapshot(for configuration: ListsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
if let entry = await timeline(for: configuration, context: context).entries.first {
return entry
}
return .init(date: Date(),
title: "List name",
statuses: [],
images: [:])
}
func timeline(for configuration: ListsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
await timeline(for: configuration, context: context)
}
private func timeline(for configuration: ListsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
do {
let timeline: TimelineFilter = .list(list: configuration.timeline.list)
let statuses = await loadStatuses(for: timeline,
account: configuration.account,
widgetFamily: context.family)
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
return Timeline(entries: [.init(date: Date(),
title: timeline.title,
statuses: statuses,
images: images)], policy: .atEnd)
} catch {
return Timeline(entries: [.init(date: Date(),
title: "List name",
statuses: [],
images: [:])],
policy: .atEnd)
}
}
}
struct ListsPostWidget: Widget {
let kind: String = "ListsPostWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind,
intent: ListsWidgetConfiguration.self,
provider: ListsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
}
.configurationDisplayName("List timeline")
.description("Show the latest post for the selected list")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
}
}
#Preview(as: .systemMedium) {
ListsPostWidget()
} timeline: {
PostsWidgetEntry(date: .now,
title: "List name",
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
}

View file

@ -0,0 +1,21 @@
import AppIntents
import WidgetKit
struct ListsWidgetConfiguration: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configuration"
static let description = IntentDescription("Choose the account and list for this widget")
@Parameter(title: "Account")
var account: AppAccountEntity
@Parameter(title: "List")
var timeline: ListEntity
}
extension ListsWidgetConfiguration {
static var previewAccount: LatestPostsWidgetConfiguration {
let intent = LatestPostsWidgetConfiguration()
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
return intent
}
}