// // MastodonConfirmEmailViewController.swift // Mastodon // // Created by sxiaojian on 2021/2/23. // import Combine import MastodonSDK import UIKit import MastodonAsset import MastodonCore import MastodonUI import MastodonLocalization final class MastodonConfirmEmailViewController: UIViewController, NeedsDependency { var disposeBag = Set() weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } var viewModel: MastodonConfirmEmailViewModel! let stackView = UIStackView() private(set) lazy var subtitleLabel: UILabel = { let label = UILabel() label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: UIFont.systemFont(ofSize: 17)) label.textColor = .label label.numberOfLines = 0 return label }() let emailImageView: UIImageView = { let imageView = UIImageView() imageView.image = Asset.Asset.email.image imageView.backgroundColor = .clear imageView.contentMode = .scaleAspectFit return imageView }() let resendEmailButton: UIButton = { let boldFont = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .bold)) let regularFont = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) var buttonConfiguration = UIButton.Configuration.plain() var boldResendString = AttributedString(L10n.Scene.ConfirmEmail.DidntGetLink.resendIn(60), attributes: .init([.font: boldFont])) var attributedTitle = AttributedString(L10n.Scene.ConfirmEmail.DidntGetLink.prefix, attributes: .init([.font: regularFont])) attributedTitle.append(AttributedString(" ")) attributedTitle.append(boldResendString) buttonConfiguration.attributedTitle = attributedTitle let button = UIButton(configuration: buttonConfiguration) button.translatesAutoresizingMaskIntoConstraints = false button.isEnabled = false return button }() var resendButtonTimer: Timer? } extension MastodonConfirmEmailViewController { override func viewDidLoad() { setupOnboardingAppearance() configureMargin() subtitleLabel.text = L10n.Scene.ConfirmEmail.tapTheLinkWeEmailedToYouToVerifyYourAccount(viewModel.email) resendEmailButton.addTarget(self, action: #selector(MastodonConfirmEmailViewController.resendButtonPressed(_:)), for: .touchUpInside) // stackView stackView.axis = .vertical stackView.distribution = .fill stackView.spacing = 10 stackView.layoutMargins = UIEdgeInsets(top: 10, left: 0, bottom: 23, right: 0) stackView.isLayoutMarginsRelativeArrangement = true stackView.addArrangedSubview(subtitleLabel) stackView.addArrangedSubview(emailImageView) stackView.addArrangedSubview(resendEmailButton) emailImageView.setContentHuggingPriority(.defaultLow, for: .vertical) emailImageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) view.addSubview(stackView) stackView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ stackView.topAnchor.constraint(equalTo: view.readableContentGuide.topAnchor), stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), stackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), stackView.bottomAnchor.constraint(equalTo: view.readableContentGuide.bottomAnchor), ]) self.viewModel.timestampUpdatePublisher .sink { [weak self] _ in guard let self = self else { return } AuthenticationViewModel.verifyAndSaveAuthentication(context: self.context, info: self.viewModel.authenticateInfo, userToken: self.viewModel.userToken) .receive(on: DispatchQueue.main) .sink { completion in switch completion { case .failure(let error): break case .finished: // upload avatar and set display name in the background Just(self.viewModel.userToken.accessToken) .asyncMap { token in try await self.context.apiService.accountUpdateCredentials( domain: self.viewModel.authenticateInfo.domain, query: self.viewModel.updateCredentialQuery, authorization: Mastodon.API.OAuth.Authorization(accessToken: token) ) } .retry(3) .sink { completion in switch completion { case .failure(let error): break case .finished: break } } receiveValue: { _ in // do nothing } .store(in: &self.context.disposeBag) // execute in the background } // end switch } receiveValue: { response in self.coordinator.setup() // self.dismiss(animated: true, completion: nil) } .store(in: &self.disposeBag) } .store(in: &self.disposeBag) title = L10n.Scene.ConfirmEmail.title } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) configureMargin() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let nowIn60Seconds = Date().addingTimeInterval(60) let boldFont = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .bold)) let regularFont = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) let digitFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .monospacedDigitSystemFont(ofSize: 15, weight: .bold)) let timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] in guard Date() < nowIn60Seconds else { self?.resendEmailButton.isEnabled = true var configuration = self?.resendEmailButton.configuration let boldResendString = AttributedString(L10n.Scene.ConfirmEmail.DidntGetLink.resendNow, attributes: .init([.font: boldFont])) var attributedTitle = AttributedString(L10n.Scene.ConfirmEmail.DidntGetLink.prefix, attributes: .init([.font: regularFont])) attributedTitle.append(AttributedString(" ")) attributedTitle.append(boldResendString) configuration?.attributedTitle = attributedTitle self?.resendEmailButton.configuration = configuration self?.resendEmailButton.setNeedsUpdateConfiguration() $0.invalidate() return } var configuration = self?.resendEmailButton.configuration let boldResendString = AttributedString(L10n.Scene.ConfirmEmail.DidntGetLink.resendIn(Int(nowIn60Seconds.timeIntervalSinceNow) + 1), attributes: .init([.font: digitFont])) var attributedTitle = AttributedString(L10n.Scene.ConfirmEmail.DidntGetLink.prefix, attributes: .init([.font: regularFont])) attributedTitle.append(AttributedString(" ")) attributedTitle.append(boldResendString) configuration?.attributedTitle = attributedTitle self?.resendEmailButton.configuration = configuration self?.resendEmailButton.setNeedsUpdateConfiguration() } RunLoop.main.add(timer, forMode: .default) } } extension MastodonConfirmEmailViewController { private func configureMargin() { switch traitCollection.horizontalSizeClass { case .regular: let margin = MastodonConfirmEmailViewController.viewEdgeMargin stackView.layoutMargins = UIEdgeInsets(top: 18, left: margin, bottom: 23, right: margin) default: stackView.layoutMargins = UIEdgeInsets(top: 10, left: 0, bottom: 23, right: 0) } } } extension MastodonConfirmEmailViewController { @objc private func resendButtonPressed(_ sender: UIButton) { let alertController = UIAlertController(title: L10n.Scene.ConfirmEmail.DontReceiveEmail.title, message: L10n.Scene.ConfirmEmail.DontReceiveEmail.description, preferredStyle: .alert) let resendAction = UIAlertAction(title: L10n.Scene.ConfirmEmail.DontReceiveEmail.resendEmail, style: .default) { _ in let url = Mastodon.API.resendEmailURL(domain: self.viewModel.authenticateInfo.domain) let viewModel = MastodonResendEmailViewModel(resendEmailURL: url, email: self.viewModel.email) _ = self.coordinator.present(scene: .mastodonResendEmail(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil)) } let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) { _ in } alertController.addAction(resendAction) alertController.addAction(okAction) _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) } } // MARK: - PanPopableViewController extension MastodonConfirmEmailViewController: PanPopableViewController { var isPanPopable: Bool { false } } // MARK: - OnboardingViewControllerAppearance extension MastodonConfirmEmailViewController: OnboardingViewControllerAppearance { } #if canImport(SwiftUI) && DEBUG import SwiftUI struct MastodonConfirmEmailViewController_Previews: PreviewProvider { static var controls: some View { UIViewControllerPreview { let viewController = MastodonConfirmEmailViewController() return viewController } .previewLayout(.fixed(width: 375, height: 800)) } static var previews: some View { Group { controls.colorScheme(.light) controls.colorScheme(.dark) } } } #endif