feat: [WIP] add authentication scene

This commit is contained in:
CMK 2021-02-02 19:31:10 +08:00
parent b749d0a7bc
commit 36c1807182
17 changed files with 600 additions and 91 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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