diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 8841f7ee3..94f6e02ef 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ DB8AF55725C137A8002E6C99 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55625C137A8002E6C99 /* HomeViewController.swift */; }; DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; }; DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */; }; + DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -161,6 +162,7 @@ DB8AF55625C137A8002E6C99 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineIndex.swift; sourceTree = ""; }; + DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Mastodon.xctestplan; path = Mastodon/Mastodon.xctestplan; sourceTree = ""; }; DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MastodonSDK.xctestplan; sourceTree = ""; }; EC6E707B68A67DB08EC288FA /* Pods-MastodonTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.debug.xcconfig"; sourceTree = ""; }; @@ -241,6 +243,7 @@ children = ( DB0140A625C40C0900F9F3CF /* PinBased */, DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */, + DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */, ); path = Authentication; sourceTree = ""; @@ -791,6 +794,7 @@ DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */, DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */, DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */, + DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */, DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 600846d4c..a8d3dbb8a 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -37,7 +37,7 @@ extension SceneCoordinator { } enum Scene { - + case authentication(viewModel: AuthenticationViewModel) } } @@ -108,8 +108,12 @@ private extension SceneCoordinator { func get(scene: Scene) -> UIViewController? { let viewController: UIViewController? - // TODO: - viewController = nil + switch scene { + case .authentication(let viewModel): + let _viewController = AuthenticationViewController() + _viewController.viewModel = viewModel + viewController = _viewController + } setupDependency(for: viewController as? NeedsDependency) diff --git a/Mastodon/Scene/Authentication/AuthenticationViewController.swift b/Mastodon/Scene/Authentication/AuthenticationViewController.swift index 12e1d14d5..bb66b1e63 100644 --- a/Mastodon/Scene/Authentication/AuthenticationViewController.swift +++ b/Mastodon/Scene/Authentication/AuthenticationViewController.swift @@ -5,6 +5,76 @@ // Created by MainasuK Cirno on 2021/1/29. // +import os.log import UIKit +import Combine +final class AuthenticationViewController: UIViewController, NeedsDependency { + + var disposeBag = Set() + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var viewModel: AuthenticationViewModel! + + let domainTextField: UITextField = { + let textField = UITextField() + textField.placeholder = "example.com" + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + textField.keyboardType = .URL + return textField + }() + + private(set) lazy var signInBarButtonItem = UIBarButtonItem(title: "Sign In", style: .plain, target: self, action: #selector(AuthenticationViewController.signInBarButtonItemPressed(_:))) + +} +extension AuthenticationViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Authentication" + view.backgroundColor = .systemBackground + navigationItem.rightBarButtonItem = signInBarButtonItem + + domainTextField.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(domainTextField) + NSLayoutConstraint.activate([ + domainTextField.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 8), + domainTextField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 8), + domainTextField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: 8), + domainTextField.heightAnchor.constraint(equalToConstant: 44), // FIXME: + ]) + + NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: domainTextField) + .compactMap { notification in + guard let textField = notification.object as? UITextField? else { return nil } + return textField?.text ?? "" + } + .assign(to: \.value, on: viewModel.input) + .store(in: &disposeBag) + + viewModel.isSignInButtonEnabled + .receive(on: DispatchQueue.main) + .assign(to: \.isEnabled, on: signInBarButtonItem) + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + domainTextField.becomeFirstResponder() + } + +} + +extension AuthenticationViewController { + + @objc private func signInBarButtonItemPressed(_ sender: UIBarButtonItem) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} diff --git a/Mastodon/Scene/Authentication/AuthenticationViewModel.swift b/Mastodon/Scene/Authentication/AuthenticationViewModel.swift new file mode 100644 index 000000000..4d0e21f33 --- /dev/null +++ b/Mastodon/Scene/Authentication/AuthenticationViewModel.swift @@ -0,0 +1,48 @@ +// +// AuthenticationViewModel.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021/2/1. +// + +import Foundation +import Combine + +final class AuthenticationViewModel { + + var disposeBag = Set() + + // input + let input = CurrentValueSubject("") + + // output + let domain = CurrentValueSubject(nil) + let isSignInButtonEnabled = CurrentValueSubject(false) + + init() { + input + .map { input in + let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + guard !trimmed.isEmpty else { return nil } + + let urlString = trimmed.hasPrefix("https://") ? trimmed : "https://" + trimmed + guard let url = URL(string: urlString), + let host = url.host else { + return nil + } + let components = host.components(separatedBy: ".") + guard (components.filter { !$0.isEmpty }).count >= 2 else { return nil } + + return host + } + .assign(to: \.value, on: domain) + .store(in: &disposeBag) + + domain + .print() + .map { $0 != nil } + .assign(to: \.value, on: isSignInButtonEnabled) + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/HomeViewController.swift b/Mastodon/Scene/HomeViewController.swift index 6a533558d..0d0f8cc96 100644 --- a/Mastodon/Scene/HomeViewController.swift +++ b/Mastodon/Scene/HomeViewController.swift @@ -14,7 +14,6 @@ final class HomeViewController: UIViewController, NeedsDependency { } - extension HomeViewController { override func viewDidLoad() { diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index ed7a36994..2cddc5873 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -24,6 +24,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { self.coordinator = sceneCoordinator sceneCoordinator.setup() + + #if DEBUG + DispatchQueue.main.async { + let authenticationViewModel = AuthenticationViewModel() + sceneCoordinator.present(scene: .authentication(viewModel: authenticationViewModel), from: nil, transition: .modal(animated: false, completion: nil)) + } + #endif window.makeKeyAndVisible() }