Widget, began Tenor integration, other imporvements and fixes
This commit is contained in:
parent
609e3b245b
commit
8abc56227d
|
@ -50,10 +50,25 @@
|
|||
B9B63B232B447B8000BBC82D /* PostCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B222B447B8000BBC82D /* PostCardView.swift */; };
|
||||
B9B63B252B44997400BBC82D /* QuotePostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B242B44997400BBC82D /* QuotePostView.swift */; };
|
||||
B9B63B272B449CDC00BBC82D /* SearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B262B449CDC00BBC82D /* SearchResults.swift */; };
|
||||
B9BCC3182B90B3BC00211976 /* Tenor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BCC3172B90B3BC00211976 /* Tenor.swift */; };
|
||||
B9BED5162B5D5E6500C9B715 /* PostInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5152B5D5E6500C9B715 /* PostInteractor.swift */; };
|
||||
B9BED5182B5D649C00C9B715 /* PostMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5172B5D649C00C9B715 /* PostMenu.swift */; };
|
||||
B9BED51A2B5D662D00C9B715 /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5192B5D662D00C9B715 /* ShareSheetController.swift */; };
|
||||
B9BF54072B6B6823004B24E7 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B9BF54062B6B6823004B24E7 /* KeychainSwift */; };
|
||||
B9C20D0B2B921C78004DC9B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9C20D0A2B921C78004DC9B3 /* WidgetKit.framework */; };
|
||||
B9C20D0D2B921C78004DC9B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9C20D0C2B921C78004DC9B3 /* SwiftUI.framework */; };
|
||||
B9C20D102B921C78004DC9B3 /* ThreadedWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D0F2B921C78004DC9B3 /* ThreadedWidgetsBundle.swift */; };
|
||||
B9C20D122B921C78004DC9B3 /* ThreadedWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D112B921C78004DC9B3 /* ThreadedWidgets.swift */; };
|
||||
B9C20D142B921C78004DC9B3 /* AppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D132B921C78004DC9B3 /* AppIntent.swift */; };
|
||||
B9C20D162B921C7B004DC9B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B9C20D152B921C7B004DC9B3 /* Assets.xcassets */; };
|
||||
B9C20D1A2B921C7B004DC9B3 /* ThreadedWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
B9C20D1F2B921E81004DC9B3 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B9C20D1E2B921E81004DC9B3 /* Localizable.xcstrings */; };
|
||||
B9C20D282B9229DF004DC9B3 /* LoggedAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98627302B86F23500844245 /* LoggedAccounts.swift */; };
|
||||
B9C20D342B9229EC004DC9B3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB949A2B2EF09A00D81C07 /* Client.swift */; };
|
||||
B9C20D372B9229EC004DC9B3 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94A12B2EF24A00D81C07 /* AppInfo.swift */; };
|
||||
B9C20D3D2B9229EC004DC9B3 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9FB94872B2E223E00D81C07 /* Emoji.swift */; };
|
||||
B9C20D562B922DAC004DC9B3 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B9C20D552B922DAC004DC9B3 /* KeychainSwift */; };
|
||||
B9C20D612B949AD7004DC9B3 /* Redeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */; };
|
||||
B9CC45B82B40A2D6001E4FA5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */; };
|
||||
B9CFC43B2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */; };
|
||||
B9D365612B79A1BE004C1255 /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D365602B79A1BE004C1255 /* MailView.swift */; };
|
||||
|
@ -102,6 +117,13 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
B9C20D182B921C7B004DC9B3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B9FB944F2B2DEECE00D81C07 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = B9C20D082B921C78004DC9B3;
|
||||
remoteInfo = ThreadedWidgetsExtension;
|
||||
};
|
||||
B9FB94B22B2F009F00D81C07 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B9FB944F2B2DEECE00D81C07 /* Project object */;
|
||||
|
@ -119,6 +141,7 @@
|
|||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
B9FB94B42B2F009F00D81C07 /* ThreadedAuthService.appex in Embed Foundation Extensions */,
|
||||
B9C20D1A2B921C7B004DC9B3 /* ThreadedWidgetsExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -162,9 +185,22 @@
|
|||
B9B63B222B447B8000BBC82D /* PostCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCardView.swift; sourceTree = "<group>"; };
|
||||
B9B63B242B44997400BBC82D /* QuotePostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotePostView.swift; sourceTree = "<group>"; };
|
||||
B9B63B262B449CDC00BBC82D /* SearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResults.swift; sourceTree = "<group>"; };
|
||||
B9BCC3172B90B3BC00211976 /* Tenor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tenor.swift; sourceTree = "<group>"; };
|
||||
B9BED5152B5D5E6500C9B715 /* PostInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostInteractor.swift; sourceTree = "<group>"; };
|
||||
B9BED5172B5D649C00C9B715 /* PostMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostMenu.swift; sourceTree = "<group>"; };
|
||||
B9BED5192B5D662D00C9B715 /* ShareSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheetController.swift; sourceTree = "<group>"; };
|
||||
B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ThreadedWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B9C20D0A2B921C78004DC9B3 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
B9C20D0C2B921C78004DC9B3 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
B9C20D0F2B921C78004DC9B3 /* ThreadedWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadedWidgetsBundle.swift; sourceTree = "<group>"; };
|
||||
B9C20D112B921C78004DC9B3 /* ThreadedWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadedWidgets.swift; sourceTree = "<group>"; };
|
||||
B9C20D132B921C78004DC9B3 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = "<group>"; };
|
||||
B9C20D152B921C7B004DC9B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
B9C20D172B921C7B004DC9B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B9C20D1E2B921E81004DC9B3 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
B9C20D582B923CDD004DC9B3 /* ThreadedWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ThreadedWidgetsExtension.entitlements; sourceTree = "<group>"; };
|
||||
B9C20D592B923D53004DC9B3 /* Threaded.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Threaded.entitlements; sourceTree = "<group>"; };
|
||||
B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Redeclarations.swift; sourceTree = "<group>"; };
|
||||
B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
B9CC45B92B40AA1E001E4FA5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchStoryboard.storyboard; sourceTree = "<group>"; };
|
||||
|
@ -216,6 +252,16 @@
|
|||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
B9C20D062B921C78004DC9B3 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B9C20D0D2B921C78004DC9B3 /* SwiftUI.framework in Frameworks */,
|
||||
B9C20D0B2B921C78004DC9B3 /* WidgetKit.framework in Frameworks */,
|
||||
B9C20D562B922DAC004DC9B3 /* KeychainSwift in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B9FB94542B2DEECE00D81C07 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -255,6 +301,7 @@
|
|||
B9842C132B2F310C00D9F3C1 /* FetchTimeline.swift */,
|
||||
B9842C152B2F363600D9F3C1 /* TimelineFilter.swift */,
|
||||
B98F47992B653CAE0092000F /* Compressor.swift */,
|
||||
B9BCC3172B90B3BC00211976 /* Tenor.swift */,
|
||||
);
|
||||
path = Content;
|
||||
sourceTree = "<group>";
|
||||
|
@ -273,6 +320,21 @@
|
|||
path = Post;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B9C20D0E2B921C78004DC9B3 /* ThreadedWidgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B9C20D582B923CDD004DC9B3 /* ThreadedWidgetsExtension.entitlements */,
|
||||
B9C20D0F2B921C78004DC9B3 /* ThreadedWidgetsBundle.swift */,
|
||||
B9C20D112B921C78004DC9B3 /* ThreadedWidgets.swift */,
|
||||
B9C20D132B921C78004DC9B3 /* AppIntent.swift */,
|
||||
B9C20D602B949AD7004DC9B3 /* Redeclarations.swift */,
|
||||
B9C20D152B921C7B004DC9B3 /* Assets.xcassets */,
|
||||
B9C20D1E2B921E81004DC9B3 /* Localizable.xcstrings */,
|
||||
B9C20D172B921C7B004DC9B3 /* Info.plist */,
|
||||
);
|
||||
path = ThreadedWidgets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B9D9C6BF2B6A56D500C26A41 /* Notifications */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -296,6 +358,7 @@
|
|||
children = (
|
||||
B9CC45B92B40AA1E001E4FA5 /* README.md */,
|
||||
B9FB94592B2DEECE00D81C07 /* Threaded */,
|
||||
B9C20D0E2B921C78004DC9B3 /* ThreadedWidgets */,
|
||||
B9FB94AB2B2F009F00D81C07 /* AuthService */,
|
||||
B9FB94A82B2F009F00D81C07 /* Frameworks */,
|
||||
B97BCE292B3ED2C80044756D /* LICENSE */,
|
||||
|
@ -309,6 +372,7 @@
|
|||
children = (
|
||||
B9FB94572B2DEECE00D81C07 /* Threaded.app */,
|
||||
B9FB94A72B2F009F00D81C07 /* ThreadedAuthService.appex */,
|
||||
B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -316,6 +380,7 @@
|
|||
B9FB94592B2DEECE00D81C07 /* Threaded */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B9C20D592B923D53004DC9B3 /* Threaded.entitlements */,
|
||||
B9FB94A02B2EF23100D81C07 /* Info.plist */,
|
||||
B9029FC12B81259400AA9B68 /* Secret.plist */,
|
||||
B9FB945A2B2DEECE00D81C07 /* ThreadedApp.swift */,
|
||||
|
@ -423,6 +488,8 @@
|
|||
children = (
|
||||
B9DC692C2B79362700E625B9 /* StoreKit.framework */,
|
||||
B9FB94A92B2F009F00D81C07 /* AuthenticationServices.framework */,
|
||||
B9C20D0A2B921C78004DC9B3 /* WidgetKit.framework */,
|
||||
B9C20D0C2B921C78004DC9B3 /* SwiftUI.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -452,6 +519,26 @@
|
|||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B9C20D1D2B921C7B004DC9B3 /* Build configuration list for PBXNativeTarget "ThreadedWidgetsExtension" */;
|
||||
buildPhases = (
|
||||
B9C20D052B921C78004DC9B3 /* Sources */,
|
||||
B9C20D062B921C78004DC9B3 /* Frameworks */,
|
||||
B9C20D072B921C78004DC9B3 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = ThreadedWidgetsExtension;
|
||||
packageProductDependencies = (
|
||||
B9C20D552B922DAC004DC9B3 /* KeychainSwift */,
|
||||
);
|
||||
productName = ThreadedWidgetsExtension;
|
||||
productReference = B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
B9FB94562B2DEECE00D81C07 /* Threaded */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B9FB94672B2DEECF00D81C07 /* Build configuration list for PBXNativeTarget "Threaded" */;
|
||||
|
@ -465,6 +552,7 @@
|
|||
);
|
||||
dependencies = (
|
||||
B9FB94B32B2F009F00D81C07 /* PBXTargetDependency */,
|
||||
B9C20D192B921C7B004DC9B3 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Threaded;
|
||||
packageProductDependencies = (
|
||||
|
@ -506,9 +594,12 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1510;
|
||||
LastSwiftUpdateCheck = 1520;
|
||||
LastUpgradeCheck = 1510;
|
||||
TargetAttributes = {
|
||||
B9C20D082B921C78004DC9B3 = {
|
||||
CreatedOnToolsVersion = 15.2;
|
||||
};
|
||||
B9FB94562B2DEECE00D81C07 = {
|
||||
CreatedOnToolsVersion = 15.1;
|
||||
};
|
||||
|
@ -540,11 +631,21 @@
|
|||
targets = (
|
||||
B9FB94562B2DEECE00D81C07 /* Threaded */,
|
||||
B9FB94A62B2F009F00D81C07 /* ThreadedAuthService */,
|
||||
B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
B9C20D072B921C78004DC9B3 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B9C20D1F2B921E81004DC9B3 /* Localizable.xcstrings in Resources */,
|
||||
B9C20D162B921C7B004DC9B3 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B9FB94552B2DEECE00D81C07 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -572,6 +673,21 @@
|
|||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
B9C20D052B921C78004DC9B3 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B9C20D282B9229DF004DC9B3 /* LoggedAccounts.swift in Sources */,
|
||||
B9C20D612B949AD7004DC9B3 /* Redeclarations.swift in Sources */,
|
||||
B9C20D122B921C78004DC9B3 /* ThreadedWidgets.swift in Sources */,
|
||||
B9C20D102B921C78004DC9B3 /* ThreadedWidgetsBundle.swift in Sources */,
|
||||
B9C20D142B921C78004DC9B3 /* AppIntent.swift in Sources */,
|
||||
B9C20D372B9229EC004DC9B3 /* AppInfo.swift in Sources */,
|
||||
B9C20D3D2B9229EC004DC9B3 /* Emoji.swift in Sources */,
|
||||
B9C20D342B9229EC004DC9B3 /* Client.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B9FB94532B2DEECE00D81C07 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -636,6 +752,7 @@
|
|||
B98627312B86F23500844245 /* LoggedAccounts.swift in Sources */,
|
||||
B9B63B272B449CDC00BBC82D /* SearchResults.swift in Sources */,
|
||||
B9B63B252B44997400BBC82D /* QuotePostView.swift in Sources */,
|
||||
B9BCC3182B90B3BC00211976 /* Tenor.swift in Sources */,
|
||||
B97BCE242B3DD8400044756D /* HapticManager.swift in Sources */,
|
||||
B9B63B212B442D1500BBC82D /* DynamicTextEditor.swift in Sources */,
|
||||
B9FB949F2B2EF0F200D81C07 /* MastodonRequest.swift in Sources */,
|
||||
|
@ -659,6 +776,11 @@
|
|||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
B9C20D192B921C7B004DC9B3 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */;
|
||||
targetProxy = B9C20D182B921C7B004DC9B3 /* PBXContainerItemProxy */;
|
||||
};
|
||||
B9FB94B32B2F009F00D81C07 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = B9FB94A62B2F009F00D81C07 /* ThreadedAuthService */;
|
||||
|
@ -678,6 +800,72 @@
|
|||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
B9C20D1B2B921C7B004DC9B3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = ThreadedWidgets/ThreadedWidgetsExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = HB5P3BML86;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ThreadedWidgets/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ThreadedWidgets;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded.ThreadedWidgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B9C20D1C2B921C7B004DC9B3 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = ThreadedWidgets/ThreadedWidgetsExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = HB5P3BML86;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ThreadedWidgets/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ThreadedWidgets;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded.ThreadedWidgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
B9FB94652B2DEECF00D81C07 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -803,8 +991,9 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Threaded/Threaded.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1425;
|
||||
CURRENT_PROJECT_VERSION = 500;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Threaded/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = HB5P3BML86;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -823,7 +1012,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.0;
|
||||
MARKETING_VERSION = 0.4.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
|
@ -842,8 +1031,9 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Threaded/Threaded.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1425;
|
||||
CURRENT_PROJECT_VERSION = 500;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Threaded/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = HB5P3BML86;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -862,7 +1052,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.0;
|
||||
MARKETING_VERSION = 0.4.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
|
@ -938,6 +1128,15 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
B9C20D1D2B921C7B004DC9B3 /* Build configuration list for PBXNativeTarget "ThreadedWidgetsExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
B9C20D1B2B921C7B004DC9B3 /* Debug */,
|
||||
B9C20D1C2B921C7B004DC9B3 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
B9FB94522B2DEECE00D81C07 /* Build configuration list for PBXProject "Threaded" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -1051,6 +1250,11 @@
|
|||
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||
productName = KeychainSwift;
|
||||
};
|
||||
B9C20D552B922DAC004DC9B3 /* KeychainSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||
productName = KeychainSwift;
|
||||
};
|
||||
B9FB94832B2E20AF00D81C07 /* SwiftSoup */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = B9FB94822B2E20AF00D81C07 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?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/>
|
||||
</plist>
|
|
@ -8,7 +8,6 @@ struct QuotePostView: View {
|
|||
|
||||
var body: some View {
|
||||
CompactPostView(status: status, quoted: true)
|
||||
.environmentObject(navigator)
|
||||
.frame(maxWidth: 250, maxHeight: 200)
|
||||
.padding(15)
|
||||
.padding([.horizontal], 20)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//Made by Lumaa
|
||||
|
||||
import Foundation
|
||||
import KeychainSwift
|
||||
|
||||
@Observable
|
||||
public class AccountManager: ObservableObject {
|
||||
|
@ -52,121 +51,3 @@ public class AccountManager: ObservableObject {
|
|||
return account
|
||||
}
|
||||
}
|
||||
|
||||
public struct AppAccount: Codable, Identifiable, Hashable {
|
||||
public let server: String
|
||||
public var accountName: String?
|
||||
public let oauthToken: OauthToken?
|
||||
|
||||
private static let saveKey: String = "threaded-appaccount.current"
|
||||
private static var keychain: KeychainSwift {
|
||||
let kc = KeychainSwift()
|
||||
// synchronise later
|
||||
return kc
|
||||
}
|
||||
|
||||
public var key: String {
|
||||
if let oauthToken {
|
||||
"\(server):\(oauthToken.createdAt)"
|
||||
} else {
|
||||
"\(server):anonymous"
|
||||
}
|
||||
}
|
||||
|
||||
public var id: String {
|
||||
key
|
||||
}
|
||||
|
||||
public init(server: String, accountName: String?, oauthToken: OauthToken? = nil) {
|
||||
self.server = server
|
||||
self.accountName = accountName
|
||||
self.oauthToken = oauthToken
|
||||
}
|
||||
|
||||
public static func clear() {
|
||||
Self.keychain.delete(Self.saveKey)
|
||||
}
|
||||
|
||||
public func clear() {
|
||||
Self.clear()
|
||||
}
|
||||
|
||||
/// This function only works with the given `AppAccount`
|
||||
public func saveAsCurrent(_ appAccount: AppAccount? = nil) {
|
||||
let encoder = JSONEncoder()
|
||||
if let data = try? encoder.encode(appAccount ?? self) {
|
||||
Self.keychain.set(data, forKey: Self.saveKey, withAccess: .accessibleWhenUnlocked)
|
||||
} else {
|
||||
fatalError("Couldn't encode AppAccount correctly to save")
|
||||
}
|
||||
}
|
||||
|
||||
/// This function only works with the last saved `AppAccount` or with the given `Data`
|
||||
public static func loadAsCurrent(_ data: Data? = nil) -> Self? {
|
||||
let decoder = JSONDecoder()
|
||||
if let newData = data ?? keychain.getData(Self.saveKey) {
|
||||
if let decoded = try? decoder.decode(Self.self, from: newData) {
|
||||
return decoded
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppAccount: Sendable {}
|
||||
|
||||
public enum Oauth: Endpoint {
|
||||
case authorize(clientId: String)
|
||||
case token(code: String, clientId: String, clientSecret: String)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
case .authorize:
|
||||
"oauth/authorize"
|
||||
case .token:
|
||||
"oauth/token"
|
||||
}
|
||||
}
|
||||
|
||||
public var jsonValue: Encodable? {
|
||||
switch self {
|
||||
case let .token(code, clientId, clientSecret):
|
||||
TokenData(clientId: clientId, clientSecret: clientSecret, code: code)
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct TokenData: Encodable {
|
||||
public let grantType = "authorization_code"
|
||||
public let clientId: String
|
||||
public let clientSecret: String
|
||||
public let redirectUri = AppInfo.scheme
|
||||
public let code: String
|
||||
public let scope = AppInfo.scopes
|
||||
}
|
||||
|
||||
public func queryItems() -> [URLQueryItem]? {
|
||||
switch self {
|
||||
case let .authorize(clientId):
|
||||
return [
|
||||
.init(name: "response_type", value: "code"),
|
||||
.init(name: "client_id", value: clientId),
|
||||
.init(name: "redirect_uri", value: AppInfo.scheme),
|
||||
.init(name: "scope", value: AppInfo.scopes),
|
||||
]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct OauthToken: Codable, Hashable, Sendable {
|
||||
public let accessToken: String
|
||||
public let tokenType: String
|
||||
public let scope: String
|
||||
public let createdAt: Double
|
||||
}
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import KeychainSwift
|
||||
|
||||
@Model
|
||||
class LoggedAccount {
|
||||
let token: OauthToken
|
||||
let token: OauthToken = OauthToken(accessToken: "ABC", tokenType: "ABC", scope: "ABC", createdAt: 0.0)
|
||||
let acct: String
|
||||
let app: AppAccount?
|
||||
|
||||
init(token: OauthToken, acct: String) {
|
||||
init(token: OauthToken = OauthToken(accessToken: "ABC", tokenType: "ABC", scope: "ABC", createdAt: 0.0), acct: String) {
|
||||
self.token = token
|
||||
self.acct = acct
|
||||
self.app = nil
|
||||
|
@ -31,3 +32,75 @@ public extension View {
|
|||
.modelContainer(for: LoggedAccount.self)
|
||||
}
|
||||
}
|
||||
|
||||
public struct AppAccount: Codable, Identifiable, Hashable {
|
||||
public let server: String
|
||||
public var accountName: String?
|
||||
public let oauthToken: OauthToken?
|
||||
|
||||
private static let saveKey: String = "threaded-appaccount.current"
|
||||
private static var keychain: KeychainSwift {
|
||||
let kc = KeychainSwift()
|
||||
// synchronise later
|
||||
return kc
|
||||
}
|
||||
|
||||
public var key: String {
|
||||
if let oauthToken {
|
||||
"\(server):\(oauthToken.createdAt)"
|
||||
} else {
|
||||
"\(server):anonymous"
|
||||
}
|
||||
}
|
||||
|
||||
public var id: String {
|
||||
key
|
||||
}
|
||||
|
||||
public init(server: String, accountName: String?, oauthToken: OauthToken? = nil) {
|
||||
self.server = server
|
||||
self.accountName = accountName
|
||||
self.oauthToken = oauthToken
|
||||
}
|
||||
|
||||
public static func clear() {
|
||||
Self.keychain.delete(Self.saveKey)
|
||||
}
|
||||
|
||||
public func clear() {
|
||||
Self.clear()
|
||||
}
|
||||
|
||||
/// This function only works with the given `AppAccount`
|
||||
public func saveAsCurrent(_ appAccount: AppAccount? = nil) {
|
||||
let encoder = JSONEncoder()
|
||||
if let data = try? encoder.encode(appAccount ?? self) {
|
||||
Self.keychain.set(data, forKey: Self.saveKey, withAccess: .accessibleWhenUnlocked)
|
||||
} else {
|
||||
fatalError("Couldn't encode AppAccount correctly to save")
|
||||
}
|
||||
}
|
||||
|
||||
/// This function only works with the last saved `AppAccount` or with the given `Data`
|
||||
public static func loadAsCurrent(_ data: Data? = nil) -> Self? {
|
||||
let decoder = JSONDecoder()
|
||||
if let newData = data ?? keychain.getData(Self.saveKey) {
|
||||
if let decoded = try? decoder.decode(Self.self, from: newData) {
|
||||
return decoded
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppAccount: Sendable {}
|
||||
|
||||
public struct OauthToken: Codable, Hashable, Sendable {
|
||||
public let accessToken: String
|
||||
public let tokenType: String
|
||||
public let scope: String
|
||||
public let createdAt: Double
|
||||
}
|
||||
|
|
|
@ -139,6 +139,10 @@ public final class Client: Equatable, Identifiable, Hashable {
|
|||
try await makeEntityRequest(endpoint: endpoint, method: "GET", forceVersion: forceVersion)
|
||||
}
|
||||
|
||||
public func getString(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> String {
|
||||
try await makeEntityRequestForString(endpoint: endpoint, method: "GET", forceVersion: forceVersion)
|
||||
}
|
||||
|
||||
public func getWithLink<Entity: Decodable>(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) {
|
||||
let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
|
||||
var linkHandler: LinkHandler?
|
||||
|
@ -180,10 +184,7 @@ public final class Client: Equatable, Identifiable, Hashable {
|
|||
return httpResponse as? HTTPURLResponse
|
||||
}
|
||||
|
||||
private func makeEntityRequest<Entity: Decodable>(endpoint: Endpoint,
|
||||
method: String,
|
||||
forceVersion: Version? = nil) async throws -> Entity
|
||||
{
|
||||
private func makeEntityRequest<Entity: Decodable>(endpoint: Endpoint, method: String, forceVersion: Version? = nil) async throws -> Entity {
|
||||
let url = try makeURL(endpoint: endpoint, forceVersion: forceVersion)
|
||||
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
|
||||
let (data, httpResponse) = try await urlSession.data(for: request)
|
||||
|
@ -202,6 +203,14 @@ public final class Client: Equatable, Identifiable, Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
private func makeEntityRequestForString(endpoint: Endpoint, method: String, forceVersion: Version? = nil) async throws -> String {
|
||||
let url = try makeURL(endpoint: endpoint, forceVersion: forceVersion)
|
||||
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
|
||||
let (data, httpResponse) = try await urlSession.data(for: request)
|
||||
logResponseOnError(httpResponse: httpResponse, data: data)
|
||||
return String(data: data, encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
public func oauthURL() async throws -> URL {
|
||||
let app: InstanceApp = try await post(endpoint: Apps.registerApp)
|
||||
critical.withLock { $0.oauthApp = app }
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//Made by Lumaa
|
||||
|
||||
import Foundation
|
||||
|
||||
class Tenor {
|
||||
private static let url: String = "https://tenor.googleapis.com/v2"
|
||||
private static var token: String = ""
|
||||
|
||||
private static let limit: Int = 20
|
||||
private static let contentFilter: String = "off" // all content other than nudity
|
||||
private static let mediaFilter: String = "preview,tinygif,gif,tinywebm,webm"
|
||||
|
||||
init(token: String) {
|
||||
_ = Self.getToken()
|
||||
}
|
||||
|
||||
static func getToken() -> String? {
|
||||
guard let plist = AppDelegate.readSecret() else { return nil }
|
||||
Self.token = plist["Tenor_Token"] ?? ""
|
||||
return Self.token
|
||||
}
|
||||
|
||||
func search(query: String) {
|
||||
let params: [URLQueryItem] = [
|
||||
.init(name: "q", value: query),
|
||||
.init(name: "key", value: Self.token),
|
||||
.init(name: "client_key", value: "\(AppInfo.clientName)-\(AppInfo.appVersion)"),
|
||||
.init(name: "contentfilter", value: Self.contentFilter),
|
||||
.init(name: "media_filter", value: Self.mediaFilter)
|
||||
]
|
||||
|
||||
if var comp = URLComponents(string: "\(Self.url)/search") {
|
||||
comp.queryItems = params
|
||||
if let url = comp.url {
|
||||
var req = URLRequest(url: url)
|
||||
req.httpMethod = "GET"
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
var jsonResponse: [String: Any]?
|
||||
URLSession.shared.dataTask(with: req) { (data, response, error) in
|
||||
defer { semaphore.signal() }
|
||||
if let data = data {
|
||||
jsonResponse = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,52 @@ extension Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public enum Oauth: Endpoint {
|
||||
case authorize(clientId: String)
|
||||
case token(code: String, clientId: String, clientSecret: String)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
case .authorize:
|
||||
"oauth/authorize"
|
||||
case .token:
|
||||
"oauth/token"
|
||||
}
|
||||
}
|
||||
|
||||
public var jsonValue: Encodable? {
|
||||
switch self {
|
||||
case let .token(code, clientId, clientSecret):
|
||||
TokenData(clientId: clientId, clientSecret: clientSecret, code: code)
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct TokenData: Encodable {
|
||||
public let grantType = "authorization_code"
|
||||
public let clientId: String
|
||||
public let clientSecret: String
|
||||
public let redirectUri = AppInfo.scheme
|
||||
public let code: String
|
||||
public let scope = AppInfo.scopes
|
||||
}
|
||||
|
||||
public func queryItems() -> [URLQueryItem]? {
|
||||
switch self {
|
||||
case let .authorize(clientId):
|
||||
return [
|
||||
.init(name: "response_type", value: "code"),
|
||||
.init(name: "client_id", value: clientId),
|
||||
.init(name: "redirect_uri", value: AppInfo.scheme),
|
||||
.init(name: "scope", value: AppInfo.scopes),
|
||||
]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct LinkHandler {
|
||||
public let rawLink: String
|
||||
|
||||
|
|
|
@ -49,6 +49,13 @@ extension [Notification] {
|
|||
}
|
||||
}
|
||||
|
||||
public extension Array where Element: Hashable {
|
||||
func uniqued() -> [Element] {
|
||||
var seen = Set<Element>()
|
||||
return filter { seen.insert($0).inserted }
|
||||
}
|
||||
}
|
||||
|
||||
public struct GroupedNotification: Identifiable, Hashable {
|
||||
public var id: String? { notifications.first?.id }
|
||||
public var notifications: [Notification]
|
||||
|
|
|
@ -2916,4 +2916,4 @@
|
|||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?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>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.lumaa.ThreadedApp</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -318,6 +318,6 @@ private extension View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
AttachmentView(attachments: [.init(id: "ABC", type: "image", url: URL(string: "https://i.stack.imgur.com/HX3Aj.png"), previewUrl: URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg"), description: String("This displays the TabView with a page indicator at the bottom"), meta: nil), .init(id: "DEF", type: "image", url: URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg"), previewUrl: URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg"), description: nil, meta: nil)])
|
||||
AttachmentView(attachments: [.init(id: "ABC", type: "image", url: URL(string: "https://i.stack.imgur.com/HX3Aj.png"), previewUrl: URL.placeholder, description: String("This displays the TabView with a page indicator at the bottom"), meta: nil), .init(id: "DEF", type: "image", url: URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg"), previewUrl: URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg"), description: nil, meta: nil)])
|
||||
.environment(AppDelegate())
|
||||
}
|
||||
|
|
|
@ -147,10 +147,3 @@ struct NotificationsView: View {
|
|||
var image: Image? = Image(systemName: "paperplane")
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array where Element: Hashable {
|
||||
func uniqued() -> [Element] {
|
||||
var seen = Set<Element>()
|
||||
return filter { seen.insert($0).inserted }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import SwiftUI
|
|||
struct PostDetailsView: View {
|
||||
@EnvironmentObject private var navigator: Navigator
|
||||
@Environment(AccountManager.self) private var accountManager: AccountManager
|
||||
@Environment(AppDelegate.self) private var delegate: AppDelegate
|
||||
|
||||
var detailedStatus: Status
|
||||
|
||||
|
@ -128,6 +129,8 @@ struct PostDetailsView: View {
|
|||
statusesContext.append(contentsOf: data.context.descendants)
|
||||
|
||||
statuses = statusesContext
|
||||
|
||||
// await loadEmbeddedStatus()
|
||||
} catch {
|
||||
if let error = error as? ServerError, error.httpCode == 404 {
|
||||
_ = navigator.path.popLast()
|
||||
|
|
|
@ -304,26 +304,6 @@ extension ShopView {
|
|||
// .environment(\.locale, Locale(identifier: "en-us"))
|
||||
}
|
||||
|
||||
private func hasActuallyPlus(customerInfo: CustomerInfo?) -> Bool {
|
||||
return customerInfo?.entitlements[PlusEntitlements.lifetime.getEntitlementId()]?.isActive == true || customerInfo?.entitlements[PlusEntitlements.monthly.getEntitlementId()]?.isActive == true || customerInfo?.entitlements[PlusEntitlements.yearly.getEntitlementId()]?.isActive == true
|
||||
}
|
||||
|
||||
private func purchase(entitlement: PlusEntitlements) {
|
||||
Purchases.shared.getOfferings { (offerings, error) in
|
||||
if let product = entitlement.toPackage(offerings: offerings) {
|
||||
Purchases.shared.purchase(package: product) { (transaction, customerInfo, error, userCancelled) in
|
||||
if hasActuallyPlus(customerInfo: customerInfo) {
|
||||
print("BOUGHT PLUS")
|
||||
AppDelegate.premium = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if let e = error {
|
||||
print(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PlusEntitlements: String {
|
||||
case monthly
|
||||
case yearly
|
||||
|
@ -355,3 +335,23 @@ enum PlusEntitlements: String {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func hasActuallyPlus(customerInfo: CustomerInfo?) -> Bool {
|
||||
return customerInfo?.entitlements[PlusEntitlements.lifetime.getEntitlementId()]?.isActive == true || customerInfo?.entitlements[PlusEntitlements.monthly.getEntitlementId()]?.isActive == true || customerInfo?.entitlements[PlusEntitlements.yearly.getEntitlementId()]?.isActive == true
|
||||
}
|
||||
|
||||
private func purchase(entitlement: PlusEntitlements) {
|
||||
Purchases.shared.getOfferings { (offerings, error) in
|
||||
if let product = entitlement.toPackage(offerings: offerings) {
|
||||
Purchases.shared.purchase(package: product) { (transaction, customerInfo, error, userCancelled) in
|
||||
if hasActuallyPlus(customerInfo: customerInfo) {
|
||||
print("BOUGHT PLUS")
|
||||
AppDelegate.premium = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if let e = error {
|
||||
print(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
//Made by Lumaa
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import WidgetKit
|
||||
import AppIntents
|
||||
|
||||
/// Widgets that require to select an account will use this `ConfigurationIntent`
|
||||
struct AccountAppIntent: WidgetConfigurationIntent {
|
||||
static var title: LocalizedStringResource = "widget.follow-count"
|
||||
static var description = IntentDescription("widget.follow-count.description")
|
||||
|
||||
@Parameter(title: "widget.select-account")
|
||||
var account: AccountEntity?
|
||||
}
|
||||
|
||||
struct AccountEntity: AppEntity {
|
||||
let client: Client
|
||||
let id: String
|
||||
let username: String
|
||||
/// Bearer token
|
||||
let token: OauthToken
|
||||
|
||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "account"
|
||||
static var defaultQuery = AccountQuery()
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(stringLiteral: "@\(username)")
|
||||
}
|
||||
|
||||
init(acct: String, username: String, token: OauthToken) {
|
||||
self.client = Client(server: String(acct.split(separator: "@")[1]), version: .v2, oauthToken: token)
|
||||
self.id = acct
|
||||
self.username = username
|
||||
self.token = token
|
||||
}
|
||||
|
||||
init(loggedAccount: LoggedAccount) {
|
||||
self.client = Client(server: String(loggedAccount.acct.split(separator: "@")[1]), version: .v2, oauthToken: loggedAccount.token)
|
||||
self.id = loggedAccount.acct
|
||||
self.username = String(loggedAccount.acct.split(separator: "@")[0])
|
||||
self.token = loggedAccount.token
|
||||
}
|
||||
|
||||
func toUIImage() -> URL? {
|
||||
Task { [self] in
|
||||
if let account: String = try? await client.getString(endpoint: Accounts.verifyCredentials) {
|
||||
do {
|
||||
if let serialized: [String : Any] = try JSONSerialization.jsonObject(with: account.data(using: String.Encoding.utf8) ?? Data()) as? [String : Any] {
|
||||
let avatar: String = serialized["avatar"] as! String
|
||||
return URL(string: avatar) ?? URL.placeholder
|
||||
}
|
||||
} catch {
|
||||
print("Error fetching image data: \(error)")
|
||||
}
|
||||
}
|
||||
return URL.placeholder
|
||||
}
|
||||
return URL.placeholder
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct AccountQuery: EntityQuery {
|
||||
private static func getAccountsQuery() -> [LoggedAccount] {
|
||||
let query = Query<LoggedAccount, [LoggedAccount]>()
|
||||
let loggedAccounts: [LoggedAccount] = query.wrappedValue
|
||||
return loggedAccounts
|
||||
}
|
||||
|
||||
private static func getAccounts() -> [LoggedAccount] {
|
||||
guard let modelContainer: ModelContainer = try? ModelContainer(for: LoggedAccount.self, configurations: ModelConfiguration(isStoredInMemoryOnly: false)) else { return [] }
|
||||
let modelContext = ModelContext(modelContainer)
|
||||
let loggedAccounts = try? modelContext.fetch(FetchDescriptor<LoggedAccount>())
|
||||
|
||||
return loggedAccounts ?? []
|
||||
}
|
||||
|
||||
func entities(for identifiers: [AccountEntity.ID]) async throws -> [AccountEntity] {
|
||||
let accountEntities: [AccountEntity] = Self.getAccounts().map({ return AccountEntity(loggedAccount: $0) })
|
||||
return accountEntities.filter({ identifiers.contains($0.id) })
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [AccountEntity] {
|
||||
let accountEntities: [AccountEntity] = Self.getAccounts().map({ return AccountEntity(loggedAccount: $0) })
|
||||
return accountEntities
|
||||
}
|
||||
|
||||
func defaultResult() async -> AccountEntity? {
|
||||
try? await suggestedEntities().first
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x10",
|
||||
"green" : "0x10",
|
||||
"red" : "0x10"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"@%@" : {
|
||||
|
||||
},
|
||||
"account" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget.follow-count" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Follower Count"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget.follow-count.description" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Get the current amount of followers of the accounts you have logged in Threaded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget.followers" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Followers"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget.select-account" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Select an account"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
//Made by Lumaa
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
// this is messy but it's alr
|
||||
|
||||
public class AppDelegate: NSObject, UIWindowSceneDelegate, Sendable, UIApplicationDelegate {
|
||||
public var window: UIWindow?
|
||||
public private(set) var windowWidth: CGFloat = UIScreen.main.bounds.size.width
|
||||
public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height
|
||||
public private(set) var secret: [String: String] = [:]
|
||||
|
||||
public func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = scene as? UIWindowScene else { return }
|
||||
window = windowScene.keyWindow
|
||||
}
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
|
||||
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
let data = try! Data(contentsOf: url)
|
||||
if let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil) as? [String: String] {
|
||||
secret = plist
|
||||
}
|
||||
}
|
||||
|
||||
windowWidth = window?.bounds.size.width ?? UIScreen.main.bounds.size.width
|
||||
windowHeight = window?.bounds.size.height ?? UIScreen.main.bounds.size.height
|
||||
Self.observedSceneDelegate.insert(self)
|
||||
_ = Self.observer // just for activating the lazy static property
|
||||
}
|
||||
|
||||
static func readSecret() -> [String: String]? {
|
||||
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
let data = try! Data(contentsOf: url)
|
||||
if let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil) as? [String: String] {
|
||||
return plist
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
Task { @MainActor in
|
||||
Self.observedSceneDelegate.remove(self)
|
||||
}
|
||||
}
|
||||
|
||||
private static var observedSceneDelegate: Set<AppDelegate> = []
|
||||
private static let observer = Task {
|
||||
while true {
|
||||
try? await Task.sleep(for: .seconds(0.1))
|
||||
for delegate in observedSceneDelegate {
|
||||
let newWidth = delegate.window?.bounds.size.width ?? UIScreen.main.bounds.size.width
|
||||
if delegate.windowWidth != newWidth {
|
||||
delegate.windowWidth = newWidth
|
||||
}
|
||||
let newHeight = delegate.window?.bounds.size.height ?? UIScreen.main.bounds.size.height
|
||||
if delegate.windowHeight != newHeight {
|
||||
delegate.windowHeight = newHeight
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension URL {
|
||||
static let placeholder: URL = URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg")!
|
||||
}
|
||||
|
||||
extension AppInfo {
|
||||
static var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
|
||||
}
|
||||
|
||||
public protocol Endpoint: Sendable {
|
||||
func path() -> String
|
||||
func queryItems() -> [URLQueryItem]?
|
||||
var jsonValue: Encodable? { get }
|
||||
}
|
||||
|
||||
public extension Endpoint {
|
||||
var jsonValue: Encodable? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint {
|
||||
func makePaginationParam(sinceId: String?, maxId: String?, mindId: String?) -> [URLQueryItem]? {
|
||||
if let sinceId {
|
||||
return [.init(name: "since_id", value: sinceId)]
|
||||
} else if let maxId {
|
||||
return [.init(name: "max_id", value: maxId)]
|
||||
} else if let mindId {
|
||||
return [.init(name: "min_id", value: mindId)]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public enum Oauth: Endpoint {
|
||||
case authorize(clientId: String)
|
||||
case token(code: String, clientId: String, clientSecret: String)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
case .authorize:
|
||||
"oauth/authorize"
|
||||
case .token:
|
||||
"oauth/token"
|
||||
}
|
||||
}
|
||||
|
||||
public var jsonValue: Encodable? {
|
||||
switch self {
|
||||
case let .token(code, clientId, clientSecret):
|
||||
TokenData(clientId: clientId, clientSecret: clientSecret, code: code)
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct TokenData: Encodable {
|
||||
public let grantType = "authorization_code"
|
||||
public let clientId: String
|
||||
public let clientSecret: String
|
||||
public let redirectUri = AppInfo.scheme
|
||||
public let code: String
|
||||
public let scope = AppInfo.scopes
|
||||
}
|
||||
|
||||
public func queryItems() -> [URLQueryItem]? {
|
||||
switch self {
|
||||
case let .authorize(clientId):
|
||||
return [
|
||||
.init(name: "response_type", value: "code"),
|
||||
.init(name: "client_id", value: clientId),
|
||||
.init(name: "redirect_uri", value: AppInfo.scheme),
|
||||
.init(name: "scope", value: AppInfo.scopes),
|
||||
]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Accounts: Endpoint {
|
||||
case verifyCredentials
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
case .verifyCredentials:
|
||||
"accounts/verify_credentials"
|
||||
}
|
||||
}
|
||||
|
||||
public func queryItems() -> [URLQueryItem]? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstanceApp: Codable, Identifiable {
|
||||
public let id: String
|
||||
public let name: String
|
||||
public let website: URL?
|
||||
public let redirectUri: String
|
||||
public let clientId: String
|
||||
public let clientSecret: String
|
||||
public let vapidKey: String?
|
||||
}
|
||||
|
||||
extension InstanceApp: Sendable {}
|
||||
|
||||
public struct ServerError: Decodable, Error {
|
||||
public let error: String?
|
||||
public var httpCode: Int?
|
||||
}
|
||||
|
||||
public enum Apps: Endpoint {
|
||||
case registerApp
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
case .registerApp:
|
||||
"apps"
|
||||
}
|
||||
}
|
||||
|
||||
public func queryItems() -> [URLQueryItem]? {
|
||||
switch self {
|
||||
case .registerApp:
|
||||
return [
|
||||
.init(name: "client_name", value: AppInfo.clientName),
|
||||
.init(name: "redirect_uris", value: AppInfo.scheme),
|
||||
.init(name: "scopes", value: AppInfo.scopes),
|
||||
.init(name: "website", value: AppInfo.website),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct LinkHandler {
|
||||
public let rawLink: String
|
||||
|
||||
public var maxId: String? {
|
||||
do {
|
||||
let regex = try Regex("max_id=[0-9]+")
|
||||
if let match = rawLink.firstMatch(of: regex) {
|
||||
return match.output.first?.substring?.replacingOccurrences(of: "max_id=", with: "")
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension LinkHandler: Sendable {}
|
|
@ -0,0 +1,186 @@
|
|||
//Made by Lumaa
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct Provider: AppIntentTimelineProvider {
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
let placeholder: UIImage = UIImage(systemName: "person.crop.circle") ?? UIImage()
|
||||
placeholder.withTintColor(UIColor.label)
|
||||
return SimpleEntry(date: Date(), pfp: placeholder, followers: 38, configuration: AccountAppIntent())
|
||||
}
|
||||
|
||||
func snapshot(for configuration: AccountAppIntent, in context: Context) async -> SimpleEntry {
|
||||
let data = await getData(configuration: configuration)
|
||||
return SimpleEntry(date: Date(), pfp: data.0, followers: data.1, configuration: configuration)
|
||||
}
|
||||
|
||||
func timeline(for configuration: AccountAppIntent, in context: Context) async -> Timeline<SimpleEntry> {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
let data = await getData(configuration: configuration)
|
||||
|
||||
// Generate a timeline consisting of two entries an hour apart, starting from the current date.
|
||||
let currentDate = Date()
|
||||
for hourOffset in 0 ..< 2 {
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||
let entry = SimpleEntry(date: entryDate, pfp: data.0, followers: data.1, configuration: configuration)
|
||||
entries.append(entry)
|
||||
}
|
||||
|
||||
return Timeline(entries: entries, policy: .atEnd)
|
||||
}
|
||||
|
||||
private func getData(configuration: AccountAppIntent) async -> (UIImage, Int) {
|
||||
var pfp: UIImage = UIImage(systemName: "person.crop.circle") ?? UIImage()
|
||||
pfp.withTintColor(UIColor.label)
|
||||
if let account = configuration.account {
|
||||
do {
|
||||
let acc = try await account.client.getString(endpoint: Accounts.verifyCredentials, forceVersion: .v1)
|
||||
|
||||
if let serialized: [String : Any] = try JSONSerialization.jsonObject(with: acc.data(using: String.Encoding.utf8) ?? Data()) as? [String : Any] {
|
||||
let avatar: String = serialized["avatar"] as! String
|
||||
let task = try await URLSession.shared.data(from: URL(string: avatar)!)
|
||||
pfp = UIImage(data: task.0) ?? UIImage()
|
||||
|
||||
let followers: Int = serialized["followers_count"] as! Int
|
||||
return (pfp, followers)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
return (pfp, 0)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let pfp: UIImage
|
||||
let followers: Int
|
||||
let configuration: AccountAppIntent
|
||||
}
|
||||
|
||||
struct FollowCountWidgetView: View {
|
||||
@Environment(\.widgetFamily) private var family: WidgetFamily
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
if let account = entry.configuration.account {
|
||||
ZStack {
|
||||
if family == WidgetFamily.systemSmall {
|
||||
small
|
||||
} else if family == WidgetFamily.systemMedium {
|
||||
medium
|
||||
} else if family == WidgetFamily.accessoryRectangular {
|
||||
rectangular
|
||||
} else {
|
||||
Text(String("Unsupported WidgetFamily"))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.modelContainer(for: [LoggedAccount.self])
|
||||
} else {
|
||||
Text("widget.select-account")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
var small: some View {
|
||||
VStack(alignment: .center) {
|
||||
Image(uiImage: entry.pfp)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 60, height: 60)
|
||||
.foregroundStyle(Color.white)
|
||||
.clipShape(Circle())
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(entry.followers, format: .number.notation(.compactName))
|
||||
.font(.title.monospacedDigit().bold())
|
||||
.contentTransition(.numericText())
|
||||
Text("widget.followers")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.gray)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("@\(entry.configuration.account!.username)")
|
||||
.redacted(reason: .privacy)
|
||||
.font(.caption.bold())
|
||||
}
|
||||
}
|
||||
|
||||
var medium: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center, spacing: 10) {
|
||||
Image(uiImage: entry.pfp)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 60, height: 60)
|
||||
.foregroundStyle(Color.white)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text("@\(entry.configuration.account!.username)")
|
||||
.redacted(reason: .privacy)
|
||||
.font(.caption.bold())
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
Text(entry.followers, format: .number.notation(.compactName))
|
||||
.font(.title.monospacedDigit().bold())
|
||||
.contentTransition(.numericText())
|
||||
Text("widget.followers")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.gray)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
var rectangular: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text("@\(entry.configuration.account!.username)")
|
||||
.multilineTextAlignment(.leading)
|
||||
.font(.caption)
|
||||
.opacity(0.7)
|
||||
|
||||
Text(entry.followers, format: .number.notation(.compactName))
|
||||
.multilineTextAlignment(.leading)
|
||||
.font(.system(size: 32, weight: .bold).monospacedDigit())
|
||||
.contentTransition(.numericText())
|
||||
}
|
||||
.padding(.horizontal, 7.5)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FollowCountWidget: Widget {
|
||||
let kind: String = "FollowCountWidget"
|
||||
let modelContainer: ModelContainer
|
||||
|
||||
init() {
|
||||
guard let modelContainer: ModelContainer = try? .init(for: LoggedAccount.self, configurations: ModelConfiguration(isStoredInMemoryOnly: true)) else { fatalError("Couldn't get LoggedAccounts") }
|
||||
self.modelContainer = modelContainer
|
||||
}
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(kind: kind, intent: AccountAppIntent.self, provider: Provider()) { entry in
|
||||
FollowCountWidgetView(entry: entry)
|
||||
.containerBackground(Color("WidgetBackground"), for: .widget)
|
||||
}
|
||||
.configurationDisplayName("widget.follow-count")
|
||||
.description("widget.follow-count.description")
|
||||
.supportedFamilies([.systemSmall, .systemMedium, .accessoryRectangular])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
//Made by Lumaa
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
struct ThreadedWidgetsBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
FollowCountWidget()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?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>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.lumaa.ThreadedApp</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
Loading…
Reference in New Issue