Merge pull request #154 from tootsuite/fix/onboarding

Fix onboarding UI/UX issues
This commit is contained in:
CMK 2021-06-16 16:19:58 +08:00 committed by GitHub
commit c456281e2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 214 additions and 96 deletions

View File

@ -115,11 +115,11 @@ extension PickServerSection {
guard let proxiedThumbnail = server.proxiedThumbnail, guard let proxiedThumbnail = server.proxiedThumbnail,
let url = URL(string: proxiedThumbnail) else { let url = URL(string: proxiedThumbnail) else {
cell.thumbnailImageView.image = placeholderImage cell.thumbnailImageView.image = placeholderImage
cell.thumbnailActivityIdicator.stopAnimating() cell.thumbnailActivityIndicator.stopAnimating()
return return
} }
cell.thumbnailImageView.isHidden = false cell.thumbnailImageView.isHidden = false
cell.thumbnailActivityIdicator.startAnimating() cell.thumbnailActivityIndicator.startAnimating()
cell.thumbnailImageView.af.setImage( cell.thumbnailImageView.af.setImage(
withURL: url, withURL: url,
@ -129,7 +129,7 @@ extension PickServerSection {
completion: { [weak cell] response in completion: { [weak cell] response in
switch response.result { switch response.result {
case .success, .failure: case .success, .failure:
cell?.thumbnailActivityIdicator.stopAnimating() cell?.thumbnailActivityIndicator.stopAnimating()
} }
} }
) )

View File

@ -91,6 +91,7 @@ internal enum Asset {
} }
internal static let battleshipGrey = ColorAsset(name: "Colors/battleshipGrey") internal static let battleshipGrey = ColorAsset(name: "Colors/battleshipGrey")
internal static let brandBlue = ColorAsset(name: "Colors/brand.blue") internal static let brandBlue = ColorAsset(name: "Colors/brand.blue")
internal static let brandBlueDarken20 = ColorAsset(name: "Colors/brand.blue.darken.20")
internal static let danger = ColorAsset(name: "Colors/danger") internal static let danger = ColorAsset(name: "Colors/danger")
internal static let disabled = ColorAsset(name: "Colors/disabled") internal static let disabled = ColorAsset(name: "Colors/disabled")
internal static let inactive = ColorAsset(name: "Colors/inactive") internal static let inactive = ColorAsset(name: "Colors/inactive")

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xB0",
"green" : "0x73",
"red" : "0x1F"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xC9",
"green" : "0x80",
"red" : "0x1B"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -22,7 +22,7 @@ final class MastodonConfirmEmailViewController: UIViewController, NeedsDependenc
let largeTitleLabel: UILabel = { let largeTitleLabel: UILabel = {
let label = UILabel() let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.boldSystemFont(ofSize: 34)) label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.systemFont(ofSize: 34, weight: .bold))
label.textColor = .label label.textColor = .label
label.text = L10n.Scene.ConfirmEmail.title label.text = L10n.Scene.ConfirmEmail.title
return label return label
@ -45,14 +45,8 @@ final class MastodonConfirmEmailViewController: UIViewController, NeedsDependenc
}() }()
let openEmailButton: UIButton = { let openEmailButton: UIButton = {
let button = UIButton(type: .system) let button = PrimaryActionButton()
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color), for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitle(L10n.Scene.ConfirmEmail.Button.openEmailApp, for: .normal) button.setTitle(L10n.Scene.ConfirmEmail.Button.openEmailApp, for: .normal)
button.layer.masksToBounds = true
button.layer.cornerRadius = 8
button.layer.cornerCurve = .continuous
button.addTarget(self, action: #selector(openEmailButtonPressed(_:)), for: UIControl.Event.touchUpInside) button.addTarget(self, action: #selector(openEmailButtonPressed(_:)), for: UIControl.Event.touchUpInside)
return button return button
}() }()

View File

