Compare commits

...

21 commits

Author SHA1 Message Date
Noah Martin cb1627082e
Merge 93b0342489 into 51fecb01f5 2024-05-07 09:12:23 +09:00
Thomas Ricouard 51fecb01f5 Bump version to 1.10.39 2024-05-06 09:25:36 +02:00
Thomas Ricouard c29de44d8c Widget: Mentions only allow large size 2024-05-06 09:20:01 +02:00
Thomas Ricouard 1d79832544 Bump version to 1.10.38 2024-05-06 08:41:33 +02:00
Thomas Ricouard a37316c56f Lint 2024-05-06 08:38:37 +02:00
Thomas Ricouard 189e10f2b4 Widget: Add mentions widget 2024-05-06 08:37:58 +02:00
Thomas Ricouard 24d5ecd119 Shortcuts: Fix image 2024-05-05 21:13:34 +02:00
Thomas Ricouard ee6f003073 Widget: Improve text size 2024-05-05 20:03:12 +02:00
Thomas Ricouard 7328c00006 Widget: Remove optional parameters 2024-05-05 19:41:04 +02:00
Thomas Ricouard a6fd8d1137 Widget: Renaming 2024-05-05 19:37:06 +02:00
Thomas Ricouard ea31cda3c2 Widget: Add Hashtag widget 2024-05-05 19:31:28 +02:00
Thomas Ricouard 8ab7b5ac69 Fix app group 2024-05-05 19:03:25 +02:00
Thomas Ricouard 7aebe530dd Widget: More UI refinements 2024-05-05 18:29:48 +02:00
Thomas Ricouard a2afd4f58f Fix widget bundle identifier 2024-05-05 18:16:20 +02:00
Thomas Ricouard 88218cd6ec Style fix 2024-05-05 18:10:32 +02:00
Thomas Ricouard c4dee39efe More fix for timeline widget 2024-05-05 18:06:47 +02:00
Thomas Ricouard 73651cb7f1 Polish on timeline widget 2024-05-05 17:47:08 +02:00
Thomas Ricouard dd1615f0e3 Fix widget entitlements 2024-05-05 17:30:09 +02:00
Thomas Ricouard 6bd14e0f8d Don't embed widgets on visionOS 2024-05-05 13:34:22 +02:00
Thomas Ricouard 1ca4a74ff0 Initial widget support 2024-05-05 13:12:19 +02:00
Noah Martin 93b0342489
Delete unused functions in TimelineDatasource.swift 2024-04-25 17:09:26 -04:00
28 changed files with 1113 additions and 82 deletions

View file

@ -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 /* LatestPostsWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.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 */; platformFilters = (ios, maccatalyst, ); 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 */; };
@ -103,6 +120,12 @@
9FE4CCAB2B4C848A00DA5F13 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 9FE4CCAA2B4C848A00DA5F13 /* GiphyUISDK */; };
9FE4CCAD2B4C849F00DA5F13 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE4CCAC2B4C849F00DA5F13 /* GiphyUISDK */; };
9FE6A42E2BD043A90055D388 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE6A42D2BD043A90055D388 /* RevenueCat */; };
9FF2FB622BE7F5D5001560CE /* HashtagPostsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB602BE7F5A7001560CE /* HashtagPostsWidget.swift */; };
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 */; };
@ -128,6 +151,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 +184,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 +257,17 @@
9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineView.swift; sourceTree = "<group>"; };
9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = "<group>"; };
9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinePostIntent.swift; sourceTree = "<group>"; };
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 = "<group>"; };
9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestPostsWidget.swift; sourceTree = "<group>"; };
9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestPostsWidgetConfiguration.swift; sourceTree = "<group>"; };
9F7788D12BE652B2004E6BEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
9F7788D32BE652B2004E6BEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesAppWidgetsExtensionExtension.entitlements; sourceTree = "<group>"; };
9F7788E72BE65533004E6BEF /* AppAccountEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccountEntity.swift; sourceTree = "<group>"; };
9F7788EC2BE78D75004E6BEF /* TimelineFilterEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFilterEntity.swift; sourceTree = "<group>"; };
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = "<group>"; };
9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = "<group>"; };
9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
@ -254,6 +296,12 @@
9FE0346A2ADD59AC00529EA8 /* MediaUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaUI; path = Packages/MediaUI; sourceTree = "<group>"; };
9FE151A5293C90F900E9683D /* IconSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectorView.swift; sourceTree = "<group>"; };
9FE3DB55296FEF5800628CB0 /* AppAccount */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = AppAccount; path = Packages/AppAccount; sourceTree = "<group>"; };
9FF2FB5E2BE7F56F001560CE /* HashtagPostsWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagPostsWidgetConfiguration.swift; sourceTree = "<group>"; };
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>"; };
@ -289,6 +337,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 +427,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 = "<group>";
@ -415,6 +480,21 @@
path = Timeline;
sourceTree = "<group>";
};
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */ = {
isa = PBXGroup;
children = (
9FF2FB6B2BE8AE78001560CE /* MentionWidget */,
9FF2FB642BE7F7FA001560CE /* Shared */,
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */,
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */,
9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */,
9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */,
9F7788D12BE652B2004E6BEF /* Assets.xcassets */,
9F7788D32BE652B2004E6BEF /* Info.plist */,
);
path = IceCubesAppWidgetsExtension;
sourceTree = "<group>";
};
9FA0D2AC29921C1F008A143B /* Embeds */ = {
isa = PBXGroup;
children = (
@ -483,6 +563,7 @@
E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */,
9F2A5417296AB631009B2D7C /* IceCubesNotifications */,
9FAD858929743F7400496AB1 /* IceCubesShareExtension */,
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */,
9FBFE63A292A715500C250E9 /* Products */,
9FBFE64C292A72BD00C250E9 /* Frameworks */,
9FE3DB55296FEF5800628CB0 /* AppAccount */,
@ -508,6 +589,7 @@
9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */,
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */,
E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */,
9F7788C52BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension.appex */,
);
name = Products;
sourceTree = "<group>";
@ -533,6 +615,8 @@
9F7335EE29674F7100AFF0BA /* QuickLook.framework */,
9F7335EB2967461B00AFF0BA /* AVKit.framework */,
E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */,
9F7788C62BE652B1004E6BEF /* WidgetKit.framework */,
9F7788C82BE652B1004E6BEF /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -562,6 +646,42 @@
path = Settings;
sourceTree = "<group>";
};
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */ = {
isa = PBXGroup;
children = (
9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */,
9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift */,
);
path = LatestPostsWidget;
sourceTree = "<group>";
};
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */ = {
isa = PBXGroup;
children = (
9FF2FB5E2BE7F56F001560CE /* HashtagPostsWidgetConfiguration.swift */,
9FF2FB602BE7F5A7001560CE /* HashtagPostsWidget.swift */,
);
path = HashtagPostsWidget;
sourceTree = "<group>";
};
9FF2FB642BE7F7FA001560CE /* Shared */ = {
isa = PBXGroup;
children = (
9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */,
9FF2FB682BE7F842001560CE /* SharedUtils.swift */,
);
path = Shared;
sourceTree = "<group>";
};
9FF2FB6B2BE8AE78001560CE /* MentionWidget */ = {
isa = PBXGroup;
children = (
9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */,
9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */,
);
path = MentionWidget;
sourceTree = "<group>";
};
E9B576C029743F2A00BCE646 /* Localization */ = {
isa = PBXGroup;
children = (
@ -613,6 +733,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 +800,7 @@
9F2A541C296AB631009B2D7C /* PBXTargetDependency */,
9FAD859129743F7400496AB1 /* PBXTargetDependency */,
E9DF420629830FEC0003AAD2 /* PBXTargetDependency */,
9F7788D52BE652B2004E6BEF /* PBXTargetDependency */,
);
name = IceCubesApp;
packageProductDependencies = (
@ -706,12 +852,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 +913,7 @@
E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */,
9F2A5415296AB631009B2D7C /* IceCubesNotifications */,
9FAD858729743F7400496AB1 /* IceCubesShareExtension */,
9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */,
);
};
/* End PBXProject section */
@ -779,6 +929,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 +991,24 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
9F7788C12BE652B1004E6BEF /* Sources */ = {
isa = PBXSourcesBuildPhase;
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 */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9FAD858429743F7400496AB1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -877,10 +1053,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 +1085,15 @@
target = 9F2A5415296AB631009B2D7C /* IceCubesNotifications */;
targetProxy = 9F2A541B296AB631009B2D7C /* PBXContainerItemProxy */;
};
9F7788D52BE652B2004E6BEF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilters = (
ios,
maccatalyst,
);
target = 9F7788C42BE652B1004E6BEF /* IceCubesAppWidgetsExtensionExtension */;
targetProxy = 9F7788D42BE652B2004E6BEF /* PBXContainerItemProxy */;
};
9FAD859129743F7400496AB1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilters = (
@ -970,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;
@ -1005,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;
@ -1021,6 +1208,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 = IceCubesAppWidgetsExtension/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 = "$(BUNDLE_ID_PREFIX).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 = IceCubesAppWidgetsExtension/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 = "$(BUNDLE_ID_PREFIX).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 = {
@ -1041,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;
@ -1075,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;
@ -1256,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;
@ -1311,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;
@ -1346,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;
@ -1381,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;
@ -1408,6 +1667,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 +1791,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;

View file

@ -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>

View file

@ -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" : {

View file

@ -0,0 +1,43 @@
import AppAccount
import AppIntents
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)
}
}

