mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-05-20 01:08:18 +00:00
Compare commits
6 commits
426c767ed1
...
7d7379357f
Author | SHA1 | Date | |
---|---|---|---|
7d7379357f | |||
51fecb01f5 | |||
c29de44d8c | |||
1d79832544 | |||
a37316c56f | |||
189e10f2b4 |
|
@ -124,6 +124,8 @@
|
|||
9FF2FB632BE7F5D9001560CE /* HashtagPostsWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB5E2BE7F56F001560CE /* HashtagPostsWidgetConfiguration.swift */; };
|
||||
9FF2FB672BE7F816001560CE /* PostsWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */; };
|
||||
9FF2FB6A2BE7F84E001560CE /* SharedUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB682BE7F842001560CE /* SharedUtils.swift */; };
|
||||
9FF2FB702BE8AE9D001560CE /* MentionWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */; };
|
||||
9FF2FB712BE8AEA0001560CE /* MentionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */; };
|
||||
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; };
|
||||
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677F299B7D2B00FE700A /* DesignSystem */; };
|
||||
9FFF6782299B7D3A00FE700A /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF6781299B7D3A00FE700A /* Account */; };
|
||||
|
@ -298,6 +300,8 @@
|
|||
9FF2FB602BE7F5A7001560CE /* HashtagPostsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagPostsWidget.swift; sourceTree = "<group>"; };
|
||||
9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsWidgetView.swift; sourceTree = "<group>"; };
|
||||
9FF2FB682BE7F842001560CE /* SharedUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedUtils.swift; sourceTree = "<group>"; };
|
||||
9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionWidget.swift; sourceTree = "<group>"; };
|
||||
9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionWidgetConfiguration.swift; sourceTree = "<group>"; };
|
||||
B0BAB49E29B3D7A9008F54D7 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
C4CBB90B298A0DA3007E1707 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
C4FBCF6F298FD88A0015DF22 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
|
@ -479,9 +483,10 @@
|
|||
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9FF2FB6B2BE8AE78001560CE /* MentionWidget */,
|
||||
9FF2FB642BE7F7FA001560CE /* Shared */,
|
||||
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */,
|
||||
9FF2FB5C2BE7F549001560CE /* LatestPosts */,
|
||||
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */,
|
||||
9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */,
|
||||
9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */,
|
||||
9F7788D12BE652B2004E6BEF /* Assets.xcassets */,
|
||||
|
@ -641,13 +646,13 @@
|
|||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9FF2FB5C2BE7F549001560CE /* LatestPosts */ = {
|
||||
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */,
|
||||
9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift */,
|
||||
);
|
||||
path = LatestPosts;
|
||||
path = LatestPostsWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */ = {
|
||||
|
@ -668,6 +673,15 @@
|
|||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9FF2FB6B2BE8AE78001560CE /* MentionWidget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */,
|
||||
9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */,
|
||||
);
|
||||
path = MentionWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E9B576C029743F2A00BCE646 /* Localization */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -982,11 +996,13 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
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 */,
|
||||
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 */,
|
||||
|
@ -1141,7 +1157,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1176,7 +1192,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1284,7 +1300,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1318,7 +1334,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1499,7 +1515,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
||||
PRODUCT_NAME = "Ice Cubes";
|
||||
SDKROOT = auto;
|
||||
|
@ -1554,7 +1570,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
|
||||
PRODUCT_NAME = "Ice Cubes";
|
||||
SDKROOT = auto;
|
||||
|
@ -1589,7 +1605,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1624,7 +1640,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.37;
|
||||
MARKETING_VERSION = 1.10.39;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1530"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9F7788C42BE652B1004E6BEF"
|
||||
BuildableName = "IceCubesAppWidgetsExtensionExtension.appex"
|
||||
BlueprintName = "IceCubesAppWidgetsExtensionExtension"
|
||||
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9FBFE638292A715500C250E9"
|
||||
BuildableName = "Ice Cubes.app"
|
||||
BlueprintName = "IceCubesApp"
|
||||
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9FBFE638292A715500C250E9"
|
||||
BuildableName = "Ice Cubes.app"
|
||||
BlueprintName = "IceCubesApp"
|
||||
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetKind"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetDefaultView"
|
||||
value = "timeline"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetFamily"
|
||||
value = "systemMedium"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9FBFE638292A715500C250E9"
|
||||
BuildableName = "Ice Cubes.app"
|
||||
BlueprintName = "IceCubesApp"
|
||||
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -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
|
||||
|
|
|
@ -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? {
|
||||
|
|
|
@ -1,60 +1,61 @@
|
|||
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(),
|
||||
timeline: .hashtag(tag: "Mastodon", accountId: nil),
|
||||
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
|
||||
}
|
||||
return .init(date: Date(),
|
||||
timeline: .hashtag(tag: "Mastodon", accountId: nil),
|
||||
title: "#Mastodon",
|
||||
statuses: [],
|
||||
images: [:])
|
||||
}
|
||||
|
||||
|
||||
func timeline(for configuration: HashtagPostsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||
await timeline(for: configuration, context: context)
|
||||
}
|
||||
|
||||
|
||||
private func timeline(for configuration: HashtagPostsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||
do {
|
||||
let statuses = await loadStatuses(for: .hashtag(tag: configuration.hashgtag, accountId: nil),
|
||||
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(),
|
||||
timeline: .hashtag(tag: configuration.hashgtag,
|
||||
accountId: nil),
|
||||
statuses: statuses,
|
||||
images: images)], policy: .atEnd)
|
||||
title: timeline.title,
|
||||
statuses: statuses,
|
||||
images: images)], policy: .atEnd)
|
||||
} catch {
|
||||
return Timeline(entries: [.init(date: Date(),
|
||||
timeline: .hashtag(tag: "Mastodon", accountId: nil),
|
||||
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,
|
||||
timeline: .hashtag(tag: "Matodon", accountId: nil),
|
||||
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||
images: [:])
|
||||
title: "#Mastodon",
|
||||
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||
images: [:])
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import WidgetKit
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
@main
|
||||
struct IceCubesAppWidgetsExtensionBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
LatestPostsWidget()
|
||||
HashtagPostsWidget()
|
||||
MentionsWidget()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,51 @@
|
|||
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(),
|
||||
timeline: .home,
|
||||
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
|
||||
}
|
||||
return .init(date: Date(),
|
||||
timeline: .home, statuses: [],
|
||||
title: configuration.timeline.timeline.title,
|
||||
statuses: [],
|
||||
images: [:])
|
||||
}
|
||||
|
||||
|
||||
func timeline(for configuration: LatestPostsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||
await timeline(for: configuration, context: context)
|
||||
}
|
||||
|
||||
|
||||
private func timeline(for configuration: LatestPostsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||
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(),
|
||||
timeline: configuration.timeline.timeline,
|
||||
statuses: statuses,
|
||||
images: images)], policy: .atEnd)
|
||||
title: configuration.timeline.timeline.title,
|
||||
statuses: statuses,
|
||||
images: images)], policy: .atEnd)
|
||||
} catch {
|
||||
return Timeline(entries: [.init(date: Date(),
|
||||
timeline: .home,
|
||||
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 {
|
||||
|
@ -53,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
|
||||
}
|
||||
}
|
||||
|
@ -67,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)
|
||||
}
|
||||
|
@ -81,12 +83,11 @@ struct LatestPostsWidget: Widget {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#Preview(as: .systemMedium) {
|
||||
LatestPostsWidget()
|
||||
} timeline: {
|
||||
PostsWidgetEntry(date: .now,
|
||||
timeline: .home,
|
||||
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||
images: [:])
|
||||
PostsWidgetEntry(date: .now,
|
||||
title: "Mastodon",
|
||||
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||
images: [:])
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import DesignSystem
|
||||
import Models
|
||||
import Network
|
||||
import SwiftUI
|
||||
import Timeline
|
||||
import WidgetKit
|
||||
|
||||
struct MentionsWidgetProvider: AppIntentTimelineProvider {
|
||||
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
|
||||
}
|
||||
return .init(date: Date(),
|
||||
title: "Mentions",
|
||||
statuses: [],
|
||||
images: [:])
|
||||
}
|
||||
|
||||
func timeline(for configuration: MentionsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
|
||||
await timeline(for: configuration, context: context)
|
||||
}
|
||||
|
||||
private func timeline(for configuration: MentionsWidgetConfiguration, context _: Context) async -> Timeline<PostsWidgetEntry> {
|
||||
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 })
|
||||
return Timeline(entries: [.init(date: Date(),
|
||||
title: "Mentions",
|
||||
statuses: statuses,
|
||||
images: images)], policy: .atEnd)
|
||||
} catch {
|
||||
return Timeline(entries: [.init(date: Date(),
|
||||
title: "Mentions",
|
||||
statuses: [],
|
||||
images: [:])],
|
||||
policy: .atEnd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MentionsWidget: Widget {
|
||||
let kind: String = "MentionsWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(kind: kind,
|
||||
intent: MentionsWidgetConfiguration.self,
|
||||
provider: MentionsWidgetProvider())
|
||||
{ entry in
|
||||
PostsWidgetView(entry: entry)
|
||||
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
|
||||
}
|
||||
.configurationDisplayName("Mentions")
|
||||
.description("Show the latest mentions for the selected account.")
|
||||
.supportedFamilies([.systemLarge, .systemExtraLarge])
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(as: .systemMedium) {
|
||||
MentionsWidget()
|
||||
} timeline: {
|
||||
PostsWidgetEntry(date: .now,
|
||||
title: "Mentions",
|
||||
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
|
||||
images: [:])
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
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
|
||||
}
|
||||
|
||||
extension MentionsWidgetConfiguration {
|
||||
static var previewAccount: MentionsWidgetConfiguration {
|
||||
let intent = MentionsWidgetConfiguration()
|
||||
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
|
||||
return intent
|
||||
}
|
||||
}
|
|
@ -1,23 +1,23 @@
|
|||
import WidgetKit
|
||||
import SwiftUI
|
||||
import Network
|
||||
import DesignSystem
|
||||
import Models
|
||||
import Network
|
||||
import SwiftUI
|
||||
import Timeline
|
||||
import WidgetKit
|
||||
|
||||
struct PostsWidgetEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let timeline: TimelineFilter
|
||||
let title: String
|
||||
let statuses: [Status]
|
||||
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,10 +37,10 @@ struct PostsWidgetView : View {
|
|||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
|
||||
private var headerView: some View {
|
||||
HStack {
|
||||
Text(entry.timeline.title)
|
||||
Text(entry.title)
|
||||
Spacer()
|
||||
Image(systemName: "cube")
|
||||
}
|
||||
|
@ -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] {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -765,7 +765,7 @@ public extension StatusEditor {
|
|||
error: nil
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue