Refactoring

This commit is contained in:
Justin Mazzocchi 2020-09-11 19:50:42 -07:00
parent 2745f2470d
commit ceff3fd4c9
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
8 changed files with 91 additions and 110 deletions

View file

@ -0,0 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
extension URL: Identifiable {
public var id: String { absoluteString }
}

View file

@ -13,7 +13,20 @@ public enum AccessTokenEndpoint {
code: String?, code: String?,
redirectURI: 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 { extension AccessTokenEndpoint: Endpoint {
@ -56,15 +69,17 @@ extension AccessTokenEndpoint: Endpoint {
params["redirect_uri"] = redirectURI params["redirect_uri"] = redirectURI
return params return params
case let .accounts(username, email, password, reason): case let .accounts(registration):
var params: [String: Any] = [ var params: [String: Any] = [
"username": username, "username": registration.username,
"email": email, "email": registration.email,
"password": password, "password": registration.password,
"locale": Locale.autoupdatingCurrent.languageCode ?? "en", // TODO: probably need to map "locale": registration.locale,
"agreement": true] "agreement": registration.agreement]
params["reason"] = reason if !registration.reason.isEmpty {
params["reason"] = registration.reason
}
return params return params
} }

View file

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */; };
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */; }; D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */; };
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; }; D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
@ -80,6 +81,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
D0030981250C6C8500EACB32 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = "<group>"; }; D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = "<group>"; };
D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; }; D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; }; D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
@ -302,6 +304,7 @@
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */, D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */, D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */, D0C7D46F24F76169001EBDBB /* View+Extensions.swift */,
D0030981250C6C8500EACB32 /* URL+Extensions.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -505,6 +508,7 @@
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */, D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */, D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */, D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */,
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */, D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */, D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */, D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,

View file

@ -0,0 +1,5 @@
// Copyright © 2020 Metabolist. All rights reserved.
import MastodonAPI
public typealias Registration = AccessTokenEndpoint.Registration

View file

