mastodon-ios/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewControl...

326 lines
13 KiB
Swift
Raw Normal View History

2021-02-05 10:53:00 +01:00
//
// MastodonRegisterViewController.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-2-5.
//
import AlamofireImage
2021-02-05 10:53:00 +01:00
import Combine
import MastodonSDK
2021-02-20 06:55:06 +01:00
import os.log
import PhotosUI
2021-02-20 06:55:06 +01:00
import UIKit
import SwiftUI
import MastodonUI
import MastodonAsset
2022-10-08 07:43:06 +02:00
import MastodonCore
import MastodonLocalization
2021-02-05 10:53:00 +01:00
2021-02-25 08:39:48 +01:00
final class MastodonRegisterViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance {
2021-04-09 11:31:43 +02:00
static let avatarImageMaxSizeInPixel = CGSize(width: 400, height: 400)
2022-01-07 11:49:37 +01:00
let logger = Logger(subsystem: "MastodonRegisterViewController", category: "ViewController")
2021-02-05 10:53:00 +01:00
var disposeBag = Set<AnyCancellable>()
2022-01-07 11:49:37 +01:00
private var observations = Set<NSKeyValueObservation>()
2021-02-05 10:53:00 +01:00
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: MastodonRegisterViewModel!
private(set) lazy var mastodonRegisterView = MastodonRegisterView(viewModel: viewModel)
2021-02-20 06:55:06 +01:00
// picker
private(set) lazy var imagePicker: PHPickerViewController = {
var configuration = PHPickerConfiguration()
configuration.filter = .images
configuration.selectionLimit = 1
let imagePicker = PHPickerViewController(configuration: configuration)
imagePicker.delegate = self
return imagePicker
}()
private(set) lazy var imagePickerController: UIImagePickerController = {
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = .camera
imagePickerController.delegate = self
return imagePickerController
}()
private(set) lazy var documentPickerController: UIDocumentPickerViewController = {
2021-03-31 04:37:38 +02:00
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image])
documentPickerController.delegate = self
return documentPickerController
}()
2022-01-07 11:49:37 +01:00
let navigationActionView: NavigationActionView = {
let navigationActionView = NavigationActionView()
2022-02-15 11:15:58 +01:00
navigationActionView.backgroundColor = Asset.Scene.Onboarding.background.color
2022-01-07 11:49:37 +01:00
return navigationActionView
2021-02-05 10:53:00 +01:00
}()
2021-02-26 11:27:47 +01:00
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
2021-02-26 11:27:47 +01:00
}
2022-01-07 11:49:37 +01:00
2021-02-05 10:53:00 +01:00
}
extension MastodonRegisterViewController {
2021-02-05 10:53:00 +01:00
override func viewDidLoad() {
super.viewDidLoad()
2022-01-07 12:11:56 +01:00
navigationItem.leftBarButtonItem = UIBarButtonItem()
setupOnboardingAppearance()
viewModel.backgroundColor = view.backgroundColor ?? .clear
defer {
setupNavigationBarBackgroundView()
}
2021-02-25 08:39:48 +01:00
let hostingViewController = UIHostingController(rootView: mastodonRegisterView)
hostingViewController.view.preservesSuperviewLayoutMargins = true
addChild(hostingViewController)
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingViewController.view)
hostingViewController.view.pinToParent()
2022-01-07 11:49:37 +01:00
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(navigationActionView)
defer {
view.bringSubviewToFront(navigationActionView)
}
2021-02-05 10:53:00 +01:00
NSLayoutConstraint.activate([
2022-01-07 11:49:37 +01:00
navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor),
2021-02-05 10:53:00 +01:00
])
2021-02-26 11:27:47 +01:00
2022-01-07 11:49:37 +01:00
navigationActionView
.observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in
guard let self = self else { return }
2022-01-07 11:49:37 +01:00
let inset = navigationActionView.frame.height
self.viewModel.bottomPaddingHeight = inset
}
2022-01-07 11:49:37 +01:00
.store(in: &observations)
navigationActionView.backButton.addTarget(self, action: #selector(MastodonRegisterViewController.backButtonPressed(_:)), for: .touchUpInside)
navigationActionView.nextButton.addTarget(self, action: #selector(MastodonRegisterViewController.nextButtonPressed(_:)), for: .touchUpInside)
2021-02-20 10:13:16 +01:00
2022-01-07 11:49:37 +01:00
viewModel.$isAllValid
.receive(on: DispatchQueue.main)
.sink { [weak self] isAllValid in
guard let self = self else { return }
2022-01-07 11:49:37 +01:00
self.navigationActionView.nextButton.isEnabled = isAllValid
}
.store(in: &disposeBag)
viewModel.endEditing
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let self = self else { return }
self.view.endEditing(true)
}
.store(in: &disposeBag)
2022-01-07 11:49:37 +01:00
// // return
// if viewModel.approvalRequired {
// reasonTextField.returnKeyType = .done
// } else {
// passwordTextField.returnKeyType = .done
// }
2022-04-26 10:48:06 +02:00
viewModel.$error
.receive(on: DispatchQueue.main)
.sink { [weak self] error in
guard let self = self else { return }
guard let error = error as? Mastodon.API.Error else { return }
let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
_ = self.coordinator.present(
2022-04-26 10:48:06 +02:00
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
)
}
.store(in: &disposeBag)
2022-01-07 11:49:37 +01:00
viewModel.avatarMediaMenuActionPublisher
2021-03-31 04:37:38 +02:00
.receive(on: DispatchQueue.main)
2022-01-07 11:49:37 +01:00
.sink { [weak self] action in
2021-03-31 04:37:38 +02:00
guard let self = self else { return }
2022-01-07 11:49:37 +01:00
switch action {
case .photoLibrary:
self.present(self.imagePicker, animated: true, completion: nil)
case .camera:
self.present(self.imagePickerController, animated: true, completion: nil)
case .browse:
self.present(self.documentPickerController, animated: true, completion: nil)
case .delete:
self.viewModel.avatarImage = nil
2021-03-31 04:37:38 +02:00
}
}
.store(in: &disposeBag)
2021-02-22 10:48:44 +01:00
2022-01-07 11:49:37 +01:00
viewModel.$isRegistering
2021-02-22 10:48:44 +01:00
.receive(on: DispatchQueue.main)
2022-01-07 11:49:37 +01:00
.sink { [weak self] isRegistering in
2021-02-20 06:55:06 +01:00
guard let self = self else { return }
2022-01-07 11:49:37 +01:00
isRegistering ? self.navigationActionView.nextButton.showLoading() : self.navigationActionView.nextButton.stopLoading()
2021-02-20 06:55:06 +01:00
}
.store(in: &disposeBag)
2021-02-05 10:53:00 +01:00
}
2022-01-07 11:49:37 +01:00
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
2021-02-22 10:48:44 +01:00
2022-01-07 11:49:37 +01:00
viewModel.viewDidAppear.send()
}
2021-02-20 06:55:06 +01:00
}
extension MastodonRegisterViewController {
2022-01-07 11:49:37 +01:00
@objc private func backButtonPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
navigationController?.popViewController(animated: true)
}
2021-02-22 10:48:44 +01:00
2022-01-07 11:49:37 +01:00
@objc private func nextButtonPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
guard viewModel.isAllValid else { return }
guard !viewModel.isRegistering else { return }
viewModel.isRegistering = true
let username = viewModel.username
let email = viewModel.email
let password = viewModel.password
let reason = viewModel.reason
let locale: String = {
guard let url = Bundle.main.url(forResource: "local-codes", withExtension: "json"),
let data = try? Data(contentsOf: url),
let localCode = try? JSONDecoder().decode(MastodonLocalCode.self, from: data)
else {
assertionFailure()
return "en"
}
let fallbackLanguageCode: String = {
let code = Locale.current.languageCode ?? "en"
guard localCode[code] != nil else { return "en" }
return code
}()
2022-01-07 11:49:37 +01:00
// pick device preferred language
guard let identifier = Locale.preferredLanguages.first else {
return fallbackLanguageCode
}
// prepare languageCode and validate then return fallback if needs
let local = Locale(identifier: identifier)
guard let languageCode = local.languageCode,
localCode[languageCode] != nil
else {
return fallbackLanguageCode
}
// prepare extendCode and validate then return fallback if needs
let extendCodes: [String] = {
let locales = Locale.preferredLanguages.map { Locale(identifier: $0) }
return locales.compactMap { locale in
guard let languageCode = locale.languageCode,
let regionCode = locale.regionCode
else { return nil }
return languageCode + "-" + regionCode
}
}()
let _firstMatchExtendCode = extendCodes.first { code in
localCode[code] != nil
}
guard let firstMatchExtendCode = _firstMatchExtendCode else {
return languageCode
}
return firstMatchExtendCode
2022-01-07 11:49:37 +01:00
}()
2021-02-26 11:27:47 +01:00
let query = Mastodon.API.Account.RegisterQuery(
2022-01-07 11:49:37 +01:00
reason: reason,
2021-02-26 11:27:47 +01:00
username: username,
email: email,
password: password,
agreement: true, // user confirmed in the server rules scene
locale: locale
2021-02-26 11:27:47 +01:00
)
2022-01-07 11:49:37 +01:00
var retryCount = 0
2022-01-07 11:49:37 +01:00
// register without show server rules
context.apiService.accountRegister(
domain: viewModel.domain,
query: query,
authorization: viewModel.applicationAuthorization
)
.tryCatch { [weak self] error -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> in
guard let self = self else { throw error }
2022-01-07 11:49:37 +01:00
guard let error = self.viewModel.error as? Mastodon.API.Error,
case let .generic(errorEntity) = error.mastodonError,
errorEntity.error == "Validation failed: Locale is not included in the list"
else {
throw error
}
guard retryCount == 0 else {
throw error
}
let retryQuery = Mastodon.API.Account.RegisterQuery(
reason: query.reason,
username: query.username,
email: query.email,
password: query.password,
agreement: query.agreement,
locale: self.viewModel.instance.languages?.first ?? "en"
)
retryCount += 1
return self.context.apiService.accountRegister(
domain: self.viewModel.domain,
query: retryQuery,
authorization: self.viewModel.applicationAuthorization
)
}
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
2022-01-07 11:49:37 +01:00
self.viewModel.isRegistering = false
switch completion {
case .failure(let error):
2022-01-07 11:49:37 +01:00
self.viewModel.error = error
case .finished:
break
2021-02-26 11:27:47 +01:00
}
} receiveValue: { [weak self] response in
guard let self = self else { return }
let userToken = response.value
let updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery = {
2022-01-07 11:49:37 +01:00
let displayName: String? = self.viewModel.name.isEmpty ? nil : self.viewModel.name
let avatar: Mastodon.Query.MediaAttachment? = {
2022-01-07 11:49:37 +01:00
guard let avatarImage = self.viewModel.avatarImage else { return nil }
2021-04-09 11:31:43 +02:00
guard avatarImage.size.width <= MastodonRegisterViewController.avatarImageMaxSizeInPixel.width else {
return .png(avatarImage.af.imageScaled(to: MastodonRegisterViewController.avatarImageMaxSizeInPixel).pngData())
}
2021-04-09 11:31:43 +02:00
return .png(avatarImage.pngData())
}()
return Mastodon.API.Account.UpdateCredentialQuery(
displayName: displayName,
avatar: avatar
)
}()
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken, updateCredentialQuery: updateCredentialQuery)
2022-11-10 17:52:48 +01:00
_ = self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
2021-02-22 09:20:44 +01:00
}
.store(in: &disposeBag)
2021-02-05 10:53:00 +01:00
}
2022-01-07 11:49:37 +01:00
}