feat: [WIP] add authentication scene
This commit is contained in:
parent
b749d0a7bc
commit
36c1807182
|
@ -11,7 +11,7 @@
|
|||
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; };
|
||||
2D152A8C25C295CC009AA50C /* TimelinePostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* TimelinePostView.swift */; };
|
||||
2D152A9225C2980C009AA50C /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A9125C2980C009AA50C /* UIFont.swift */; };
|
||||
2D42FF6125C8177C004A627A /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* SwiftPackageProductDependency */; };
|
||||
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; };
|
||||
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonContent.swift */; };
|
||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; };
|
||||
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */; };
|
||||
|
@ -20,7 +20,7 @@
|
|||
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46976325C2A71500CF4AA9 /* UIIamge.swift */; };
|
||||
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335725C188A000CAE157 /* APIService+Persist+Timeline.swift */; };
|
||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335D25C1894B00CAE157 /* APIService.swift */; };
|
||||
2D61336925C18A4F00CAE157 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* SwiftPackageProductDependency */; };
|
||||
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */; };
|
||||
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; };
|
||||
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */; };
|
||||
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */; };
|
||||
|
@ -35,7 +35,7 @@
|
|||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; };
|
||||
3533495136D843E85211E3E2 /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1B4523A7981F1044DE89C21 /* Pods_Mastodon_MastodonUITests.framework */; };
|
||||
45B49097460EDE530AD5AA72 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
|
||||
5D526FE225BE9AC400460CB9 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* SwiftPackageProductDependency */; };
|
||||
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; };
|
||||
5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; };
|
||||
5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; };
|
||||
7A9135D4559750AF07CA9BE4 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 602D783BEC22881EBAD84419 /* Pods_Mastodon.framework */; };
|
||||
|
@ -43,12 +43,10 @@
|
|||
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; };
|
||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */; };
|
||||
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */; };
|
||||
DB0140BD25C40D7500F9F3CF /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* SwiftPackageProductDependency */; };
|
||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
|
||||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
|
||||
DB3D0FF325BAA61700EAA174 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */; };
|
||||
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; };
|
||||
DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; };
|
||||
DB3D102425BAA7B400EAA174 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3D102225BAA7B400EAA174 /* Assets.swift */; };
|
||||
DB3D102525BAA7B400EAA174 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3D102325BAA7B400EAA174 /* Strings.swift */; };
|
||||
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; };
|
||||
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; };
|
||||
DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; };
|
||||
|
@ -80,6 +78,12 @@
|
|||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; };
|
||||
DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */; };
|
||||
DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */; };
|
||||
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; };
|
||||
DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; };
|
||||
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; };
|
||||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338525C945ED00AD9700 /* Strings.swift */; };
|
||||
DB98338825C945ED00AD9700 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338625C945ED00AD9700 /* Assets.swift */; };
|
||||
DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98339B25C96DE600AD9700 /* APIService+Account.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -176,8 +180,6 @@
|
|||
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
|
||||
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
|
||||
DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
DB3D102225BAA7B400EAA174 /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = "<group>"; };
|
||||
DB3D102325BAA7B400EAA174 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||
DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DB427DD525BAA00100D1B89D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
DB427DD725BAA00100D1B89D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -217,6 +219,12 @@
|
|||
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
|
||||
DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineIndex.swift; sourceTree = "<group>"; };
|
||||
DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
|
||||
DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = "<group>"; };
|
||||
DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = "<group>"; };
|
||||
DB98337E25C9452D00AD9700 /* APIService+APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+APIError.swift"; sourceTree = "<group>"; };
|
||||
DB98338525C945ED00AD9700 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||
DB98338625C945ED00AD9700 /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = "<group>"; };
|
||||
DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = "<group>"; };
|
||||
DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Mastodon.xctestplan; path = Mastodon/Mastodon.xctestplan; sourceTree = "<group>"; };
|
||||
DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MastodonSDK.xctestplan; sourceTree = "<group>"; };
|
||||
EC6E707B68A67DB08EC288FA /* Pods-MastodonTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
|
@ -227,13 +235,13 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DB0140BD25C40D7500F9F3CF /* BuildFile in Frameworks */,
|
||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */,
|
||||
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
|
||||
2D42FF6125C8177C004A627A /* BuildFile in Frameworks */,
|
||||
5D526FE225BE9AC400460CB9 /* BuildFile in Frameworks */,
|
||||
2D61336925C18A4F00CAE157 /* BuildFile in Frameworks */,
|
||||
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */,
|
||||
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */,
|
||||
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
||||
7A9135D4559750AF07CA9BE4 /* Pods_Mastodon.framework in Frameworks */,
|
||||
DB3D0FF325BAA61700EAA174 /* BuildFile in Frameworks */,
|
||||
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
|
||||
45B49097460EDE530AD5AA72 /* Pods_Mastodon.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -315,6 +323,10 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
2D61335D25C1894B00CAE157 /* APIService.swift */,
|
||||
DB98337E25C9452D00AD9700 /* APIService+APIError.swift */,
|
||||
DB98336A25C9420100AD9700 /* APIService+App.swift */,
|
||||
DB98337025C9443200AD9700 /* APIService+Authentication.swift */,
|
||||
DB98339B25C96DE600AD9700 /* APIService+Account.swift */,
|
||||
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */,
|
||||
2D61335625C1887F00CAE157 /* Persist */,
|
||||
);
|
||||
|
@ -444,15 +456,6 @@
|
|||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB3D101B25BAA79200EAA174 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB3D102225BAA7B400EAA174 /* Assets.swift */,
|
||||
DB3D102325BAA7B400EAA174 /* Strings.swift */,
|
||||
);
|
||||
path = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB427DC925BAA00100D1B89D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -467,6 +470,7 @@
|
|||
DB427DD325BAA00100D1B89D /* Products */,
|
||||
1EBA4F56E920856A3FC84ACB /* Pods */,
|
||||
3FE14AD363ED19AE7FF210A6 /* Frameworks */,
|
||||
DB98335F25C93B0400AD9700 /* Recovered References */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -485,15 +489,15 @@
|
|||
DB427DD425BAA00100D1B89D /* Mastodon */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
|
||||
DB427DE325BAA00100D1B89D /* Info.plist */,
|
||||
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
|
||||
2D76319C25C151DE00929FB9 /* Diffiable */,
|
||||
DB8AF52A25C13561002E6C99 /* State */,
|
||||
2D61335525C1886800CAE157 /* Service */,
|
||||
DB8AF56225C138BC002E6C99 /* Extension */,
|
||||
DB8AF55525C1379F002E6C99 /* Scene */,
|
||||
DB8AF54125C13647002E6C99 /* Coordinator */,
|
||||
DB3D101B25BAA79200EAA174 /* Generated */,
|
||||
DB8AF56225C138BC002E6C99 /* Extension */,
|
||||
DB98338425C945ED00AD9700 /* Generated */,
|
||||
DB3D0FF825BAA6B200EAA174 /* Resources */,
|
||||
DB3D0FF725BAA68500EAA174 /* Supporting Files */,
|
||||
);
|
||||
|
@ -578,9 +582,9 @@
|
|||
DB8AF52A25C13561002E6C99 /* State */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB8AF52D25C13561002E6C99 /* AppContext.swift */,
|
||||
DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */,
|
||||
DB8AF52C25C13561002E6C99 /* DocumentStore.swift */,
|
||||
DB8AF52D25C13561002E6C99 /* AppContext.swift */,
|
||||
);
|
||||
path = State;
|
||||
sourceTree = "<group>";
|
||||
|
@ -606,10 +610,10 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
2D7631A425C1532200929FB9 /* Share */,
|
||||
DB01409B25C40BB600F9F3CF /* Authentication */,
|
||||
2D76316325C14BAC00929FB9 /* PublicTimeline */,
|
||||
DB8AF54E25C13703002E6C99 /* MainTab */,
|
||||
DB8AF55625C137A8002E6C99 /* HomeViewController.swift */,
|
||||
DB01409B25C40BB600F9F3CF /* Authentication */,
|
||||
);
|
||||
path = Scene;
|
||||
sourceTree = "<group>";
|
||||
|
@ -628,6 +632,23 @@
|
|||
path = Extension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB98335F25C93B0400AD9700 /* Recovered References */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB98338425C945ED00AD9700 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB98338525C945ED00AD9700 /* Strings.swift */,
|
||||
DB98338625C945ED00AD9700 /* Assets.swift */,
|
||||
);
|
||||
path = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
|
@ -661,11 +682,11 @@
|
|||
);
|
||||
name = Mastodon;
|
||||
packageProductDependencies = (
|
||||
DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */,
|
||||
5D526FE125BE9AC400460CB9 /* SwiftPackageProductDependency */,
|
||||
2D61336825C18A4F00CAE157 /* SwiftPackageProductDependency */,
|
||||
2D42FF6025C8177C004A627A /* SwiftPackageProductDependency */,
|
||||
DB0140BC25C40D7500F9F3CF /* SwiftPackageProductDependency */,
|
||||
DB3D0FF225BAA61700EAA174 /* AlamofireImage */,
|
||||
5D526FE125BE9AC400460CB9 /* MastodonSDK */,
|
||||
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */,
|
||||
2D42FF6025C8177C004A627A /* ActiveLabel */,
|
||||
DB0140BC25C40D7500F9F3CF /* CommonOSLog */,
|
||||
);
|
||||
productName = Mastodon;
|
||||
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
||||
|
@ -788,10 +809,10 @@
|
|||
);
|
||||
mainGroup = DB427DC925BAA00100D1B89D;
|
||||
packageReferences = (
|
||||
DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */,
|
||||
2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */,
|
||||
2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */,
|
||||
DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */,
|
||||
DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */,
|
||||
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */,
|
||||
2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */,
|
||||
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */,
|
||||
);
|
||||
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -973,6 +994,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */,
|
||||
2D7631B325C159F700929FB9 /* Item.swift in Sources */,
|
||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
||||
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
|
||||
|
@ -980,7 +1002,9 @@
|
|||
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
||||
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
|
||||
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */,
|
||||
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
|
||||
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */,
|
||||
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
|
||||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */,
|
||||
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */,
|
||||
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */,
|
||||
|
@ -995,18 +1019,19 @@
|
|||
2D76319F25C1521200929FB9 /* TimelineSection.swift in Sources */,
|
||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
||||
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
|
||||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
||||
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
||||
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */,
|
||||
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */,
|
||||
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */,
|
||||
DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */,
|
||||
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */,
|
||||
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */,
|
||||
DB8AF55725C137A8002E6C99 /* HomeViewController.swift in Sources */,
|
||||
2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */,
|
||||
2D7631A825C1535600929FB9 /* TimelinePostTableViewCell.swift in Sources */,
|
||||
DB3D102525BAA7B400EAA174 /* Strings.swift in Sources */,
|
||||
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */,
|
||||
DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */,
|
||||
DB3D102425BAA7B400EAA174 /* Assets.swift in Sources */,
|
||||
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */,
|
||||
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */,
|
||||
);
|
||||
|
@ -1518,7 +1543,7 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */ = {
|
||||
2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift";
|
||||
requirement = {
|
||||
|
@ -1526,7 +1551,7 @@
|
|||
minimumVersion = 3.0.0;
|
||||
};
|
||||
};
|
||||
2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */ = {
|
||||
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Alamofire/AlamofireNetworkActivityIndicator";
|
||||
requirement = {
|
||||
|
@ -1534,7 +1559,7 @@
|
|||
minimumVersion = 3.1.0;
|
||||
};
|
||||
};
|
||||
DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */ = {
|
||||
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/MainasuK/CommonOSLog";
|
||||
requirement = {
|
||||
|
@ -1542,7 +1567,7 @@
|
|||
minimumVersion = 0.1.1;
|
||||
};
|
||||
};
|
||||
DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */ = {
|
||||
DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Alamofire/AlamofireImage.git";
|
||||
requirement = {
|
||||
|
@ -1553,28 +1578,28 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
2D42FF6025C8177C004A627A /* SwiftPackageProductDependency */ = {
|
||||
2D42FF6025C8177C004A627A /* ActiveLabel */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */;
|
||||
package = 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */;
|
||||
productName = ActiveLabel;
|
||||
};
|
||||
2D61336825C18A4F00CAE157 /* SwiftPackageProductDependency */ = {
|
||||
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */;
|
||||
package = 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */;
|
||||
productName = AlamofireNetworkActivityIndicator;
|
||||
};
|
||||
5D526FE125BE9AC400460CB9 /* SwiftPackageProductDependency */ = {
|
||||
5D526FE125BE9AC400460CB9 /* MastodonSDK */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = MastodonSDK;
|
||||
};
|
||||
DB0140BC25C40D7500F9F3CF /* SwiftPackageProductDependency */ = {
|
||||
DB0140BC25C40D7500F9F3CF /* CommonOSLog */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */;
|
||||
package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */;
|
||||
productName = CommonOSLog;
|
||||
};
|
||||
DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */ = {
|
||||
DB3D0FF225BAA61700EAA174 /* AlamofireImage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */;
|
||||
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
||||
productName = AlamofireImage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
|
|
@ -38,6 +38,7 @@ extension SceneCoordinator {
|
|||
|
||||
enum Scene {
|
||||
case authentication(viewModel: AuthenticationViewModel)
|
||||
case mastodonPinBasedAuthentication(viewModel: MastodonPinBasedAuthenticationViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,6 +114,10 @@ private extension SceneCoordinator {
|
|||
let _viewController = AuthenticationViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .mastodonPinBasedAuthentication(let viewModel):
|
||||
let _viewController = MastodonPinBasedAuthenticationViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
}
|
||||
|
||||
setupDependency(for: viewController as? NeedsDependency)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
|
||||
final class AuthenticationViewController: UIViewController, NeedsDependency {
|
||||
|
||||
|
@ -17,6 +18,7 @@ final class AuthenticationViewController: UIViewController, NeedsDependency {
|
|||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var viewModel: AuthenticationViewModel!
|
||||
var mastodonPinBasedAuthenticationViewController: UIViewController?
|
||||
|
||||
let domainTextField: UITextField = {
|
||||
let textField = UITextField()
|
||||
|
@ -75,6 +77,18 @@ extension AuthenticationViewController {
|
|||
|
||||
@objc private func signInBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
guard let domain = viewModel.domain.value else {
|
||||
// TODO: alert error
|
||||
return
|
||||
}
|
||||
viewModel.signInAction.send(domain)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
extension AuthenticationViewController: UIAdaptivePresentationControllerDelegate {
|
||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||
return .fullScreen
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,21 +5,34 @@
|
|||
// Created by MainasuK Cirno on 2021/2/1.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
|
||||
final class AuthenticationViewModel {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
// input
|
||||
let context: AppContext
|
||||
let coordinator: SceneCoordinator
|
||||
let input = CurrentValueSubject<String, Never>("")
|
||||
let signInAction = PassthroughSubject<String, Never>()
|
||||
|
||||
// output
|
||||
let domain = CurrentValueSubject<String?, Never>(nil)
|
||||
let isSignInButtonEnabled = CurrentValueSubject<Bool, Never>(false)
|
||||
let isAuthenticating = CurrentValueSubject<Bool, Never>(false)
|
||||
let authenticated = PassthroughSubject<Void, Never>()
|
||||
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||
|
||||
init() {
|
||||
private var mastodonPinBasedAuthenticationViewController: UIViewController?
|
||||
|
||||
init(context: AppContext, coordinator: SceneCoordinator) {
|
||||
self.context = context
|
||||
self.coordinator = coordinator
|
||||
|
||||
input
|
||||
.map { input in
|
||||
let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
|
@ -39,10 +52,135 @@ final class AuthenticationViewModel {
|
|||
.store(in: &disposeBag)
|
||||
|
||||
domain
|
||||
.print()
|
||||
.map { $0 != nil }
|
||||
.assign(to: \.value, on: isSignInButtonEnabled)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
signInAction
|
||||
.handleEvents(receiveOutput: { [weak self] _ in
|
||||
// trigger state change
|
||||
guard let self = self else { return }
|
||||
self.isAuthenticating.value = true
|
||||
})
|
||||
.flatMap { domain in
|
||||
context.apiService.createApplication(domain: domain)
|
||||
.retry(3)
|
||||
.tryMap { response -> AuthenticateInfo in
|
||||
let application = response.value
|
||||
guard let clientID = application.clientID,
|
||||
let clientSecret = application.clientSecret else {
|
||||
throw APIService.APIError.explicit(.badResponse)
|
||||
}
|
||||
let query = Mastodon.API.OAuth.AuthorizeQuery(clientID: clientID)
|
||||
let url = Mastodon.API.OAuth.authorizeURL(domain: domain, query: query)
|
||||
return AuthenticateInfo(
|
||||
domain: domain,
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
url: url
|
||||
)
|
||||
}
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
// trigger state update
|
||||
self.isAuthenticating.value = false
|
||||
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
// TODO: handle error
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign in fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
self.error.value = error
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
} receiveValue: { [weak self] info in
|
||||
guard let self = self else { return }
|
||||
let mastodonPinBasedAuthenticationViewModel = MastodonPinBasedAuthenticationViewModel(authenticateURL: info.url)
|
||||
self.authenticate(
|
||||
info: info,
|
||||
pinCodePublisher: mastodonPinBasedAuthenticationViewModel.pinCodePublisher
|
||||
)
|
||||
self.mastodonPinBasedAuthenticationViewController = self.coordinator.present(
|
||||
scene: .mastodonPinBasedAuthentication(viewModel: mastodonPinBasedAuthenticationViewModel),
|
||||
from: nil,
|
||||
transition: .modal(animated: true, completion: nil)
|
||||
)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AuthenticationViewModel {
|
||||
|
||||
struct AuthenticateInfo {
|
||||
let domain: String
|
||||
let clientID: String
|
||||
let clientSecret: String
|
||||
let url: URL
|
||||
}
|
||||
|
||||
func authenticate(info: AuthenticateInfo, pinCodePublisher: PassthroughSubject<String, Never>) {
|
||||
pinCodePublisher
|
||||
.handleEvents(receiveOutput: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.isAuthenticating.value = true
|
||||
self.mastodonPinBasedAuthenticationViewController?.dismiss(animated: true, completion: nil)
|
||||
self.mastodonPinBasedAuthenticationViewController = nil
|
||||
})
|
||||
.compactMap { [weak self] code -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error>? in
|
||||
guard let self = self else { return nil }
|
||||
return self.context.apiService
|
||||
.userAccessToken(
|
||||
domain: info.domain,
|
||||
clientID: info.clientID,
|
||||
clientSecret: info.clientSecret,
|
||||
code: code
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
||||
let token = response.value
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign in success. Token: %s", ((#file as NSString).lastPathComponent), #line, #function, token.accessToken)
|
||||
return AuthenticationViewModel.verifyAndSaveAuthentication(
|
||||
context: self.context,
|
||||
info: info,
|
||||
token: token
|
||||
)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.switchToLatest()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: swap user access token swap fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
self.isAuthenticating.value = false
|
||||
self.error.value = error
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
let account = response.value
|
||||
// TODO:
|
||||
}
|
||||
.store(in: &self.disposeBag)
|
||||
}
|
||||
|
||||
static func verifyAndSaveAuthentication(
|
||||
context: AppContext,
|
||||
info: AuthenticateInfo,
|
||||
token: Mastodon.Entity.Token
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: token.accessToken)
|
||||
return context.apiService.accountVerifyCredentials(
|
||||
domain: info.domain,
|
||||
authorization: authorization
|
||||
)
|
||||
// TODO: add persist logic
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,20 +6,70 @@
|
|||
//
|
||||
|
||||
import os.log
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
import WebKit
|
||||
|
||||
final class MastodonPinBasedAuthenticationViewController: NSObject {
|
||||
final class MastodonPinBasedAuthenticationViewController: UIViewController, NeedsDependency {
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
weak var viewModel: MastodonPinBasedAuthenticationViewModel?
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var viewModel: MastodonPinBasedAuthenticationViewModel!
|
||||
|
||||
init(viewModel: MastodonPinBasedAuthenticationViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
let webView: WKWebView = {
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.processPool = WKProcessPool()
|
||||
let webView = WKWebView(frame: .zero, configuration: configuration)
|
||||
return webView
|
||||
}()
|
||||
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
|
||||
// cleanup cookie
|
||||
let httpCookieStore = webView.configuration.websiteDataStore.httpCookieStore
|
||||
httpCookieStore.getAllCookies { cookies in
|
||||
for cookie in cookies {
|
||||
httpCookieStore.delete(cookie, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension MastodonPinBasedAuthenticationViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = "Authentication"
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(MastodonPinBasedAuthenticationViewController.cancelBarButtonItemPressed(_:)))
|
||||
|
||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(webView)
|
||||
NSLayoutConstraint.activate([
|
||||
webView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
let request = URLRequest(url: viewModel.authenticateURL)
|
||||
webView.navigationDelegate = viewModel.navigationDelegate
|
||||
webView.load(request)
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: authenticate via: %s", ((#file as NSString).lastPathComponent), #line, #function, viewModel.authenticateURL.debugDescription)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonPinBasedAuthenticationViewController {
|
||||
|
||||
@objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,8 +5,36 @@
|
|||
// Created by MainasuK Cirno on 2021/1/29.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import Foundation
|
||||
import Combine
|
||||
import WebKit
|
||||
|
||||
final class MastodonPinBasedAuthenticationViewModel {
|
||||
|
||||
// input
|
||||
let authenticateURL: URL
|
||||
|
||||
// output
|
||||
let pinCodePublisher = PassthroughSubject<String, Never>()
|
||||
private var navigationDelegateShim: MastodonPinBasedAuthenticationViewModelNavigationDelegateShim?
|
||||
|
||||
init(authenticateURL: URL) {
|
||||
self.authenticateURL = authenticateURL
|
||||
}
|
||||
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonPinBasedAuthenticationViewModel {
|
||||
|
||||
var navigationDelegate: WKNavigationDelegate {
|
||||
let navigationDelegateShim = MastodonPinBasedAuthenticationViewModelNavigationDelegateShim(viewModel: self)
|
||||
self.navigationDelegateShim = navigationDelegateShim
|
||||
return navigationDelegateShim
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,14 @@ final class MastodonPinBasedAuthenticationViewModelNavigationDelegateShim: NSObj
|
|||
extension MastodonPinBasedAuthenticationViewModelNavigationDelegateShim: WKNavigationDelegate {
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
// TODO:
|
||||
guard let url = webView.url,
|
||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
let codeQueryItem = components.queryItems?.first(where: { $0.name == "code" }),
|
||||
let code = codeQueryItem.value else {
|
||||
return
|
||||
}
|
||||
|
||||
viewModel?.pinCodePublisher.send(code)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// APIService+Error.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-2-2.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
enum APIError: Error {
|
||||
|
||||
case implicit(ErrorReason)
|
||||
case explicit(ErrorReason)
|
||||
|
||||
enum ErrorReason {
|
||||
// application internal error
|
||||
case authenticationMissing
|
||||
case badRequest
|
||||
case badResponse
|
||||
case requestThrottle
|
||||
|
||||
// Server API error
|
||||
case mastodonAPIError(Mastodon.API.Error)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// APIService+Account.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/2/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
func accountVerifyCredentials(
|
||||
domain: String,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
return Mastodon.API.Account.verifyCredentials(
|
||||
session: session,
|
||||
domain: domain,
|
||||
authorization: authorization
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// APIService+App.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/2/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
#if DEBUG
|
||||
private static let clientName = "Skimming"
|
||||
#else
|
||||
private static let clientName = "Mastodon for iOS"
|
||||
#endif
|
||||
|
||||
func createApplication(domain: String) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error> {
|
||||
let query = Mastodon.API.App.CreateQuery(clientName: APIService.clientName, website: nil)
|
||||
return Mastodon.API.App.create(
|
||||
session: session,
|
||||
domain: domain,
|
||||
query: query
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// APIService+Authentication.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/2/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
func userAccessToken(
|
||||
domain: String,
|
||||
clientID: String,
|
||||
clientSecret: String,
|
||||
code: String
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
|
||||
let query = Mastodon.API.OAuth.AccessTokenQuery(
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
code: code,
|
||||
grantType: "authorization_code"
|
||||
)
|
||||
return Mastodon.API.OAuth.accessToken(
|
||||
session: session,
|
||||
domain: domain,
|
||||
query: query
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -26,7 +26,7 @@ extension APIService {
|
|||
domain: domain,
|
||||
query: Mastodon.API.Timeline.PublicTimelineQuery()
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Toot]>,Error> in
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Toot]>, Error> in
|
||||
return APIService.Persist.persistTimeline(
|
||||
domain: domain,
|
||||
managedObjectContext: self.backgroundManagedObjectContext,
|
||||
|
@ -46,4 +46,5 @@ extension APIService {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
|
||||
#if DEBUG
|
||||
DispatchQueue.main.async {
|
||||
let authenticationViewModel = AuthenticationViewModel()
|
||||
let authenticationViewModel = AuthenticationViewModel(context: appContext, coordinator: sceneCoordinator)
|
||||
sceneCoordinator.present(scene: .authentication(viewModel: authenticationViewModel), from: nil, transition: .modal(animated: false, completion: nil))
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Mastodon+API+Account.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/2/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
extension Mastodon.API.Account {
|
||||
|
||||
static func verifyCredentialsEndpointURL(domain: String) -> URL {
|
||||
return Mastodon.API.endpointURL(domain: domain)
|
||||
}
|
||||
|
||||
public static func verifyCredentials(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: verifyCredentialsEndpointURL(domain: domain),
|
||||
query: nil,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
|
@ -14,6 +14,10 @@ extension Mastodon.API.OAuth {
|
|||
|
||||
public struct Authorization {
|
||||
public let accessToken: String
|
||||
|
||||
public init(accessToken: String) {
|
||||
self.accessToken = accessToken
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +27,9 @@ extension Mastodon.API.OAuth {
|
|||
static func authorizeEndpointURL(domain: String) -> URL {
|
||||
return Mastodon.API.oauthEndpointURL(domain: domain).appendingPathComponent("authorize")
|
||||
}
|
||||
static func accessTokenEndpointURL(domain: String) -> URL {
|
||||
return Mastodon.API.oauthEndpointURL(domain: domain).appendingPathComponent("token")
|
||||
}
|
||||
|
||||
/// Construct user authorize endpoint URL
|
||||
///
|
||||
|
@ -38,7 +45,7 @@ extension Mastodon.API.OAuth {
|
|||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - query: `AuthorizeQuery`
|
||||
static func authorizeURL(
|
||||
public static func authorizeURL(
|
||||
domain: String,
|
||||
query: AuthorizeQuery
|
||||
) -> URL {
|
||||
|
@ -51,27 +58,41 @@ extension Mastodon.API.OAuth {
|
|||
return url
|
||||
}
|
||||
|
||||
// static func authorize(
|
||||
// session: URLSession,
|
||||
// domain: String,
|
||||
// query: AuthorizeQuery
|
||||
// ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
|
||||
// let request = Mastodon.API.post(
|
||||
// url: authorizeEndpointURL(domain: domain),
|
||||
// query: query,
|
||||
// authorization: nil
|
||||
// )
|
||||
// return session.dataTaskPublisher(for: request)
|
||||
// .tryMap { data, response in
|
||||
// let value = try Mastodon.API.decode(type: Mastodon.Entity.Token.self, from: data, response: response)
|
||||
// return Mastodon.Response.Content(value: value, response: response)
|
||||
// }
|
||||
// .eraseToAnyPublisher()
|
||||
// }
|
||||
/// Obtain User Access Token
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/2/2
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/apps/oauth/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - query: `AccessTokenQuery`
|
||||
/// - Returns: `AnyPublisher` contains `Token` nested in the response
|
||||
public static func accessToken(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
query: AccessTokenQuery
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
|
||||
let request = Mastodon.API.post(
|
||||
url: accessTokenEndpointURL(domain: domain),
|
||||
query: query,
|
||||
authorization: nil
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Token.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.OAuth {
|
||||
|
||||
public struct AuthorizeQuery: GetQuery {
|
||||
|
||||
public let forceLogin: String?
|
||||
|
@ -106,7 +127,7 @@ extension Mastodon.API.OAuth {
|
|||
var items: [URLQueryItem] = []
|
||||
forceLogin.flatMap { items.append(URLQueryItem(name: "force_login", value: $0)) }
|
||||
items.append(URLQueryItem(name: "response_type", value: responseType))
|
||||
items.append(URLQueryItem(name: "clientID", value: clientID))
|
||||
items.append(URLQueryItem(name: "client_id", value: clientID))
|
||||
items.append(URLQueryItem(name: "redirect_uri", value: redirectURI))
|
||||
scope.flatMap { items.append(URLQueryItem(name: "scope", value: $0)) }
|
||||
guard !items.isEmpty else { return nil }
|
||||
|
@ -114,4 +135,46 @@ extension Mastodon.API.OAuth {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
public struct AccessTokenQuery: Codable, PostQuery {
|
||||
public init(
|
||||
clientID: String,
|
||||
clientSecret: String,
|
||||
redirectURI: String = "urn:ietf:wg:oauth:2.0:oob",
|
||||
scope: String? = "read write follow push",
|
||||
code: String?,
|
||||
grantType: String
|
||||
) {
|
||||
self.clientID = clientID
|
||||
self.clientSecret = clientSecret
|
||||
self.redirectURI = redirectURI
|
||||
self.scope = scope
|
||||
self.code = code
|
||||
self.grantType = grantType
|
||||
}
|
||||
|
||||
|
||||
public let clientID: String
|
||||
public let clientSecret: String
|
||||
public let redirectURI: String
|
||||
public let scope: String?
|
||||
public let code: String?
|
||||
public let grantType: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientID = "client_id"
|
||||
case clientSecret = "client_secret"
|
||||
case redirectURI = "redirect_uri"
|
||||
case scope
|
||||
case code
|
||||
case grantType = "grant_type"
|
||||
|
||||
}
|
||||
|
||||
var body: Data? {
|
||||
return try? Mastodon.API.encoder.encode(self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,17 +37,33 @@ extension Mastodon.API {
|
|||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.custom { decoder throws -> Date in
|
||||
let container = try decoder.singleValueContainer()
|
||||
let string = try container.decode(String.self)
|
||||
|
||||
|
||||
if let date = fractionalSecondsPreciseISO8601Formatter.date(from: string) {
|
||||
return date
|
||||
}
|
||||
if let date = fullDatePreciseISO8601Formatter.date(from: string) {
|
||||
return date
|
||||
var logInfo = ""
|
||||
do {
|
||||
let string = try container.decode(String.self)
|
||||
logInfo += string
|
||||
|
||||
if let date = fractionalSecondsPreciseISO8601Formatter.date(from: string) {
|
||||
return date
|
||||
}
|
||||
if let date = fullDatePreciseISO8601Formatter.date(from: string) {
|
||||
return date
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
|
||||
var numberValue = ""
|
||||
do {
|
||||
let number = try container.decode(Double.self)
|
||||
logInfo += "\(number)"
|
||||
|
||||
return Date(timeIntervalSince1970: number)
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "[Decoder] Invalid date: \(logInfo)")
|
||||
}
|
||||
|
||||
return decoder
|
||||
|
@ -66,6 +82,7 @@ extension Mastodon.API {
|
|||
}
|
||||
|
||||
extension Mastodon.API {
|
||||
public enum Account { }
|
||||
public enum App { }
|
||||
public enum OAuth { }
|
||||
public enum Timeline { }
|
||||
|
|
|
@ -21,5 +21,12 @@ extension Mastodon.Entity {
|
|||
public let tokenType: String
|
||||
public let scope: String
|
||||
public let createdAt: Date
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case accessToken = "access_token"
|
||||
case tokenType = "token_type"
|
||||
case scope
|
||||
case createdAt = "created_at"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue