From 1b3c9b2099639aeed3d2b8504c12c2f0bfdfd0a0 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 5 Feb 2021 15:58:48 +0800 Subject: [PATCH] feat: add sign out debug menu --- Mastodon.xcodeproj/project.pbxproj | 86 ++++++++++--------- .../AuthenticationViewController.swift | 65 +++++++++++--- .../AuthenticationViewModel.swift | 45 +++++++++- .../HomeViewController+DebugAction.swift | 63 ++++++++++++++ .../HomeTimeline/HomeViewController.swift | 6 +- .../APIService/APIService+APIError.swift | 4 - .../APIService+Authentication.swift | 18 ++++ .../APIService/APIService+Instance.swift | 24 ++++++ MastodonSDK.xctestplan | 24 ++++++ .../API/MastodonSDK+API+Instance.swift | 2 +- 10 files changed, 279 insertions(+), 58 deletions(-) create mode 100644 Mastodon/Scene/HomeTimeline/HomeViewController+DebugAction.swift create mode 100644 Mastodon/Service/APIService/APIService+Instance.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 4baac3f56..2ef2884b7 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -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 */; }; @@ -21,7 +21,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 */; }; 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */; }; 2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */; }; 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; }; @@ -40,17 +40,19 @@ 2DA7D05725CA693F00804E11 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D05625CA693F00804E11 /* Application.swift */; }; 2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; }; 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 */; }; DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */; }; 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 */; }; DB084B5725CBC56C00F898ED /* Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Toot.swift */; }; - DB3D0FF325BAA61700EAA174 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */; }; + DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; }; + DB0AC70A25CD2E0300D75117 /* HomeViewController+DebugAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC70925CD2E0300D75117 /* HomeViewController+DebugAction.swift */; }; + DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; }; @@ -69,7 +71,7 @@ DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */; }; DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; }; DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; }; - DB5086B825CC0D6400C2C187 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* SwiftPackageProductDependency */; }; + DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; }; DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; }; DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; }; DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */; }; @@ -202,6 +204,8 @@ DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModel.swift; sourceTree = ""; }; DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Toot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toot.swift; sourceTree = ""; }; + DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; + DB0AC70925CD2E0300D75117 /* HomeViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeViewController+DebugAction.swift"; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -271,13 +275,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 */, - DB5086B825CC0D6400C2C187 /* BuildFile in Frameworks */, - 2D61336925C18A4F00CAE157 /* BuildFile in Frameworks */, - DB3D0FF325BAA61700EAA174 /* BuildFile in Frameworks */, + 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, + 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */, + DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */, + 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, + DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */, 45B49097460EDE530AD5AA72 /* Pods_Mastodon.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -586,6 +590,7 @@ DB98339B25C96DE600AD9700 /* APIService+Account.swift */, 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */, DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */, + DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */, ); path = APIService; sourceTree = ""; @@ -745,6 +750,7 @@ isa = PBXGroup; children = ( DB8AF55625C137A8002E6C99 /* HomeViewController.swift */, + DB0AC70925CD2E0300D75117 /* HomeViewController+DebugAction.swift */, DBD4ED1025CC0FEB0041B741 /* HomeViewModel.swift */, ); path = HomeTimeline; @@ -783,12 +789,12 @@ ); name = Mastodon; packageProductDependencies = ( - DB3D0FF225BAA61700EAA174 /* SwiftPackageProductDependency */, - 5D526FE125BE9AC400460CB9 /* SwiftPackageProductDependency */, - 2D61336825C18A4F00CAE157 /* SwiftPackageProductDependency */, - 2D42FF6025C8177C004A627A /* SwiftPackageProductDependency */, - DB0140BC25C40D7500F9F3CF /* SwiftPackageProductDependency */, - DB5086B725CC0D6400C2C187 /* SwiftPackageProductDependency */, + DB3D0FF225BAA61700EAA174 /* AlamofireImage */, + 5D526FE125BE9AC400460CB9 /* MastodonSDK */, + 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */, + 2D42FF6025C8177C004A627A /* ActiveLabel */, + DB0140BC25C40D7500F9F3CF /* CommonOSLog */, + DB5086B725CC0D6400C2C187 /* Kingfisher */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -911,11 +917,11 @@ ); mainGroup = DB427DC925BAA00100D1B89D; packageReferences = ( - DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */, - 2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */, - 2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */, - DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */, - DB5086B625CC0D6400C2C187 /* RemoteSwiftPackageReference */, + DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */, + 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */, + 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */, + DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */, + DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = DB427DD325BAA00100D1B89D /* Products */; projectDirPath = ""; @@ -1108,9 +1114,11 @@ 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */, 2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */, DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */, + DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */, 2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */, DB98338825C945ED00AD9700 /* Assets.swift in Sources */, 2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */, + DB0AC70A25CD2E0300D75117 /* HomeViewController+DebugAction.swift in Sources */, DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */, DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */, 2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */, @@ -1665,7 +1673,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 2D42FF5F25C8177C004A627A /* RemoteSwiftPackageReference */ = { + 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift"; requirement = { @@ -1673,7 +1681,7 @@ minimumVersion = 4.0.0; }; }; - 2D61336725C18A4F00CAE157 /* RemoteSwiftPackageReference */ = { + 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/AlamofireNetworkActivityIndicator"; requirement = { @@ -1681,7 +1689,7 @@ minimumVersion = 3.1.0; }; }; - DB0140BB25C40D7500F9F3CF /* RemoteSwiftPackageReference */ = { + DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/MainasuK/CommonOSLog"; requirement = { @@ -1689,7 +1697,7 @@ minimumVersion = 0.1.1; }; }; - DB3D0FF125BAA61700EAA174 /* RemoteSwiftPackageReference */ = { + DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/AlamofireImage.git"; requirement = { @@ -1697,7 +1705,7 @@ minimumVersion = 4.1.0; }; }; - DB5086B625CC0D6400C2C187 /* RemoteSwiftPackageReference */ = { + DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/onevcat/Kingfisher.git"; requirement = { @@ -1708,33 +1716,33 @@ /* 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; }; - DB5086B725CC0D6400C2C187 /* SwiftPackageProductDependency */ = { + DB5086B725CC0D6400C2C187 /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; - package = DB5086B625CC0D6400C2C187 /* RemoteSwiftPackageReference */; + package = DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Mastodon/Scene/Authentication/AuthenticationViewController.swift b/Mastodon/Scene/Authentication/AuthenticationViewController.swift index 4c20528a5..3b4a8bfc2 100644 --- a/Mastodon/Scene/Authentication/AuthenticationViewController.swift +++ b/Mastodon/Scene/Authentication/AuthenticationViewController.swift @@ -41,6 +41,7 @@ final class AuthenticationViewController: UIViewController, NeedsDependency { let button = UIButton(type: .system) button.titleLabel?.font = .preferredFont(forTextStyle: .headline) button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Background.secondarySystemBackground.color), for: .normal) + button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Background.secondarySystemBackground.color.withAlphaComponent(0.8)), for: .disabled) button.setTitleColor(Asset.Colors.Label.primary.color, for: .normal) button.setTitle("Sign in", for: .normal) button.layer.masksToBounds = true @@ -53,6 +54,7 @@ final class AuthenticationViewController: UIViewController, NeedsDependency { let button = UIButton(type: .system) button.titleLabel?.font = .preferredFont(forTextStyle: .subheadline) button.setTitleColor(Asset.Colors.Button.highlight.color, for: .normal) + button.setTitleColor(.systemGray, for: .disabled) button.setTitle("Sign up", for: .normal) return button }() @@ -126,6 +128,9 @@ extension AuthenticationViewController { .sink { [weak self] isAuthenticating in guard let self = self else { return } isAuthenticating ? self.activityIndicatorView.startAnimating() : self.activityIndicatorView.stopAnimating() + self.signInButton.setTitle(isAuthenticating ? "" : "Sign in", for: .normal) + self.signInButton.isEnabled = !isAuthenticating + self.signUpButton.isEnabled = !isAuthenticating } .store(in: &disposeBag) @@ -171,6 +176,8 @@ extension AuthenticationViewController { .store(in: &disposeBag) signInButton.addTarget(self, action: #selector(AuthenticationViewController.signInButtonPressed(_:)), for: .touchUpInside) + signUpButton.addTarget(self, action: #selector(AuthenticationViewController.signUpButtonPressed(_:)), for: .touchUpInside) + } override func viewWillAppear(_ animated: Bool) { @@ -194,18 +201,10 @@ extension AuthenticationViewController { context.apiService.createApplication(domain: domain) .tryMap { response -> AuthenticationViewModel.AuthenticateInfo in let application = response.value - guard let clientID = application.clientID, - let clientSecret = application.clientSecret else { + guard let info = AuthenticationViewModel.AuthenticateInfo(domain: domain, application: application) 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 AuthenticationViewModel.AuthenticateInfo( - domain: domain, - clientID: clientID, - clientSecret: clientSecret, - url: url - ) + return info } .receive(on: DispatchQueue.main) .sink { [weak self] completion in @@ -222,7 +221,7 @@ extension AuthenticationViewController { } } receiveValue: { [weak self] info in guard let self = self else { return } - let mastodonPinBasedAuthenticationViewModel = MastodonPinBasedAuthenticationViewModel(authenticateURL: info.url) + let mastodonPinBasedAuthenticationViewModel = MastodonPinBasedAuthenticationViewModel(authenticateURL: info.authorizeURL) self.viewModel.authenticate( info: info, pinCodePublisher: mastodonPinBasedAuthenticationViewModel.pinCodePublisher @@ -236,6 +235,50 @@ extension AuthenticationViewController { .store(in: &disposeBag) } + @objc private func signUpButtonPressed(_ sender: UIButton) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + guard viewModel.isDomainValid.value, let domain = viewModel.domain.value else { + domainTextField.shake() + return + } + guard !viewModel.isAuthenticating.value else { return } + + context.apiService.instance(domain: domain) + .compactMap { [weak self] response -> AnyPublisher, Error>? in + guard let self = self else { return nil } + guard response.value.registrations != false else { + return Fail(error: AuthenticationViewModel.AuthenticationError.registrationClosed).eraseToAnyPublisher() + } + return self.context.apiService.createApplication(domain: domain) + } + .switchToLatest() + .tryMap { response -> AuthenticationViewModel.AuthenticateInfo in + let application = response.value + guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(domain: domain, application: application) else { + throw APIService.APIError.explicit(.badResponse) + } + return authenticateInfo + } + .compactMap { [weak self] authenticateInfo -> AnyPublisher, Error>? in + guard let self = self else { return nil } + return self.context.apiService.applicationAccessToken(domain: domain, clientID: authenticateInfo.clientID, clientSecret: authenticateInfo.clientSecret) + } + .switchToLatest() + .receive(on: DispatchQueue.main) + .sink { completion in + switch completion { + case .failure(let error): + break + case .finished: + break + } + } receiveValue: { [weak self] response in + guard let self = self else { return } + print(response) + } + .store(in: &disposeBag) + } + } // MARK: - UIAdaptivePresentationControllerDelegate diff --git a/Mastodon/Scene/Authentication/AuthenticationViewModel.swift b/Mastodon/Scene/Authentication/AuthenticationViewModel.swift index 1a93681fa..c213e73d0 100644 --- a/Mastodon/Scene/Authentication/AuthenticationViewModel.swift +++ b/Mastodon/Scene/Authentication/AuthenticationViewModel.swift @@ -67,13 +67,54 @@ final class AuthenticationViewModel { } +extension AuthenticationViewModel { + enum AuthenticationError: Error, LocalizedError { + case badCredentials + case registrationClosed + + var errorDescription: String? { + switch self { + case .badCredentials: return "Bad Credentials" + case .registrationClosed: return "Registration Closed" + } + } + + var failureReason: String? { + switch self { + case .badCredentials: return "Credentials invalid." + case .registrationClosed: return "Server disallow registration." + } + } + + var helpAnchor: String? { + switch self { + case .badCredentials: return "Please try again." + case .registrationClosed: return "Please try another domain." + } + } + } +} + extension AuthenticationViewModel { struct AuthenticateInfo { let domain: String let clientID: String let clientSecret: String - let url: URL + let authorizeURL: URL + + init?(domain: String, application: Mastodon.Entity.Application) { + self.domain = domain + guard let clientID = application.clientID, + let clientSecret = application.clientSecret else { return nil } + self.clientID = clientID + self.clientSecret = clientSecret + self.authorizeURL = { + let query = Mastodon.API.OAuth.AuthorizeQuery(clientID: clientID) + let url = Mastodon.API.OAuth.authorizeURL(domain: domain, query: query) + return url + }() + } } func authenticate(info: AuthenticateInfo, pinCodePublisher: PassthroughSubject) { @@ -144,7 +185,7 @@ extension AuthenticationViewModel { mastodonUserRequest.predicate = MastodonUser.predicate(domain: info.domain, id: account.id) mastodonUserRequest.fetchLimit = 1 guard let mastodonUser = try? managedObjectContext.fetch(mastodonUserRequest).first else { - return Fail(error: APIService.APIError.explicit(.badCredentials)).eraseToAnyPublisher() + return Fail(error: AuthenticationError.badCredentials).eraseToAnyPublisher() } let property = MastodonAuthentication.Property( diff --git a/Mastodon/Scene/HomeTimeline/HomeViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeViewController+DebugAction.swift new file mode 100644 index 000000000..2839065dc --- /dev/null +++ b/Mastodon/Scene/HomeTimeline/HomeViewController+DebugAction.swift @@ -0,0 +1,63 @@ +// +// HomeViewController+DebugAction.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-2-5. +// + +import os.log +import UIKit + +#if DEBUG +extension HomeViewController { + var debugMenu: UIMenu { + let menu = UIMenu( + title: "Debug Tools", + image: nil, + identifier: nil, + options: .displayInline, + children: [ + UIAction(title: "Sign Out", image: UIImage(systemName: "escape"), attributes: .destructive) { [weak self] action in + guard let self = self else { return } + self.signOutAction(action) + } + ] + ) + return menu + } +} + +extension HomeViewController { + + @objc private func signOutAction(_ sender: UIAction) { + guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { + return + } + let currentAccountCount = context.authenticationService.mastodonAuthentications.value.count + let isAuthenticationExistWhenSignOut = currentAccountCount - 1 > 0 + // prepare advance + let authenticationViewModel = AuthenticationViewModel(context: context, coordinator: coordinator, isAuthenticationExist: isAuthenticationExistWhenSignOut) + + context.authenticationService.signOutMastodonUser( + domain: activeMastodonAuthenticationBox.domain, + userID: activeMastodonAuthenticationBox.userID + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard let self = self else { return } + switch result { + case .failure(let error): + assertionFailure(error.localizedDescription) + case .success(let isSignOut): + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign out %s", ((#file as NSString).lastPathComponent), #line, #function, isSignOut ? "success" : "fail") + guard isSignOut else { return } + if !isAuthenticationExistWhenSignOut { + self.coordinator.present(scene: .authentication(viewModel: authenticationViewModel), from: nil, transition: .modal(animated: true, completion: nil)) + } + } + } + .store(in: &disposeBag) + } + +} +#endif diff --git a/Mastodon/Scene/HomeTimeline/HomeViewController.swift b/Mastodon/Scene/HomeTimeline/HomeViewController.swift index 8934d3c33..afcd7def0 100644 --- a/Mastodon/Scene/HomeTimeline/HomeViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeViewController.swift @@ -31,7 +31,11 @@ extension HomeViewController { view.backgroundColor = Asset.Colors.Background.systemBackground.color navigationItem.leftBarButtonItem = avatarBarButtonItem avatarBarButtonItem.avatarButton.addTarget(self, action: #selector(HomeViewController.avatarBarButtonItemDidPressed(_:)), for: .touchUpInside) - + #if DEBUG + avatarBarButtonItem.avatarButton.menu = debugMenu + avatarBarButtonItem.avatarButton.showsMenuAsPrimaryAction = true + #endif + Publishers.CombineLatest( context.authenticationService.activeMastodonAuthentication.eraseToAnyPublisher(), viewModel.viewDidAppear.eraseToAnyPublisher() diff --git a/Mastodon/Service/APIService/APIService+APIError.swift b/Mastodon/Service/APIService/APIService+APIError.swift index 9a72e0e4f..7fd29b6b3 100644 --- a/Mastodon/Service/APIService/APIService+APIError.swift +++ b/Mastodon/Service/APIService/APIService+APIError.swift @@ -17,7 +17,6 @@ extension APIService { enum ErrorReason { // application internal error case authenticationMissing - case badCredentials case badRequest case badResponse case requestThrottle @@ -42,7 +41,6 @@ extension APIService.APIError: LocalizedError { var errorDescription: String? { switch errorReason { case .authenticationMissing: return "Fail to Authenticatie" - case .badCredentials: return "Bad Credentials" case .badRequest: return "Bad Request" case .badResponse: return "Bad Response" case .requestThrottle: return "Request Throttled" @@ -61,7 +59,6 @@ extension APIService.APIError: LocalizedError { var failureReason: String? { switch errorReason { case .authenticationMissing: return "Account credential not found." - case .badCredentials: return "Credentials invalid." case .badRequest: return "Request invalid." case .badResponse: return "Response invalid." case .requestThrottle: return "Request too frequency." @@ -76,7 +73,6 @@ extension APIService.APIError: LocalizedError { var helpAnchor: String? { switch errorReason { case .authenticationMissing: return "Please request after authenticated." - case .badCredentials: return "Please try again.." case .badRequest: return "Please try again." case .badResponse: return "Please try again." case .requestThrottle: return "Please try again later." diff --git a/Mastodon/Service/APIService/APIService+Authentication.swift b/Mastodon/Service/APIService/APIService+Authentication.swift index 486a27c44..55a188a5c 100644 --- a/Mastodon/Service/APIService/APIService+Authentication.swift +++ b/Mastodon/Service/APIService/APIService+Authentication.swift @@ -31,5 +31,23 @@ extension APIService { query: query ) } + + func applicationAccessToken( + domain: String, + clientID: String, + clientSecret: String + ) -> AnyPublisher, Error> { + let query = Mastodon.API.OAuth.AccessTokenQuery( + clientID: clientID, + clientSecret: clientSecret, + code: nil, + grantType: "client_credentials" + ) + return Mastodon.API.OAuth.accessToken( + session: session, + domain: domain, + query: query + ) + } } diff --git a/Mastodon/Service/APIService/APIService+Instance.swift b/Mastodon/Service/APIService/APIService+Instance.swift new file mode 100644 index 000000000..4bc613b55 --- /dev/null +++ b/Mastodon/Service/APIService/APIService+Instance.swift @@ -0,0 +1,24 @@ +// +// APIService+Instance.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-2-5. +// + +import Foundation +import Combine +import CoreData +import CoreDataStack +import CommonOSLog +import DateToolsSwift +import MastodonSDK + +extension APIService { + + func instance( + domain: String + ) -> AnyPublisher, Error> { + return Mastodon.API.Instance.instance(session: session, domain: domain) + } + +} diff --git a/MastodonSDK.xctestplan b/MastodonSDK.xctestplan index f37534015..6d1b34603 100644 --- a/MastodonSDK.xctestplan +++ b/MastodonSDK.xctestplan @@ -12,6 +12,18 @@ ] } }, + { + "id" : "9DA3EAE9-A502-49E9-BC74-5ED71250F4A8", + "name" : "mastodon.social", + "options" : { + "environmentVariableEntries" : [ + { + "key" : "domain", + "value" : "mastodon.social" + } + ] + } + }, { "id" : "C5184AF3-B83B-4A7E-949C-6B1AA3ABE7D1", "name" : "pawoo.net", @@ -23,6 +35,18 @@ } ] } + }, + { + "id" : "71C5BF3D-A8CE-468B-B22E-E6DA4AD5AF4E", + "name" : "freespeechextremist.com", + "options" : { + "environmentVariableEntries" : [ + { + "key" : "domain", + "value" : "freespeechextremist.com" + } + ] + } } ], "defaultOptions" : { diff --git a/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+Instance.swift b/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+Instance.swift index b5de3fe31..211c70d00 100644 --- a/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+Instance.swift +++ b/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+Instance.swift @@ -29,7 +29,7 @@ extension MastodonSDKTests { break } } receiveValue: { response in - XCTAssertEqual(response.value.uri, domain) + XCTAssertNotEqual(response.value.uri, "") print(response.value) theExpectation.fulfill() }