@ -237,21 +237,23 @@ extension MastodonPickServerViewController {
.store(in: &disposeBag) .store(in: &disposeBag)
viewModel.emptyStateViewState viewModel.emptyStateViewState
.receive(on: DispatchQueue.main) .receive(on: RunLoop.main)
.sink { [weak self] state in .sink { [weak self] state in
guard let self = self else { return } guard let self = self else { return }
switch state { switch state {
case .none: case .none:
self.emptyStateView.isHidden = true UIView.animate(withDuration: 0.3) {
self.emptyStateView.alpha = 0
}
case .loading: case .loading:
self.emptyStateView.isHidden = false self.emptyStateView.alpha = 1
self.emptyStateView.networkIndicatorImageView.isHidden = true self.emptyStateView.networkIndicatorImageView.isHidden = true
self.emptyStateView.activityIndicatorView.startAnimating() self.emptyStateView.activityIndicatorView.startAnimating()
self.emptyStateView.infoLabel.isHidden = false self.emptyStateView.infoLabel.isHidden = false
self.emptyStateView.infoLabel.text = L10n.Scene.ServerPicker.EmptyState.findingServers self.emptyStateView.infoLabel.text = L10n.Scene.ServerPicker.EmptyState.findingServers
self.emptyStateView.infoLabel.textAlignment = self.traitCollection.layoutDirection == .rightToLeft ? .right : .left self.emptyStateView.infoLabel.textAlignment = self.traitCollection.layoutDirection == .rightToLeft ? .right : .left
case .badNetwork: case .badNetwork:
self.emptyStateView.isHidden = false self.emptyStateView.alpha = 1
self.emptyStateView.networkIndicatorImageView.isHidden = false self.emptyStateView.networkIndicatorImageView.isHidden = false
self.emptyStateView.activityIndicatorView.stopAnimating() self.emptyStateView.activityIndicatorView.stopAnimating()
self.emptyStateView.infoLabel.isHidden = false self.emptyStateView.infoLabel.isHidden = false

View File

@ -45,9 +45,10 @@ extension MastodonPickServerViewModel.LoadIndexedServerState {
viewModel.context.apiService.servers(language: nil, category: nil) viewModel.context.apiService.servers(language: nil, category: nil)
.sink { completion in .sink { completion in
switch completion { switch completion {
case .failure: case .failure(let error):
// TODO: handle error // TODO: handle error
stateMachine.enter(Fail.self) stateMachine.enter(Fail.self)
viewModel.loadingIndexedServersError.value = error
case .finished: case .finished:
break break
} }

View File

@ -61,6 +61,7 @@ class MastodonPickServerViewModel: NSObject {
let error = CurrentValueSubject<Error?, Never>(nil) let error = CurrentValueSubject<Error?, Never>(nil)
let isLoadingIndexedServers = CurrentValueSubject<Bool, Never>(false) let isLoadingIndexedServers = CurrentValueSubject<Bool, Never>(false)
let loadingIndexedServersError = CurrentValueSubject<Error?, Never>(nil)
let emptyStateViewState = CurrentValueSubject<EmptyStateViewState, Never>(.none) let emptyStateViewState = CurrentValueSubject<EmptyStateViewState, Never>(.none)
init(context: AppContext, mode: PickServerMode) { init(context: AppContext, mode: PickServerMode) {
@ -142,16 +143,23 @@ extension MastodonPickServerViewModel {
}) })
.store(in: &disposeBag) .store(in: &disposeBag)
isLoadingIndexedServers Publishers.CombineLatest(
.map { isLoadingIndexedServers -> EmptyStateViewState in isLoadingIndexedServers,
if isLoadingIndexedServers { loadingIndexedServersError
return .loading )
.map { isLoadingIndexedServers, loadingIndexedServersError -> EmptyStateViewState in
if isLoadingIndexedServers {
if loadingIndexedServersError != nil {
return .badNetwork
} else { } else {
return .none return .loading
} }
} else {
return .none
} }
.assign(to: \.value, on: emptyStateViewState) }
.store(in: &disposeBag) .assign(to: \.value, on: emptyStateViewState)
.store(in: &disposeBag)
Publishers.CombineLatest3( Publishers.CombineLatest3(
indexedServers.eraseToAnyPublisher(), indexedServers.eraseToAnyPublisher(),

View File

@ -60,7 +60,7 @@ class PickServerCell: UITableViewCell {
return label return label
}() }()
let thumbnailActivityIdicator = UIActivityIndicatorView(style: .medium) let thumbnailActivityIndicator = UIActivityIndicatorView(style: .medium)
let thumbnailImageView: UIImageView = { let thumbnailImageView: UIImageView = {
let imageView = UIImageView() let imageView = UIImageView()
@ -99,7 +99,7 @@ class PickServerCell: UITableViewCell {
return button return button
}() }()
let seperator: UIView = { let separator: UIView = {
let view = UIView() let view = UIView()
view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
@ -177,7 +177,7 @@ class PickServerCell: UITableViewCell {
thumbnailImageView.isHidden = false thumbnailImageView.isHidden = false
thumbnailImageView.af.cancelImageRequest() thumbnailImageView.af.cancelImageRequest()
thumbnailActivityIdicator.stopAnimating() thumbnailActivityIndicator.stopAnimating()
disposeBag.removeAll() disposeBag.removeAll()
} }
@ -203,7 +203,7 @@ extension PickServerCell {
containerView.addSubview(domainLabel) containerView.addSubview(domainLabel)
containerView.addSubview(checkbox) containerView.addSubview(checkbox)
containerView.addSubview(descriptionLabel) containerView.addSubview(descriptionLabel)
containerView.addSubview(seperator) containerView.addSubview(separator)
containerView.addSubview(expandButton) containerView.addSubview(expandButton)
@ -231,13 +231,13 @@ extension PickServerCell {
containerView.topAnchor.constraint(equalTo: contentView.topAnchor), containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), containerView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 1), contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
// Set bottom separator // Set bottom separator
seperator.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), separator.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: seperator.trailingAnchor), containerView.trailingAnchor.constraint(equalTo: separator.trailingAnchor),
containerView.topAnchor.constraint(equalTo: seperator.topAnchor), containerView.topAnchor.constraint(equalTo: separator.topAnchor),
seperator.heightAnchor.constraint(equalToConstant: 1).priority(.defaultHigh), separator.heightAnchor.constraint(equalToConstant: 1).priority(.defaultHigh),
domainLabel.topAnchor.constraint(equalTo: containerView.layoutMarginsGuide.topAnchor), domainLabel.topAnchor.constraint(equalTo: containerView.layoutMarginsGuide.topAnchor),
domainLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor), domainLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
@ -272,14 +272,14 @@ extension PickServerCell {
containerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: expandButton.bottomAnchor), containerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: expandButton.bottomAnchor),
]) ])
thumbnailActivityIdicator.translatesAutoresizingMaskIntoConstraints = false thumbnailActivityIndicator.translatesAutoresizingMaskIntoConstraints = false
thumbnailImageView.addSubview(thumbnailActivityIdicator) thumbnailImageView.addSubview(thumbnailActivityIndicator)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
thumbnailActivityIdicator.centerXAnchor.constraint(equalTo: thumbnailImageView.centerXAnchor), thumbnailActivityIndicator.centerXAnchor.constraint(equalTo: thumbnailImageView.centerXAnchor),
thumbnailActivityIdicator.centerYAnchor.constraint(equalTo: thumbnailImageView.centerYAnchor), thumbnailActivityIndicator.centerYAnchor.constraint(equalTo: thumbnailImageView.centerYAnchor),
]) ])
thumbnailActivityIdicator.hidesWhenStopped = true thumbnailActivityIndicator.hidesWhenStopped = true
thumbnailActivityIdicator.stopAnimating() thumbnailActivityIndicator.stopAnimating()
NSLayoutConstraint.activate(collapseConstraints) NSLayoutConstraint.activate(collapseConstraints)

