238 lines
13 KiB
Swift
238 lines
13 KiB
Swift
//
|
|
// MastodonRegisterViewModel+Diffable.swift
|
|
// Mastodon
|
|
//
|
|
// Created by MainasuK on 2022-1-5.
|
|
//
|
|
|
|
import UIKit
|
|
import Combine
|
|
import MastodonAsset
|
|
import MastodonLocalization
|
|
|
|
extension MastodonRegisterViewModel {
|
|
func setupDiffableDataSource(
|
|
tableView: UITableView
|
|
) {
|
|
tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self))
|
|
tableView.register(MastodonRegisterAvatarTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self))
|
|
tableView.register(MastodonRegisterTextFieldTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self))
|
|
tableView.register(MastodonRegisterPasswordHintTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self))
|
|
|
|
diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
|
|
switch item {
|
|
case .header(let domain):
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: OnboardingHeadlineTableViewCell.self), for: indexPath) as! OnboardingHeadlineTableViewCell
|
|
cell.titleLabel.text = L10n.Scene.Register.title(domain)
|
|
cell.subTitleLabel.isHidden = true
|
|
return cell
|
|
case .avatar:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self), for: indexPath) as! MastodonRegisterAvatarTableViewCell
|
|
self.configureAvatar(cell: cell)
|
|
return cell
|
|
case .name:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
|
|
cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.DisplayName.placeholder)
|
|
cell.textField.keyboardType = .default
|
|
cell.textField.autocapitalizationType = .words
|
|
cell.textField.text = self.name
|
|
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
|
|
.receive(on: DispatchQueue.main)
|
|
.compactMap { notification in
|
|
guard let textField = notification.object as? UITextField else {
|
|
assertionFailure()
|
|
return nil
|
|
}
|
|
return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
}
|
|
.assign(to: \.name, on: self)
|
|
.store(in: &cell.disposeBag)
|
|
return cell
|
|
case .username:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
|
|
cell.setupTextViewRightView(text: "@" + self.domain)
|
|
cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Username.placeholder)
|
|
cell.textField.keyboardType = .alphabet
|
|
cell.textField.autocorrectionType = .no
|
|
cell.textField.text = self.username
|
|
cell.textField.textAlignment = .left
|
|
cell.textField.semanticContentAttribute = .forceLeftToRight
|
|
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
|
|
.receive(on: DispatchQueue.main)
|
|
.compactMap { notification in
|
|
guard let textField = notification.object as? UITextField else {
|
|
assertionFailure()
|
|
return nil
|
|
}
|
|
return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
}
|
|
.assign(to: \.username, on: self)
|
|
.store(in: &cell.disposeBag)
|
|
self.configureTextFieldCell(cell: cell, validateState: self.$usernameValidateState)
|
|
return cell
|
|
case .email:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
|
|
cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Email.placeholder)
|
|
cell.textField.keyboardType = .emailAddress
|
|
cell.textField.autocorrectionType = .no
|
|
cell.textField.text = self.email
|
|
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
|
|
.receive(on: DispatchQueue.main)
|
|
.compactMap { notification in
|
|
guard let textField = notification.object as? UITextField else {
|
|
assertionFailure()
|
|
return nil
|
|
}
|
|
return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
}
|
|
.assign(to: \.email, on: self)
|
|
.store(in: &cell.disposeBag)
|
|
self.configureTextFieldCell(cell: cell, validateState: self.$emailValidateState)
|
|
return cell
|
|
case .password:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
|
|
cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Password.placeholder)
|
|
cell.textField.keyboardType = .alphabet
|
|
cell.textField.autocorrectionType = .no
|
|
cell.textField.isSecureTextEntry = true
|
|
cell.textField.text = self.password
|
|
cell.textField.textAlignment = .left
|
|
cell.textField.semanticContentAttribute = .forceLeftToRight
|
|
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
|
|
.receive(on: DispatchQueue.main)
|
|
.compactMap { notification in
|
|
guard let textField = notification.object as? UITextField else {
|
|
assertionFailure()
|
|
return nil
|
|
}
|
|
return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
}
|
|
.assign(to: \.password, on: self)
|
|
.store(in: &cell.disposeBag)
|
|
self.configureTextFieldCell(cell: cell, validateState: self.$passwordValidateState)
|
|
return cell
|
|
case .hint:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self), for: indexPath) as! MastodonRegisterPasswordHintTableViewCell
|
|
return cell
|
|
case .reason:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell
|
|
cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest)
|
|
cell.textField.keyboardType = .default
|
|
cell.textField.text = self.reason
|
|
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField)
|
|
.receive(on: DispatchQueue.main)
|
|
.compactMap { notification in
|
|
guard let textField = notification.object as? UITextField else {
|
|
assertionFailure()
|
|
return nil
|
|
}
|
|
return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
}
|
|
.assign(to: \.reason, on: self)
|
|
.store(in: &cell.disposeBag)
|
|
self.configureTextFieldCell(cell: cell, validateState: self.$reasonValidateState)
|
|
return cell
|
|
default:
|
|
assertionFailure()
|
|
return UITableViewCell()
|
|
}
|
|
}
|
|
|
|
var snapshot = NSDiffableDataSourceSnapshot<RegisterSection, RegisterItem>()
|
|
snapshot.appendSections([.main])
|
|
snapshot.appendItems([.header(domain: domain)], toSection: .main)
|
|
snapshot.appendItems([.avatar, .name, .username, .email, .password, .hint], toSection: .main)
|
|
if approvalRequired {
|
|
snapshot.appendItems([.reason], toSection: .main)
|
|
}
|
|
diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil)
|
|
}
|
|
}
|
|
|
|
extension MastodonRegisterViewModel {
|
|
private func configureAvatar(cell: MastodonRegisterAvatarTableViewCell) {
|
|
self.$avatarImage
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self, weak cell] image in
|
|
guard let self = self else { return }
|
|
guard let cell = cell else { return }
|
|
let image = image ?? Asset.Scene.Onboarding.avatarPlaceholder.image
|
|
cell.avatarButton.setImage(image, for: .normal)
|
|
cell.avatarButton.menu = self.createAvatarMediaContextMenu()
|
|
cell.avatarButton.showsMenuAsPrimaryAction = true
|
|
}
|
|
.store(in: &cell.disposeBag)
|
|
}
|
|
|
|
enum AvatarMediaMenuAction {
|
|
case photoLibrary
|
|
case camera
|
|
case browse
|
|
case delete
|
|
}
|
|
|
|
private func createAvatarMediaContextMenu() -> UIMenu {
|
|
var children: [UIMenuElement] = []
|
|
|
|
// Photo Library
|
|
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
self.avatarMediaMenuActionPublisher.send(.photoLibrary)
|
|
}
|
|
children.append(photoLibraryAction)
|
|
|
|
// Camera
|
|
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
|
let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
|
|
guard let self = self else { return }
|
|
self.avatarMediaMenuActionPublisher.send(.camera)
|
|
})
|
|
children.append(cameraAction)
|
|
}
|
|
|
|
// Browse
|
|
let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
self.avatarMediaMenuActionPublisher.send(.browse)
|
|
}
|
|
children.append(browseAction)
|
|
|
|
// Delete
|
|
if avatarImage != nil {
|
|
let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
self.avatarMediaMenuActionPublisher.send(.delete)
|
|
}
|
|
children.append(deleteAction)
|
|
}
|
|
|
|
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
|
}
|
|
|
|
private func configureTextFieldCell(
|
|
cell: MastodonRegisterTextFieldTableViewCell,
|
|
validateState: Published<ValidateState>.Publisher
|
|
) {
|
|
Publishers.CombineLatest(
|
|
validateState,
|
|
cell.textField.publisher(for: \.isFirstResponder)
|
|
)
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak cell] validateState, isFirstResponder in
|
|
guard let cell = cell else { return }
|
|
switch validateState {
|
|
case .empty:
|
|
cell.textFieldShadowContainer.shadowColor = isFirstResponder ? Asset.Colors.brandBlue.color : .black
|
|
cell.textFieldShadowContainer.shadowAlpha = isFirstResponder ? 1 : 0.25
|
|
case .valid:
|
|
cell.textFieldShadowContainer.shadowColor = Asset.Colors.TextField.valid.color
|
|
cell.textFieldShadowContainer.shadowAlpha = 1
|
|
case .invalid:
|
|
cell.textFieldShadowContainer.shadowColor = Asset.Colors.TextField.invalid.color
|
|
cell.textFieldShadowContainer.shadowAlpha = 1
|
|
}
|
|
}
|
|
.store(in: &cell.disposeBag)
|
|
}
|
|
}
|