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 */; };
|
B9B63B232B447B8000BBC82D /* PostCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B222B447B8000BBC82D /* PostCardView.swift */; };
|
||||||
B9B63B252B44997400BBC82D /* QuotePostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B242B44997400BBC82D /* QuotePostView.swift */; };
|
B9B63B252B44997400BBC82D /* QuotePostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B242B44997400BBC82D /* QuotePostView.swift */; };
|
||||||
B9B63B272B449CDC00BBC82D /* SearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B63B262B449CDC00BBC82D /* SearchResults.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 */; };
|
B9BED5162B5D5E6500C9B715 /* PostInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5152B5D5E6500C9B715 /* PostInteractor.swift */; };
|
||||||
B9BED5182B5D649C00C9B715 /* PostMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5172B5D649C00C9B715 /* PostMenu.swift */; };
|
B9BED5182B5D649C00C9B715 /* PostMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5172B5D649C00C9B715 /* PostMenu.swift */; };
|
||||||
B9BED51A2B5D662D00C9B715 /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5192B5D662D00C9B715 /* ShareSheetController.swift */; };
|
B9BED51A2B5D662D00C9B715 /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5192B5D662D00C9B715 /* ShareSheetController.swift */; };
|
||||||
B9BF54072B6B6823004B24E7 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B9BF54062B6B6823004B24E7 /* KeychainSwift */; };
|
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 */; };
|
B9CC45B82B40A2D6001E4FA5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */; };
|
||||||
B9CFC43B2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */; };
|
B9CFC43B2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */; };
|
||||||
B9D365612B79A1BE004C1255 /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D365602B79A1BE004C1255 /* MailView.swift */; };
|
B9D365612B79A1BE004C1255 /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D365602B79A1BE004C1255 /* MailView.swift */; };
|
||||||
|
@ -102,6 +117,13 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
B9C20D182B921C7B004DC9B3 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = B9FB944F2B2DEECE00D81C07 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = B9C20D082B921C78004DC9B3;
|
||||||
|
remoteInfo = ThreadedWidgetsExtension;
|
||||||
|
};
|
||||||
B9FB94B22B2F009F00D81C07 /* PBXContainerItemProxy */ = {
|
B9FB94B22B2F009F00D81C07 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = B9FB944F2B2DEECE00D81C07 /* Project object */;
|
containerPortal = B9FB944F2B2DEECE00D81C07 /* Project object */;
|
||||||
|
@ -119,6 +141,7 @@
|
||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
B9FB94B42B2F009F00D81C07 /* ThreadedAuthService.appex in Embed Foundation Extensions */,
|
B9FB94B42B2F009F00D81C07 /* ThreadedAuthService.appex in Embed Foundation Extensions */,
|
||||||
|
B9C20D1A2B921C7B004DC9B3 /* ThreadedWidgetsExtension.appex in Embed Foundation Extensions */,
|
||||||
);
|
);
|
||||||
name = "Embed Foundation Extensions";
|
name = "Embed Foundation Extensions";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -162,9 +185,22 @@
|
||||||
B9B63B222B447B8000BBC82D /* PostCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCardView.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchStoryboard.storyboard; sourceTree = "<group>"; };
|
||||||
|
@ -216,6 +252,16 @@
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase 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 */ = {
|
B9FB94542B2DEECE00D81C07 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -255,6 +301,7 @@
|
||||||
B9842C132B2F310C00D9F3C1 /* FetchTimeline.swift */,
|
B9842C132B2F310C00D9F3C1 /* FetchTimeline.swift */,
|
||||||
B9842C152B2F363600D9F3C1 /* TimelineFilter.swift */,
|
B9842C152B2F363600D9F3C1 /* TimelineFilter.swift */,
|
||||||
B98F47992B653CAE0092000F /* Compressor.swift */,
|
B98F47992B653CAE0092000F /* Compressor.swift */,
|
||||||
|
B9BCC3172B90B3BC00211976 /* Tenor.swift */,
|
||||||
);
|
);
|
||||||
path = Content;
|
path = Content;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -273,6 +320,21 @@
|
||||||
path = Post;
|
path = Post;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
B9D9C6BF2B6A56D500C26A41 /* Notifications */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -296,6 +358,7 @@
|
||||||
children = (
|
children = (
|
||||||
B9CC45B92B40AA1E001E4FA5 /* README.md */,
|
B9CC45B92B40AA1E001E4FA5 /* README.md */,
|
||||||
B9FB94592B2DEECE00D81C07 /* Threaded */,
|
B9FB94592B2DEECE00D81C07 /* Threaded */,
|
||||||
|
B9C20D0E2B921C78004DC9B3 /* ThreadedWidgets */,
|
||||||
B9FB94AB2B2F009F00D81C07 /* AuthService */,
|
B9FB94AB2B2F009F00D81C07 /* AuthService */,
|
||||||
B9FB94A82B2F009F00D81C07 /* Frameworks */,
|
B9FB94A82B2F009F00D81C07 /* Frameworks */,
|
||||||
B97BCE292B3ED2C80044756D /* LICENSE */,
|
B97BCE292B3ED2C80044756D /* LICENSE */,
|
||||||
|
@ -309,6 +372,7 @@
|
||||||
children = (
|
children = (
|
||||||
B9FB94572B2DEECE00D81C07 /* Threaded.app */,
|
B9FB94572B2DEECE00D81C07 /* Threaded.app */,
|
||||||
B9FB94A72B2F009F00D81C07 /* ThreadedAuthService.appex */,
|
B9FB94A72B2F009F00D81C07 /* ThreadedAuthService.appex */,
|
||||||
|
B9C20D092B921C78004DC9B3 /* ThreadedWidgetsExtension.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -316,6 +380,7 @@
|
||||||
B9FB94592B2DEECE00D81C07 /* Threaded */ = {
|
B9FB94592B2DEECE00D81C07 /* Threaded */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
B9C20D592B923D53004DC9B3 /* Threaded.entitlements */,
|
||||||
B9FB94A02B2EF23100D81C07 /* Info.plist */,
|
B9FB94A02B2EF23100D81C07 /* Info.plist */,
|
||||||
B9029FC12B81259400AA9B68 /* Secret.plist */,
|
B9029FC12B81259400AA9B68 /* Secret.plist */,
|
||||||
B9FB945A2B2DEECE00D81C07 /* ThreadedApp.swift */,
|
B9FB945A2B2DEECE00D81C07 /* ThreadedApp.swift */,
|
||||||
|
@ -423,6 +488,8 @@
|
||||||
children = (
|
children = (
|
||||||
B9DC692C2B79362700E625B9 /* StoreKit.framework */,
|
B9DC692C2B79362700E625B9 /* StoreKit.framework */,
|
||||||
B9FB94A92B2F009F00D81C07 /* AuthenticationServices.framework */,
|
B9FB94A92B2F009F00D81C07 /* AuthenticationServices.framework */,
|
||||||
|
B9C20D0A2B921C78004DC9B3 /* WidgetKit.framework */,
|
||||||
|
B9C20D0C2B921C78004DC9B3 /* SwiftUI.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -452,6 +519,26 @@
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget 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 */ = {
|
B9FB94562B2DEECE00D81C07 /* Threaded */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = B9FB94672B2DEECF00D81C07 /* Build configuration list for PBXNativeTarget "Threaded" */;
|
buildConfigurationList = B9FB94672B2DEECF00D81C07 /* Build configuration list for PBXNativeTarget "Threaded" */;
|
||||||
|
@ -465,6 +552,7 @@
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
B9FB94B32B2F009F00D81C07 /* PBXTargetDependency */,
|
B9FB94B32B2F009F00D81C07 /* PBXTargetDependency */,
|
||||||
|
B9C20D192B921C7B004DC9B3 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Threaded;
|
name = Threaded;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
@ -506,9 +594,12 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1510;
|
LastSwiftUpdateCheck = 1520;
|
||||||
LastUpgradeCheck = 1510;
|
LastUpgradeCheck = 1510;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
B9C20D082B921C78004DC9B3 = {
|
||||||
|
CreatedOnToolsVersion = 15.2;
|
||||||
|
};
|
||||||
B9FB94562B2DEECE00D81C07 = {
|
B9FB94562B2DEECE00D81C07 = {
|
||||||
CreatedOnToolsVersion = 15.1;
|
CreatedOnToolsVersion = 15.1;
|
||||||
};
|
};
|
||||||
|
@ -540,11 +631,21 @@
|
||||||
targets = (
|
targets = (
|
||||||
B9FB94562B2DEECE00D81C07 /* Threaded */,
|
B9FB94562B2DEECE00D81C07 /* Threaded */,
|
||||||
B9FB94A62B2F009F00D81C07 /* ThreadedAuthService */,
|
B9FB94A62B2F009F00D81C07 /* ThreadedAuthService */,
|
||||||
|
B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase 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 */ = {
|
B9FB94552B2DEECE00D81C07 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -572,6 +673,21 @@
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase 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 */ = {
|
B9FB94532B2DEECE00D81C07 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -636,6 +752,7 @@
|
||||||
B98627312B86F23500844245 /* LoggedAccounts.swift in Sources */,
|
B98627312B86F23500844245 /* LoggedAccounts.swift in Sources */,
|
||||||
B9B63B272B449CDC00BBC82D /* SearchResults.swift in Sources */,
|
B9B63B272B449CDC00BBC82D /* SearchResults.swift in Sources */,
|
||||||
B9B63B252B44997400BBC82D /* QuotePostView.swift in Sources */,
|
B9B63B252B44997400BBC82D /* QuotePostView.swift in Sources */,
|
||||||
|
B9BCC3182B90B3BC00211976 /* Tenor.swift in Sources */,
|
||||||
B97BCE242B3DD8400044756D /* HapticManager.swift in Sources */,
|
B97BCE242B3DD8400044756D /* HapticManager.swift in Sources */,
|
||||||
B9B63B212B442D1500BBC82D /* DynamicTextEditor.swift in Sources */,
|
B9B63B212B442D1500BBC82D /* DynamicTextEditor.swift in Sources */,
|
||||||
B9FB949F2B2EF0F200D81C07 /* MastodonRequest.swift in Sources */,
|
B9FB949F2B2EF0F200D81C07 /* MastodonRequest.swift in Sources */,
|
||||||
|
@ -659,6 +776,11 @@
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
B9C20D192B921C7B004DC9B3 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = B9C20D082B921C78004DC9B3 /* ThreadedWidgetsExtension */;
|
||||||
|
targetProxy = B9C20D182B921C7B004DC9B3 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
B9FB94B32B2F009F00D81C07 /* PBXTargetDependency */ = {
|
B9FB94B32B2F009F00D81C07 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = B9FB94A62B2F009F00D81C07 /* ThreadedAuthService */;
|
target = B9FB94A62B2F009F00D81C07 /* ThreadedAuthService */;
|
||||||
|
@ -678,6 +800,72 @@
|
||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration 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 */ = {
|
B9FB94652B2DEECF00D81C07 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
@ -803,8 +991,9 @@
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Threaded/Threaded.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1425;
|
CURRENT_PROJECT_VERSION = 500;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Threaded/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Threaded/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = HB5P3BML86;
|
DEVELOPMENT_TEAM = HB5P3BML86;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
@ -823,7 +1012,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.2.0;
|
MARKETING_VERSION = 0.4.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded;
|
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
@ -842,8 +1031,9 @@
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Threaded/Threaded.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1425;
|
CURRENT_PROJECT_VERSION = 500;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Threaded/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Threaded/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = HB5P3BML86;
|
DEVELOPMENT_TEAM = HB5P3BML86;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
@ -862,7 +1052,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.2.0;
|
MARKETING_VERSION = 0.4.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded;
|
PRODUCT_BUNDLE_IDENTIFIER = fr.lumaa.Threaded;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
@ -938,6 +1128,15 @@
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList 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" */ = {
|
B9FB94522B2DEECE00D81C07 /* Build configuration list for PBXProject "Threaded" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
@ -1051,6 +1250,11 @@
|
||||||
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||||
productName = KeychainSwift;
|
productName = KeychainSwift;
|
||||||
};
|
};
|
||||||
|
B9C20D552B922DAC004DC9B3 /* KeychainSwift */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = B9BF54052B6B6823004B24E7 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||||
|
productName = KeychainSwift;
|
||||||
|
};
|
||||||
B9FB94832B2E20AF00D81C07 /* SwiftSoup */ = {
|
B9FB94832B2E20AF00D81C07 /* SwiftSoup */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = B9FB94822B2E20AF00D81C07 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
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 {
|
var body: some View {
|
||||||
CompactPostView(status: status, quoted: true)
|
CompactPostView(status: status, quoted: true)
|
||||||
.environmentObject(navigator)
|
|
||||||
.frame(maxWidth: 250, maxHeight: 200)
|
.frame(maxWidth: 250, maxHeight: 200)
|
||||||
.padding(15)
|
.padding(15)
|
||||||
.padding([.horizontal], 20)
|
.padding([.horizontal], 20)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//Made by Lumaa
|
//Made by Lumaa
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import KeychainSwift
|
|
||||||
|
|
||||||
@Observable
|
@Observable
|
||||||
public class AccountManager: ObservableObject {
|
public class AccountManager: ObservableObject {
|
||||||
|
@ -52,121 +51,3 @@ public class AccountManager: ObservableObject {
|
||||||
return account
|
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 Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftData
|
import SwiftData
|
||||||
|
import KeychainSwift
|
||||||
|
|
||||||
@Model
|
@Model
|
||||||
class LoggedAccount {
|
class LoggedAccount {
|
||||||
let token: OauthToken
|
let token: OauthToken = OauthToken(accessToken: "ABC", tokenType: "ABC", scope: "ABC", createdAt: 0.0)
|
||||||
let acct: String
|
let acct: String
|
||||||
let app: AppAccount?
|
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.token = token
|
||||||
self.acct = acct
|
self.acct = acct
|
||||||
self.app = nil
|
self.app = nil
|
||||||
|
@ -31,3 +32,75 @@ public extension View {
|
||||||
.modelContainer(for: LoggedAccount.self)
|
.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)
|
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?) {
|
public func getWithLink<Entity: Decodable>(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) {
|
||||||
let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
|
let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
|
||||||
var linkHandler: LinkHandler?
|
var linkHandler: LinkHandler?
|
||||||
|
@ -180,10 +184,7 @@ public final class Client: Equatable, Identifiable, Hashable {
|
||||||
return httpResponse as? HTTPURLResponse
|
return httpResponse as? HTTPURLResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeEntityRequest<Entity: Decodable>(endpoint: Endpoint,
|
private func makeEntityRequest<Entity: Decodable>(endpoint: Endpoint, method: String, forceVersion: Version? = nil) async throws -> Entity {
|
||||||
method: String,
|
|
||||||
forceVersion: Version? = nil) async throws -> Entity
|
|
||||||
{
|
|
||||||
let url = try makeURL(endpoint: endpoint, forceVersion: forceVersion)
|
let url = try makeURL(endpoint: endpoint, forceVersion: forceVersion)
|
||||||
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
|
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
|
||||||
let (data, httpResponse) = try await urlSession.data(for: request)
|
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 {
|
public func oauthURL() async throws -> URL {
|
||||||
let app: InstanceApp = try await post(endpoint: Apps.registerApp)
|
let app: InstanceApp = try await post(endpoint: Apps.registerApp)
|
||||||
critical.withLock { $0.oauthApp = app }
|
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 struct LinkHandler {
|
||||||
public let rawLink: String
|
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 struct GroupedNotification: Identifiable, Hashable {
|
||||||
public var id: String? { notifications.first?.id }
|
public var id: String? { notifications.first?.id }
|
||||||
public var notifications: [Notification]
|
public var notifications: [Notification]
|
||||||
|
|
|
@ -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 {
|
#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())
|
.environment(AppDelegate())
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,10 +147,3 @@ struct NotificationsView: View {
|
||||||
var image: Image? = Image(systemName: "paperplane")
|
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 {
|
struct PostDetailsView: View {
|
||||||
@EnvironmentObject private var navigator: Navigator
|
@EnvironmentObject private var navigator: Navigator
|
||||||
@Environment(AccountManager.self) private var accountManager: AccountManager
|
@Environment(AccountManager.self) private var accountManager: AccountManager
|
||||||
|
@Environment(AppDelegate.self) private var delegate: AppDelegate
|
||||||
|
|
||||||
var detailedStatus: Status
|
var detailedStatus: Status
|
||||||
|
|
||||||
|
@ -128,6 +129,8 @@ struct PostDetailsView: View {
|
||||||
statusesContext.append(contentsOf: data.context.descendants)
|
statusesContext.append(contentsOf: data.context.descendants)
|
||||||
|
|
||||||
statuses = statusesContext
|
statuses = statusesContext
|
||||||
|
|
||||||
|
// await loadEmbeddedStatus()
|
||||||
} catch {
|
} catch {
|
||||||
if let error = error as? ServerError, error.httpCode == 404 {
|
if let error = error as? ServerError, error.httpCode == 404 {
|
||||||
_ = navigator.path.popLast()
|
_ = navigator.path.popLast()
|
||||||
|
|
|
@ -304,26 +304,6 @@ extension ShopView {
|
||||||
// .environment(\.locale, Locale(identifier: "en-us"))
|
// .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 {
|
enum PlusEntitlements: String {
|
||||||
case monthly
|
case monthly
|
||||||
case yearly
|
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