View File

@ -41,13 +41,44 @@ class PickServerSearchCell: UITableViewCell {
let searchTextField: UITextField = { let searchTextField: UITextField = {
let textField = UITextField() let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = .preferredFont(forTextStyle: .headline) textField.leftView = {
let imageView = UIImageView(
image: UIImage(
systemName: "magnifyingglass",
withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
)
)
imageView.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)
let containerView = UIView()
imageView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
let paddingView = UIView()
paddingView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(paddingView)
NSLayoutConstraint.activate([
paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
paddingView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor),
paddingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
paddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
])
return containerView
}()
textField.leftViewMode = .always
textField.font = .systemFont(ofSize: 15, weight: .regular)
textField.tintColor = Asset.Colors.Label.primary.color textField.tintColor = Asset.Colors.Label.primary.color
textField.textColor = Asset.Colors.Label.primary.color textField.textColor = Asset.Colors.Label.primary.color
textField.adjustsFontForContentSizeCategory = true textField.adjustsFontForContentSizeCategory = true
textField.attributedPlaceholder = textField.attributedPlaceholder =
NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder, NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder,
attributes: [.font: UIFont.preferredFont(forTextStyle: .headline), attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
.foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)]) .foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
textField.clearButtonMode = .whileEditing textField.clearButtonMode = .whileEditing
textField.autocapitalizationType = .none textField.autocapitalizationType = .none