View file

@ -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(

View file

@ -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

View file

@ -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",

View file

@ -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)

View file

@ -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")

View file

@ -0,0 +1,37 @@
import AppAccount
import AppIntents
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 _: [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)
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,75 @@
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct HashtagPostsWidgetProvider: AppIntentTimelineProvider {
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
}
return .init(date: Date(),
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 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 })
return Timeline(entries: [.init(date: Date(),
title: timeline.title,
statuses: statuses,
images: images)], policy: .atEnd)
} catch {
return Timeline(entries: [.init(date: Date(),
title: "#Mastodon",
statuses: [],
images: [:])],
policy: .atEnd)
}
}
}
struct HashtagPostsWidget: Widget {
let kind: String = "HashtagPostsWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind,
intent: HashtagPostsWidgetConfiguration.self,
provider: HashtagPostsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
}
.configurationDisplayName("Hashtag timeline")
.description("Show the latest post for the selected hashtag")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
}
}
#Preview(as: .systemMedium) {
HashtagPostsWidget()
} timeline: {
PostsWidgetEntry(date: .now,
title: "#Mastodon",
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
}

View file

@ -0,0 +1,22 @@
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
}
extension HashtagPostsWidgetConfiguration {
static var previewAccount: HashtagPostsWidgetConfiguration {
let intent = HashtagPostsWidgetConfiguration()
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
intent.hashgtag = "Mastodon"
return intent
}
}

View file

@ -0,0 +1,11 @@
import SwiftUI
import WidgetKit
@main
struct IceCubesAppWidgetsExtensionBundle: WidgetBundle {
var body: some Widget {
LatestPostsWidget()
HashtagPostsWidget()
MentionsWidget()
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).IceCubesApp</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(BUNDLE_ID_PREFIX).IceCubesApp</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,93 @@
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct LatestPostsWidgetProvider: AppIntentTimelineProvider {
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
}
return .init(date: Date(),
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 })
return Timeline(entries: [.init(date: Date(),
title: configuration.timeline.timeline.title,
statuses: statuses,
images: images)], policy: .atEnd)
} catch {
return Timeline(entries: [.init(date: Date(),
title: configuration.timeline.timeline.title,
statuses: [],
images: [:])],
policy: .atEnd)
}
}
private func loadImages(urls: [URL]) async throws -> [URL: UIImage] {
try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in
for url in urls {
group.addTask {
let response = try await URLSession.shared.data(from: url)
return (url, UIImage(data: response.0))
}
}
var images: [URL: UIImage] = [:]
for try await (url, image) in group {
images[url] = image
}
return images
}
}
}
struct LatestPostsWidget: Widget {
let kind: String = "LatestPostsWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind,
intent: LatestPostsWidgetConfiguration.self,
provider: LatestPostsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
}
.configurationDisplayName("Latest posts")
.description("Show the latest post for the selected timeline")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
}
}
#Preview(as: .systemMedium) {
LatestPostsWidget()
} timeline: {
PostsWidgetEntry(date: .now,
title: "Mastodon",
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
}

View file

@ -0,0 +1,22 @@
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
}
extension LatestPostsWidgetConfiguration {
static var previewAccount: LatestPostsWidgetConfiguration {
let intent = LatestPostsWidgetConfiguration()
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
intent.timeline = .init(timeline: .home)
return intent
}
}