@ -37,18 +37,12 @@ public extension AllIdentitiesService {
: nil) : nil)
} }
func createIdentity( func createIdentity(id: UUID, url: URL, registration: Registration) -> AnyPublisher<Never, Error> {
id: UUID,
url: URL,
username: String,
email: String,
password: String,
reason: String?) -> AnyPublisher<Never, Error> {
createIdentity( createIdentity(
id: id, id: id,
url: url, url: url,
authenticationPublisher: AuthenticationService(url: url, environment: environment) authenticationPublisher: AuthenticationService(url: url, environment: environment)
.register(username: username, email: email, password: password, reason: reason)) .register(registration))
} }
func deleteIdentity(id: UUID) -> AnyPublisher<Never, Error> { func deleteIdentity(id: UUID) -> AnyPublisher<Never, Error> {

View file

@ -29,10 +29,7 @@ extension AuthenticationService {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func register(username: String, func register(_ registration: Registration) -> AnyPublisher<(AppAuthorization, AccessToken), Error> {
email: String,
password: String,
reason: String?) -> AnyPublisher<(AppAuthorization, AccessToken), Error> {
let authorization = appAuthorization() let authorization = appAuthorization()
.share() .share()
@ -49,12 +46,7 @@ extension AuthenticationService {
.flatMap { accessToken -> AnyPublisher<AccessToken, Error> in .flatMap { accessToken -> AnyPublisher<AccessToken, Error> in
mastodonAPIClient.accessToken = accessToken.accessToken mastodonAPIClient.accessToken = accessToken.accessToken
return mastodonAPIClient.request( return mastodonAPIClient.request(AccessTokenEndpoint.accounts(registration))
AccessTokenEndpoint.accounts(
username: username,
email: email,
password: password,
reason: reason))
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
}) })

View file

@ -3,7 +3,6 @@
import Combine import Combine
import Foundation import Foundation
import Mastodon import Mastodon
import MastodonAPI
import ServiceLayer import ServiceLayer
public enum RegistrationError: Error { public enum RegistrationError: Error {
@ -15,14 +14,9 @@ public final class RegistrationViewModel: ObservableObject {
public let serverRulesURL: URL public let serverRulesURL: URL
public let termsOfServiceURL: URL public let termsOfServiceURL: URL
@Published public var alertItem: AlertItem? @Published public var alertItem: AlertItem?
@Published public var username = "" @Published public var registration = Registration()
@Published public var email = ""
@Published public var password = ""
@Published public var passwordConfirmation = "" @Published public var passwordConfirmation = ""
@Published public var reason = "" @Published public private(set) var registerDisabled = true
@Published public var passwordsMatch = false
@Published public var agreement = false
@Published public private(set) var registerButtonEnabled = false
@Published public private(set) var registering = false @Published public private(set) var registering = false
private let url: URL private let url: URL
@ -36,34 +30,27 @@ public final class RegistrationViewModel: ObservableObject {
self.termsOfServiceURL = url.appendingPathComponent("terms") self.termsOfServiceURL = url.appendingPathComponent("terms")
self.allIdentitiesService = allIdentitiesService self.allIdentitiesService = allIdentitiesService
Publishers.CombineLatest4($username, $email, $password, $reason) $registration
.map { username, email, password, reason in .map {
!username.isEmpty $0.username.isEmpty
&& !email.isEmpty || $0.email.isEmpty
&& !password.isEmpty || $0.password.isEmpty
&& (!instance.approvalRequired || !reason.isEmpty) || ($0.reason.isEmpty && instance.approvalRequired)
|| !$0.agreement
} }
.combineLatest($agreement) .assign(to: &$registerDisabled)
.map { $0 && $1 }
.assign(to: &$registerButtonEnabled)
} }
} }
public extension RegistrationViewModel { public extension RegistrationViewModel {
func registerTapped() { func registerTapped() {
guard password == passwordConfirmation else { guard registration.password == passwordConfirmation else {
alertItem = AlertItem(error: RegistrationError.passwordConfirmationMismatch) alertItem = AlertItem(error: RegistrationError.passwordConfirmationMismatch)
return return
} }
allIdentitiesService.createIdentity( allIdentitiesService.createIdentity(id: UUID(), url: url, registration: registration)
id: UUID(),
url: url,
username: username,
email: email,
password: password,
reason: reason)
.handleEvents(receiveSubscription: { [weak self] _ in self?.registering = true }) .handleEvents(receiveSubscription: { [weak self] _ in self?.registering = true })
.mapError { error -> Error in .mapError { error -> Error in
if error is URLError { if error is URLError {

View file

@ -6,75 +6,52 @@ import ViewModels
struct RegistrationView: View { struct RegistrationView: View {
@StateObject var viewModel: RegistrationViewModel @StateObject var viewModel: RegistrationViewModel
@State private var presentWebView = false @State private var presentURL: URL?
@State private var toReview = ToReview.serverRules
var body: some View { var body: some View {
Form { Form {
Section { HStack {
HStack { TextField("registration.username", text: $viewModel.registration.username)
TextField("registration.username", text: $viewModel.username)
.autocapitalization(.none)
.disableAutocorrection(true)
Text("@" + viewModel.instance.uri)
.foregroundColor(.secondary)
}
TextField("registration.email", text: $viewModel.email)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.keyboardType(.emailAddress) Text("@" + viewModel.instance.uri)
SecureField("registration.password", text: $viewModel.password) .foregroundColor(.secondary)
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)
} }
Section { TextField("registration.email", text: $viewModel.registration.email)
Group { .autocapitalization(.none)
if viewModel.registering { .disableAutocorrection(true)
ProgressView() .keyboardType(.emailAddress)
} else { SecureField("registration.password", text: $viewModel.registration.password)
Button(viewModel.instance.approvalRequired SecureField("registration.password-confirmation", text: $viewModel.passwordConfirmation)
? "add-identity.request-invite" if viewModel.instance.approvalRequired {
: "add-identity.join", VStack(alignment: .leading) {
action: viewModel.registerTapped) Text("registration.reason-\(viewModel.instance.uri)")
.disabled(!viewModel.registerButtonEnabled) 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) .alertItem($viewModel.alertItem)
.sheet(isPresented: $presentWebView) { () -> SafariView in .sheet(item: $presentURL) { SafariView(url: $0) }
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
} }
} }