View File

@ -76,22 +76,19 @@ extension PickServerEmptyStateView {
]) ])
containerStackView.addArrangedSubview(networkIndicatorImageView) containerStackView.addArrangedSubview(networkIndicatorImageView)
let infoContainerView = UIView() let infoContainerStackView = UIStackView()
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false infoContainerStackView.axis = .horizontal
infoContainerView.addSubview(activityIndicatorView) infoContainerStackView.distribution = .fill
NSLayoutConstraint.activate([
activityIndicatorView.leadingAnchor.constraint(equalTo: infoContainerView.leadingAnchor), infoContainerStackView.addArrangedSubview(activityIndicatorView)
activityIndicatorView.centerYAnchor.constraint(equalTo: infoContainerView.centerYAnchor), infoContainerStackView.spacing = 4
activityIndicatorView.bottomAnchor.constraint(equalTo: infoContainerView.bottomAnchor), activityIndicatorView.setContentHuggingPriority(.required - 1, for: .horizontal)
])
infoLabel.translatesAutoresizingMaskIntoConstraints = false infoContainerStackView.addArrangedSubview(infoLabel)
infoContainerView.addSubview(infoLabel) infoLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
NSLayoutConstraint.activate([ infoLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
infoLabel.leadingAnchor.constraint(equalTo: activityIndicatorView.trailingAnchor, constant: 4),
infoLabel.centerYAnchor.constraint(equalTo: infoContainerView.centerYAnchor), containerStackView.addArrangedSubview(infoContainerStackView)
infoLabel.trailingAnchor.constraint(equalTo: infoContainerView.trailingAnchor),
])
containerStackView.addArrangedSubview(infoContainerView)
let bottomPaddingView = UIView() let bottomPaddingView = UIView()
bottomPaddingView.translatesAutoresizingMaskIntoConstraints = false bottomPaddingView.translatesAutoresizingMaskIntoConstraints = false
@ -104,7 +101,7 @@ extension PickServerEmptyStateView {
]) ])
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
bottomPaddingView.heightAnchor.constraint(equalTo: topPaddingView.heightAnchor, multiplier: 2.0), bottomPaddingView.heightAnchor.constraint(equalTo: topPaddingView.heightAnchor, multiplier: 1.0).priority(.defaultHigh),
]) ])
activityIndicatorView.hidesWhenStopped = true activityIndicatorView.hidesWhenStopped = true
@ -126,15 +123,18 @@ struct PickServerEmptyStateView_Previews: PreviewProvider {
emptyStateView.activityIndicatorView.stopAnimating() emptyStateView.activityIndicatorView.stopAnimating()
return emptyStateView return emptyStateView
} }
.previewLayout(.fixed(width: 375, height: 400)) .previewLayout(.fixed(width: 375, height: 150))
.previewDisplayName("Bad Network")
UIViewPreview(width: 375) { UIViewPreview(width: 375) {
let emptyStateView = PickServerEmptyStateView() let emptyStateView = PickServerEmptyStateView()
emptyStateView.networkIndicatorImageView.isHidden = true
emptyStateView.infoLabel.text = L10n.Scene.ServerPicker.EmptyState.findingServers emptyStateView.infoLabel.text = L10n.Scene.ServerPicker.EmptyState.findingServers
emptyStateView.infoLabel.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? .right : .left emptyStateView.infoLabel.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? .right : .left
emptyStateView.activityIndicatorView.startAnimating() emptyStateView.activityIndicatorView.startAnimating()
return emptyStateView return emptyStateView
} }
.previewLayout(.fixed(width: 375, height: 400)) .previewLayout(.fixed(width: 375, height: 44))
.previewDisplayName("Loading…")
} }
} }
} }