View file

@ -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: [:])
}

View file

@ -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
}
}

View file

@ -0,0 +1,96 @@
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct PostsWidgetEntry: TimelineEntry {
let date: Date
let title: String
let statuses: [Status]
let images: [URL: UIImage]
}
struct PostsWidgetView: View {
var entry: LatestPostsWidgetProvider.Entry
@Environment(\.widgetFamily) var family
@Environment(\.redactionReasons) var redacted
var contentLineLimit: Int {
switch family {
case .systemSmall, .systemMedium:
return 5
default:
return 2
}
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
headerView
ForEach(entry.statuses) { status in
makeStatusView(status)
}
Spacer()
}
.frame(maxWidth: .infinity)
}
private var headerView: some View {
HStack {
Text(entry.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: 2) {
makeStatusHeaderView(status)
Text(status.content.asSafeMarkdownAttributedString)
.font(.footnote)
.lineLimit(contentLineLimit)
.fixedSize(horizontal: false, vertical: true)
.padding(.leading, 20)
}
})
}
}
private func makeStatusHeaderView(_ status: Status) -> some View {
HStack(alignment: .center, spacing: 4) {
if let image = entry.images[status.account.avatar] {
Image(uiImage: image)
.resizable()
.frame(width: 16, height: 16)
.clipShape(Circle())
} else {
Circle()
.foregroundStyle(.secondary)
.frame(width: 16, height: 16)
}
HStack(spacing: 0) {
Text(status.account.safeDisplayName)
.foregroundStyle(.primary)
if family != .systemSmall {
Text(" @")
.foregroundStyle(.tertiary)
Text(status.account.username)
.foregroundStyle(.tertiary)
}
Spacer()
}
.font(.footnote)
.fontWeight(.semibold)
.lineLimit(1)
}
}
}

View file

@ -0,0 +1,56 @@
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]
{
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 }
switch widgetFamily {
case .systemSmall, .systemMedium:
if statuses.count >= 1 {
statuses = statuses.prefix(upTo: 1).map { $0 }
}
case .systemLarge, .systemExtraLarge:
if statuses.count >= 5 {
statuses = statuses.prefix(upTo: 5).map { $0 }
}
default:
break
}
return statuses
} catch {
return []
}
}
func loadImages(urls: [URL]) async throws -> [URL: UIImage] {
try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in
for url in urls {
group.addTask {
let response = try await URLSession.shared.data(from: url)
return (url, UIImage(data: response.0))
}
}
var images: [URL: UIImage] = [:]
for try await (url, image) in group {
images[url] = image
}
return images
}
}

View file

@ -752,7 +752,7 @@ public extension StatusEditor {
for url in urls {
let compressor = Compressor()
_ = url.startAccessingSecurityScopedResource()
if let compressedData = await compressor.compressImageFrom(url: url),
let image = UIImage(data: compressedData)
{
@ -765,6 +765,8 @@ public extension StatusEditor {
error: nil
))
}
url.stopAccessingSecurityScopedResource()
}
return containers

View file

@ -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?)

View file

@ -32,10 +32,6 @@ actor TimelineDatasource {
}
}
func count() -> Int {
statuses.count
}
func reset() {
statuses = []
}
@ -52,10 +48,6 @@ actor TimelineDatasource {
self.statuses = statuses
}
func append(_ status: Status) {
statuses.append(status)
}
func append(contentOf: [Status]) {
statuses.append(contentsOf: contentOf)
}