diff --git a/IceCubesAppIntents/AppAccountEntity.swift b/IceCubesAppIntents/AppAccountEntity.swift index 5b87adb6..e6cb0f09 100644 --- a/IceCubesAppIntents/AppAccountEntity.swift +++ b/IceCubesAppIntents/AppAccountEntity.swift @@ -1,13 +1,12 @@ -import AppIntents import AppAccount +import AppIntents import Env import Foundation import Models import Network - -extension IntentDescription: @unchecked Sendable { } -extension TypeDisplayRepresentation: @unchecked Sendable { } +extension IntentDescription: @unchecked Sendable {} +extension TypeDisplayRepresentation: @unchecked Sendable {} public struct AppAccountEntity: Identifiable, AppEntity { public var id: String { account.id } @@ -24,8 +23,8 @@ public struct AppAccountEntity: Identifiable, AppEntity { } public struct DefaultAppAccountEntityQuery: EntityQuery { - public init() { } - + public init() {} + public func entities(for identifiers: [AppAccountEntity.ID]) async throws -> [AppAccountEntity] { return await AppAccountsManager.shared.availableAccounts.filter { account in identifiers.contains { id in diff --git a/IceCubesAppIntents/TimelineFilterEntity.swift b/IceCubesAppIntents/TimelineFilterEntity.swift index 9a1c348a..97512cc8 100644 --- a/IceCubesAppIntents/TimelineFilterEntity.swift +++ b/IceCubesAppIntents/TimelineFilterEntity.swift @@ -1,5 +1,5 @@ -import AppIntents import AppAccount +import AppIntents import Env import Foundation import Models @@ -21,14 +21,14 @@ public struct TimelineFilterEntity: Identifiable, AppEntity { } public struct DefaultTimelineEntityQuery: EntityQuery { - public init() { } - - public func entities(for identifiers: [TimelineFilter.ID]) async throws -> [TimelineFilterEntity] { - [.home, .trending, .federated, .local].map{ .init(timeline: $0) } + public init() {} + + public func entities(for _: [TimelineFilter.ID]) async throws -> [TimelineFilterEntity] { + [.home, .trending, .federated, .local].map { .init(timeline: $0) } } 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? { diff --git a/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidget.swift b/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidget.swift index 08478b6c..82b020d6 100644 --- a/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidget.swift +++ b/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidget.swift @@ -1,18 +1,18 @@ -import WidgetKit -import SwiftUI -import Network import DesignSystem import Models +import Network +import SwiftUI import Timeline +import WidgetKit struct HashtagPostsWidgetProvider: AppIntentTimelineProvider { - func placeholder(in context: Context) -> PostsWidgetEntry { + func placeholder(in _: Context) -> PostsWidgetEntry { .init(date: Date(), title: "#Mastodon", statuses: [.placeholder()], images: [:]) } - + func snapshot(for configuration: HashtagPostsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry { if let entry = await timeline(for: configuration, context: context).entries.first { return entry @@ -22,39 +22,40 @@ struct HashtagPostsWidgetProvider: AppIntentTimelineProvider { statuses: [], images: [:]) } - + func timeline(for configuration: HashtagPostsWidgetConfiguration, in context: Context) async -> Timeline { await timeline(for: configuration, context: context) } - + private func timeline(for configuration: HashtagPostsWidgetConfiguration, context: Context) async -> Timeline { do { let timeline: TimelineFilter = .hashtag(tag: configuration.hashgtag, accountId: nil) let statuses = await loadStatuses(for: timeline, account: configuration.account, 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(), - title: timeline.title, - statuses: statuses, - images: images)], policy: .atEnd) + title: timeline.title, + statuses: statuses, + images: images)], policy: .atEnd) } catch { return Timeline(entries: [.init(date: Date(), title: "#Mastodon", statuses: [], images: [:])], - policy: .atEnd) + policy: .atEnd) } } } struct HashtagPostsWidget: Widget { let kind: String = "HashtagPostsWidget" - + var body: some WidgetConfiguration { AppIntentConfiguration(kind: kind, intent: HashtagPostsWidgetConfiguration.self, - provider: HashtagPostsWidgetProvider()) { entry in + provider: HashtagPostsWidgetProvider()) + { entry in PostsWidgetView(entry: entry) .containerBackground(Color("WidgetBackground").gradient, for: .widget) } @@ -64,12 +65,11 @@ struct HashtagPostsWidget: Widget { } } - #Preview(as: .systemMedium) { HashtagPostsWidget() } timeline: { PostsWidgetEntry(date: .now, - title: "#Mastodon", - statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()], - images: [:]) + title: "#Mastodon", + statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()], + images: [:]) } diff --git a/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidgetConfiguration.swift b/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidgetConfiguration.swift index d638eb39..8aa70af9 100644 --- a/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidgetConfiguration.swift +++ b/IceCubesAppWidgetsExtension/HashtagPostsWidget/HashtagPostsWidgetConfiguration.swift @@ -1,13 +1,13 @@ -import WidgetKit import AppIntents +import WidgetKit struct HashtagPostsWidgetConfiguration: WidgetConfigurationIntent { static let title: LocalizedStringResource = "Configuration" static let description = IntentDescription("Choose the account and hashtag for this widget") - + @Parameter(title: "Account") var account: AppAccountEntity - + @Parameter(title: "Hashtag") var hashgtag: String } diff --git a/IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift b/IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift index ba5f7cea..f4684eb0 100644 --- a/IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift +++ b/IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift @@ -1,5 +1,5 @@ -import WidgetKit import SwiftUI +import WidgetKit @main struct IceCubesAppWidgetsExtensionBundle: WidgetBundle { diff --git a/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidget.swift b/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidget.swift index a6484b1c..a79111b5 100644 --- a/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidget.swift +++ b/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidget.swift @@ -1,18 +1,18 @@ -import WidgetKit -import SwiftUI -import Network import DesignSystem import Models +import Network +import SwiftUI import Timeline +import WidgetKit struct LatestPostsWidgetProvider: AppIntentTimelineProvider { - func placeholder(in context: Context) -> PostsWidgetEntry { + func placeholder(in _: Context) -> PostsWidgetEntry { .init(date: Date(), title: "Home", statuses: [.placeholder()], images: [:]) } - + func snapshot(for configuration: LatestPostsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry { if let entry = await timeline(for: configuration, context: context).entries.first { return entry @@ -22,30 +22,30 @@ struct LatestPostsWidgetProvider: AppIntentTimelineProvider { statuses: [], images: [:]) } - + func timeline(for configuration: LatestPostsWidgetConfiguration, in context: Context) async -> Timeline { await timeline(for: configuration, context: context) } - + private func timeline(for configuration: LatestPostsWidgetConfiguration, context: Context) async -> Timeline { do { let statuses = await loadStatuses(for: configuration.timeline.timeline, account: configuration.account, 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(), title: configuration.timeline.timeline.title, - statuses: statuses, - images: images)], policy: .atEnd) + statuses: statuses, + images: images)], policy: .atEnd) } catch { return Timeline(entries: [.init(date: Date(), title: configuration.timeline.timeline.title, statuses: [], images: [:])], - policy: .atEnd) + policy: .atEnd) } } - + private func loadImages(urls: [URL]) async throws -> [URL: UIImage] { try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in for url in urls { @@ -54,13 +54,13 @@ struct LatestPostsWidgetProvider: AppIntentTimelineProvider { return (url, UIImage(data: response.0)) } } - + var images: [URL: UIImage] = [:] - + for try await (url, image) in group { images[url] = image } - + return images } } @@ -68,11 +68,12 @@ struct LatestPostsWidgetProvider: AppIntentTimelineProvider { struct LatestPostsWidget: Widget { let kind: String = "LatestPostsWidget" - + var body: some WidgetConfiguration { AppIntentConfiguration(kind: kind, intent: LatestPostsWidgetConfiguration.self, - provider: LatestPostsWidgetProvider()) { entry in + provider: LatestPostsWidgetProvider()) + { entry in PostsWidgetView(entry: entry) .containerBackground(Color("WidgetBackground").gradient, for: .widget) } @@ -82,11 +83,10 @@ struct LatestPostsWidget: Widget { } } - #Preview(as: .systemMedium) { LatestPostsWidget() } timeline: { - PostsWidgetEntry(date: .now, + PostsWidgetEntry(date: .now, title: "Mastodon", statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()], images: [:]) diff --git a/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidgetConfiguration.swift b/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidgetConfiguration.swift index b92cbb81..11453c01 100644 --- a/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidgetConfiguration.swift +++ b/IceCubesAppWidgetsExtension/LatestPostsWidget/LatestPostsWidgetConfiguration.swift @@ -1,13 +1,13 @@ -import WidgetKit import AppIntents +import WidgetKit struct LatestPostsWidgetConfiguration: WidgetConfigurationIntent { static let title: LocalizedStringResource = "Configuration" static let description = IntentDescription("Choose the account and timeline for this widget") - + @Parameter(title: "Account") var account: AppAccountEntity - + @Parameter(title: "Timeline") var timeline: TimelineFilterEntity } diff --git a/IceCubesAppWidgetsExtension/MentionWidget/MentionWidget.swift b/IceCubesAppWidgetsExtension/MentionWidget/MentionWidget.swift index 046dc74c..421044c0 100644 --- a/IceCubesAppWidgetsExtension/MentionWidget/MentionWidget.swift +++ b/IceCubesAppWidgetsExtension/MentionWidget/MentionWidget.swift @@ -1,18 +1,18 @@ -import WidgetKit -import SwiftUI -import Network import DesignSystem import Models +import Network +import SwiftUI import Timeline +import WidgetKit struct MentionsWidgetProvider: AppIntentTimelineProvider { - func placeholder(in context: Context) -> PostsWidgetEntry { + func placeholder(in _: Context) -> PostsWidgetEntry { .init(date: Date(), title: "Mentions", statuses: [.placeholder()], images: [:]) } - + func snapshot(for configuration: MentionsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry { if let entry = await timeline(for: configuration, context: context).entries.first { return entry @@ -22,45 +22,46 @@ struct MentionsWidgetProvider: AppIntentTimelineProvider { statuses: [], images: [:]) } - + func timeline(for configuration: MentionsWidgetConfiguration, in context: Context) async -> Timeline { await timeline(for: configuration, context: context) } - - private func timeline(for configuration: MentionsWidgetConfiguration, context: Context) async -> Timeline { + + private func timeline(for configuration: MentionsWidgetConfiguration, context _: Context) async -> Timeline { do { let client = Client(server: configuration.account.account.server, oauthToken: configuration.account.account.oauthToken) var excludedTypes = Models.Notification.NotificationType.allCases excludedTypes.removeAll(where: { $0 == .mention }) var notifications: [Models.Notification] = - try await client.get(endpoint: Notifications.notifications(minId: nil, - maxId: nil, - types: excludedTypes.map(\.rawValue), - limit: 5)) - let statuses = notifications.compactMap{ $0.status } - let images = try await loadImages(urls: statuses.map{ $0.account.avatar } ) + try await client.get(endpoint: Notifications.notifications(minId: nil, + maxId: nil, + types: excludedTypes.map(\.rawValue), + limit: 5)) + let statuses = notifications.compactMap { $0.status } + let images = try await loadImages(urls: statuses.map { $0.account.avatar }) return Timeline(entries: [.init(date: Date(), - title: "Mentions", - statuses: statuses, - images: images)], policy: .atEnd) + title: "Mentions", + statuses: statuses, + images: images)], policy: .atEnd) } catch { return Timeline(entries: [.init(date: Date(), title: "Mentions", statuses: [], images: [:])], - policy: .atEnd) + policy: .atEnd) } } } struct MentionsWidget: Widget { let kind: String = "MentionsWidget" - + var body: some WidgetConfiguration { AppIntentConfiguration(kind: kind, intent: MentionsWidgetConfiguration.self, - provider: MentionsWidgetProvider()) { entry in + provider: MentionsWidgetProvider()) + { entry in PostsWidgetView(entry: entry) .containerBackground(Color("WidgetBackground").gradient, for: .widget) } @@ -70,7 +71,6 @@ struct MentionsWidget: Widget { } } - #Preview(as: .systemMedium) { MentionsWidget() } timeline: { diff --git a/IceCubesAppWidgetsExtension/MentionWidget/MentionWidgetConfiguration.swift b/IceCubesAppWidgetsExtension/MentionWidget/MentionWidgetConfiguration.swift index 7ad55a49..0f0fc81b 100644 --- a/IceCubesAppWidgetsExtension/MentionWidget/MentionWidgetConfiguration.swift +++ b/IceCubesAppWidgetsExtension/MentionWidget/MentionWidgetConfiguration.swift @@ -1,10 +1,10 @@ -import WidgetKit import AppIntents +import WidgetKit struct MentionsWidgetConfiguration: WidgetConfigurationIntent { static let title: LocalizedStringResource = "Configuration" static let description = IntentDescription("Choose the account for this widget") - + @Parameter(title: "Account") var account: AppAccountEntity } diff --git a/IceCubesAppWidgetsExtension/Shared/PostsWidgetView.swift b/IceCubesAppWidgetsExtension/Shared/PostsWidgetView.swift index 7b6c6821..eeeeccf0 100644 --- a/IceCubesAppWidgetsExtension/Shared/PostsWidgetView.swift +++ b/IceCubesAppWidgetsExtension/Shared/PostsWidgetView.swift @@ -1,9 +1,9 @@ -import WidgetKit -import SwiftUI -import Network import DesignSystem import Models +import Network +import SwiftUI import Timeline +import WidgetKit struct PostsWidgetEntry: TimelineEntry { let date: Date @@ -12,12 +12,12 @@ struct PostsWidgetEntry: TimelineEntry { let images: [URL: UIImage] } -struct PostsWidgetView : View { +struct PostsWidgetView: View { var entry: LatestPostsWidgetProvider.Entry - + @Environment(\.widgetFamily) var family @Environment(\.redactionReasons) var redacted - + var contentLineLimit: Int { switch family { case .systemSmall, .systemMedium: @@ -26,6 +26,7 @@ struct PostsWidgetView : View { return 2 } } + var body: some View { VStack(alignment: .leading, spacing: 8) { headerView @@ -36,7 +37,7 @@ struct PostsWidgetView : View { } .frame(maxWidth: .infinity) } - + private var headerView: some View { HStack { Text(entry.title) @@ -47,7 +48,7 @@ struct PostsWidgetView : View { .fontWeight(.bold) .foregroundStyle(Color("AccentColor")) } - + @ViewBuilder private func makeStatusView(_ status: Status) -> some View { if let url = URL(string: status.url ?? "") { @@ -63,7 +64,7 @@ struct PostsWidgetView : View { }) } } - + private func makeStatusHeaderView(_ status: Status) -> some View { HStack(alignment: .center, spacing: 4) { if let image = entry.images[status.account.avatar] { diff --git a/IceCubesAppWidgetsExtension/Shared/SharedUtils.swift b/IceCubesAppWidgetsExtension/Shared/SharedUtils.swift index db1aca41..b8c76f44 100644 --- a/IceCubesAppWidgetsExtension/Shared/SharedUtils.swift +++ b/IceCubesAppWidgetsExtension/Shared/SharedUtils.swift @@ -1,30 +1,31 @@ -import StatusKit -import WidgetKit -import Timeline -import Foundation -import UIKit import AppAccount +import Foundation import Models import Network +import StatusKit +import Timeline +import UIKit +import WidgetKit func loadStatuses(for timeline: TimelineFilter, account: AppAccountEntity, - widgetFamily: WidgetFamily) async -> [Status] { + widgetFamily: WidgetFamily) async -> [Status] +{ let client = Client(server: account.account.server, oauthToken: account.account.oauthToken) do { var statuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil, maxId: nil, minId: 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 { case .systemSmall, .systemMedium: if statuses.count >= 1 { - statuses = statuses.prefix(upTo: 1).map{ $0 } + statuses = statuses.prefix(upTo: 1).map { $0 } } case .systemLarge, .systemExtraLarge: if statuses.count >= 5 { - statuses = statuses.prefix(upTo: 5).map{ $0 } + statuses = statuses.prefix(upTo: 5).map { $0 } } default: break @@ -34,7 +35,7 @@ func loadStatuses(for timeline: TimelineFilter, return [] } } - + func loadImages(urls: [URL]) async throws -> [URL: UIImage] { try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in for url in urls { @@ -43,13 +44,13 @@ func loadImages(urls: [URL]) async throws -> [URL: UIImage] { return (url, UIImage(data: response.0)) } } - + var images: [URL: UIImage] = [:] - + for try await (url, image) in group { images[url] = image } - + return images } } diff --git a/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift b/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift index 3eb630ec..9d443b10 100644 --- a/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift +++ b/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift @@ -765,7 +765,7 @@ public extension StatusEditor { error: nil )) } - + url.stopAccessingSecurityScopedResource() }