View File

@ -63,7 +63,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let largeTitleLabel: UILabel = { let largeTitleLabel: UILabel = {
let label = UILabel() let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.boldSystemFont(ofSize: 34)) label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 34, weight: .bold))
label.textColor = Asset.Colors.Label.primary.color label.textColor = Asset.Colors.Label.primary.color
label.text = L10n.Scene.Register.title label.text = L10n.Scene.Register.title
label.numberOfLines = 0 label.numberOfLines = 0
@ -93,11 +93,10 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let plusIconImageView: UIImageView = { let plusIconImageView: UIImageView = {
let icon = UIImageView() let icon = UIImageView()
let image = Asset.Circles.plusCircleFill.image.withRenderingMode(.alwaysTemplate) let image = Asset.Circles.plusCircleFill.image.withRenderingMode(.alwaysTemplate)
icon.image = image icon.image = image
icon.tintColor = Asset.Colors.Icon.plus.color icon.tintColor = Asset.Colors.Icon.plus.color
icon.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color icon.backgroundColor = .white
return icon return icon
}() }()
@ -110,7 +109,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let usernameTextField: UITextField = { let usernameTextField: UITextField = {
let textField = UITextField() let textField = UITextField()
textField.returnKeyType = .next
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color
@ -119,8 +118,35 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
NSAttributedString.Key.font: MastodonRegisterViewController.textFieldLabelFont]) NSAttributedString.Key.font: MastodonRegisterViewController.textFieldLabelFont])
textField.borderStyle = UITextField.BorderStyle.roundedRect textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) textField.font = MastodonRegisterViewController.textFieldLabelFont
textField.leftView = paddingView textField.leftView = {
let containerView = UIView()
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
paddingView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(paddingView)
NSLayoutConstraint.activate([
paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
paddingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
paddingView.widthAnchor.constraint(equalToConstant: 5).priority(.defaultHigh),
])
let label = UILabel()
label.font = MastodonRegisterViewController.textFieldLabelFont
label.textColor = Asset.Colors.Label.primary.color
label.text = " @"
label.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: containerView.topAnchor),
label.leadingAnchor.constraint(equalTo: paddingView.trailingAnchor),
label.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
return containerView
}()
textField.leftViewMode = .always textField.leftViewMode = .always
return textField return textField
}() }()
@ -134,6 +160,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let displayNameTextField: UITextField = { let displayNameTextField: UITextField = {
let textField = UITextField() let textField = UITextField()
textField.returnKeyType = .next
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color
@ -145,11 +172,13 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView textField.leftView = paddingView
textField.leftViewMode = .always textField.leftViewMode = .always
textField.font = MastodonRegisterViewController.textFieldLabelFont
return textField return textField
}() }()
let emailTextField: UITextField = { let emailTextField: UITextField = {
let textField = UITextField() let textField = UITextField()
textField.returnKeyType = .next
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.keyboardType = .emailAddress textField.keyboardType = .emailAddress
@ -162,6 +191,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView textField.leftView = paddingView
textField.leftViewMode = .always textField.leftViewMode = .always
textField.font = MastodonRegisterViewController.textFieldLabelFont
return textField return textField
}() }()
@ -174,6 +204,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let passwordTextField: UITextField = { let passwordTextField: UITextField = {
let textField = UITextField() let textField = UITextField()
textField.returnKeyType = .next // set to "Return" depends on if the last input field or not
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.keyboardType = .asciiCapable textField.keyboardType = .asciiCapable
@ -187,6 +218,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView textField.leftView = paddingView
textField.leftViewMode = .always textField.leftViewMode = .always
textField.font = MastodonRegisterViewController.textFieldLabelFont
return textField return textField
}() }()
@ -206,6 +238,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
lazy var reasonTextField: UITextField = { lazy var reasonTextField: UITextField = {
let textField = UITextField() let textField = UITextField()
textField.returnKeyType = .next // set to "Return" depends on if the last input field or not
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color
@ -217,6 +250,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView textField.leftView = paddingView
textField.leftViewMode = .always textField.leftViewMode = .always
textField.font = MastodonRegisterViewController.textFieldLabelFont
return textField return textField
}() }()
@ -361,14 +395,13 @@ extension MastodonRegisterViewController {
stackView.setCustomSpacing(6, after: passwordTextField) stackView.setCustomSpacing(6, after: passwordTextField)
stackView.setCustomSpacing(32, after: passwordCheckLabel) stackView.setCustomSpacing(32, after: passwordCheckLabel)
//return // return
if viewModel.approvalRequired { if viewModel.approvalRequired {
passwordTextField.returnKeyType = .continue reasonTextField.returnKeyType = .done
} else { } else {
passwordTextField.returnKeyType = .done passwordTextField.returnKeyType = .done
} }
reasonTextField.returnKeyType = .done
// button // button
stackView.addArrangedSubview(buttonContainer) stackView.addArrangedSubview(buttonContainer)
signUpButton.translatesAutoresizingMaskIntoConstraints = false signUpButton.translatesAutoresizingMaskIntoConstraints = false
@ -626,6 +659,25 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
break break
} }
} }
func textFieldDidEndEditing(_ textField: UITextField) {
let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
switch textField {
case usernameTextField:
viewModel.username.value = text
case displayNameTextField:
viewModel.displayName.value = text
case emailTextField:
viewModel.email.value = text
case passwordTextField:
viewModel.password.value = text
case reasonTextField:
viewModel.reason.value = text
default:
break
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool { func textFieldShouldReturn(_ textField: UITextField) -> Bool {
switch textField { switch textField {

View File

@ -106,6 +106,7 @@ final class MastodonRegisterViewModel {
case .success: case .success:
let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username) let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username)
self.usernameErrorPrompt.value = MastodonRegisterViewModel.errorPromptAttributedString(for: text) self.usernameErrorPrompt.value = MastodonRegisterViewModel.errorPromptAttributedString(for: text)
self.usernameValidateState.value = .invalid
case .failure: case .failure:
break break
} }

View File

@ -40,7 +40,7 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
let rulesLabel: UILabel = { let rulesLabel: UILabel = {
let label = UILabel() let label = UILabel()
label.font = .preferredFont(forTextStyle: .body) label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
label.textColor = Asset.Colors.Label.primary.color label.textColor = Asset.Colors.Label.primary.color
label.text = "Rules" label.text = "Rules"
label.numberOfLines = 0 label.numberOfLines = 0
@ -66,8 +66,6 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
let confirmButton: PrimaryActionButton = { let confirmButton: PrimaryActionButton = {
let button = PrimaryActionButton() let button = PrimaryActionButton()
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
button.setTitleColor(.white, for: .normal)
button.setTitle(L10n.Scene.ServerRules.Button.confirm, for: .normal) button.setTitle(L10n.Scene.ServerRules.Button.confirm, for: .normal)
return button return button
}() }()

View File

@ -10,14 +10,13 @@ import Combine
import MastodonSDK import MastodonSDK
final class MastodonServerRulesViewModel { final class MastodonServerRulesViewModel {
// input // input
let domain: String let domain: String
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
let rules: [Mastodon.Entity.Instance.Rule] let rules: [Mastodon.Entity.Instance.Rule]
let instance: Mastodon.Entity.Instance let instance: Mastodon.Entity.Instance
let applicationToken: Mastodon.Entity.Token let applicationToken: Mastodon.Entity.Token
init( init(
domain: String, domain: String,
@ -36,14 +35,19 @@ final class MastodonServerRulesViewModel {
var rulesAttributedString: NSAttributedString { var rulesAttributedString: NSAttributedString {
let attributedString = NSMutableAttributedString(string: "\n") let attributedString = NSMutableAttributedString(string: "\n")
let configuration = UIImage.SymbolConfiguration(font: .preferredFont(forTextStyle: .title3)) let configuration = UIImage.SymbolConfiguration(font: .preferredFont(forTextStyle: .title3))
let separatorString = Array(repeating: " ", count: 4).joined()
for (i, rule) in rules.enumerated() { for (i, rule) in rules.enumerated() {
guard i < 50 else {
return NSAttributedString(string: "\(i)" + separatorString + rule.text + "\n\n")
}
let imageName = String(i + 1) + ".circle.fill" let imageName = String(i + 1) + ".circle.fill"
let image = UIImage(systemName: imageName, withConfiguration: configuration)! let image = UIImage(systemName: imageName, withConfiguration: configuration)!
let attachment = NSTextAttachment() let attachment = NSTextAttachment()
attachment.image = image.withTintColor(Asset.Colors.Label.primary.color) attachment.image = image.withTintColor(Asset.Colors.Label.primary.color)
let imageAttribute = NSAttributedString(attachment: attachment) let imageAttribute = NSMutableAttributedString(attachment: attachment)
imageAttribute.addAttributes([NSAttributedString.Key.baselineOffset : -1.5], range: NSRange(location: 0, length: imageAttribute.length))
let ruleString = NSAttributedString(string: " " + rule.text + "\n\n")
let ruleString = NSAttributedString(string: separatorString + rule.text + "\n\n")
attributedString.append(imageAttribute) attributedString.append(imageAttribute)
attributedString.append(ruleString) attributedString.append(ruleString)
} }

View File

@ -37,9 +37,10 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
private(set) lazy var signUpButton: PrimaryActionButton = { private(set) lazy var signUpButton: PrimaryActionButton = {
let button = PrimaryActionButton() let button = PrimaryActionButton()
button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal) button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal)
let backgroundImageColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? .white : Asset.Colors.Button.normal.color let backgroundImageColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? .white : Asset.Colors.brandBlue.color
let backgroundImageHighlightedColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? UIColor(white: 0.8, alpha: 1.0) : Asset.Colors.brandBlueDarken20.color
button.setBackgroundImage(.placeholder(color: backgroundImageColor), for: .normal) button.setBackgroundImage(.placeholder(color: backgroundImageColor), for: .normal)
button.setBackgroundImage(.placeholder(color: backgroundImageColor.withAlphaComponent(0.9)), for: .highlighted) button.setBackgroundImage(.placeholder(color: backgroundImageHighlightedColor), for: .highlighted)
let titleColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? Asset.Colors.Button.normal.color : UIColor.white let titleColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? Asset.Colors.Button.normal.color : UIColor.white
button.setTitleColor(titleColor, for: .normal) button.setTitleColor(titleColor, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false button.translatesAutoresizingMaskIntoConstraints = false
@ -67,6 +68,8 @@ extension WelcomeViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
view.overrideUserInterfaceStyle = .light
setupOnboardingAppearance() setupOnboardingAppearance()
setupIllustrationLayout() setupIllustrationLayout()

View File

@ -39,7 +39,8 @@ extension PrimaryActionButton {
titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)) titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
setTitleColor(.white, for: .normal) setTitleColor(.white, for: .normal)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color), for: .normal) setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color), for: .normal)
setupButtonBackground() setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlueDarken20.color), for: .highlighted)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
applyCornerRadius(radius: 10) applyCornerRadius(radius: 10)
} }
@ -68,20 +69,4 @@ extension PrimaryActionButton {
self.setTitle(originalButtonTitle, for: .disabled) self.setTitle(originalButtonTitle, for: .disabled)
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setupButtonBackground()
}
func setupButtonBackground() {
if UIScreen.main.traitCollection.userInterfaceStyle == .light {
setTitleColor(.white, for: .disabled)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Button.normal.color.withAlphaComponent(0.5)), for: .highlighted)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
} else {
setTitleColor(UIColor.white.withAlphaComponent(0.5), for: .disabled)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color.withAlphaComponent(0.5)), for: .highlighted)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color.withAlphaComponent(0.5)), for: .disabled)
}
}
} }