From ceff3fd4c9194f1f22e6759f4558b06a2074193b Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Fri, 11 Sep 2020 19:50:42 -0700 Subject: [PATCH] Refactoring --- Extensions/URL+Extensions.swift | 7 ++ .../Endpoints/AccessTokenEndpoint.swift | 31 ++++-- Metatext.xcodeproj/project.pbxproj | 4 + .../ServiceLayer/Entities/Registration.swift | 5 + .../Services/AllIdentitiesService.swift | 10 +- .../Services/AuthenticationService.swift | 12 +-- .../ViewModels/RegistrationViewModel.swift | 37 +++----- Views/RegistrationView.swift | 95 +++++++------------ 8 files changed, 91 insertions(+), 110 deletions(-) create mode 100644 Extensions/URL+Extensions.swift create mode 100644 ServiceLayer/Sources/ServiceLayer/Entities/Registration.swift diff --git a/Extensions/URL+Extensions.swift b/Extensions/URL+Extensions.swift new file mode 100644 index 0000000..d048857 --- /dev/null +++ b/Extensions/URL+Extensions.swift @@ -0,0 +1,7 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation + +extension URL: Identifiable { + public var id: String { absoluteString } +} diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift index 6cd214c..3f259d8 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift @@ -13,7 +13,20 @@ public enum AccessTokenEndpoint { code: String?, redirectURI: String? ) - case accounts(username: String, email: String, password: String, reason: String?) + case accounts(Registration) +} + +public extension AccessTokenEndpoint { + struct Registration { + public var username = "" + public var email = "" + public var password = "" + public var locale = "en" + public var reason = "" + public var agreement = false + + public init() {} + } } extension AccessTokenEndpoint: Endpoint { @@ -56,15 +69,17 @@ extension AccessTokenEndpoint: Endpoint { params["redirect_uri"] = redirectURI return params - case let .accounts(username, email, password, reason): + case let .accounts(registration): var params: [String: Any] = [ - "username": username, - "email": email, - "password": password, - "locale": Locale.autoupdatingCurrent.languageCode ?? "en", // TODO: probably need to map - "agreement": true] + "username": registration.username, + "email": registration.email, + "password": registration.password, + "locale": registration.locale, + "agreement": registration.agreement] - params["reason"] = reason + if !registration.reason.isEmpty { + params["reason"] = registration.reason + } return params } diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 59233f5..bc4dadd 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0030981250C6C8500EACB32 /* URL+Extensions.swift */; }; D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */; }; D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */; }; D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; }; @@ -80,6 +81,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + D0030981250C6C8500EACB32 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = ""; }; D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = ""; }; D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = ""; }; D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = ""; }; @@ -302,6 +304,7 @@ D0C7D46A24F76169001EBDBB /* String+Extensions.swift */, D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */, D0C7D46F24F76169001EBDBB /* View+Extensions.swift */, + D0030981250C6C8500EACB32 /* URL+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -505,6 +508,7 @@ D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */, D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */, D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */, + D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */, D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */, D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */, D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */, diff --git a/ServiceLayer/Sources/ServiceLayer/Entities/Registration.swift b/ServiceLayer/Sources/ServiceLayer/Entities/Registration.swift new file mode 100644 index 0000000..710bebd --- /dev/null +++ b/ServiceLayer/Sources/ServiceLayer/Entities/Registration.swift @@ -0,0 +1,5 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import MastodonAPI + +public typealias Registration = AccessTokenEndpoint.Registration diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift index fd8c457..fbeb381 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift @@ -37,18 +37,12 @@ public extension AllIdentitiesService { : nil) } - func createIdentity( - id: UUID, - url: URL, - username: String, - email: String, - password: String, - reason: String?) -> AnyPublisher { + func createIdentity(id: UUID, url: URL, registration: Registration) -> AnyPublisher { createIdentity( id: id, url: url, authenticationPublisher: AuthenticationService(url: url, environment: environment) - .register(username: username, email: email, password: password, reason: reason)) + .register(registration)) } func deleteIdentity(id: UUID) -> AnyPublisher { diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AuthenticationService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AuthenticationService.swift index c843e22..fb53b41 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AuthenticationService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AuthenticationService.swift @@ -29,10 +29,7 @@ extension AuthenticationService { .eraseToAnyPublisher() } - func register(username: String, - email: String, - password: String, - reason: String?) -> AnyPublisher<(AppAuthorization, AccessToken), Error> { + func register(_ registration: Registration) -> AnyPublisher<(AppAuthorization, AccessToken), Error> { let authorization = appAuthorization() .share() @@ -49,12 +46,7 @@ extension AuthenticationService { .flatMap { accessToken -> AnyPublisher in mastodonAPIClient.accessToken = accessToken.accessToken - return mastodonAPIClient.request( - AccessTokenEndpoint.accounts( - username: username, - email: email, - password: password, - reason: reason)) + return mastodonAPIClient.request(AccessTokenEndpoint.accounts(registration)) } .eraseToAnyPublisher() }) diff --git a/ViewModels/Sources/ViewModels/RegistrationViewModel.swift b/ViewModels/Sources/ViewModels/RegistrationViewModel.swift index 9c194c0..e17eecf 100644 --- a/ViewModels/Sources/ViewModels/RegistrationViewModel.swift +++ b/ViewModels/Sources/ViewModels/RegistrationViewModel.swift @@ -3,7 +3,6 @@ import Combine import Foundation import Mastodon -import MastodonAPI import ServiceLayer public enum RegistrationError: Error { @@ -15,14 +14,9 @@ public final class RegistrationViewModel: ObservableObject { public let serverRulesURL: URL public let termsOfServiceURL: URL @Published public var alertItem: AlertItem? - @Published public var username = "" - @Published public var email = "" - @Published public var password = "" + @Published public var registration = Registration() @Published public var passwordConfirmation = "" - @Published public var reason = "" - @Published public var passwordsMatch = false - @Published public var agreement = false - @Published public private(set) var registerButtonEnabled = false + @Published public private(set) var registerDisabled = true @Published public private(set) var registering = false private let url: URL @@ -36,34 +30,27 @@ public final class RegistrationViewModel: ObservableObject { self.termsOfServiceURL = url.appendingPathComponent("terms") self.allIdentitiesService = allIdentitiesService - Publishers.CombineLatest4($username, $email, $password, $reason) - .map { username, email, password, reason in - !username.isEmpty - && !email.isEmpty - && !password.isEmpty - && (!instance.approvalRequired || !reason.isEmpty) + $registration + .map { + $0.username.isEmpty + || $0.email.isEmpty + || $0.password.isEmpty + || ($0.reason.isEmpty && instance.approvalRequired) + || !$0.agreement } - .combineLatest($agreement) - .map { $0 && $1 } - .assign(to: &$registerButtonEnabled) + .assign(to: &$registerDisabled) } } public extension RegistrationViewModel { func registerTapped() { - guard password == passwordConfirmation else { + guard registration.password == passwordConfirmation else { alertItem = AlertItem(error: RegistrationError.passwordConfirmationMismatch) return } - allIdentitiesService.createIdentity( - id: UUID(), - url: url, - username: username, - email: email, - password: password, - reason: reason) + allIdentitiesService.createIdentity(id: UUID(), url: url, registration: registration) .handleEvents(receiveSubscription: { [weak self] _ in self?.registering = true }) .mapError { error -> Error in if error is URLError { diff --git a/Views/RegistrationView.swift b/Views/RegistrationView.swift index 2bbce09..adbfe9c 100644 --- a/Views/RegistrationView.swift +++ b/Views/RegistrationView.swift @@ -6,75 +6,52 @@ import ViewModels struct RegistrationView: View { @StateObject var viewModel: RegistrationViewModel - @State private var presentWebView = false - @State private var toReview = ToReview.serverRules + @State private var presentURL: URL? var body: some View { Form { - Section { - HStack { - TextField("registration.username", text: $viewModel.username) - .autocapitalization(.none) - .disableAutocorrection(true) - Text("@" + viewModel.instance.uri) - .foregroundColor(.secondary) - } - TextField("registration.email", text: $viewModel.email) + HStack { + TextField("registration.username", text: $viewModel.registration.username) .autocapitalization(.none) .disableAutocorrection(true) - .keyboardType(.emailAddress) - SecureField("registration.password", text: $viewModel.password) - SecureField("registration.password-confirmation", text: $viewModel.passwordConfirmation) - if viewModel.instance.approvalRequired { - VStack(alignment: .leading) { - Text("registration.reason-\(viewModel.instance.uri)") - TextEditor(text: $viewModel.reason) - } - } - Button("registration.server-rules") { - toReview = .serverRules - presentWebView = true - } - Button("registration.terms-of-service") { - toReview = .termsOfService - presentWebView = true - } - Toggle("registration.agree-to-server-rules-and-terms-of-service", - isOn: $viewModel.agreement) + Text("@" + viewModel.instance.uri) + .foregroundColor(.secondary) } - Section { - Group { - if viewModel.registering { - ProgressView() - } else { - Button(viewModel.instance.approvalRequired - ? "add-identity.request-invite" - : "add-identity.join", - action: viewModel.registerTapped) - .disabled(!viewModel.registerButtonEnabled) - } + TextField("registration.email", text: $viewModel.registration.email) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.emailAddress) + SecureField("registration.password", text: $viewModel.registration.password) + SecureField("registration.password-confirmation", text: $viewModel.passwordConfirmation) + if viewModel.instance.approvalRequired { + VStack(alignment: .leading) { + Text("registration.reason-\(viewModel.instance.uri)") + TextEditor(text: $viewModel.registration.reason) } - .frame(maxWidth: .infinity, alignment: .center) } + Button("registration.server-rules") { + presentURL = viewModel.serverRulesURL + } + Button("registration.terms-of-service") { + presentURL = viewModel.termsOfServiceURL + } + Toggle("registration.agree-to-server-rules-and-terms-of-service", + isOn: $viewModel.registration.agreement) + Group { + if viewModel.registering { + ProgressView() + } else { + Button(viewModel.instance.approvalRequired + ? "add-identity.request-invite" + : "add-identity.join", + action: viewModel.registerTapped) + .disabled(viewModel.registerDisabled) + } + } + .frame(maxWidth: .infinity, alignment: .center) } .alertItem($viewModel.alertItem) - .sheet(isPresented: $presentWebView) { () -> SafariView in - let url: URL - - switch toReview { - case .serverRules: url = viewModel.serverRulesURL - case .termsOfService: url = viewModel.termsOfServiceURL - } - - return SafariView(url: url) - } - } -} - -private extension RegistrationView { - enum ToReview { - case serverRules - case termsOfService + .sheet(item: $presentURL) { SafariView(url: $0) } } }