diff --git a/Localizations/en.lproj/Localizable.strings b/Localizations/en.lproj/Localizable.strings index 32db735..84a7bf5 100644 --- a/Localizations/en.lproj/Localizable.strings +++ b/Localizations/en.lproj/Localizable.strings @@ -68,6 +68,7 @@ "app-icon.rainbow-brutalist" = "Rainbow Brutalist"; "app-icon.classic" = "Classic"; "app-icon.rainbow" = "Rainbow"; +"add-identity.get-started" = "Get started"; "add-identity.instance-url" = "Instance URL"; "add-identity.log-in" = "Log in"; "add-identity.browse" = "Browse"; diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 7086e9f..357c4cc 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -228,6 +228,7 @@ D0F4362D25C10B9600E4F896 /* AddIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F4362C25C10B9600E4F896 /* AddIdentityViewController.swift */; }; D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */; }; D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */; }; + D0FCD6AD261AB2DD00113701 /* InstancePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCD6AC261AB2DD00113701 /* InstancePickerViewController.swift */; }; D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C8E253686F9003EF1EB /* PlayerView.swift */; }; D0FE1C9825368A9D003EF1EB /* PlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */; }; D0FE7C8025C4C79F00203957 /* PreviewViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0FE7C7F25C4C79F00203957 /* PreviewViewModels */; }; @@ -469,6 +470,7 @@ D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionItem+Extensions.swift"; sourceTree = ""; }; D0F4362C25C10B9600E4F896 /* AddIdentityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIdentityViewController.swift; sourceTree = ""; }; D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusViewController.swift; sourceTree = ""; }; + D0FCD6AC261AB2DD00113701 /* InstancePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancePickerViewController.swift; sourceTree = ""; }; D0FE1C8E253686F9003EF1EB /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerCache.swift; sourceTree = ""; }; D9E658692601CF76007C426E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -803,6 +805,7 @@ D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */, D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */, D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */, + D0FCD6AC261AB2DD00113701 /* InstancePickerViewController.swift */, D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */, D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */, D097F4C025BFA04C00859F2C /* NotificationsViewController.swift */, @@ -1240,6 +1243,7 @@ D00702312555F4AE00F38136 /* ConversationView.swift in Sources */, D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */, D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */, + D0FCD6AD261AB2DD00113701 /* InstancePickerViewController.swift in Sources */, D0070252255921B100F38136 /* AccountFieldView.swift in Sources */, D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */, D0BE97A325CF44310057E161 /* CGRect+Extensions.swift in Sources */, diff --git a/View Controllers/AddIdentityViewController.swift b/View Controllers/AddIdentityViewController.swift index b5bc4b9..f14fc9a 100644 --- a/View Controllers/AddIdentityViewController.swift +++ b/View Controllers/AddIdentityViewController.swift @@ -2,10 +2,10 @@ import Combine import Mastodon -import SafariServices import SDWebImage import SwiftUI import ViewModels +import WebKit final class AddIdentityViewController: UIViewController { private let viewModel: AddIdentityViewModel @@ -26,7 +26,11 @@ final class AddIdentityViewController: UIViewController { private let activityIndicator = UIActivityIndicatorView(style: .large) private let joinButton = CapsuleButton() private let browseButton = CapsuleButton() - private let whatIsMastodonButton = UIButton(type: .system) + private let whatIsMastodonBackgroundView = UIView() + private let whatIsMastodonStackView = UIStackView() + private let whatIsMastodonLabel = UILabel() + private let whatIsMastodonVideoView: WKWebView + private let getStartedButton = CapsuleButton() private var cancellables = Set() init(viewModel: AddIdentityViewModel, rootViewModel: RootViewModel, displayWelcome: Bool) { @@ -34,6 +38,12 @@ final class AddIdentityViewController: UIViewController { self.rootViewModel = rootViewModel self.displayWelcome = displayWelcome + let configuration = WKWebViewConfiguration() + + configuration.allowsInlineMediaPlayback = true + + whatIsMastodonVideoView = WKWebView(frame: .zero, configuration: configuration) + super.init(nibName: nil, bundle: nil) } @@ -55,7 +65,7 @@ final class AddIdentityViewController: UIViewController { private extension AddIdentityViewController { static let verticalSpacing: CGFloat = 20 - static let whatIsMastodonURL = URL(string: "https://joinmastodon.org")! + static let whatIsMastodonVideoURL = URL(string: "https://www.youtube.com/embed/IPSbNdBmWKE?playsinline=1")! // swiftlint:disable:next function_body_length func configureViews() { @@ -134,14 +144,40 @@ private extension AddIdentityViewController { for: .touchUpInside) browseButton.isHidden_stackViewSafe = true - whatIsMastodonButton.setTitle(NSLocalizedString("add-identity.what-is-mastodon", comment: ""), for: .normal) - whatIsMastodonButton.addAction( + whatIsMastodonBackgroundView.backgroundColor = .secondarySystemBackground + whatIsMastodonBackgroundView.clipsToBounds = true + whatIsMastodonBackgroundView.layer.cornerRadius = .defaultCornerRadius + + whatIsMastodonStackView.translatesAutoresizingMaskIntoConstraints = false + whatIsMastodonStackView.axis = .vertical + whatIsMastodonStackView.spacing = .defaultSpacing * 2 + + whatIsMastodonLabel.adjustsFontForContentSizeCategory = true + whatIsMastodonLabel.font = .preferredFont(forTextStyle: .headline) + whatIsMastodonLabel.textAlignment = .center + whatIsMastodonLabel.text = NSLocalizedString("add-identity.what-is-mastodon", comment: "") + + getStartedButton.setTitle(NSLocalizedString("add-identity.get-started", comment: ""), for: .normal) + getStartedButton.addAction( UIAction { [weak self] _ in - self?.present(SFSafariViewController(url: Self.whatIsMastodonURL), animated: true) + self?.urlTextField.resignFirstResponder() + self?.present( + UINavigationController(rootViewController: InstancePickerViewController { + self?.viewModel.urlFieldText = $1 + self?.urlTextField.text = $1 + self?.urlTextField.becomeFirstResponder() + self?.dismiss(animated: true) + }), + animated: true) }, for: .touchUpInside) - for button in [logInButton, joinButton, browseButton, whatIsMastodonButton] { + whatIsMastodonVideoView.scrollView.isScrollEnabled = false + whatIsMastodonVideoView.clipsToBounds = true + whatIsMastodonVideoView.layer.cornerRadius = .defaultCornerRadius + whatIsMastodonVideoView.load(.init(url: Self.whatIsMastodonVideoURL)) + + for button in [logInButton, joinButton, browseButton] { button.setContentCompressionResistancePriority(.required, for: .vertical) } } @@ -163,7 +199,11 @@ private extension AddIdentityViewController { buttonsStackView.addArrangedSubview(joinButton) buttonsStackView.addArrangedSubview(browseButton) buttonsStackView.addArrangedSubview(UIView()) - stackView.addArrangedSubview(whatIsMastodonButton) + stackView.addArrangedSubview(whatIsMastodonBackgroundView) + whatIsMastodonBackgroundView.addSubview(whatIsMastodonStackView) + whatIsMastodonStackView.addArrangedSubview(whatIsMastodonLabel) + whatIsMastodonStackView.addArrangedSubview(whatIsMastodonVideoView) + whatIsMastodonStackView.addArrangedSubview(getStartedButton) } func setupConstraints() { @@ -183,7 +223,17 @@ private extension AddIdentityViewController { stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), stackView.widthAnchor.constraint(equalTo: scrollView.readableContentGuide.widthAnchor), stackView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor), - instanceImageViewWidthConstraint + instanceImageViewWidthConstraint, + whatIsMastodonStackView.leadingAnchor.constraint(equalTo: whatIsMastodonBackgroundView.leadingAnchor, + constant: .defaultSpacing * 2), + whatIsMastodonStackView.topAnchor.constraint(equalTo: whatIsMastodonBackgroundView.topAnchor, + constant: .defaultSpacing * 2), + whatIsMastodonStackView.trailingAnchor.constraint(equalTo: whatIsMastodonBackgroundView.trailingAnchor, + constant: -.defaultSpacing * 2), + whatIsMastodonStackView.bottomAnchor.constraint(equalTo: whatIsMastodonBackgroundView.bottomAnchor, + constant: -.defaultSpacing * 2), + whatIsMastodonVideoView.widthAnchor.constraint(equalTo: whatIsMastodonVideoView.heightAnchor, + multiplier: 16 / 9) ]) } @@ -213,7 +263,7 @@ private extension AddIdentityViewController { promptLabel.alpha = 0 urlTextField.alpha = 0 logInButton.alpha = 0 - whatIsMastodonButton.alpha = 0 + whatIsMastodonBackgroundView.alpha = 0 UIView.animate(withDuration: .longAnimationDuration * 2) { self.welcomeLabel.alpha = 1 @@ -228,14 +278,13 @@ private extension AddIdentityViewController { UIView.animate(withDuration: .longAnimationDuration) { self.urlTextField.alpha = 1 } completion: { _ in - self.urlTextField.becomeFirstResponder() UIView.animate(withDuration: .longAnimationDuration) { self.logInButton.alpha = 1 } completion: { _ in - self.whatIsMastodonButton.isHidden_stackViewSafe = false - self.whatIsMastodonButton.alpha = 0 UIView.animate(withDuration: .longAnimationDuration) { - self.whatIsMastodonButton.alpha = 1 + self.whatIsMastodonBackgroundView.alpha = 1 + } completion: { _ in + self.urlTextField.becomeFirstResponder() } } } @@ -244,7 +293,6 @@ private extension AddIdentityViewController { } } else { welcomeLabel.isHidden_stackViewSafe = true - whatIsMastodonButton.isHidden_stackViewSafe = !displayWelcome urlTextField.becomeFirstResponder() } } @@ -281,13 +329,10 @@ private extension AddIdentityViewController { } self.browseButton.isHidden_stackViewSafe = !isPublicTimelineAvailable || loading - self.whatIsMastodonButton.isHidden_stackViewSafe = true } else { self.instanceStackView.isHidden_stackViewSafe = true self.joinButton.isHidden_stackViewSafe = true self.browseButton.isHidden_stackViewSafe = true - self.whatIsMastodonButton.isHidden_stackViewSafe = - !self.displayWelcome || self.logInButton.alpha < 1 || loading } } } diff --git a/View Controllers/InstancePickerViewController.swift b/View Controllers/InstancePickerViewController.swift new file mode 100644 index 0000000..2c9270d --- /dev/null +++ b/View Controllers/InstancePickerViewController.swift @@ -0,0 +1,82 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Combine +import UIKit +import WebKit + +final class InstancePickerViewController: UIViewController { + private let selectionAction: (InstancePickerViewController, String) -> Void + private let webView: WKWebView + private let backButton: UIBarButtonItem + private let forwardButton: UIBarButtonItem + private var cancellables = Set() + + init(selectionAction: @escaping (InstancePickerViewController, String) -> Void) { + let webView = WKWebView() + + webView.allowsBackForwardNavigationGestures = true + self.webView = webView + self.selectionAction = selectionAction + backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), + primaryAction: UIAction { _ in webView.goBack() }) + forwardButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.forward"), + primaryAction: UIAction { _ in webView.goForward() }) + + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = webView + webView.navigationDelegate = self + } + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.leftBarButtonItem = UIBarButtonItem( + systemItem: .done, + primaryAction: UIAction { [weak self] _ in self?.presentingViewController?.dismiss(animated: true) }) + navigationItem.rightBarButtonItems = [forwardButton, backButton] + + webView.publisher(for: \.canGoBack) + .sink { [weak self] in self?.backButton.isEnabled = $0 } + .store(in: &cancellables) + + webView.publisher(for: \.canGoForward) + .sink { [weak self] in self?.forwardButton.isEnabled = $0 } + .store(in: &cancellables) + + webView.publisher(for: \.title) + .sink { [weak self] in self?.navigationItem.title = $0 } + .store(in: &cancellables) + + webView.load(.init(url: Self.url)) + } +} + +extension InstancePickerViewController: WKNavigationDelegate { + func webView(_ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + preferences: WKWebpagePreferences, + decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) { + if webView.url?.host == "joinmastodon.org", + let url = navigationAction.request.url, + let host = url.host, + host != "joinmastodon.org", + url.pathComponents == ["/", "about"] { + decisionHandler(.cancel, preferences) + selectionAction(self, host) + } else { + decisionHandler(.allow, preferences) + } + } +} + +private extension InstancePickerViewController { + static let url = URL(string: "https://joinmastodon.org/communities")! +}