Files
omnivore/apple/OmnivoreKit/Sources/App/Views/CreateProfileContainerView.swift
2022-02-24 17:09:47 -08:00

127 lines
4.0 KiB
Swift

import Combine
import Models
import Services
import SwiftUI
import Utils
import Views
final class CreateProfileViewModel: ObservableObject {
let initialUserProfile: UserProfile
var hasSuggestedProfile: Bool {
!(initialUserProfile.name.isEmpty && initialUserProfile.username.isEmpty)
}
var headlineText: String {
hasSuggestedProfile ? "Confirm Your Profile" : "Create Your Profile"
}
var submitButtonText: String {
hasSuggestedProfile ? "Confirm" : "Submit"
}
@Published var loginError: LoginError?
@Published var validationErrorMessage: String?
@Published var potentialUsernameStatus = PotentialUsernameStatus.noUsername
@Published var potentialUsername: String
var subscriptions = Set<AnyCancellable>()
init(initialUserProfile: UserProfile, dataService: DataService) {
self.initialUserProfile = initialUserProfile
self.potentialUsername = initialUserProfile.username
$potentialUsername
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.sink(receiveValue: { [weak self] username in
self?.validateUsername(username: username, dataService: dataService)
})
.store(in: &subscriptions)
}
func submitProfile(name: String, bio: String, authenticator: Authenticator) {
let profileOrError = UserProfile.make(
username: potentialUsername,
name: name,
bio: bio.isEmpty ? nil : bio
)
switch profileOrError {
case let .left(userProfile):
submitProfile(userProfile: userProfile, authenticator: authenticator)
case let .right(errorMessage):
validationErrorMessage = errorMessage
}
}
func validateUsername(username: String, dataService: DataService) {
if let status = PotentialUsernameStatus.validationError(username: username.lowercased()) {
potentialUsernameStatus = status
return
}
dataService.validateUsernamePublisher(username: username).sink(
receiveCompletion: { [weak self] completion in
guard case let .failure(usernameError) = completion else { return }
switch usernameError {
case .tooShort:
self?.potentialUsernameStatus = .tooShort
case .tooLong:
self?.potentialUsernameStatus = .tooLong
case .invalidPattern:
self?.potentialUsernameStatus = .invalidPattern
case .nameUnavailable:
self?.potentialUsernameStatus = .unavailable
case .internalServer, .unknown:
self?.loginError = .unknown
case .network:
self?.loginError = .network
}
},
receiveValue: { [weak self] in
self?.potentialUsernameStatus = .available
}
)
.store(in: &subscriptions)
}
func submitProfile(userProfile: UserProfile, authenticator: Authenticator) {
authenticator
.createAccount(userProfile: userProfile).sink(
receiveCompletion: { [weak self] completion in
guard case let .failure(loginError) = completion else { return }
self?.loginError = loginError
},
receiveValue: { _ in }
)
.store(in: &subscriptions)
}
}
struct CreateProfileContainerView: View {
@EnvironmentObject var authenticator: Authenticator
@EnvironmentObject var dataService: DataService
@ObservedObject private var viewModel: CreateProfileViewModel
@State private var name: String
@State private var bio = ""
init(userProfile: UserProfile, dataService: DataService) {
self.viewModel = CreateProfileViewModel(initialUserProfile: userProfile, dataService: dataService)
self._name = State(initialValue: userProfile.name)
}
var body: some View {
CreateProfileView(
name: $name,
potentialUsername: $viewModel.potentialUsername,
headlineText: viewModel.headlineText,
submitButtonText: viewModel.submitButtonText,
validationErrorMessage: viewModel.validationErrorMessage,
loginError: viewModel.loginError,
potentialUsernameStatus: viewModel.potentialUsernameStatus,
submitProfileAction: { viewModel.submitProfile(name: $0, bio: $1, authenticator: authenticator) }
)
}
}