This commit is contained in:
Thomas Ricouard 2024-05-06 08:38:37 +02:00
parent 189e10f2b4
commit a37316c56f
12 changed files with 103 additions and 102 deletions

View file

@ -1,13 +1,12 @@
import AppIntents
import AppAccount import AppAccount
import AppIntents
import Env import Env
import Foundation import Foundation
import Models import Models
import Network import Network
extension IntentDescription: @unchecked Sendable {}
extension IntentDescription: @unchecked Sendable { } extension TypeDisplayRepresentation: @unchecked Sendable {}
extension TypeDisplayRepresentation: @unchecked Sendable { }
public struct AppAccountEntity: Identifiable, AppEntity { public struct AppAccountEntity: Identifiable, AppEntity {
public var id: String { account.id } public var id: String { account.id }
@ -24,7 +23,7 @@ public struct AppAccountEntity: Identifiable, AppEntity {
} }
public struct DefaultAppAccountEntityQuery: EntityQuery { public struct DefaultAppAccountEntityQuery: EntityQuery {
public init() { } public init() {}
public func entities(for identifiers: [AppAccountEntity.ID]) async throws -> [AppAccountEntity] { public func entities(for identifiers: [AppAccountEntity.ID]) async throws -> [AppAccountEntity] {
return await AppAccountsManager.shared.availableAccounts.filter { account in return await AppAccountsManager.shared.availableAccounts.filter { account in

View file

@ -1,5 +1,5 @@
import AppIntents
import AppAccount import AppAccount
import AppIntents
import Env import Env
import Foundation import Foundation
import Models import Models
@ -21,14 +21,14 @@ public struct TimelineFilterEntity: Identifiable, AppEntity {
} }
public struct DefaultTimelineEntityQuery: EntityQuery { public struct DefaultTimelineEntityQuery: EntityQuery {
public init() { } public init() {}
public func entities(for identifiers: [TimelineFilter.ID]) async throws -> [TimelineFilterEntity] { public func entities(for _: [TimelineFilter.ID]) async throws -> [TimelineFilterEntity] {
[.home, .trending, .federated, .local].map{ .init(timeline: $0) } [.home, .trending, .federated, .local].map { .init(timeline: $0) }
} }
public func suggestedEntities() async throws -> [TimelineFilterEntity] { public func suggestedEntities() async throws -> [TimelineFilterEntity] {
[.home, .trending, .federated, .local].map{ .init(timeline: $0) } [.home, .trending, .federated, .local].map { .init(timeline: $0) }
} }
public func defaultResult() async -> TimelineFilterEntity? { public func defaultResult() async -> TimelineFilterEntity? {

View file

@ -1,12 +1,12 @@
import WidgetKit
import SwiftUI
import Network
import DesignSystem import DesignSystem
import Models import Models
import Network
import SwiftUI
import Timeline import Timeline
import WidgetKit
struct HashtagPostsWidgetProvider: AppIntentTimelineProvider { struct HashtagPostsWidgetProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> PostsWidgetEntry { func placeholder(in _: Context) -> PostsWidgetEntry {
.init(date: Date(), .init(date: Date(),
title: "#Mastodon", title: "#Mastodon",
statuses: [.placeholder()], statuses: [.placeholder()],
@ -33,17 +33,17 @@ struct HashtagPostsWidgetProvider: AppIntentTimelineProvider {
let statuses = await loadStatuses(for: timeline, let statuses = await loadStatuses(for: timeline,
account: configuration.account, account: configuration.account,
widgetFamily: context.family) widgetFamily: context.family)
let images = try await loadImages(urls: statuses.map{ $0.account.avatar } ) let images = try await loadImages(urls: statuses.map { $0.account.avatar })
return Timeline(entries: [.init(date: Date(), return Timeline(entries: [.init(date: Date(),
title: timeline.title, title: timeline.title,
statuses: statuses, statuses: statuses,
images: images)], policy: .atEnd) images: images)], policy: .atEnd)
} catch { } catch {
return Timeline(entries: [.init(date: Date(), return Timeline(entries: [.init(date: Date(),
title: "#Mastodon", title: "#Mastodon",
statuses: [], statuses: [],
images: [:])], images: [:])],
policy: .atEnd) policy: .atEnd)
} }
} }
} }
@ -54,7 +54,8 @@ struct HashtagPostsWidget: Widget {
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, AppIntentConfiguration(kind: kind,
intent: HashtagPostsWidgetConfiguration.self, intent: HashtagPostsWidgetConfiguration.self,
provider: HashtagPostsWidgetProvider()) { entry in provider: HashtagPostsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry) PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget) .containerBackground(Color("WidgetBackground").gradient, for: .widget)
} }
@ -64,12 +65,11 @@ struct HashtagPostsWidget: Widget {
} }
} }
#Preview(as: .systemMedium) { #Preview(as: .systemMedium) {
HashtagPostsWidget() HashtagPostsWidget()
} timeline: { } timeline: {
PostsWidgetEntry(date: .now, PostsWidgetEntry(date: .now,
title: "#Mastodon", title: "#Mastodon",
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()], statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:]) images: [:])
} }

View file

@ -1,5 +1,5 @@
import WidgetKit
import AppIntents import AppIntents
import WidgetKit
struct HashtagPostsWidgetConfiguration: WidgetConfigurationIntent { struct HashtagPostsWidgetConfiguration: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configuration" static let title: LocalizedStringResource = "Configuration"

View file

@ -1,5 +1,5 @@
import WidgetKit
import SwiftUI import SwiftUI
import WidgetKit
@main @main
struct IceCubesAppWidgetsExtensionBundle: WidgetBundle { struct IceCubesAppWidgetsExtensionBundle: WidgetBundle {

View file

@ -1,12 +1,12 @@
import WidgetKit
import SwiftUI
import Network
import DesignSystem import DesignSystem
import Models import Models
import Network
import SwiftUI
import Timeline import Timeline
import WidgetKit
struct LatestPostsWidgetProvider: AppIntentTimelineProvider { struct LatestPostsWidgetProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> PostsWidgetEntry { func placeholder(in _: Context) -> PostsWidgetEntry {
.init(date: Date(), .init(date: Date(),
title: "Home", title: "Home",
statuses: [.placeholder()], statuses: [.placeholder()],
@ -32,17 +32,17 @@ struct LatestPostsWidgetProvider: AppIntentTimelineProvider {
let statuses = await loadStatuses(for: configuration.timeline.timeline, let statuses = await loadStatuses(for: configuration.timeline.timeline,
account: configuration.account, account: configuration.account,
widgetFamily: context.family) widgetFamily: context.family)
let images = try await loadImages(urls: statuses.map{ $0.account.avatar } ) let images = try await loadImages(urls: statuses.map { $0.account.avatar })
return Timeline(entries: [.init(date: Date(), return Timeline(entries: [.init(date: Date(),
title: configuration.timeline.timeline.title, title: configuration.timeline.timeline.title,
statuses: statuses, statuses: statuses,
images: images)], policy: .atEnd) images: images)], policy: .atEnd)
} catch { } catch {
return Timeline(entries: [.init(date: Date(), return Timeline(entries: [.init(date: Date(),
title: configuration.timeline.timeline.title, title: configuration.timeline.timeline.title,
statuses: [], statuses: [],
images: [:])], images: [:])],
policy: .atEnd) policy: .atEnd)
} }
} }
@ -72,7 +72,8 @@ struct LatestPostsWidget: Widget {
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, AppIntentConfiguration(kind: kind,
intent: LatestPostsWidgetConfiguration.self, intent: LatestPostsWidgetConfiguration.self,
provider: LatestPostsWidgetProvider()) { entry in provider: LatestPostsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry) PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget) .containerBackground(Color("WidgetBackground").gradient, for: .widget)
} }
@ -82,7 +83,6 @@ struct LatestPostsWidget: Widget {
} }
} }
#Preview(as: .systemMedium) { #Preview(as: .systemMedium) {
LatestPostsWidget() LatestPostsWidget()
} timeline: { } timeline: {

View file

@ -1,5 +1,5 @@
import WidgetKit
import AppIntents import AppIntents
import WidgetKit
struct LatestPostsWidgetConfiguration: WidgetConfigurationIntent { struct LatestPostsWidgetConfiguration: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configuration" static let title: LocalizedStringResource = "Configuration"

View file

@ -1,12 +1,12 @@
import WidgetKit
import SwiftUI
import Network
import DesignSystem import DesignSystem
import Models import Models
import Network
import SwiftUI
import Timeline import Timeline
import WidgetKit
struct MentionsWidgetProvider: AppIntentTimelineProvider { struct MentionsWidgetProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> PostsWidgetEntry { func placeholder(in _: Context) -> PostsWidgetEntry {
.init(date: Date(), .init(date: Date(),
title: "Mentions", title: "Mentions",
statuses: [.placeholder()], statuses: [.placeholder()],
@ -27,29 +27,29 @@ struct MentionsWidgetProvider: AppIntentTimelineProvider {
await timeline(for: configuration, context: context) await timeline(for: configuration, context: context)
} }
private func timeline(for configuration: MentionsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> { private func timeline(for configuration: MentionsWidgetConfiguration, context _: Context) async -> Timeline<PostsWidgetEntry> {
do { do {
let client = Client(server: configuration.account.account.server, let client = Client(server: configuration.account.account.server,
oauthToken: configuration.account.account.oauthToken) oauthToken: configuration.account.account.oauthToken)
var excludedTypes = Models.Notification.NotificationType.allCases var excludedTypes = Models.Notification.NotificationType.allCases
excludedTypes.removeAll(where: { $0 == .mention }) excludedTypes.removeAll(where: { $0 == .mention })
var notifications: [Models.Notification] = var notifications: [Models.Notification] =
try await client.get(endpoint: Notifications.notifications(minId: nil, try await client.get(endpoint: Notifications.notifications(minId: nil,
maxId: nil, maxId: nil,
types: excludedTypes.map(\.rawValue), types: excludedTypes.map(\.rawValue),
limit: 5)) limit: 5))
let statuses = notifications.compactMap{ $0.status } let statuses = notifications.compactMap { $0.status }
let images = try await loadImages(urls: statuses.map{ $0.account.avatar } ) let images = try await loadImages(urls: statuses.map { $0.account.avatar })
return Timeline(entries: [.init(date: Date(), return Timeline(entries: [.init(date: Date(),
title: "Mentions", title: "Mentions",
statuses: statuses, statuses: statuses,
images: images)], policy: .atEnd) images: images)], policy: .atEnd)
} catch { } catch {
return Timeline(entries: [.init(date: Date(), return Timeline(entries: [.init(date: Date(),
title: "Mentions", title: "Mentions",
statuses: [], statuses: [],
images: [:])], images: [:])],
policy: .atEnd) policy: .atEnd)
} }
} }
} }
@ -60,7 +60,8 @@ struct MentionsWidget: Widget {
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, AppIntentConfiguration(kind: kind,
intent: MentionsWidgetConfiguration.self, intent: MentionsWidgetConfiguration.self,
provider: MentionsWidgetProvider()) { entry in provider: MentionsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry) PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget) .containerBackground(Color("WidgetBackground").gradient, for: .widget)
} }
@ -70,7 +71,6 @@ struct MentionsWidget: Widget {
} }
} }
#Preview(as: .systemMedium) { #Preview(as: .systemMedium) {
MentionsWidget() MentionsWidget()
} timeline: { } timeline: {

View file

@ -1,5 +1,5 @@
import WidgetKit
import AppIntents import AppIntents
import WidgetKit
struct MentionsWidgetConfiguration: WidgetConfigurationIntent { struct MentionsWidgetConfiguration: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configuration" static let title: LocalizedStringResource = "Configuration"

View file

@ -1,9 +1,9 @@
import WidgetKit
import SwiftUI
import Network
import DesignSystem import DesignSystem
import Models import Models
import Network
import SwiftUI
import Timeline import Timeline
import WidgetKit
struct PostsWidgetEntry: TimelineEntry { struct PostsWidgetEntry: TimelineEntry {
let date: Date let date: Date
@ -12,7 +12,7 @@ struct PostsWidgetEntry: TimelineEntry {
let images: [URL: UIImage] let images: [URL: UIImage]
} }
struct PostsWidgetView : View { struct PostsWidgetView: View {
var entry: LatestPostsWidgetProvider.Entry var entry: LatestPostsWidgetProvider.Entry
@Environment(\.widgetFamily) var family @Environment(\.widgetFamily) var family
@ -26,6 +26,7 @@ struct PostsWidgetView : View {
return 2 return 2
} }
} }
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
headerView headerView

View file

@ -1,30 +1,31 @@
import StatusKit
import WidgetKit
import Timeline
import Foundation
import UIKit
import AppAccount import AppAccount
import Foundation
import Models import Models
import Network import Network
import StatusKit
import Timeline
import UIKit
import WidgetKit
func loadStatuses(for timeline: TimelineFilter, func loadStatuses(for timeline: TimelineFilter,
account: AppAccountEntity, account: AppAccountEntity,
widgetFamily: WidgetFamily) async -> [Status] { widgetFamily: WidgetFamily) async -> [Status]
{
let client = Client(server: account.account.server, oauthToken: account.account.oauthToken) let client = Client(server: account.account.server, oauthToken: account.account.oauthToken)
do { do {
var statuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil, var statuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
maxId: nil, maxId: nil,
minId: nil, minId: nil,
offset: nil)) offset: nil))
statuses = statuses.filter{ $0.reblog == nil && !$0.content.asRawText.isEmpty } statuses = statuses.filter { $0.reblog == nil && !$0.content.asRawText.isEmpty }
switch widgetFamily { switch widgetFamily {
case .systemSmall, .systemMedium: case .systemSmall, .systemMedium:
if statuses.count >= 1 { if statuses.count >= 1 {
statuses = statuses.prefix(upTo: 1).map{ $0 } statuses = statuses.prefix(upTo: 1).map { $0 }
} }
case .systemLarge, .systemExtraLarge: case .systemLarge, .systemExtraLarge:
if statuses.count >= 5 { if statuses.count >= 5 {
statuses = statuses.prefix(upTo: 5).map{ $0 } statuses = statuses.prefix(upTo: 5).map { $0 }
} }
default: default:
break break