From 1ca4a74ff071c9d3b0ea6862dacd96f3c5c0daeb Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sun, 5 May 2024 13:12:19 +0200 Subject: [PATCH] Initial widget support --- IceCubesApp.xcodeproj/project.pbxproj | 242 +++++++++++++++++- .../Localization/Localizable.xcstrings | 23 +- IceCubesAppIntents/AppAccountEntity.swift | 44 ++++ IceCubesAppIntents/AppShortcuts.swift | 8 +- IceCubesAppIntents/InlinePostIntent.swift | 41 +-- IceCubesAppIntents/PostImageIntent.swift | 5 +- IceCubesAppIntents/PostIntent.swift | 7 +- IceCubesAppIntents/TabIntent.swift | 5 +- IceCubesAppIntents/TimelineFilterEntity.swift | 37 +++ .../AccentColor.colorset/Contents.json | 20 ++ .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../WidgetBackground.colorset/Contents.json | 38 +++ .../IceCubesAppWidgetsExtensionBundle.swift | 9 + .../IceCubesWidgetConfigurationIntent.swift | 22 ++ IceCubesAppWidgetsExtension/Info.plist | 11 + .../LatestPostsWidget.swift | 152 +++++++++++ ...sAppWidgetsExtensionExtension.entitlements | 14 + ...idgetsExtensionExtensionDebug.entitlements | 18 ++ .../Sources/Timeline/TimelineFilter.swift | 4 +- 20 files changed, 654 insertions(+), 65 deletions(-) create mode 100644 IceCubesAppIntents/AppAccountEntity.swift create mode 100644 IceCubesAppIntents/TimelineFilterEntity.swift create mode 100644 IceCubesAppWidgetsExtension/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 IceCubesAppWidgetsExtension/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 IceCubesAppWidgetsExtension/Assets.xcassets/Contents.json create mode 100644 IceCubesAppWidgetsExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json create mode 100644 IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift create mode 100644 IceCubesAppWidgetsExtension/IceCubesWidgetConfigurationIntent.swift create mode 100644 IceCubesAppWidgetsExtension/Info.plist create mode 100644 IceCubesAppWidgetsExtension/LatestPostsWidget.swift create mode 100644 IceCubesAppWidgetsExtensionExtension.entitlements create mode 100644 IceCubesAppWidgetsExtensionExtensionDebug.entitlements diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 86970016..d2100fd1 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -67,6 +67,23 @@ 9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */; }; 9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; }; 9F7788C02BE63935004E6BEF /* InlinePostIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */; }; + 9F7788C72BE652B1004E6BEF /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7788C62BE652B1004E6BEF /* WidgetKit.framework */; }; + 9F7788C92BE652B1004E6BEF /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7788C82BE652B1004E6BEF /* SwiftUI.framework */; }; + 9F7788CC2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */; }; + 9F7788CE2BE652B1004E6BEF /* LatestPostsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */; }; + 9F7788D02BE652B1004E6BEF /* IceCubesWidgetConfigurationIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788CF2BE652B1004E6BEF /* IceCubesWidgetConfigurationIntent.swift */; }; + 9F7788D22BE652B2004E6BEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F7788D12BE652B2004E6BEF /* Assets.xcassets */; }; + 9F7788D62BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9F7788DE2BE6543D004E6BEF /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788DD2BE6543D004E6BEF /* Account */; }; + 9F7788E02BE6543D004E6BEF /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788DF2BE6543D004E6BEF /* AppAccount */; }; + 9F7788E22BE6543D004E6BEF /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788E12BE6543D004E6BEF /* Env */; }; + 9F7788E42BE6543D004E6BEF /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788E32BE6543D004E6BEF /* Models */; }; + 9F7788E62BE6543D004E6BEF /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788E52BE6543D004E6BEF /* Network */; }; + 9F7788E82BE65533004E6BEF /* AppAccountEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */; }; + 9F7788EA2BE65585004E6BEF /* AppAccountEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */; }; + 9F7788ED2BE78D75004E6BEF /* TimelineFilterEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */; }; + 9F7788EE2BE78D7B004E6BEF /* TimelineFilterEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */; }; + 9F7788F02BE78E77004E6BEF /* Timeline in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7788EF2BE78E77004E6BEF /* Timeline */; }; 9F7D93942980063100EE6B7A /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7D93932980063100EE6B7A /* AppAccount */; }; 9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */; }; 9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */; }; @@ -128,6 +145,13 @@ remoteGlobalIDString = 9F2A5415296AB631009B2D7C; remoteInfo = IceCubesNotifications; }; + 9F7788D42BE652B2004E6BEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9FBFE631292A715500C250E9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9F7788C42BE652B1004E6BEF; + remoteInfo = IceCubesAppWidgetsExtensionExtension; + }; 9FAD859029743F7400496AB1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9FBFE631292A715500C250E9 /* Project object */; @@ -154,6 +178,7 @@ E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */, 9F2A541D296AB631009B2D7C /* IceCubesNotifications.appex in Embed Foundation Extensions */, 9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */, + 9F7788D62BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -226,6 +251,18 @@ 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineView.swift; sourceTree = ""; }; 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = ""; }; 9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinePostIntent.swift; sourceTree = ""; }; + 9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesAppWidgetsExtensionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F7788C62BE652B1004E6BEF /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 9F7788C82BE652B1004E6BEF /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesAppWidgetsExtensionBundle.swift; sourceTree = ""; }; + 9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestPostsWidget.swift; sourceTree = ""; }; + 9F7788CF2BE652B1004E6BEF /* IceCubesWidgetConfigurationIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesWidgetConfigurationIntent.swift; sourceTree = ""; }; + 9F7788D12BE652B2004E6BEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 9F7788D32BE652B2004E6BEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesAppWidgetsExtensionExtension.entitlements; sourceTree = ""; }; + 9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccountEntity.swift; sourceTree = ""; }; + 9F7788EB2BE65689004E6BEF /* IceCubesAppWidgetsExtensionExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesAppWidgetsExtensionExtensionDebug.entitlements; sourceTree = ""; }; + 9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFilterEntity.swift; sourceTree = ""; }; 9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = ""; }; 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = ""; }; 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = ""; }; @@ -289,6 +326,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9F7788C22BE652B1004E6BEF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F7788E42BE6543D004E6BEF /* Models in Frameworks */, + 9F7788E62BE6543D004E6BEF /* Network in Frameworks */, + 9F7788E02BE6543D004E6BEF /* AppAccount in Frameworks */, + 9F7788DE2BE6543D004E6BEF /* Account in Frameworks */, + 9F7788E22BE6543D004E6BEF /* Env in Frameworks */, + 9F7788C92BE652B1004E6BEF /* SwiftUI.framework in Frameworks */, + 9F7788C72BE652B1004E6BEF /* WidgetKit.framework in Frameworks */, + 9F7788F02BE78E77004E6BEF /* Timeline in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FAD858529743F7400496AB1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -364,12 +416,14 @@ 9F37BDD92BE36E08007F28AD /* IceCubesAppIntents */ = { isa = PBXGroup; children = ( - 9F37BDDA2BE36E22007F28AD /* PostIntent.swift */, + 9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */, 9F37BDDC2BE37193007F28AD /* AppIntentService.swift */, - 9F37BDDE2BE37C35007F28AD /* TabIntent.swift */, - 9F37BDE02BE38646007F28AD /* PostImageIntent.swift */, 9F37BDE22BE393A7007F28AD /* AppShortcuts.swift */, 9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */, + 9F37BDE02BE38646007F28AD /* PostImageIntent.swift */, + 9F37BDDA2BE36E22007F28AD /* PostIntent.swift */, + 9F37BDDE2BE37C35007F28AD /* TabIntent.swift */, + 9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */, ); path = IceCubesAppIntents; sourceTree = ""; @@ -415,6 +469,18 @@ path = Timeline; sourceTree = ""; }; + 9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */ = { + isa = PBXGroup; + children = ( + 9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */, + 9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */, + 9F7788CF2BE652B1004E6BEF /* IceCubesWidgetConfigurationIntent.swift */, + 9F7788D12BE652B2004E6BEF /* Assets.xcassets */, + 9F7788D32BE652B2004E6BEF /* Info.plist */, + ); + path = IceCubesAppWidgetsExtension; + sourceTree = ""; + }; 9FA0D2AC29921C1F008A143B /* Embeds */ = { isa = PBXGroup; children = ( @@ -476,6 +542,8 @@ 9FBFE630292A715500C250E9 = { isa = PBXGroup; children = ( + 9F7788EB2BE65689004E6BEF /* IceCubesAppWidgetsExtensionExtensionDebug.entitlements */, + 9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */, DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */, 9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */, 9FBFE63B292A715500C250E9 /* IceCubesApp */, @@ -483,6 +551,7 @@ E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */, 9F2A5417296AB631009B2D7C /* IceCubesNotifications */, 9FAD858929743F7400496AB1 /* IceCubesShareExtension */, + 9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */, 9FBFE63A292A715500C250E9 /* Products */, 9FBFE64C292A72BD00C250E9 /* Frameworks */, 9FE3DB55296FEF5800628CB0 /* AppAccount */, @@ -508,6 +577,7 @@ 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */, 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */, E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */, + 9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */, ); name = Products; sourceTree = ""; @@ -533,6 +603,8 @@ 9F7335EE29674F7100AFF0BA /* QuickLook.framework */, 9F7335EB2967461B00AFF0BA /* AVKit.framework */, E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */, + 9F7788C62BE652B1004E6BEF /* WidgetKit.framework */, + 9F7788C82BE652B1004E6BEF /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -613,6 +685,31 @@ productReference = 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */; productType = "com.apple.product-type.app-extension"; }; + 9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9F7788D82BE652B2004E6BEF /* Build configuration list for PBXNativeTarget "IceCubesAppWidgetsExtensionExtension" */; + buildPhases = ( + 9F7788C12BE652B1004E6BEF /* Sources */, + 9F7788C22BE652B1004E6BEF /* Frameworks */, + 9F7788C32BE652B1004E6BEF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IceCubesAppWidgetsExtensionExtension; + packageProductDependencies = ( + 9F7788DD2BE6543D004E6BEF /* Account */, + 9F7788DF2BE6543D004E6BEF /* AppAccount */, + 9F7788E12BE6543D004E6BEF /* Env */, + 9F7788E32BE6543D004E6BEF /* Models */, + 9F7788E52BE6543D004E6BEF /* Network */, + 9F7788EF2BE78E77004E6BEF /* Timeline */, + ); + productName = IceCubesAppWidgetsExtensionExtension; + productReference = 9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 9FAD858729743F7400496AB1 /* IceCubesShareExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */; @@ -655,6 +752,7 @@ 9F2A541C296AB631009B2D7C /* PBXTargetDependency */, 9FAD859129743F7400496AB1 /* PBXTargetDependency */, E9DF420629830FEC0003AAD2 /* PBXTargetDependency */, + 9F7788D52BE652B2004E6BEF /* PBXTargetDependency */, ); name = IceCubesApp; packageProductDependencies = ( @@ -706,12 +804,15 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1420; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1500; TargetAttributes = { 9F2A5415296AB631009B2D7C = { CreatedOnToolsVersion = 14.2; }; + 9F7788C42BE652B1004E6BEF = { + CreatedOnToolsVersion = 15.3; + }; 9FAD858729743F7400496AB1 = { CreatedOnToolsVersion = 14.2; }; @@ -764,6 +865,7 @@ E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */, 9F2A5415296AB631009B2D7C /* IceCubesNotifications */, 9FAD858729743F7400496AB1 /* IceCubesShareExtension */, + 9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */, ); }; /* End PBXProject section */ @@ -779,6 +881,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9F7788C32BE652B1004E6BEF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F7788D22BE652B2004E6BEF /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FAD858629743F7400496AB1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -833,6 +943,18 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9F7788C12BE652B1004E6BEF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F7788EA2BE65585004E6BEF /* AppAccountEntity.swift in Sources */, + 9F7788CE2BE652B1004E6BEF /* LatestPostsWidget.swift in Sources */, + 9F7788EE2BE78D7B004E6BEF /* TimelineFilterEntity.swift in Sources */, + 9F7788CC2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift in Sources */, + 9F7788D02BE652B1004E6BEF /* IceCubesWidgetConfigurationIntent.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FAD858429743F7400496AB1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -877,10 +999,12 @@ 9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */, 639CDF9C296AC82F00C35E58 /* SafariRouter.swift in Sources */, 9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */, + 9F7788ED2BE78D75004E6BEF /* TimelineFilterEntity.swift in Sources */, 9F37BDE32BE393A7007F28AD /* AppShortcuts.swift in Sources */, 9F654BEF299AC45B00D27FA5 /* ReportView.swift in Sources */, D08A9C3529956CFA00204A4A /* SwipeActionsSettingsView.swift in Sources */, 9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */, + 9F7788E82BE65533004E6BEF /* AppAccountEntity.swift in Sources */, 9FC14EF62B494DFF0006CEE1 /* RecenTagsSettingView.swift in Sources */, 9F6028562B3F36AE00476078 /* AppView.swift in Sources */, 9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */, @@ -907,6 +1031,11 @@ target = 9F2A5415296AB631009B2D7C /* IceCubesNotifications */; targetProxy = 9F2A541B296AB631009B2D7C /* PBXContainerItemProxy */; }; + 9F7788D52BE652B2004E6BEF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */; + targetProxy = 9F7788D42BE652B2004E6BEF /* PBXContainerItemProxy */; + }; 9FAD859129743F7400496AB1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; platformFilters = ( @@ -1021,6 +1150,78 @@ }; name = Release; }; + 9F7788D92BE652B2004E6BEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = IceCubesAppWidgetsExtensionExtensionDebug.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IceCubesAppWidgetsExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = IceCubesAppWidgetsExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp.IceCubesAppWidgetsExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 9F7788DA2BE652B2004E6BEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = IceCubesAppWidgetsExtensionExtension.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IceCubesAppWidgetsExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = IceCubesAppWidgetsExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp.IceCubesAppWidgetsExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; 9FAD859429743F7400496AB1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1408,6 +1609,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9F7788D82BE652B2004E6BEF /* Build configuration list for PBXNativeTarget "IceCubesAppWidgetsExtensionExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9F7788D92BE652B2004E6BEF /* Debug */, + 9F7788DA2BE652B2004E6BEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1523,6 +1733,30 @@ isa = XCSwiftPackageProductDependency; productName = Conversations; }; + 9F7788DD2BE6543D004E6BEF /* Account */ = { + isa = XCSwiftPackageProductDependency; + productName = Account; + }; + 9F7788DF2BE6543D004E6BEF /* AppAccount */ = { + isa = XCSwiftPackageProductDependency; + productName = AppAccount; + }; + 9F7788E12BE6543D004E6BEF /* Env */ = { + isa = XCSwiftPackageProductDependency; + productName = Env; + }; + 9F7788E32BE6543D004E6BEF /* Models */ = { + isa = XCSwiftPackageProductDependency; + productName = Models; + }; + 9F7788E52BE6543D004E6BEF /* Network */ = { + isa = XCSwiftPackageProductDependency; + productName = Network; + }; + 9F7788EF2BE78E77004E6BEF /* Timeline */ = { + isa = XCSwiftPackageProductDependency; + productName = Timeline; + }; 9F7D93932980063100EE6B7A /* AppAccount */ = { isa = XCSwiftPackageProductDependency; productName = AppAccount; diff --git a/IceCubesApp/Resources/Localization/Localizable.xcstrings b/IceCubesApp/Resources/Localization/Localizable.xcstrings index 77eca02c..0839af4e 100644 --- a/IceCubesApp/Resources/Localization/Localizable.xcstrings +++ b/IceCubesApp/Resources/Localization/Localizable.xcstrings @@ -20539,7 +20539,10 @@ } } }, - "Compose a status" : { + "Compose a post" : { + + }, + "Compose a post to Mastodon" : { }, "Content of the post to be sent to Mastodon" : { @@ -40167,6 +40170,7 @@ } }, "Post status to Mastodon" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -40692,13 +40696,13 @@ } } }, - "Send a text status" : { + "Send a post" : { }, - "Send a text status to Mastodon using Ice Cubes" : { + "Send a text post to Mastodon with Ice Cubes" : { }, - "Send text status to Mastodon" : { + "Send post to Mastodon" : { }, "Settings" : { @@ -80417,6 +80421,9 @@ } } } + }, + "TimelineFilter" : { + }, "Trending Links" : { "localizations" : { @@ -80796,8 +80803,15 @@ } } } + }, + "Use Ice Cubes to compose a post for Mastodon" : { + + }, + "Use Ice Cubes to compose a post with an image to Mastodon" : { + }, "Use Ice Cubes to post a status to Mastodon" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -80808,6 +80822,7 @@ } }, "Use Ice Cubes to post a status with an image to Mastodon" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { diff --git a/IceCubesAppIntents/AppAccountEntity.swift b/IceCubesAppIntents/AppAccountEntity.swift new file mode 100644 index 00000000..5b87adb6 --- /dev/null +++ b/IceCubesAppIntents/AppAccountEntity.swift @@ -0,0 +1,44 @@ +import AppIntents +import AppAccount +import Env +import Foundation +import Models +import Network + + +extension IntentDescription: @unchecked Sendable { } +extension TypeDisplayRepresentation: @unchecked Sendable { } + +public struct AppAccountEntity: Identifiable, AppEntity { + public var id: String { account.id } + + public let account: AppAccount + + public static let defaultQuery = DefaultAppAccountEntityQuery() + + public static let typeDisplayRepresentation: TypeDisplayRepresentation = "AppAccount" + + public var displayRepresentation: DisplayRepresentation { + DisplayRepresentation(title: "\(account.accountName ?? account.server)") + } +} + +public struct DefaultAppAccountEntityQuery: EntityQuery { + public init() { } + + public func entities(for identifiers: [AppAccountEntity.ID]) async throws -> [AppAccountEntity] { + return await AppAccountsManager.shared.availableAccounts.filter { account in + identifiers.contains { id in + id == account.id + } + }.map { AppAccountEntity(account: $0) } + } + + public func suggestedEntities() async throws -> [AppAccountEntity] { + await AppAccountsManager.shared.availableAccounts.map { .init(account: $0) } + } + + public func defaultResult() async -> AppAccountEntity? { + await .init(account: AppAccountsManager.shared.currentAccount) + } +} diff --git a/IceCubesAppIntents/AppShortcuts.swift b/IceCubesAppIntents/AppShortcuts.swift index 0d297ab4..7b5ded40 100644 --- a/IceCubesAppIntents/AppShortcuts.swift +++ b/IceCubesAppIntents/AppShortcuts.swift @@ -8,16 +8,16 @@ struct AppShortcuts: AppShortcutsProvider { "Post \(\.$content) in \(.applicationName)", "Post a status on Mastodon with \(.applicationName)", ], - shortTitle: "Compose a status", + shortTitle: "Compose a post", systemImageName: "square.and.pencil" ) AppShortcut( intent: InlinePostIntent(), phrases: [ - "Write a status with \(.applicationName)", - "Send on Status on Mastodon with \(.applicationName)", + "Write a post with \(.applicationName)", + "Send on post on Mastodon with \(.applicationName)", ], - shortTitle: "Send a text status", + shortTitle: "Send a post", systemImageName: "square.and.pencil" ) AppShortcut( diff --git a/IceCubesAppIntents/InlinePostIntent.swift b/IceCubesAppIntents/InlinePostIntent.swift index 69c316ba..93cb19ca 100644 --- a/IceCubesAppIntents/InlinePostIntent.swift +++ b/IceCubesAppIntents/InlinePostIntent.swift @@ -33,48 +33,13 @@ enum PostVisibility: String, AppEnum { } } -struct AppAccountWrapper: Identifiable, AppEntity { - var id: String { account.id } - - let account: AppAccount - - static var defaultQuery = DefaultAppAccountQuery() - - static var typeDisplayRepresentation: TypeDisplayRepresentation = "AppAccount" - - var displayRepresentation: DisplayRepresentation { - DisplayRepresentation(title: "\(account.accountName ?? account.server)") - } -} - -struct DefaultAppAccountQuery: EntityQuery { - func entities(for identifiers: [AppAccountWrapper.ID]) async throws -> [AppAccountWrapper] { - return await AppAccountsManager.shared.availableAccounts.filter { account in - identifiers.contains { id in - id == account.id - } - }.map { AppAccountWrapper(account: $0) } - } - - func suggestedEntities() async throws -> [AppAccountWrapper] { - await AppAccountsManager.shared.availableAccounts.map { .init(account: $0) } - } - - func defaultResult() async -> AppAccountWrapper? { - await .init(account: AppAccountsManager.shared.currentAccount) - } -} - struct InlinePostIntent: AppIntent { - static let title: LocalizedStringResource = "Send text status to Mastodon" - static var description: IntentDescription { - "Send a text status to Mastodon using Ice Cubes" - } - + static let title: LocalizedStringResource = "Send post to Mastodon" + static let description: IntentDescription = "Send a text post to Mastodon with Ice Cubes" static let openAppWhenRun: Bool = false @Parameter(title: "Account", requestValueDialog: IntentDialog("Account")) - var account: AppAccountWrapper + var account: AppAccountEntity @Parameter(title: "Post visibility", requestValueDialog: IntentDialog("Visibility of your post")) var visibility: PostVisibility diff --git a/IceCubesAppIntents/PostImageIntent.swift b/IceCubesAppIntents/PostImageIntent.swift index ab27e1dd..8b312a7b 100644 --- a/IceCubesAppIntents/PostImageIntent.swift +++ b/IceCubesAppIntents/PostImageIntent.swift @@ -3,10 +3,7 @@ import Foundation struct PostImageIntent: AppIntent { static let title: LocalizedStringResource = "Post an image to Mastodon" - static var description: IntentDescription { - "Use Ice Cubes to post a status with an image to Mastodon" - } - + static let description: IntentDescription = "Use Ice Cubes to compose a post with an image to Mastodon" static let openAppWhenRun: Bool = true @Parameter(title: "Image", diff --git a/IceCubesAppIntents/PostIntent.swift b/IceCubesAppIntents/PostIntent.swift index ebb3ff63..e305fa25 100644 --- a/IceCubesAppIntents/PostIntent.swift +++ b/IceCubesAppIntents/PostIntent.swift @@ -2,11 +2,8 @@ import AppIntents import Foundation struct PostIntent: AppIntent { - static let title: LocalizedStringResource = "Post status to Mastodon" - static var description: IntentDescription { - "Use Ice Cubes to post a status to Mastodon" - } - + static let title: LocalizedStringResource = "Compose a post to Mastodon" + static let description: IntentDescription = "Use Ice Cubes to compose a post for Mastodon" static let openAppWhenRun: Bool = true @Parameter(title: "Post content", inputConnectionBehavior: .connectToPreviousIntentResult) diff --git a/IceCubesAppIntents/TabIntent.swift b/IceCubesAppIntents/TabIntent.swift index 61cc415e..b4bb34ae 100644 --- a/IceCubesAppIntents/TabIntent.swift +++ b/IceCubesAppIntents/TabIntent.swift @@ -75,10 +75,7 @@ enum TabEnum: String, AppEnum, Sendable { struct TabIntent: AppIntent { static let title: LocalizedStringResource = "Open on a tab" - static var description: IntentDescription { - "Open the app on a specific tab" - } - + static let description: IntentDescription = "Open the app on a specific tab" static let openAppWhenRun: Bool = true @Parameter(title: "Selected tab") diff --git a/IceCubesAppIntents/TimelineFilterEntity.swift b/IceCubesAppIntents/TimelineFilterEntity.swift new file mode 100644 index 00000000..9a1c348a --- /dev/null +++ b/IceCubesAppIntents/TimelineFilterEntity.swift @@ -0,0 +1,37 @@ +import AppIntents +import AppAccount +import Env +import Foundation +import Models +import Network +import Timeline + +public struct TimelineFilterEntity: Identifiable, AppEntity { + public var id: String { timeline.id } + + public let timeline: TimelineFilter + + public static let defaultQuery = DefaultTimelineEntityQuery() + + public static let typeDisplayRepresentation: TypeDisplayRepresentation = "TimelineFilter" + + public var displayRepresentation: DisplayRepresentation { + DisplayRepresentation(title: "\(timeline.title)") + } +} + +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 func suggestedEntities() async throws -> [TimelineFilterEntity] { + [.home, .trending, .federated, .local].map{ .init(timeline: $0) } + } + + public func defaultResult() async -> TimelineFilterEntity? { + .init(timeline: .home) + } +} diff --git a/IceCubesAppWidgetsExtension/Assets.xcassets/AccentColor.colorset/Contents.json b/IceCubesAppWidgetsExtension/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..8691996d --- /dev/null +++ b/IceCubesAppWidgetsExtension/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.859", + "green" : "0.267", + "red" : "0.675" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesAppWidgetsExtension/Assets.xcassets/AppIcon.appiconset/Contents.json b/IceCubesAppWidgetsExtension/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/IceCubesAppWidgetsExtension/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesAppWidgetsExtension/Assets.xcassets/Contents.json b/IceCubesAppWidgetsExtension/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/IceCubesAppWidgetsExtension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesAppWidgetsExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json b/IceCubesAppWidgetsExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..470890bc --- /dev/null +++ b/IceCubesAppWidgetsExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.949", + "green" : "0.945", + "red" : "0.941" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.082", + "red" : "0.067" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift b/IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift new file mode 100644 index 00000000..fe7d3461 --- /dev/null +++ b/IceCubesAppWidgetsExtension/IceCubesAppWidgetsExtensionBundle.swift @@ -0,0 +1,9 @@ +import WidgetKit +import SwiftUI + +@main +struct IceCubesAppWidgetsExtensionBundle: WidgetBundle { + var body: some Widget { + LatestPostsWidget() + } +} diff --git a/IceCubesAppWidgetsExtension/IceCubesWidgetConfigurationIntent.swift b/IceCubesAppWidgetsExtension/IceCubesWidgetConfigurationIntent.swift new file mode 100644 index 00000000..924bf169 --- /dev/null +++ b/IceCubesAppWidgetsExtension/IceCubesWidgetConfigurationIntent.swift @@ -0,0 +1,22 @@ +import WidgetKit +import AppIntents + +struct IceCubesWidgetConfigurationIntent: 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? +} + +extension IceCubesWidgetConfigurationIntent { + static var previewAccount: IceCubesWidgetConfigurationIntent { + let intent = IceCubesWidgetConfigurationIntent() + intent.account = .init(account: .init(server: "Test", accountName: "Test account")) + intent.timeline = .init(timeline: .home) + return intent + } +} diff --git a/IceCubesAppWidgetsExtension/Info.plist b/IceCubesAppWidgetsExtension/Info.plist new file mode 100644 index 00000000..0f118fb7 --- /dev/null +++ b/IceCubesAppWidgetsExtension/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/IceCubesAppWidgetsExtension/LatestPostsWidget.swift b/IceCubesAppWidgetsExtension/LatestPostsWidget.swift new file mode 100644 index 00000000..24f7c3d2 --- /dev/null +++ b/IceCubesAppWidgetsExtension/LatestPostsWidget.swift @@ -0,0 +1,152 @@ +import WidgetKit +import SwiftUI +import Network +import DesignSystem +import Models +import Timeline + +struct LatestPostsWidgetProvider: AppIntentTimelineProvider { + func placeholder(in context: Context) -> LatestPostWidgetEntry { + .init(date: Date(), configuration: IceCubesWidgetConfigurationIntent(), timeline: .home, statuses: [ + .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder() + ]) + } + + func snapshot(for configuration: IceCubesWidgetConfigurationIntent, in context: Context) async -> LatestPostWidgetEntry { + if let entry = await timeline(for: configuration, context: context).entries.first { + return entry + } + return .init(date: Date(), configuration: configuration, timeline: .home, statuses: []) + } + + func timeline(for configuration: IceCubesWidgetConfigurationIntent, in context: Context) async -> Timeline { + await timeline(for: configuration, context: context) + } + + private func timeline(for configuration: IceCubesWidgetConfigurationIntent, context: Context) async -> Timeline { + guard let account = configuration.account, let timeline = configuration.timeline else { + return Timeline(entries: [.init(date: Date(), + configuration: configuration, + timeline: .home, + statuses: [])], + policy: .atEnd) + } + let client = Client(server: account.account.server, oauthToken: account.account.oauthToken) + do { + var statuses: [Status] = try await client.get(endpoint: timeline.timeline.endpoint(sinceId: nil, + maxId: nil, + minId: nil, + offset: nil)) + statuses = statuses.filter{ $0.reblog == nil && !$0.content.asRawText.isEmpty } + switch context.family { + case .systemMedium: + if statuses.count >= 2 { + statuses = statuses.prefix(upTo: 2).map{ $0 } + } + case .systemLarge: + if statuses.count >= 4 { + statuses = statuses.prefix(upTo: 4).map{ $0 } + } + case .systemExtraLarge: + if statuses.count >= 6 { + statuses = statuses.prefix(upTo: 6).map{ $0 } + } + default: + break + } + return Timeline(entries: [.init(date: Date(), configuration: configuration, + timeline: timeline.timeline, + statuses: statuses)], policy: .atEnd) + } catch { + return Timeline(entries: [.init(date: Date(), + configuration: configuration, + timeline: .home, + statuses: [])], policy: .atEnd) + } + } +} + +struct LatestPostWidgetEntry: TimelineEntry { + let date: Date + let configuration: IceCubesWidgetConfigurationIntent + let timeline: TimelineFilter + let statuses: [Status] +} + +struct LatestPostsWidgetView : View { + var entry: LatestPostsWidgetProvider.Entry + + @Environment(\.widgetFamily) var family + @Environment(\.redactionReasons) var redacted + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + headerView + ForEach(entry.statuses) { status in + makeStatusView(status) + } + } + .frame(maxWidth: .infinity) + } + + private var headerView: some View { + HStack { + Text(entry.timeline.title) + Spacer() + Image(systemName: "cube") + } + .font(.subheadline) + .fontWeight(.bold) + .foregroundStyle(Color("AccentColor")) + } + + @ViewBuilder + private func makeStatusView(_ status: Status) -> some View { + if let url = URL(string: status.url ?? "") { + Link(destination: url, label: { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 0) { + Text(status.account.safeDisplayName) + Text(" @") + Text(status.account.username) + Spacer() + } + .font(.subheadline) + .fontWeight(.semibold) + .foregroundStyle(.secondary) + .lineLimit(1) + + Text(status.content.asRawText) + .font(.body) + .lineLimit(2) + } + }) + } + } +} + +struct LatestPostsWidget: Widget { + let kind: String = "LatestPostsWidget" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, + intent: IceCubesWidgetConfigurationIntent.self, + provider: LatestPostsWidgetProvider()) { entry in + LatestPostsWidgetView(entry: entry) + .containerBackground(Color("WidgetBackground").gradient, for: .widget) + } + .configurationDisplayName("Latest posts") + .description("Show the latest post for the selected timeline") + .supportedFamilies([.systemMedium, .systemLarge, .systemExtraLarge]) + } +} + + +#Preview(as: .systemMedium) { + LatestPostsWidget() +} timeline: { + LatestPostWidgetEntry(date: .now, + configuration: .previewAccount, + timeline: .home, + statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()]) +} diff --git a/IceCubesAppWidgetsExtensionExtension.entitlements b/IceCubesAppWidgetsExtensionExtension.entitlements new file mode 100644 index 00000000..a31bf1a4 --- /dev/null +++ b/IceCubesAppWidgetsExtensionExtension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.com.thomasricouard.IceCubesApp + + com.apple.security.network.client + + + diff --git a/IceCubesAppWidgetsExtensionExtensionDebug.entitlements b/IceCubesAppWidgetsExtensionExtensionDebug.entitlements new file mode 100644 index 00000000..d59836e7 --- /dev/null +++ b/IceCubesAppWidgetsExtensionExtensionDebug.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.com.thomasricouard.IceCubesApp + + com.apple.security.network.client + + keychain-access-groups + + $(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).IceCubesApp + + + diff --git a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift index d1277709..2c7fe901 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift @@ -3,7 +3,7 @@ import Models import Network import SwiftUI -public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable { +public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable, Sendable { case local, federated, trending public func localizedTitle() -> LocalizedStringKey { @@ -29,7 +29,7 @@ public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable { } } -public enum TimelineFilter: Hashable, Equatable, Identifiable { +public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable { case home, local, federated, trending case hashtag(tag: String, accountId: String?) case tagGroup(title: String, tags: [String], symbolName: String?)