From 72c08642d893ed9d9190c75dd8fea960a74f9eeb Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Sat, 30 Jan 2021 01:03:05 -0800 Subject: [PATCH] Add account overhaul --- Metatext.xcodeproj/project.pbxproj | 4 + .../AddIdentityViewController.swift | 159 ++++++++++-------- .../AddIdentityView.swift | 1 + Views/UIKit/CapsuleButton.swift | 49 ++++++ 4 files changed, 145 insertions(+), 68 deletions(-) create mode 100644 Views/UIKit/CapsuleButton.swift diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index b2115d0..ee62b3c 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ D025B16025C4EA81001C69A8 /* ImageCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B14525C4D26A001C69A8 /* ImageCacheSerializer.swift */; }; D025B16A25C4EB18001C69A8 /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D025B16925C4EB18001C69A8 /* ServiceLayer */; }; D025B17025C4EB58001C69A8 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D025B16F25C4EB58001C69A8 /* Kingfisher */; }; + D025B17E25C500BC001C69A8 /* CapsuleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D025B17D25C500BC001C69A8 /* CapsuleButton.swift */; }; D02E1F95250B13210071AD56 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02E1F94250B13210071AD56 /* SafariView.swift */; }; D035F86925B7F2ED00DC75ED /* MainNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */; }; D035F86F25B7F30E00DC75ED /* MainNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F86E25B7F30E00DC75ED /* MainNavigationView.swift */; }; @@ -229,6 +230,7 @@ D021A63525C38ADB008A0C0D /* AcknowledgmentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgmentsView.swift; sourceTree = ""; }; D025B14525C4D26A001C69A8 /* ImageCacheSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCacheSerializer.swift; sourceTree = ""; }; D025B14C25C4E482001C69A8 /* ImageCacheConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheConfiguration.swift; sourceTree = ""; }; + D025B17D25C500BC001C69A8 /* CapsuleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleButton.swift; sourceTree = ""; }; D02E1F94250B13210071AD56 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationViewController.swift; sourceTree = ""; }; D035F86E25B7F30E00DC75ED /* MainNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationView.swift; sourceTree = ""; }; @@ -401,6 +403,7 @@ D01F41E224F8889700D55A2D /* AttachmentsView.swift */, D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */, D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */, + D025B17D25C500BC001C69A8 /* CapsuleButton.swift */, D0EA593F2522AC8700804347 /* CardView.swift */, D021A66F25C3E1F9008A0C0D /* Collection View Cells */, D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */, @@ -1015,6 +1018,7 @@ D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */, D00CB2ED2533ACC00080096B /* StatusView.swift in Sources */, D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */, + D025B17E25C500BC001C69A8 /* CapsuleButton.swift in Sources */, D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */, D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */, D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */, diff --git a/View Controllers/AddIdentityViewController.swift b/View Controllers/AddIdentityViewController.swift index b87eb8d..f284984 100644 --- a/View Controllers/AddIdentityViewController.swift +++ b/View Controllers/AddIdentityViewController.swift @@ -16,15 +16,16 @@ final class AddIdentityViewController: UIViewController { private let promptLabel = UILabel() private let urlTextField = UITextField() private let welcomeLabel = UILabel() - private let instanceVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)) + private let instanceAndButtonsStackView = UIStackView() private let instanceStackView = UIStackView() + private let instanceImageView = AnimatedImageView() private let instanceTitleLabel = UILabel() private let instanceURLLabel = UILabel() - private let instanceImageView = AnimatedImageView() - private let logInButton = UIButton(type: .system) - private let activityIndicator = UIActivityIndicatorView() - private let joinButton = UIButton(type: .system) - private let browseButton = UIButton(type: .system) + private let buttonsStackView = UIStackView() + private let logInButton = CapsuleButton() + private let activityIndicator = UIActivityIndicatorView(style: .large) + private let joinButton = CapsuleButton() + private let browseButton = CapsuleButton() private let whatIsMastodonButton = UIButton(type: .system) private var cancellables = Set() @@ -53,6 +54,7 @@ final class AddIdentityViewController: UIViewController { } private extension AddIdentityViewController { + static let verticalSpacing: CGFloat = 20 static let whatIsMastodonURL = URL(string: "https://joinmastodon.org")! // swiftlint:disable:next function_body_length @@ -60,10 +62,10 @@ private extension AddIdentityViewController { scrollView.translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.spacing = .defaultSpacing + stackView.spacing = Self.verticalSpacing stackView.axis = .vertical - stackView.distribution = .equalSpacing + welcomeLabel.translatesAutoresizingMaskIntoConstraints = false welcomeLabel.numberOfLines = 0 welcomeLabel.textAlignment = .center welcomeLabel.adjustsFontForContentSizeCategory = true @@ -86,18 +88,13 @@ private extension AddIdentityViewController { UIAction { [weak self] _ in self?.viewModel.urlFieldText = self?.urlTextField.text ?? "" }, for: .editingChanged) - logInButton.setTitle(NSLocalizedString("add-identity.log-in", comment: ""), for: .normal) - logInButton.addAction( - UIAction { [weak self] _ in self?.viewModel.logInTapped() }, - for: .touchUpInside) - - activityIndicator.hidesWhenStopped = true - - instanceVisualEffectView.translatesAutoresizingMaskIntoConstraints = false + instanceAndButtonsStackView.spacing = .defaultSpacing + instanceAndButtonsStackView.distribution = .fillEqually instanceStackView.translatesAutoresizingMaskIntoConstraints = false instanceStackView.axis = .vertical instanceStackView.spacing = .compactSpacing + instanceStackView.isHidden_stackViewSafe = true instanceTitleLabel.numberOfLines = 0 instanceTitleLabel.textAlignment = .center @@ -114,15 +111,25 @@ private extension AddIdentityViewController { instanceImageView.layer.cornerRadius = .defaultCornerRadius instanceImageView.clipsToBounds = true instanceImageView.kf.indicatorType = .activity - instanceImageView.isHidden = true + + buttonsStackView.axis = .vertical + buttonsStackView.spacing = .defaultSpacing + + activityIndicator.hidesWhenStopped = true + + logInButton.setTitle(NSLocalizedString("add-identity.log-in", comment: ""), for: .normal) + logInButton.addAction( + UIAction { [weak self] _ in self?.viewModel.logInTapped() }, + for: .touchUpInside) joinButton.addAction(UIAction { [weak self] _ in self?.join() }, for: .touchUpInside) + joinButton.isHidden_stackViewSafe = true browseButton.setTitle(NSLocalizedString("add-identity.browse", comment: ""), for: .normal) - browseButton.isHidden = true browseButton.addAction( UIAction { [weak self] _ in self?.viewModel.browseTapped() }, for: .touchUpInside) + browseButton.isHidden_stackViewSafe = true whatIsMastodonButton.setTitle(NSLocalizedString("add-identity.what-is-mastodon", comment: ""), for: .normal) whatIsMastodonButton.addAction( @@ -131,27 +138,28 @@ private extension AddIdentityViewController { }, for: .touchUpInside) - for button in [logInButton, browseButton, joinButton, whatIsMastodonButton] { - button.titleLabel?.adjustsFontForContentSizeCategory = true - button.titleLabel?.font = .preferredFont(forTextStyle: .title3) + for button in [logInButton, joinButton, browseButton, whatIsMastodonButton] { + button.setContentCompressionResistancePriority(.required, for: .vertical) } } func setupViewHierarchy() { + view.addSubview(welcomeLabel) view.addSubview(scrollView) scrollView.addSubview(stackView) stackView.addArrangedSubview(promptLabel) stackView.addArrangedSubview(urlTextField) - stackView.addArrangedSubview(welcomeLabel) + stackView.addArrangedSubview(instanceAndButtonsStackView) + instanceStackView.addArrangedSubview(instanceImageView) instanceStackView.addArrangedSubview(instanceTitleLabel) instanceStackView.addArrangedSubview(instanceURLLabel) - instanceVisualEffectView.contentView.addSubview(instanceStackView) - instanceImageView.addSubview(instanceVisualEffectView) - stackView.addArrangedSubview(instanceImageView) - stackView.addArrangedSubview(activityIndicator) - stackView.addArrangedSubview(logInButton) - stackView.addArrangedSubview(joinButton) - stackView.addArrangedSubview(browseButton) + instanceAndButtonsStackView.addArrangedSubview(instanceStackView) + instanceAndButtonsStackView.addArrangedSubview(buttonsStackView) + buttonsStackView.addArrangedSubview(activityIndicator) + buttonsStackView.addArrangedSubview(logInButton) + buttonsStackView.addArrangedSubview(joinButton) + buttonsStackView.addArrangedSubview(browseButton) + buttonsStackView.addArrangedSubview(UIView()) stackView.addArrangedSubview(whatIsMastodonButton) } @@ -161,6 +169,9 @@ private extension AddIdentityViewController { instanceImageViewWidthConstraint.priority = .justBelowMax NSLayoutConstraint.activate([ + welcomeLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor), + welcomeLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor), + welcomeLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), scrollView.topAnchor.constraint(equalTo: view.topAnchor), scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), @@ -169,16 +180,7 @@ private extension AddIdentityViewController { stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), stackView.widthAnchor.constraint(equalTo: scrollView.readableContentGuide.widthAnchor), stackView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor), - instanceImageViewWidthConstraint, - instanceVisualEffectView.leadingAnchor.constraint(equalTo: instanceImageView.leadingAnchor), - instanceVisualEffectView.trailingAnchor.constraint(equalTo: instanceImageView.trailingAnchor), - instanceVisualEffectView.bottomAnchor.constraint(equalTo: instanceImageView.bottomAnchor), - instanceStackView.leadingAnchor.constraint(equalTo: instanceVisualEffectView.contentView.leadingAnchor), - instanceStackView.topAnchor.constraint(equalTo: instanceVisualEffectView.contentView.topAnchor, - constant: .defaultSpacing), - instanceStackView.trailingAnchor.constraint(equalTo: instanceVisualEffectView.contentView.trailingAnchor), - instanceStackView.bottomAnchor.constraint(equalTo: instanceVisualEffectView.contentView.bottomAnchor, - constant: -.defaultSpacing) + instanceImageViewWidthConstraint ]) } @@ -188,21 +190,23 @@ private extension AddIdentityViewController { if $0 { self.activityIndicator.startAnimating() - self.logInButton.isHidden = true - self.joinButton.isHidden = true - self.browseButton.isHidden = true - self.whatIsMastodonButton.isHidden = true + self.logInButton.isHidden_stackViewSafe = true + self.joinButton.isHidden_stackViewSafe = true + self.browseButton.isHidden_stackViewSafe = true + self.whatIsMastodonButton.isHidden_stackViewSafe = true } else { self.activityIndicator.stopAnimating() - self.logInButton.isHidden = false - self.joinButton.isHidden = !(self.viewModel.instance?.registrations ?? true) - self.browseButton.isHidden = !self.viewModel.isPublicTimelineAvailable - self.whatIsMastodonButton.isHidden = false + self.logInButton.isHidden_stackViewSafe = false + self.joinButton.isHidden_stackViewSafe = !(self.viewModel.instance?.registrations ?? false) + self.browseButton.isHidden_stackViewSafe = !self.viewModel.isPublicTimelineAvailable + self.whatIsMastodonButton.isHidden_stackViewSafe = + !self.displayWelcome || self.viewModel.instance == nil } } .store(in: &cancellables) viewModel.$instance.combineLatest(viewModel.$isPublicTimelineAvailable) + .throttle(for: .seconds(.defaultAnimationDuration), scheduler: DispatchQueue.main, latest: true) .sink { [weak self] in self?.configure(instance: $0, isPublicTimelineAvailable: $1) } .store(in: &cancellables) @@ -245,6 +249,8 @@ private extension AddIdentityViewController { 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 } @@ -254,39 +260,43 @@ private extension AddIdentityViewController { } } } else { - welcomeLabel.isHidden = true - whatIsMastodonButton.isHidden = true + welcomeLabel.isHidden_stackViewSafe = true + whatIsMastodonButton.isHidden_stackViewSafe = true urlTextField.becomeFirstResponder() } } func configure(instance: Instance?, isPublicTimelineAvailable: Bool) { - if let instance = instance { - instanceTitleLabel.text = instance.title - instanceURLLabel.text = instance.uri - instanceImageView.kf.setImage(with: instance.thumbnail) - instanceImageView.isHidden = false + UIView.animate(withDuration: .zeroIfReduceMotion(.defaultAnimationDuration)) { + if let instance = instance { + self.instanceTitleLabel.text = instance.title + self.instanceURLLabel.text = instance.uri + self.instanceImageView.kf.setImage(with: instance.thumbnail) + self.instanceStackView.isHidden_stackViewSafe = false - if instance.registrations { - let joinButtonTitle: String + if instance.registrations { + let joinButtonTitle: String - if instance.approvalRequired { - joinButtonTitle = NSLocalizedString("add-identity.request-invite", comment: "") + if instance.approvalRequired { + joinButtonTitle = NSLocalizedString("add-identity.request-invite", comment: "") + } else { + joinButtonTitle = NSLocalizedString("add-identity.join", comment: "") + } + + self.joinButton.setTitle(joinButtonTitle, for: .normal) + self.joinButton.isHidden_stackViewSafe = false } else { - joinButtonTitle = NSLocalizedString("add-identity.join", comment: "") + self.joinButton.isHidden_stackViewSafe = true } - joinButton.setTitle(joinButtonTitle, for: .normal) - joinButton.isHidden = false + self.browseButton.isHidden_stackViewSafe = !isPublicTimelineAvailable + self.whatIsMastodonButton.isHidden_stackViewSafe = true } else { - joinButton.isHidden = true + 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 } - - browseButton.isHidden = !isPublicTimelineAvailable - } else { - instanceImageView.isHidden = true - joinButton.isHidden = true - browseButton.isHidden = true } } @@ -300,3 +310,16 @@ private extension AddIdentityViewController { show(registrationViewController, sender: self) } } + +// http://www.openradar.me/25087688 +extension UIView { + var isHidden_stackViewSafe: Bool { + get { isHidden } + set { + if isHidden != newValue { + isHidden = newValue + alpha = isHidden ? 0 : 1 + } + } + } +} diff --git a/Views/SwiftUI/View Controller Representables/AddIdentityView.swift b/Views/SwiftUI/View Controller Representables/AddIdentityView.swift index 16e536c..775ba41 100644 --- a/Views/SwiftUI/View Controller Representables/AddIdentityView.swift +++ b/Views/SwiftUI/View Controller Representables/AddIdentityView.swift @@ -40,6 +40,7 @@ struct AddAccountView_Previews: PreviewProvider { NavigationView { AddIdentityView(viewModelClosure: { RootViewModel.preview.addIdentityViewModel() }, displayWelcome: false) .navigationBarTitleDisplayMode(.inline) + .environmentObject(RootViewModel.preview) } } } diff --git a/Views/UIKit/CapsuleButton.swift b/Views/UIKit/CapsuleButton.swift new file mode 100644 index 0000000..df0e4f1 --- /dev/null +++ b/Views/UIKit/CapsuleButton.swift @@ -0,0 +1,49 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import UIKit + +final class CapsuleButton: UIButton { + override init(frame: CGRect) { + super.init(frame: frame) + + initialSetup() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + layer.cornerRadius = bounds.height / 2 + } + + override var isHighlighted: Bool { + didSet { + backgroundColor = isHighlighted ? Self.highlightedColor : .link + } + } +} + +private extension CapsuleButton { + static let highlightedColor: UIColor = { + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + + UIColor.link.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + + return UIColor(hue: hue, saturation: saturation, brightness: brightness * 3 / 4, alpha: alpha) + }() + + func initialSetup() { + backgroundColor = .link + titleLabel?.adjustsFontForContentSizeCategory = true + titleLabel?.font = .preferredFont(forTextStyle: .headline) + setTitleColor(.white, for: .normal) + setTitleColor(.lightText, for: .highlighted) + } +}