feat: update server pick scene UI

This commit is contained in:
CMK 2022-01-04 18:30:21 +08:00
parent cff048c2a3
commit 223049a3f5
42 changed files with 1390 additions and 1103 deletions

View File

@ -198,7 +198,9 @@
"log_in": "Log In"
},
"server_picker": {
"title": "Pick a server,\nany server.",
"title": "Mastodon is made of users in different communities.",
"subtitle": "Pick a community based on your interests, region, or a general purpose one.",
"subtitle_extend": "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual.",
"button": {
"category": {
"all": "All",
@ -225,7 +227,7 @@
"category": "CATEGORY"
},
"input": {
"placeholder": "Find a server or join your own..."
"placeholder": "Search communities"
},
"empty_state": {
"finding_servers": "Finding available servers...",
@ -234,7 +236,7 @@
}
},
"register": {
"title": "Tell us about you.",
"title": "Lets get you set up on %s",
"input": {
"avatar": {
"delete": "Delete"
@ -288,7 +290,7 @@
},
"server_rules": {
"title": "Some ground rules.",
"subtitle": "These rules are set by the admins of %s.",
"subtitle": "These are set and enforced by the %s moderators.",
"prompt": "By continuing, youre subject to the terms of service and privacy policy for %s.",
"terms_of_service": "terms of service",
"privacy_policy": "privacy policy",
@ -298,7 +300,7 @@
},
"confirm_email": {
"title": "One last thing.",
"subtitle": "We just sent an email to %s,\ntap the link to confirm your account.",
"subtitle": "Tap the link we emailed to you to verify your account.",
"button": {
"open_email_app": "Open Email App",
"dont_receive_email": "I never got an email"

View File

@ -193,6 +193,8 @@
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; };
DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EA277EF3820030EE79 /* GradientBorderView.swift */; };
DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */; };
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EE277F12720030EE79 /* NavigationActionView.swift */; };
DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */; };
DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; };
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; };
@ -973,6 +975,8 @@
DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = "<group>"; };
DB0617EA277EF3820030EE79 /* GradientBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientBorderView.swift; sourceTree = "<group>"; };
DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = "<group>"; };
DB0617EE277F12720030EE79 /* NavigationActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationActionView.swift; sourceTree = "<group>"; };
DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerServerSectionTableHeaderView.swift; sourceTree = "<group>"; };
DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonUser+Property.swift"; sourceTree = "<group>"; };
@ -1595,8 +1599,6 @@
isa = PBXGroup;
children = (
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */,
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */,
0FB3D33725E6401400AAD544 /* PickServerCell.swift */,
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */,
);
@ -1608,6 +1610,7 @@
children = (
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */,
DB9282B125F3222800823B15 /* PickServerEmptyStateView.swift */,
DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */,
);
path = View;
sourceTree = "<group>";
@ -2097,6 +2100,15 @@
path = TableViewCell;
sourceTree = "<group>";
};
DB0617F3278436360030EE79 /* Deprecated */ = {
isa = PBXGroup;
children = (
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */,
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
);
path = Deprecated;
sourceTree = "<group>";
};
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
isa = PBXGroup;
children = (
@ -2228,6 +2240,7 @@
children = (
DB427DE325BAA00100D1B89D /* Info.plist */,
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
DB0617F3278436360030EE79 /* Deprecated */,
2D76319C25C151DE00929FB9 /* Diffiable */,
DB8AF52A25C13561002E6C99 /* State */,
2D61335525C1886800CAE157 /* Service */,
@ -2524,6 +2537,7 @@
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */,
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */,
DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */,
DB0617EE277F12720030EE79 /* NavigationActionView.swift */,
);
path = Share;
sourceTree = "<group>";
@ -3972,6 +3986,7 @@
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */,
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */,
DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */,
DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */,
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */,
DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */,
@ -4132,6 +4147,7 @@
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */,

View File

@ -216,6 +216,15 @@
"revision": "dad97167bf1be16aeecd109130900995dd01c515",
"version": "2.6.0"
}
},
{
"package": "UITextView+Placeholder",
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder",
"state": {
"branch": null,
"revision": "20f513ded04a040cdf5467f0891849b1763ede3b",
"version": "1.4.1"
}
}
]
},

View File

@ -0,0 +1,145 @@
//
// PickServerCategoriesCell.swift
// Mastodon
//
// Created by BradGao on 2021/2/23.
//
//import os.log
//import UIKit
//import MastodonSDK
//
//protocol PickServerCategoriesCellDelegate: AnyObject {
// func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
//}
//
//final class PickServerCategoriesCell: UITableViewCell {
//
// weak var delegate: PickServerCategoriesCellDelegate?
//
// var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
//
// let metricView = UIView()
//
// let collectionView: UICollectionView = {
// let flowLayout = UICollectionViewFlowLayout()
// flowLayout.scrollDirection = .horizontal
// let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
// view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
// view.backgroundColor = .clear
// view.showsHorizontalScrollIndicator = false
// view.showsVerticalScrollIndicator = false
// view.layer.masksToBounds = false
// view.translatesAutoresizingMaskIntoConstraints = false
// return view
// }()
//
// override func prepareForReuse() {
// super.prepareForReuse()
//
// delegate = nil
// }
//
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
// super.init(style: style, reuseIdentifier: reuseIdentifier)
// _init()
// }
//
// required init?(coder: NSCoder) {
// super.init(coder: coder)
// _init()
// }
//}
//
//extension PickServerCategoriesCell {
//
// private func _init() {
// selectionStyle = .none
// backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
// configureMargin()
//
// metricView.translatesAutoresizingMaskIntoConstraints = false
// contentView.addSubview(metricView)
// NSLayoutConstraint.activate([
// metricView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
// metricView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
// metricView.topAnchor.constraint(equalTo: contentView.topAnchor),
// metricView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
// metricView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
// ])
//
// contentView.addSubview(collectionView)
// NSLayoutConstraint.activate([
// collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
// collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
// collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
// contentView.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20),
// collectionView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
// ])
//
// collectionView.delegate = self
// }
//
// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// super.traitCollectionDidChange(previousTraitCollection)
//
// configureMargin()
// }
//
// override func layoutSubviews() {
// super.layoutSubviews()
//
// collectionView.collectionViewLayout.invalidateLayout()
// }
//
//}
//
//extension PickServerCategoriesCell {
// private func configureMargin() {
// switch traitCollection.horizontalSizeClass {
// case .regular:
// let margin = MastodonPickServerViewController.viewEdgeMargin
// contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
// default:
// contentView.layoutMargins = .zero
// }
// }
//}
//
//// MARK: - UICollectionViewDelegateFlowLayout
//extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
//
// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
// collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
// delegate?.pickServerCategoriesCell(self, collectionView: collectionView, didSelectItemAt: indexPath)
// }
//
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
// layoutIfNeeded()
// return UIEdgeInsets(top: 0, left: metricView.frame.minX - collectionView.frame.minX, bottom: 0, right: collectionView.frame.maxX - metricView.frame.maxX)
// }
//
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
// return 16
// }
//
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// return CGSize(width: 60, height: 80)
// }
//
//}
//
//extension PickServerCategoriesCell {
//
// override func accessibilityElementCount() -> Int {
// guard let diffableDataSource = diffableDataSource else { return 0 }
// return diffableDataSource.snapshot().itemIdentifiers.count
// }
//
// override func accessibilityElement(at index: Int) -> Any? {
// guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
// return item
// }
//
//}

View File

@ -0,0 +1,171 @@
//
// PickServerSearchCell.swift
// Mastodon
//
// Created by BradGao on 2021/2/24.
//
import UIKit
//protocol PickServerSearchCellDelegate: AnyObject {
// func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?)
//}
//
//class PickServerSearchCell: UITableViewCell {
//
// weak var delegate: PickServerSearchCellDelegate?
//
// private var bgView: UIView = {
// let view = UIView()
// view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
// view.translatesAutoresizingMaskIntoConstraints = false
// view.layer.maskedCorners = [
// .layerMinXMinYCorner,
// .layerMaxXMinYCorner
// ]
// view.layer.cornerCurve = .continuous
// view.layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
// return view
// }()
//
// private var textFieldBgView: UIView = {
// let view = UIView()
// view.backgroundColor = Asset.Colors.TextField.background.color
// view.translatesAutoresizingMaskIntoConstraints = false
// view.layer.masksToBounds = true
// view.layer.cornerRadius = 6
// view.layer.cornerCurve = .continuous
// return view
// }()
//
// let searchTextField: UITextField = {
// let textField = UITextField()
// textField.translatesAutoresizingMaskIntoConstraints = false
// 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.textColor = Asset.Colors.Label.primary.color
// textField.adjustsFontForContentSizeCategory = true
// textField.attributedPlaceholder =
// NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder,
// attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
// .foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
// textField.clearButtonMode = .whileEditing
// textField.autocapitalizationType = .none
// textField.autocorrectionType = .no
// textField.returnKeyType = .done
// textField.keyboardType = .URL
// return textField
// }()
//
// override func prepareForReuse() {
// super.prepareForReuse()
//
// delegate = nil
// }
//
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
// super.init(style: style, reuseIdentifier: reuseIdentifier)
// _init()
// }
//
// required init?(coder: NSCoder) {
// super.init(coder: coder)
// _init()
// }
//}
//
//extension PickServerSearchCell {
// private func _init() {
// selectionStyle = .none
// backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
// configureMargin()
//
// searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
// searchTextField.delegate = self
//
// contentView.addSubview(bgView)
// contentView.addSubview(textFieldBgView)
// contentView.addSubview(searchTextField)
//
// NSLayoutConstraint.activate([
// bgView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
// bgView.topAnchor.constraint(equalTo: contentView.topAnchor),
// bgView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
// bgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
//
// textFieldBgView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 14),
// textFieldBgView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 12),
// bgView.trailingAnchor.constraint(equalTo: textFieldBgView.trailingAnchor, constant: 14),
// bgView.bottomAnchor.constraint(equalTo: textFieldBgView.bottomAnchor, constant: 13),
//
// searchTextField.leadingAnchor.constraint(equalTo: textFieldBgView.leadingAnchor, constant: 11),
// searchTextField.topAnchor.constraint(equalTo: textFieldBgView.topAnchor, constant: 4),
// textFieldBgView.trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 11),
// textFieldBgView.bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 4),
// ])
// }
//
// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// super.traitCollectionDidChange(previousTraitCollection)
//
// configureMargin()
// }
//}
//
//extension PickServerSearchCell {
// private func configureMargin() {
// switch traitCollection.horizontalSizeClass {
// case .regular:
// let margin = MastodonPickServerViewController.viewEdgeMargin
// contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
// default:
// contentView.layoutMargins = .zero
// }
// }
//}
//
//extension PickServerSearchCell {
// @objc private func textFieldDidChange(_ textField: UITextField) {
// delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
// }
//}
//
//// MARK: - UITextFieldDelegate
//extension PickServerSearchCell: UITextFieldDelegate {
//
// func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// textField.resignFirstResponder()
// return false
// }
//}

View File

@ -15,10 +15,11 @@ enum CategoryPickerItem {
}
extension CategoryPickerItem {
var title: String {
var emoji: String {
switch self {
case .all:
return L10n.Scene.ServerPicker.Button.Category.all
return "💬"
case .category(let category):
switch category.category {
case .academia:
@ -32,7 +33,7 @@ extension CategoryPickerItem {
case .games:
return "🕹"
case .general:
return "💬"
return "🐘"
case .journalism:
return "📰"
case .lgbt:
@ -50,6 +51,41 @@ extension CategoryPickerItem {
}
}
}
var title: String {
switch self {
case .all:
return L10n.Scene.ServerPicker.Button.Category.all
case .category(let category):
switch category.category {
case .academia:
return L10n.Scene.ServerPicker.Button.Category.academia
case .activism:
return L10n.Scene.ServerPicker.Button.Category.activism
case .food:
return L10n.Scene.ServerPicker.Button.Category.food
case .furry:
return L10n.Scene.ServerPicker.Button.Category.furry
case .games:
return L10n.Scene.ServerPicker.Button.Category.games
case .general:
return L10n.Scene.ServerPicker.Button.Category.general
case .journalism:
return L10n.Scene.ServerPicker.Button.Category.journalism
case .lgbt:
return L10n.Scene.ServerPicker.Button.Category.lgbt
case .regional:
return L10n.Scene.ServerPicker.Button.Category.regional
case .art:
return L10n.Scene.ServerPicker.Button.Category.art
case .music:
return L10n.Scene.ServerPicker.Button.Category.music
case .tech:
return L10n.Scene.ServerPicker.Button.Category.tech
case ._other:
return "-" // FIXME:
}
}
}
var accessibilityDescription: String {
switch self {
@ -82,7 +118,7 @@ extension CategoryPickerItem {
case .tech:
return L10n.Scene.ServerPicker.Button.Category.tech
case ._other:
return "" // FIXME:
return "-" // FIXME:
}
}
}

View File

@ -12,8 +12,6 @@ import MastodonSDK
/// Note: update Equatable when change case
enum PickServerItem {
case header
case categoryPicker(items: [CategoryPickerItem])
case search
case server(server: Mastodon.Entity.Server, attribute: ServerItemAttribute)
case loader(attribute: LoaderItemAttribute)
}
@ -63,10 +61,6 @@ extension PickServerItem: Equatable {
switch (lhs, rhs) {
case (.header, .header):
return true
case (.categoryPicker(let itemsLeft), .categoryPicker(let itemsRight)):
return itemsLeft == itemsRight
case (.search, .search):
return true
case (.server(let serverLeft, _), .server(let serverRight, _)):
return serverLeft.domain == serverRight.domain
case (.loader(let attributeLeft), loader(let attributeRight)):
@ -82,10 +76,6 @@ extension PickServerItem: Hashable {
switch self {
case .header:
hasher.combine(String(describing: PickServerItem.header.self))
case .categoryPicker(let items):
hasher.combine(items)
case .search:
hasher.combine(String(describing: PickServerItem.search.self))
case .server(let server, _):
hasher.combine(server.domain)
case .loader(let attribute):

View File

@ -19,27 +19,11 @@ extension CategoryPickerSection {
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in
guard let _ = dependency else { return nil }
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
switch item {
case .all:
cell.categoryView.titleLabel.font = .systemFont(ofSize: 17)
case .category:
cell.categoryView.titleLabel.font = .systemFont(ofSize: 28)
}
cell.categoryView.emojiLabel.text = item.emoji
cell.categoryView.titleLabel.text = item.title
cell.observe(\.isSelected, options: [.initial, .new]) { cell, _ in
if cell.isSelected {
cell.categoryView.bgView.backgroundColor = Asset.Colors.brandBlue.color
cell.categoryView.bgView.applyShadow(color: Asset.Colors.brandBlue.color, alpha: 1, x: 0, y: 0, blur: 4.0)
if case .all = item {
cell.categoryView.titleLabel.textColor = .white
}
} else {
cell.categoryView.bgView.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
cell.categoryView.bgView.applyShadow(color: Asset.Colors.brandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0)
if case .all = item {
cell.categoryView.titleLabel.textColor = Asset.Colors.brandBlue.color
}
}
cell.categoryView.highlightedIndicatorView.alpha = cell.isSelected ? 1 : 0
cell.categoryView.titleLabel.textColor = cell.isSelected ? Asset.Colors.Label.primary.color : Asset.Colors.Label.secondary.color
}
.store(in: &cell.observations)

View File

@ -12,8 +12,6 @@ import AlamofireImage
enum PickServerSection: Equatable, Hashable {
case header
case category
case search
case servers
}
@ -21,14 +19,10 @@ extension PickServerSection {
static func tableViewDiffableDataSource(
for tableView: UITableView,
dependency: NeedsDependency,
pickServerCategoriesCellDelegate: PickServerCategoriesCellDelegate,
pickServerSearchCellDelegate: PickServerSearchCellDelegate,
pickServerCellDelegate: PickServerCellDelegate
) -> UITableViewDiffableDataSource<PickServerSection, PickServerItem> {
UITableViewDiffableDataSource(tableView: tableView) { [
weak dependency,
weak pickServerCategoriesCellDelegate,
weak pickServerSearchCellDelegate,
weak pickServerCellDelegate
] tableView, indexPath, item -> UITableViewCell? in
guard let dependency = dependency else { return nil }
@ -36,22 +30,6 @@ extension PickServerSection {
case .header:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerTitleCell.self), for: indexPath) as! PickServerTitleCell
return cell
case .categoryPicker(let items):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCategoriesCell.self), for: indexPath) as! PickServerCategoriesCell
cell.delegate = pickServerCategoriesCellDelegate
cell.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
for: cell.collectionView,
dependency: dependency
)
var snapshot = NSDiffableDataSourceSnapshot<CategoryPickerSection, CategoryPickerItem>()
snapshot.appendSections([.main])
snapshot.appendItems(items, toSection: .main)
cell.diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
return cell
case .search:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerSearchCell.self), for: indexPath) as! PickServerSearchCell
cell.delegate = pickServerSearchCellDelegate
return cell
case .server(let server, let attribute):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
PickServerSection.configure(cell: cell, server: server, attribute: attribute)
@ -70,19 +48,63 @@ extension PickServerSection {
static func configure(cell: PickServerCell, server: Mastodon.Entity.Server, attribute: PickServerItem.ServerItemAttribute) {
cell.domainLabel.text = server.domain
cell.descriptionLabel.text = {
guard let html = try? HTML(html: server.description, encoding: .utf8) else {
return server.description
}
cell.descriptionLabel.attributedText = {
let content: String = {
guard let html = try? HTML(html: server.description, encoding: .utf8) else {
return server.description
}
return html.text ?? server.description
}()
return html.text ?? server.description
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 1.16
return NSAttributedString(
string: content,
attributes: [
.paragraphStyle: paragraphStyle
]
)
}()
cell.langValueLabel.text = server.language.uppercased()
cell.usersValueLabel.text = parseUsersCount(server.totalUsers)
cell.categoryValueLabel.text = server.category.uppercased()
cell.updateExpandMode(mode: attribute.isExpand ? .expand : .collapse)
cell.usersValueLabel.attributedText = {
let attributedString = NSMutableAttributedString()
let attachment = NSTextAttachment(image: UIImage(systemName: "person.2.fill")!)
let attachmentAttributedString = NSAttributedString(attachment: attachment)
attributedString.append(attachmentAttributedString)
attributedString.append(NSAttributedString(string: " "))
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 1.12
let valueAttributedString = NSAttributedString(
string: parseUsersCount(server.totalUsers),
attributes: [
.paragraphStyle: paragraphStyle
]
)
attributedString.append(valueAttributedString)
return attributedString
}()
cell.langValueLabel.attributedText = {
let attributedString = NSMutableAttributedString()
let attachment = NSTextAttachment(image: UIImage(systemName: "text.bubble.fill")!)
let attachmentAttributedString = NSAttributedString(attachment: attachment)
attributedString.append(attachmentAttributedString)
attributedString.append(NSAttributedString(string: " "))
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 1.12
let valueAttributedString = NSAttributedString(
string: server.language.uppercased(),
attributes: [
.paragraphStyle: paragraphStyle
]
)
attributedString.append(valueAttributedString)
return attributedString
}()
attribute.isLast
.receive(on: DispatchQueue.main)
.sink { [weak cell] isLast in
@ -101,41 +123,6 @@ extension PickServerSection {
}
}
.store(in: &cell.disposeBag)
cell.expandMode
.receive(on: DispatchQueue.main)
.sink { mode in
switch mode {
case .collapse:
// do nothing
break
case .expand:
let placeholderImage = UIImage.placeholder(size: cell.thumbnailImageView.frame.size, color: .systemFill)
.af.imageRounded(withCornerRadius: 3.0, divideRadiusByImageScale: false)
guard let proxiedThumbnail = server.proxiedThumbnail,
let url = URL(string: proxiedThumbnail) else {
cell.thumbnailImageView.image = placeholderImage
cell.thumbnailActivityIndicator.stopAnimating()
return
}
cell.thumbnailImageView.isHidden = false
cell.thumbnailActivityIndicator.startAnimating()
cell.thumbnailImageView.af.setImage(
withURL: url,
placeholderImage: placeholderImage,
filter: AspectScaledToFillSizeWithRoundedCornersFilter(size: cell.thumbnailImageView.frame.size, radius: 3),
imageTransition: .crossDissolve(0.33),
completion: { [weak cell] response in
switch response.result {
case .success, .failure:
cell?.thumbnailActivityIndicator.stopAnimating()
}
}
)
}
}
.store(in: &cell.disposeBag)
}
private static func parseUsersCount(_ usersCount: Int) -> String {

View File

@ -47,6 +47,7 @@ internal enum Asset {
}
internal enum Label {
internal static let primary = ColorAsset(name: "Colors/Label/primary")
internal static let primaryReverse = ColorAsset(name: "Colors/Label/primary.reverse")
internal static let secondary = ColorAsset(name: "Colors/Label/secondary")
internal static let tertiary = ColorAsset(name: "Colors/Label/tertiary")
}
@ -89,6 +90,14 @@ internal enum Asset {
internal static let faceSmilingAdaptive = ImageAsset(name: "Human/face.smiling.adaptive")
}
internal enum Scene {
internal enum Onboarding {
internal static let navigationBackButtonBackground = ColorAsset(name: "Scene/Onboarding/navigation.back.button.background")
internal static let navigationBackButtonBackgroundHighlighted = ColorAsset(name: "Scene/Onboarding/navigation.back.button.background.highlighted")
internal static let navigationNextButtonBackground = ColorAsset(name: "Scene/Onboarding/navigation.next.button.background")
internal static let navigationNextButtonBackgroundHighlighted = ColorAsset(name: "Scene/Onboarding/navigation.next.button.background.highlighted")
internal static let onboardingBackground = ColorAsset(name: "Scene/Onboarding/onboarding.background")
internal static let searchBarBackground = ColorAsset(name: "Scene/Onboarding/search.bar.background")
}
internal enum Profile {
internal enum Banner {
internal static let bioEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/bio.edit.background.gray")

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x00",
"red" : "0x00"
"blue" : "0.216",
"green" : "0.173",
"red" : "0.157"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
"blue" : "0xEE",
"green" : "0xEE",
"red" : "0xEE"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.933",
"green" : "0.933",
"red" : "0.933"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.216",
"green" : "0.173",
"red" : "0.157"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -22,10 +22,10 @@
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.600",
"blue" : "0xF5",
"green" : "0xEB",
"red" : "0xEB"
"alpha" : "1.000",
"blue" : "0xAD",
"green" : "0x9D",
"red" : "0x97"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

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

View File

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

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x37",
"green" : "0x2C",
"red" : "0x28"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xEE",
"green" : "0xEE",
"red" : "0xEE"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF7",
"green" : "0xF2",
"red" : "0xF2"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x21",
"green" : "0x1B",
"red" : "0x19"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.200",
"blue" : "0x80",
"green" : "0x78",
"red" : "0x78"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.240",
"blue" : "0x80",
"green" : "0x76",
"red" : "0x76"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.922",
"green" : "0.898",
"red" : "0.867"
"blue" : "0xEB",
"green" : "0xE4",
"red" : "0xDD"
}
},
"idiom" : "universal"

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.910",
"green" : "0.882",
"red" : "0.851"
"blue" : "0xE8",
"green" : "0xE0",
"red" : "0xD9"
}
},
"idiom" : "universal"

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.910",
"green" : "0.882",
"red" : "0.851"
"blue" : "0xE8",
"green" : "0xE0",
"red" : "0xD9"
}
},
"idiom" : "universal"

View File

@ -8,14 +8,10 @@
import UIKit
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
var observations = Set<NSKeyValueObservation>()
var categoryView: PickServerCategoryView = {
let view = PickServerCategoryView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var categoryView = PickServerCategoryView()
override func prepareForReuse() {
super.prepareForReuse()
@ -35,13 +31,15 @@ class PickServerCategoryCollectionViewCell: UICollectionViewCell {
extension PickServerCategoryCollectionViewCell {
private func configure() {
contentView.addSubview(categoryView)
backgroundColor = .clear
categoryView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(categoryView)
NSLayoutConstraint.activate([
categoryView.topAnchor.constraint(equalTo: contentView.topAnchor),
categoryView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
categoryView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
categoryView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor),
])
}
}

View File

@ -14,6 +14,7 @@ import AuthenticationServices
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
private var disposeBag = Set<AnyCancellable>()
private var observations = Set<NSKeyValueObservation>()
private var tableViewObservation: NSKeyValueObservation?
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
@ -31,21 +32,16 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
private let emptyStateView = PickServerEmptyStateView()
private var emptyStateViewLeadingLayoutConstraint: NSLayoutConstraint!
private var emptyStateViewTrailingLayoutConstraint: NSLayoutConstraint!
let tableViewTopPaddingView = UIView() // fix empty state view background display when tableView bounce scrolling
var tableViewTopPaddingViewHeightLayoutConstraint: NSLayoutConstraint!
let tableView: UITableView = {
let tableView = ControlContainableTableView()
tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
tableView.register(PickServerCategoriesCell.self, forCellReuseIdentifier: String(describing: PickServerCategoriesCell.self))
tableView.register(PickServerSearchCell.self, forCellReuseIdentifier: String(describing: PickServerSearchCell.self))
tableView.register(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self))
tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self))
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.keyboardDismissMode = .onDrag
tableView.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude
} else {
@ -54,14 +50,11 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
return tableView
}()
let buttonContainer = UIView()
let nextStepButton: PrimaryActionButton = {
let button = PrimaryActionButton()
button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
let navigationActionView: NavigationActionView = {
let navigationActionView = NavigationActionView()
navigationActionView.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
return navigationActionView
}()
var buttonContainerBottomLayoutConstraint: NSLayoutConstraint!
var mastodonAuthenticationController: MastodonAuthenticationController?
@ -72,16 +65,15 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
}
extension MastodonPickServerViewController {
extension MastodonPickServerViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem()
setupOnboardingAppearance()
defer { setupNavigationBarBackgroundView() }
configureTitleLabel()
configureMargin()
#if DEBUG
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: nil, action: nil)
@ -94,26 +86,36 @@ extension MastodonPickServerViewController {
navigationItem.rightBarButtonItem?.menu = UIMenu(title: "Debug Tool", image: nil, identifier: nil, options: [], children: children)
#endif
buttonContainer.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.preservesSuperviewLayoutMargins = true
view.addSubview(buttonContainer)
buttonContainerBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor, constant: 0).priority(.defaultHigh)
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
buttonContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
buttonContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
view.safeAreaLayoutGuide.bottomAnchor.constraint(greaterThanOrEqualTo: buttonContainer.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
buttonContainerBottomLayoutConstraint,
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
view.addSubview(nextStepButton)
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(navigationActionView)
defer {
view.bringSubviewToFront(navigationActionView)
}
NSLayoutConstraint.activate([
nextStepButton.topAnchor.constraint(equalTo: buttonContainer.topAnchor),
nextStepButton.leadingAnchor.constraint(equalTo: buttonContainer.layoutMarginsGuide.leadingAnchor),
buttonContainer.layoutMarginsGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor),
nextStepButton.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor),
nextStepButton.heightAnchor.constraint(equalToConstant: MastodonPickServerViewController.actionButtonHeight).priority(.defaultHigh),
navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor),
])
navigationActionView
.observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in
guard let self = self else { return }
let inset = navigationActionView.frame.height
print("*** \(inset) ***")
self.tableView.contentInset.bottom = inset
}
.store(in: &observations)
// fix AutoLayout warning when observe before view appear
viewModel.viewWillAppear
.receive(on: DispatchQueue.main)
@ -125,26 +127,7 @@ extension MastodonPickServerViewController {
}
}
.store(in: &disposeBag)
tableViewTopPaddingView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableViewTopPaddingView)
tableViewTopPaddingViewHeightLayoutConstraint = tableViewTopPaddingView.heightAnchor.constraint(equalToConstant: 0.0).priority(.defaultHigh)
NSLayoutConstraint.activate([
tableViewTopPaddingView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
tableViewTopPaddingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableViewTopPaddingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableViewTopPaddingViewHeightLayoutConstraint,
])
tableViewTopPaddingView.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
buttonContainer.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 7),
])
emptyStateView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(emptyStateView)
emptyStateViewLeadingLayoutConstraint = emptyStateView.leadingAnchor.constraint(equalTo: tableView.leadingAnchor)
@ -153,64 +136,24 @@ extension MastodonPickServerViewController {
emptyStateView.topAnchor.constraint(equalTo: view.topAnchor),
emptyStateViewLeadingLayoutConstraint,
emptyStateViewTrailingLayoutConstraint,
buttonContainer.topAnchor.constraint(equalTo: emptyStateView.bottomAnchor, constant: 21),
navigationActionView.topAnchor.constraint(equalTo: emptyStateView.bottomAnchor, constant: 21),
])
view.sendSubviewToBack(emptyStateView)
// update layout when keyboard show/dismiss
let keyboardEventPublishers = Publishers.CombineLatest3(
KeyboardResponderService.shared.isShow,
KeyboardResponderService.shared.state,
KeyboardResponderService.shared.endFrame
)
keyboardEventPublishers
.sink { [weak self] keyboardEvents in
guard let self = self else { return }
let (isShow, state, endFrame) = keyboardEvents
// guard external keyboard connected
guard isShow, state == .dock, GCKeyboard.coalesced != nil else {
self.buttonContainerBottomLayoutConstraint.constant = WelcomeViewController.viewBottomPaddingHeight
return
}
let externalKeyboardToolbarHeight = self.view.frame.maxY - endFrame.minY
guard externalKeyboardToolbarHeight > 0 else {
self.buttonContainerBottomLayoutConstraint.constant = WelcomeViewController.viewBottomPaddingHeight
return
}
UIView.animate(withDuration: 0.3) {
self.buttonContainerBottomLayoutConstraint.constant = externalKeyboardToolbarHeight + 16
self.view.layoutIfNeeded()
}
}
.store(in: &disposeBag)
switch viewModel.mode {
case .signIn:
nextStepButton.setTitle(L10n.Common.Controls.Actions.signIn, for: .normal)
case .signUp:
nextStepButton.setTitle(L10n.Common.Controls.Actions.continue, for: .normal)
}
nextStepButton.addTarget(self, action: #selector(nextStepButtonDidClicked(_:)), for: .touchUpInside)
tableView.delegate = self
viewModel.setupDiffableDataSource(
for: tableView,
dependency: self,
pickServerCategoriesCellDelegate: self,
pickServerSearchCellDelegate: self,
pickServerServerSectionTableHeaderViewDelegate: self,
pickServerCellDelegate: self
)
viewModel
.selectedServer
.map { $0 != nil }
.assign(to: \.isEnabled, on: nextStepButton)
.assign(to: \.isEnabled, on: navigationActionView.nextButton)
.store(in: &disposeBag)
Publishers.Merge(
viewModel.error,
authenticationViewModel.error
@ -229,7 +172,7 @@ extension MastodonPickServerViewController {
)
}
.store(in: &disposeBag)
authenticationViewModel
.authenticated
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
@ -249,17 +192,17 @@ extension MastodonPickServerViewController {
}
}
.store(in: &disposeBag)
authenticationViewModel.isAuthenticating
.receive(on: DispatchQueue.main)
.sink { [weak self] isAuthenticating in
guard let self = self else { return }
isAuthenticating ? self.nextStepButton.showLoading() : self.nextStepButton.stopLoading()
isAuthenticating ? self.navigationActionView.nextButton.showLoading() : self.navigationActionView.nextButton.stopLoading()
}
.store(in: &disposeBag)
viewModel.emptyStateViewState
.receive(on: RunLoop.main)
.receive(on: DispatchQueue.main)
.sink { [weak self] state in
guard let self = self else { return }
switch state {
@ -284,6 +227,9 @@ extension MastodonPickServerViewController {
}
}
.store(in: &disposeBag)
navigationActionView.backButton.addTarget(self, action: #selector(MastodonPickServerViewController.backButtonDidPressed(_:)), for: .touchUpInside)
navigationActionView.nextButton.addTarget(self, action: #selector(MastodonPickServerViewController.nextButtonDidPressed(_:)), for: .touchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
@ -296,38 +242,20 @@ extension MastodonPickServerViewController {
setupNavigationBarAppearance()
updateEmptyStateViewLayout()
configureTitleLabel()
configureMargin()
}
}
extension MastodonPickServerViewController {
private func configureTitleLabel() {
guard UIDevice.current.userInterfaceIdiom == .pad else {
return
}
switch traitCollection.horizontalSizeClass {
case .regular:
navigationItem.largeTitleDisplayMode = .always
navigationItem.title = L10n.Scene.ServerPicker.title.replacingOccurrences(of: "\n", with: " ")
default:
navigationItem.largeTitleDisplayMode = .never
navigationItem.title = nil
}
}
}
extension MastodonPickServerViewController {
@objc
private func nextStepButtonDidClicked(_ sender: UIButton) {
@objc private func backButtonDidPressed(_ sender: UIButton) {
navigationController?.popViewController(animated: true)
}
@objc private func nextButtonDidPressed(_ sender: UIButton) {
switch viewModel.mode {
case .signIn:
doSignIn()
case .signUp:
doSignUp()
case .signIn: doSignIn()
case .signUp: doSignUp()
}
}
@ -458,16 +386,6 @@ extension MastodonPickServerViewController {
// MARK: - UITableViewDelegate
extension MastodonPickServerViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView === tableView else { return }
let offsetY = scrollView.contentOffset.y + scrollView.safeAreaInsets.top
if offsetY < 0 {
tableViewTopPaddingViewHeightLayoutConstraint.constant = abs(offsetY)
} else {
tableViewTopPaddingViewHeightLayoutConstraint.constant = 0
}
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil }
@ -500,87 +418,89 @@ extension MastodonPickServerViewController: UITableViewDelegate {
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
switch item {
case .categoryPicker:
guard let cell = cell as? PickServerCategoriesCell else { return }
guard let diffableDataSource = cell.diffableDataSource else { return }
let snapshot = diffableDataSource.snapshot()
let item = viewModel.selectCategoryItem.value
guard let section = snapshot.indexOfSection(.main),
let row = snapshot.indexOfItem(item) else { return }
cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally)
case .search:
guard let cell = cell as? PickServerSearchCell else { return }
cell.searchTextField.text = viewModel.searchText.value
// case .categoryPicker:
// guard let cell = cell as? PickServerCategoriesCell else { return }
// guard let diffableDataSource = cell.diffableDataSource else { return }
// let snapshot = diffableDataSource.snapshot()
//
// let item = viewModel.selectCategoryItem.value
// guard let section = snapshot.indexOfSection(.main),
// let row = snapshot.indexOfItem(item) else { return }
// cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally)
// case .search:
// guard let cell = cell as? PickServerSearchCell else { return }
// cell.searchTextField.text = viewModel.searchText.value
default:
break
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
let snapshot = diffableDataSource.snapshot()
guard section < snapshot.numberOfSections else { return nil }
let section = snapshot.sectionIdentifiers[section]
switch section {
case .servers:
return viewModel.serverSectionHeaderView
default:
return UIView()
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard let diffableDataSource = viewModel.diffableDataSource else { return .leastNonzeroMagnitude }
let snapshot = diffableDataSource.snapshot()
guard section < snapshot.numberOfSections else { return .leastNonzeroMagnitude }
let section = snapshot.sectionIdentifiers[section]
switch section {
case .servers:
return PickServerServerSectionTableHeaderView.height
default:
return .leastNonzeroMagnitude
}
}
}
extension MastodonPickServerViewController {
private func updateEmptyStateViewLayout() {
guard let diffableDataSource = self.viewModel.diffableDataSource else { return }
guard let indexPath = diffableDataSource.indexPath(for: .search) else { return }
let rectInTableView = tableView.rectForRow(at: indexPath)
emptyStateView.topPaddingViewTopLayoutConstraint.constant = rectInTableView.maxY
switch traitCollection.horizontalSizeClass {
case .regular:
emptyStateViewLeadingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
emptyStateViewTrailingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
default:
let margin = tableView.layoutMarginsGuide.layoutFrame.origin.x
emptyStateViewLeadingLayoutConstraint.constant = margin
emptyStateViewTrailingLayoutConstraint.constant = margin
}
}
private func configureMargin() {
switch traitCollection.horizontalSizeClass {
case .regular:
let margin = MastodonPickServerViewController.viewEdgeMargin
buttonContainer.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
default:
buttonContainer.layoutMargins = .zero
}
// guard let diffableDataSource = self.viewModel.diffableDataSource else { return }
// guard let indexPath = diffableDataSource.indexPath(for: .search) else { return }
// let rectInTableView = tableView.rectForRow(at: indexPath)
//
// emptyStateView.topPaddingViewTopLayoutConstraint.constant = rectInTableView.maxY
//
// switch traitCollection.horizontalSizeClass {
// case .regular:
// emptyStateViewLeadingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
// emptyStateViewTrailingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
// default:
// let margin = tableView.layoutMarginsGuide.layoutFrame.origin.x
// emptyStateViewLeadingLayoutConstraint.constant = margin
// emptyStateViewTrailingLayoutConstraint.constant = margin
// }
}
}
// MARK: - PickServerCategoriesCellDelegate
extension MastodonPickServerViewController: PickServerCategoriesCellDelegate {
func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let diffableDataSource = cell.diffableDataSource else { return }
// MARK: - PickServerServerSectionTableHeaderViewDelegate
extension MastodonPickServerViewController: PickServerServerSectionTableHeaderViewDelegate {
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let diffableDataSource = headerView.diffableDataSource else { return }
let item = diffableDataSource.itemIdentifier(for: indexPath)
viewModel.selectCategoryItem.value = item ?? .all
}
}
// MARK: - PickServerSearchCellDelegate
extension MastodonPickServerViewController: PickServerSearchCellDelegate {
func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?) {
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, searchTextDidChange searchText: String?) {
viewModel.searchText.send(searchText ?? "")
}
}
// MARK: - PickServerCellDelegate
extension MastodonPickServerViewController: PickServerCellDelegate {
func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard let indexPath = tableView.indexPath(for: cell) else { return }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
guard case let .server(_, attribute) = item else { return }
attribute.isExpand.toggle()
tableView.beginUpdates()
cell.updateExpandMode(mode: attribute.isExpand ? .expand : .collapse)
tableView.endUpdates()
// expand attribute change do not needs apply snapshot to diffable data source
// but should I block the viewModel data binding during tableView.beginUpdates/endUpdates?
}
}
// MARK: - OnboardingViewControllerAppearance

View File

@ -6,32 +6,101 @@
//
import UIKit
import Combine
extension MastodonPickServerViewModel {
func setupDiffableDataSource(
for tableView: UITableView,
dependency: NeedsDependency,
pickServerCategoriesCellDelegate: PickServerCategoriesCellDelegate,
pickServerSearchCellDelegate: PickServerSearchCellDelegate,
pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate,
pickServerCellDelegate: PickServerCellDelegate
) {
// set section header
serverSectionHeaderView.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
for: serverSectionHeaderView.collectionView,
dependency: dependency
)
var sectionHeaderSnapshot = NSDiffableDataSourceSnapshot<CategoryPickerSection, CategoryPickerItem>()
sectionHeaderSnapshot.appendSections([.main])
sectionHeaderSnapshot.appendItems(categoryPickerItems, toSection: .main)
serverSectionHeaderView.delegate = pickServerServerSectionTableHeaderViewDelegate
serverSectionHeaderView.diffableDataSource?.applySnapshot(sectionHeaderSnapshot, animated: false)
// set tableView
diffableDataSource = PickServerSection.tableViewDiffableDataSource(
for: tableView,
dependency: dependency,
pickServerCategoriesCellDelegate: pickServerCategoriesCellDelegate,
pickServerSearchCellDelegate: pickServerSearchCellDelegate,
pickServerCellDelegate: pickServerCellDelegate
)
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
snapshot.appendSections([.header, .category, .search, .servers])
snapshot.appendSections([.header, .servers])
snapshot.appendItems([.header], toSection: .header)
snapshot.appendItems([.categoryPicker(items: categoryPickerItems)], toSection: .category)
snapshot.appendItems([.search], toSection: .search)
diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
loadIndexedServerStateMachine.enter(LoadIndexedServerState.Loading.self)
Publishers.CombineLatest(
filteredIndexedServers,
unindexedServers
)
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] indexedServers, unindexedServers in
guard let self = self else { return }
guard let diffableDataSource = self.diffableDataSource else { return }
let oldSnapshot = diffableDataSource.snapshot()
var oldSnapshotServerItemAttributeDict: [String : PickServerItem.ServerItemAttribute] = [:]
for item in oldSnapshot.itemIdentifiers {
guard case let .server(server, attribute) = item else { continue }
oldSnapshotServerItemAttributeDict[server.domain] = attribute
}
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
snapshot.appendSections([.header, .servers])
snapshot.appendItems([.header], toSection: .header)
// TODO: handle filter
var serverItems: [PickServerItem] = []
for server in indexedServers {
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
attribute.isLast.value = false
let item = PickServerItem.server(server: server, attribute: attribute)
guard !serverItems.contains(item) else { continue }
serverItems.append(item)
}
if let unindexedServers = unindexedServers {
if !unindexedServers.isEmpty {
for server in unindexedServers {
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
attribute.isLast.value = false
let item = PickServerItem.server(server: server, attribute: attribute)
guard !serverItems.contains(item) else { continue }
serverItems.append(item)
}
} else {
if indexedServers.isEmpty && !self.isLoadingIndexedServers.value {
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: true)))
}
}
} else {
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: false)))
}
if case let .server(_, attribute) = serverItems.last {
attribute.isLast.value = true
}
if case let .loader(attribute) = serverItems.last {
attribute.isLast = true
}
snapshot.appendItems(serverItems, toSection: .servers)
diffableDataSource.defaultRowAnimation = .fade
diffableDataSource.apply(snapshot, animatingDifferences: true, completion: nil)
})
.store(in: &disposeBag)
}
}

View File

@ -12,6 +12,7 @@ import GameplayKit
import MastodonSDK
import CoreDataStack
import OrderedCollections
import Tabman
class MastodonPickServerViewModel: NSObject {
@ -27,6 +28,8 @@ class MastodonPickServerViewModel: NSObject {
}
var disposeBag = Set<AnyCancellable>()
let serverSectionHeaderView = PickServerServerSectionTableHeaderView()
// input
let mode: PickServerMode
@ -82,68 +85,6 @@ class MastodonPickServerViewModel: NSObject {
extension MastodonPickServerViewModel {
private func configure() {
Publishers.CombineLatest(
filteredIndexedServers,
unindexedServers
)
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] indexedServers, unindexedServers in
guard let self = self else { return }
guard let diffableDataSource = self.diffableDataSource else { return }
let oldSnapshot = diffableDataSource.snapshot()
var oldSnapshotServerItemAttributeDict: [String : PickServerItem.ServerItemAttribute] = [:]
for item in oldSnapshot.itemIdentifiers {
guard case let .server(server, attribute) = item else { continue }
oldSnapshotServerItemAttributeDict[server.domain] = attribute
}
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
snapshot.appendSections([.header, .category, .search, .servers])
snapshot.appendItems([.header], toSection: .header)
snapshot.appendItems([.categoryPicker(items: self.categoryPickerItems)], toSection: .category)
snapshot.appendItems([.search], toSection: .search)
// TODO: handle filter
var serverItems: [PickServerItem] = []
for server in indexedServers {
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
attribute.isLast.value = false
let item = PickServerItem.server(server: server, attribute: attribute)
guard !serverItems.contains(item) else { continue }
serverItems.append(item)
}
if let unindexedServers = unindexedServers {
if !unindexedServers.isEmpty {
for server in unindexedServers {
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
attribute.isLast.value = false
let item = PickServerItem.server(server: server, attribute: attribute)
guard !serverItems.contains(item) else { continue }
serverItems.append(item)
}
} else {
if indexedServers.isEmpty && !self.isLoadingIndexedServers.value {
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: true)))
}
}
} else {
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: false)))
}
if case let .server(_, attribute) = serverItems.last {
attribute.isLast.value = true
}
if case let .loader(attribute) = serverItems.last {
attribute.isLast = true
}
snapshot.appendItems(serverItems, toSection: .servers)
diffableDataSource.defaultRowAnimation = .fade
diffableDataSource.apply(snapshot, animatingDifferences: true, completion: nil)
})
.store(in: &disposeBag)
Publishers.CombineLatest(
isLoadingIndexedServers,
loadingIndexedServersError
@ -301,3 +242,12 @@ extension MastodonPickServerViewModel {
let applicationToken: Mastodon.Response.Content<Mastodon.Entity.Token>
}
}
// MARK: - TMBarDataSource
extension MastodonPickServerViewModel: TMBarDataSource {
func barItem(for bar: TMBar, at index: Int) -> TMBarItemable {
let item = categoryPickerItems[index]
let barItem = TMBarItem(title: item.title)
return barItem
}
}

View File

@ -1,145 +0,0 @@
//
// PickServerCategoriesCell.swift
// Mastodon
//
// Created by BradGao on 2021/2/23.
//
import os.log
import UIKit
import MastodonSDK
protocol PickServerCategoriesCellDelegate: AnyObject {
func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
}
final class PickServerCategoriesCell: UITableViewCell {
weak var delegate: PickServerCategoriesCellDelegate?
var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
let metricView = UIView()
let collectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
view.backgroundColor = .clear
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = false
view.layer.masksToBounds = false
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func prepareForReuse() {
super.prepareForReuse()
delegate = nil
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension PickServerCategoriesCell {
private func _init() {
selectionStyle = .none
backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
configureMargin()
metricView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(metricView)
NSLayoutConstraint.activate([
metricView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
metricView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
metricView.topAnchor.constraint(equalTo: contentView.topAnchor),
metricView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
metricView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
])
contentView.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20),
collectionView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
])
collectionView.delegate = self
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
configureMargin()
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
}
extension PickServerCategoriesCell {
private func configureMargin() {
switch traitCollection.horizontalSizeClass {
case .regular:
let margin = MastodonPickServerViewController.viewEdgeMargin
contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
default:
contentView.layoutMargins = .zero
}
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
delegate?.pickServerCategoriesCell(self, collectionView: collectionView, didSelectItemAt: indexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
layoutIfNeeded()
return UIEdgeInsets(top: 0, left: metricView.frame.minX - collectionView.frame.minX, bottom: 0, right: collectionView.frame.maxX - metricView.frame.maxX)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 16
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 60, height: 80)
}
}
extension PickServerCategoriesCell {
override func accessibilityElementCount() -> Int {
guard let diffableDataSource = diffableDataSource else { return 0 }
return diffableDataSource.snapshot().itemIdentifiers.count
}
override func accessibilityElement(at index: Int) -> Any? {
guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
return item
}
}

View File

@ -13,7 +13,7 @@ import AlamofireImage
import Kanna
protocol PickServerCellDelegate: AnyObject {
func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
// func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
}
class PickServerCell: UITableViewCell {
@ -21,20 +21,17 @@ class PickServerCell: UITableViewCell {
weak var delegate: PickServerCellDelegate?
var disposeBag = Set<AnyCancellable>()
let expandMode = CurrentValueSubject<ExpandMode, Never>(.collapse)
let containerView: UIView = {
let view = UIView()
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
view.translatesAutoresizingMaskIntoConstraints = false
let containerView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 4
return view
}()
let domainLabel: UILabel = {
let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
label.textColor = Asset.Colors.Label.primary.color
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
@ -52,7 +49,7 @@ class PickServerCell: UITableViewCell {
let descriptionLabel: UILabel = {
let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 13, weight: .regular))
label.numberOfLines = 0
label.textColor = Asset.Colors.Label.primary.color
label.adjustsFontForContentSizeCategory = true
@ -60,112 +57,33 @@ class PickServerCell: UITableViewCell {
return label
}()
let thumbnailActivityIndicator = UIActivityIndicatorView(style: .medium)
let thumbnailImageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let infoStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 16
return stackView
}()
let expandBox: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let expandButton: UIButton = {
let button = HitTestExpandedButton(type: .custom)
button.setImage(UIImage(systemName: "chevron.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13)), for: .normal)
button.setTitle(L10n.Scene.ServerPicker.Button.seeMore, for: .normal)
button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 13, weight: .regular)
button.translatesAutoresizingMaskIntoConstraints = false
button.imageView?.transform = CGAffineTransform(scaleX: -1, y: 1)
button.titleLabel?.transform = CGAffineTransform(scaleX: -1, y: 1)
button.transform = CGAffineTransform(scaleX: -1, y: 1)
return button
}()
let separator: UIView = {
let view = UIView()
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = Asset.Theme.System.separator.color
return view
}()
let langValueLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .semibold), maximumPointSize: 27)
label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 12, weight: .regular))
label.textAlignment = .center
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let usersValueLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .semibold), maximumPointSize: 27)
label.textAlignment = .center
label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 12, weight: .regular))
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let categoryValueLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .semibold), maximumPointSize: 27)
label.textAlignment = .center
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let langTitleLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 11, weight: .regular), maximumPointSize: 16)
label.text = L10n.Scene.ServerPicker.Label.language
label.textAlignment = .center
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let usersTitleLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 11, weight: .regular), maximumPointSize: 16)
label.text = L10n.Scene.ServerPicker.Label.users
label.textAlignment = .center
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let categoryTitleLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.primary.color
label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 11, weight: .regular), maximumPointSize: 16)
label.text = L10n.Scene.ServerPicker.Label.category
label.textAlignment = .center
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
@ -175,9 +93,6 @@ class PickServerCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
thumbnailImageView.isHidden = false
thumbnailImageView.af.cancelImageRequest()
thumbnailActivityIndicator.stopAnimating()
disposeBag.removeAll()
}
@ -197,172 +112,55 @@ class PickServerCell: UITableViewCell {
extension PickServerCell {
private func _init() {
selectionStyle = .none
backgroundColor = .clear
configureMargin()
backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
checkbox.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(checkbox)
NSLayoutConstraint.activate([
checkbox.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor, constant: 1),
checkbox.heightAnchor.constraint(equalToConstant: 32).priority(.required - 1),
checkbox.widthAnchor.constraint(equalToConstant: 32).priority(.required - 1),
])
containerView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerView)
containerView.addSubview(domainLabel)
containerView.addSubview(checkbox)
containerView.addSubview(descriptionLabel)
containerView.addSubview(separator)
containerView.addSubview(expandButton)
// Always add the expandbox which contains elements only visible in expand mode
containerView.addSubview(expandBox)
expandBox.addSubview(thumbnailImageView)
expandBox.addSubview(infoStackView)
expandBox.isHidden = true
let verticalInfoStackViewLang = makeVerticalInfoStackView(arrangedView: langValueLabel, langTitleLabel)
let verticalInfoStackViewUsers = makeVerticalInfoStackView(arrangedView: usersValueLabel, usersTitleLabel)
let verticalInfoStackViewCategory = makeVerticalInfoStackView(arrangedView: categoryValueLabel, categoryTitleLabel)
infoStackView.addArrangedSubview(verticalInfoStackViewLang)
infoStackView.addArrangedSubview(verticalInfoStackViewUsers)
infoStackView.addArrangedSubview(verticalInfoStackViewCategory)
let expandButtonTopConstraintInCollapse = expandButton.topAnchor.constraint(equalTo: descriptionLabel.lastBaselineAnchor, constant: 12).priority(.required - 1)
collapseConstraints.append(expandButtonTopConstraintInCollapse)
let expandButtonTopConstraintInExpand = expandButton.topAnchor.constraint(equalTo: expandBox.bottomAnchor, constant: 8).priority(.defaultHigh)
expandConstraints.append(expandButtonTopConstraintInExpand)
NSLayoutConstraint.activate([
// Set background view
containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
// Set bottom separator
separator.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: separator.trailingAnchor),
containerView.topAnchor.constraint(equalTo: separator.topAnchor),
separator.heightAnchor.constraint(equalToConstant: 1).priority(.defaultHigh),
domainLabel.topAnchor.constraint(equalTo: containerView.layoutMarginsGuide.topAnchor),
domainLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
checkbox.widthAnchor.constraint(equalToConstant: 23),
checkbox.heightAnchor.constraint(equalToConstant: 22),
containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: checkbox.trailingAnchor),
checkbox.leadingAnchor.constraint(equalTo: domainLabel.trailingAnchor, constant: 16),
checkbox.centerYAnchor.constraint(equalTo: domainLabel.centerYAnchor),
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
descriptionLabel.topAnchor.constraint(equalTo: domainLabel.bottomAnchor, constant: 8),
containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor),
// Set expandBox constraints
expandBox.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: expandBox.trailingAnchor),
expandBox.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 8),
expandBox.bottomAnchor.constraint(equalTo: infoStackView.bottomAnchor).priority(.defaultHigh),
thumbnailImageView.topAnchor.constraint(equalTo: expandBox.topAnchor),
thumbnailImageView.leadingAnchor.constraint(equalTo: expandBox.leadingAnchor),
expandBox.trailingAnchor.constraint(equalTo: thumbnailImageView.trailingAnchor),
thumbnailImageView.heightAnchor.constraint(equalTo: thumbnailImageView.widthAnchor, multiplier: 151.0 / 303.0).priority(.defaultHigh),
infoStackView.leadingAnchor.constraint(equalTo: expandBox.leadingAnchor),
expandBox.trailingAnchor.constraint(equalTo: infoStackView.trailingAnchor),
infoStackView.topAnchor.constraint(equalTo: thumbnailImageView.bottomAnchor, constant: 16),
expandButton.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
containerView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: expandButton.trailingAnchor),
containerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: expandButton.bottomAnchor),
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11),
containerView.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 22),
containerView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 11),
checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
])
thumbnailActivityIndicator.translatesAutoresizingMaskIntoConstraints = false
thumbnailImageView.addSubview(thumbnailActivityIndicator)
containerView.addArrangedSubview(domainLabel)
containerView.addArrangedSubview(descriptionLabel)
containerView.setCustomSpacing(6, after: descriptionLabel)
containerView.addArrangedSubview(infoStackView)
infoStackView.addArrangedSubview(usersValueLabel)
infoStackView.addArrangedSubview(langValueLabel)
infoStackView.addArrangedSubview(UIView())
separator.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(separator)
NSLayoutConstraint.activate([
thumbnailActivityIndicator.centerXAnchor.constraint(equalTo: thumbnailImageView.centerXAnchor),
thumbnailActivityIndicator.centerYAnchor.constraint(equalTo: thumbnailImageView.centerYAnchor),
separator.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: separator.trailingAnchor),
separator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
separator.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)).priority(.required - 1),
])
thumbnailActivityIndicator.hidesWhenStopped = true
thumbnailActivityIndicator.stopAnimating()
NSLayoutConstraint.activate(collapseConstraints)
domainLabel.setContentHuggingPriority(.required - 1, for: .vertical)
domainLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
descriptionLabel.setContentHuggingPriority(.required - 2, for: .vertical)
descriptionLabel.setContentCompressionResistancePriority(.required - 2, for: .vertical)
expandButton.addTarget(self, action: #selector(expandButtonDidPressed(_:)), for: .touchUpInside)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
configureMargin()
}
private func makeVerticalInfoStackView(arrangedView: UIView...) -> UIStackView {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalCentering
stackView.spacing = 2
arrangedView.forEach { stackView.addArrangedSubview($0) }
return stackView
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
checkbox.image = UIImage(systemName: "checkmark.circle.fill")
checkbox.tintColor = Asset.Colors.Label.primary.color
} else {
checkbox.image = UIImage(systemName: "circle")
checkbox.tintColor = Asset.Colors.Label.secondary.color
}
}
@objc
private func expandButtonDidPressed(_ sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
delegate?.pickServerCell(self, expandButtonPressed: sender)
}
}
extension PickServerCell {
private func configureMargin() {
switch traitCollection.horizontalSizeClass {
case .regular:
let margin = MastodonPickServerViewController.viewEdgeMargin
contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
default:
contentView.layoutMargins = .zero
}
}
}
extension PickServerCell {
enum ExpandMode {
case collapse
case expand
}
func updateExpandMode(mode: ExpandMode) {
switch mode {
case .collapse:
expandButton.setImage(UIImage(systemName: "chevron.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13)), for: .normal)
expandButton.setTitle(L10n.Scene.ServerPicker.Button.seeMore, for: .normal)
expandBox.isHidden = true
expandButton.isSelected = false
NSLayoutConstraint.deactivate(expandConstraints)
NSLayoutConstraint.activate(collapseConstraints)
case .expand:
expandButton.setImage(UIImage(systemName: "chevron.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13)), for: .normal)
expandButton.setTitle(L10n.Scene.ServerPicker.Button.seeLess, for: .normal)
expandBox.isHidden = false
expandButton.isSelected = true
NSLayoutConstraint.activate(expandConstraints)
NSLayoutConstraint.deactivate(collapseConstraints)
}
expandMode.value = mode
}
}

View File

@ -13,15 +13,7 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
let containerView: UIView = {
let view = UIView()
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let seperator: UIView = {
let view = UIView()
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
return view
}()
@ -30,30 +22,22 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
label.text = L10n.Scene.ServerPicker.EmptyState.noResults
label.textColor = Asset.Colors.Label.secondary.color
label.textAlignment = .center
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 14, weight: .semibold), maximumPointSize: 19)
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 14, weight: .semibold))
return label
}()
override func _init() {
super._init()
configureMargin()
contentView.addSubview(containerView)
contentView.addSubview(seperator)
// Set background view
containerView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerView)
NSLayoutConstraint.activate([
// Set background view
containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 1),
// Set bottom separator
seperator.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: seperator.trailingAnchor),
containerView.topAnchor.constraint(equalTo: seperator.topAnchor),
seperator.heightAnchor.constraint(equalToConstant: 1).priority(.defaultHigh),
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
emptyStatusLabel.translatesAutoresizingMaskIntoConstraints = false
@ -69,24 +53,7 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
activityIndicatorView.isHidden = false
startAnimating()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
configureMargin()
}
}
extension PickServerLoaderTableViewCell {
private func configureMargin() {
switch traitCollection.horizontalSizeClass {
case .regular:
let margin = MastodonPickServerViewController.viewEdgeMargin
contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
default:
contentView.layoutMargins = .zero
}
}
}
#if canImport(SwiftUI) && DEBUG

View File

@ -1,171 +0,0 @@
//
// PickServerSearchCell.swift
// Mastodon
//
// Created by BradGao on 2021/2/24.
//
import UIKit
protocol PickServerSearchCellDelegate: AnyObject {
func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?)
}
class PickServerSearchCell: UITableViewCell {
weak var delegate: PickServerSearchCellDelegate?
private var bgView: UIView = {
let view = UIView()
view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.maskedCorners = [
.layerMinXMinYCorner,
.layerMaxXMinYCorner
]
view.layer.cornerCurve = .continuous
view.layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
return view
}()
private var textFieldBgView: UIView = {
let view = UIView()
view.backgroundColor = Asset.Colors.TextField.background.color
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.masksToBounds = true
view.layer.cornerRadius = 6
view.layer.cornerCurve = .continuous
return view
}()
let searchTextField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
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.textColor = Asset.Colors.Label.primary.color
textField.adjustsFontForContentSizeCategory = true
textField.attributedPlaceholder =
NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder,
attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
.foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
textField.clearButtonMode = .whileEditing
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.returnKeyType = .done
textField.keyboardType = .URL
return textField
}()
override func prepareForReuse() {
super.prepareForReuse()
delegate = nil
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension PickServerSearchCell {
private func _init() {
selectionStyle = .none
backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
configureMargin()
searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
searchTextField.delegate = self
contentView.addSubview(bgView)
contentView.addSubview(textFieldBgView)
contentView.addSubview(searchTextField)
NSLayoutConstraint.activate([
bgView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
bgView.topAnchor.constraint(equalTo: contentView.topAnchor),
bgView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
bgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
textFieldBgView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 14),
textFieldBgView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 12),
bgView.trailingAnchor.constraint(equalTo: textFieldBgView.trailingAnchor, constant: 14),
bgView.bottomAnchor.constraint(equalTo: textFieldBgView.bottomAnchor, constant: 13),
searchTextField.leadingAnchor.constraint(equalTo: textFieldBgView.leadingAnchor, constant: 11),
searchTextField.topAnchor.constraint(equalTo: textFieldBgView.topAnchor, constant: 4),
textFieldBgView.trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 11),
textFieldBgView.bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 4),
])
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
configureMargin()
}
}
extension PickServerSearchCell {
private func configureMargin() {
switch traitCollection.horizontalSizeClass {
case .regular:
let margin = MastodonPickServerViewController.viewEdgeMargin
contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
default:
contentView.layoutMargins = .zero
}
}
}
extension PickServerSearchCell {
@objc private func textFieldDidChange(_ textField: UITextField) {
delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
}
}
// MARK: - UITextFieldDelegate
extension PickServerSearchCell: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
}

View File

@ -11,17 +11,24 @@ final class PickServerTitleCell: UITableViewCell {
let titleLabel: UILabel = {
let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 34, weight: .bold))
label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 28, weight: .bold))
label.textColor = Asset.Colors.Label.primary.color
label.text = L10n.Scene.ServerPicker.title
label.adjustsFontForContentSizeCategory = true
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
var containerHeightLayoutConstraint: NSLayoutConstraint!
let subTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
label.textColor = Asset.Colors.Label.secondary.color
label.text = "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual."
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
@ -37,46 +44,22 @@ extension PickServerTitleCell {
private func _init() {
selectionStyle = .none
backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
let container = UIStackView()
container.axis = .vertical
container.spacing = 16
container.translatesAutoresizingMaskIntoConstraints = false
containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: .leastNonzeroMagnitude)
contentView.addSubview(container)
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: contentView.topAnchor),
container.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
container.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
container.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
contentView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 11),
])
container.addArrangedSubview(titleLabel)
configureTitleLabelDisplay()
container.addArrangedSubview(subTitleLabel)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
configureTitleLabelDisplay()
}
}
extension PickServerTitleCell {
private func configureTitleLabelDisplay() {
guard traitCollection.userInterfaceIdiom == .pad else {
titleLabel.isHidden = false
return
}
switch traitCollection.horizontalSizeClass {
case .regular:
titleLabel.isHidden = true
containerHeightLayoutConstraint.isActive = true
default:
titleLabel.isHidden = false
containerHeightLayoutConstraint.isActive = false
}
}
}

View File

@ -10,24 +10,24 @@ import MastodonSDK
class PickServerCategoryView: UIView {
var bgShadowView: UIView = {
let highlightedIndicatorView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = Asset.Colors.Label.primary.color
return view
}()
var bgView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.masksToBounds = true
view.layer.cornerRadius = 30
return view
}()
var titleLabel: UILabel = {
let emojiLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 34, weight: .regular)
return label
}()
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.font = .systemFont(ofSize: 17, weight: .semibold)
label.textColor = Asset.Colors.Label.secondary.color
return label
}()
@ -45,20 +45,27 @@ class PickServerCategoryView: UIView {
extension PickServerCategoryView {
private func configure() {
addSubview(bgView)
addSubview(titleLabel)
bgView.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
let container = UIStackView()
container.axis = .vertical
container.distribution = .fillProportionally
container.translatesAutoresizingMaskIntoConstraints = false
addSubview(container)
NSLayoutConstraint.activate([
bgView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
bgView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
bgView.topAnchor.constraint(equalTo: self.topAnchor),
bgView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
container.topAnchor.constraint(equalTo: topAnchor),
container.leadingAnchor.constraint(equalTo: leadingAnchor),
container.trailingAnchor.constraint(equalTo: trailingAnchor),
container.bottomAnchor.constraint(equalTo: bottomAnchor),
])
container.addArrangedSubview(emojiLabel)
container.addArrangedSubview(titleLabel)
highlightedIndicatorView.translatesAutoresizingMaskIntoConstraints = false
container.addArrangedSubview(highlightedIndicatorView)
NSLayoutConstraint.activate([
highlightedIndicatorView.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self) * 3).priority(.required - 1),
])
titleLabel.setContentHuggingPriority(.required - 1, for: .vertical)
}
}

View File

@ -44,13 +44,7 @@ final class PickServerEmptyStateView: UIView {
extension PickServerEmptyStateView {
private func _init() {
backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
layer.maskedCorners = [
.layerMinXMaxYCorner,
.layerMaxXMaxYCorner
]
layer.cornerCurve = .continuous
layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
backgroundColor = .clear
let topPaddingView = UIView()
topPaddingView.translatesAutoresizingMaskIntoConstraints = false
@ -101,7 +95,7 @@ extension PickServerEmptyStateView {
])
NSLayoutConstraint.activate([
bottomPaddingView.heightAnchor.constraint(equalTo: topPaddingView.heightAnchor, multiplier: 1.0).priority(.defaultHigh),
topPaddingView.heightAnchor.constraint(equalTo: bottomPaddingView.heightAnchor, multiplier: 2.5).priority(.defaultHigh), // magic scale
])
activityIndicatorView.hidesWhenStopped = true

View File

@ -0,0 +1,204 @@
//
// PickServerServerSectionTableHeaderView.swift
// Mastodon
//
// Created by MainasuK on 2022-1-4.
//
import os.log
import UIKit
import Tabman
protocol PickServerServerSectionTableHeaderViewDelegate: AnyObject {
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, searchTextDidChange searchText: String?)
}
final class PickServerServerSectionTableHeaderView: UIView {
static let collectionViewHeight: CGFloat = 88
static let searchTextFieldHeight: CGFloat = 38
static let spacing: CGFloat = 11
static let height: CGFloat = collectionViewHeight + spacing + searchTextFieldHeight + spacing
weak var delegate: PickServerServerSectionTableHeaderViewDelegate?
var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
static func createCollectionViewLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(88), heightDimension: .absolute(PickServerServerSectionTableHeaderView.collectionViewHeight))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: itemSize.widthDimension, heightDimension: itemSize.heightDimension)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.contentInsetsReference = .readableContent
section.interGroupSpacing = 16
return UICollectionViewCompositionalLayout(section: section)
}
let collectionView: UICollectionView = {
let collectionViewLayout = PickServerServerSectionTableHeaderView.createCollectionViewLayout()
let view = ControlContainableCollectionView(
frame: CGRect(origin: .zero, size: CGSize(width: 100, height: PickServerServerSectionTableHeaderView.collectionViewHeight)),
collectionViewLayout: collectionViewLayout
)
view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
view.backgroundColor = .clear
view.alwaysBounceVertical = false
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = false
view.layer.masksToBounds = false
return view
}()
let searchTextField: UITextField = {
let textField = UITextField()
textField.backgroundColor = Asset.Scene.Onboarding.searchBarBackground.color
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, constant: 8),
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.textColor = Asset.Colors.Label.primary.color
textField.adjustsFontForContentSizeCategory = true
textField.attributedPlaceholder =
NSAttributedString(
string: L10n.Scene.ServerPicker.Input.placeholder,
attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
.foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
textField.clearButtonMode = .whileEditing
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.returnKeyType = .done
textField.keyboardType = .URL
textField.borderStyle = .none
textField.layer.masksToBounds = true
textField.layer.cornerRadius = 10
textField.layer.cornerCurve = .continuous
return textField
}()
override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.invalidateIntrinsicContentSize()
}
}
extension PickServerServerSectionTableHeaderView {
private func _init() {
preservesSuperviewLayoutMargins = true
backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.preservesSuperviewLayoutMargins = true
addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: topAnchor),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionView.heightAnchor.constraint(equalToConstant: PickServerServerSectionTableHeaderView.collectionViewHeight).priority(.required - 1),
])
searchTextField.translatesAutoresizingMaskIntoConstraints = false
addSubview(searchTextField)
NSLayoutConstraint.activate([
searchTextField.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: PickServerServerSectionTableHeaderView.spacing),
searchTextField.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
searchTextField.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: PickServerServerSectionTableHeaderView.spacing),
searchTextField.heightAnchor.constraint(equalToConstant: PickServerServerSectionTableHeaderView.searchTextFieldHeight).priority(.required - 1),
])
collectionView.delegate = self
searchTextField.delegate = self
searchTextField.addTarget(self, action: #selector(PickServerServerSectionTableHeaderView.textFieldDidChange(_:)), for: .editingChanged)
}
}
extension PickServerServerSectionTableHeaderView {
@objc private func textFieldDidChange(_ textField: UITextField) {
delegate?.pickServerServerSectionTableHeaderView(self, searchTextDidChange: textField.text)
}
}
// MARK: - UICollectionViewDelegate
extension PickServerServerSectionTableHeaderView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
delegate?.pickServerServerSectionTableHeaderView(self, collectionView: collectionView, didSelectItemAt: indexPath)
}
}
extension PickServerServerSectionTableHeaderView {
override func accessibilityElementCount() -> Int {
guard let diffableDataSource = diffableDataSource else { return 0 }
return diffableDataSource.snapshot().itemIdentifiers.count
}
override func accessibilityElement(at index: Int) -> Any? {
guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
return item
}
}
// MARK: - UITextFieldDelegate
extension PickServerServerSectionTableHeaderView: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
}

View File

@ -0,0 +1,69 @@
//
// NavigationActionView.swift
// Mastodon
//
// Created by MainasuK on 2021-12-31.
//
import UIKit
final class NavigationActionView: UIView {
static let buttonHeight: CGFloat = 50
let buttonContainer: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.spacing = 18
return stackView
}()
let backButton: PrimaryActionButton = {
let button = PrimaryActionButton()
button.action = .back
button.setTitle(L10n.Common.Controls.Actions.back, for: .normal)
return button
}()
let nextButton: PrimaryActionButton = {
let button = PrimaryActionButton()
button.action = .next
button.setTitle(L10n.Common.Controls.Actions.next, for: .normal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension NavigationActionView {
private func _init() {
buttonContainer.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.preservesSuperviewLayoutMargins = true
addSubview(buttonContainer)
NSLayoutConstraint.activate([
buttonContainer.topAnchor.constraint(equalTo: topAnchor, constant: 16),
buttonContainer.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
buttonContainer.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor, constant: 8),
])
backButton.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addArrangedSubview(backButton)
nextButton.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addArrangedSubview(nextButton)
NSLayoutConstraint.activate([
backButton.heightAnchor.constraint(equalToConstant: NavigationActionView.buttonHeight).priority(.required - 1),
nextButton.heightAnchor.constraint(equalToConstant: NavigationActionView.buttonHeight).priority(.required - 1),
nextButton.widthAnchor.constraint(equalTo: backButton.widthAnchor, multiplier: 2).priority(.required - 1),
])
}
}

View File

@ -34,7 +34,7 @@ extension OnboardingNavigationController {
super.traitCollectionDidChange(previousTraitCollection)
}
}
extension OnboardingNavigationController {
@ -47,4 +47,5 @@ extension OnboardingNavigationController {
gradientBorderView.isHidden = false
}
}
}

View File

@ -22,7 +22,7 @@ extension OnboardingViewControllerAppearance {
static var viewBottomPaddingHeightExtend: CGFloat { return 22 }
func setupOnboardingAppearance() {
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
view.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
setupNavigationBarAppearance()
@ -39,31 +39,22 @@ extension OnboardingViewControllerAppearance {
// use TransparentBackground so view push / dismiss will be more visual nature
// please add opaque background for status bar manually if needs
switch traitCollection.userInterfaceIdiom {
case .pad:
if traitCollection.horizontalSizeClass == .regular {
// do nothing
} else {
fallthrough
}
default:
let barAppearance = UINavigationBarAppearance()
barAppearance.configureWithTransparentBackground()
navigationItem.standardAppearance = barAppearance
navigationItem.compactAppearance = barAppearance
navigationItem.scrollEdgeAppearance = barAppearance
if #available(iOS 15.0, *) {
navigationItem.compactScrollEdgeAppearance = barAppearance
} else {
// Fallback on earlier versions
}
let barAppearance = UINavigationBarAppearance()
barAppearance.configureWithTransparentBackground()
navigationItem.standardAppearance = barAppearance
navigationItem.compactAppearance = barAppearance
navigationItem.scrollEdgeAppearance = barAppearance
if #available(iOS 15.0, *) {
navigationItem.compactScrollEdgeAppearance = barAppearance
} else {
// Fallback on earlier versions
}
}
func setupNavigationBarBackgroundView() {
let navigationBarBackgroundView: UIView = {
let view = UIView()
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
view.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
return view
}()

View File

@ -12,6 +12,10 @@ final class GradientBorderView: UIView {
let gradientLayer = CAGradientLayer()
let maskLayer = CAShapeLayer()
var cornerRadius: CGFloat = 9 {
didSet { setNeedsLayout() }
}
override init(frame: CGRect) {
super.init(frame: frame)
_init()
@ -48,7 +52,7 @@ extension GradientBorderView {
super.layoutSubviews()
let bezierPath = UIBezierPath(rect: bounds)
bezierPath.append(UIBezierPath(roundedRect: bounds.insetBy(dx: 3, dy: 3), cornerRadius: 10))
bezierPath.append(UIBezierPath(roundedRect: bounds.insetBy(dx: 3, dy: 3), cornerRadius: cornerRadius))
maskLayer.fillRule = .evenOdd
maskLayer.path = bezierPath.cgPath

View File

@ -21,6 +21,12 @@ class PrimaryActionButton: UIButton {
private var originalButtonTitle: String?
var action: Action = .next {
didSet {
setupAppearance(action: action)
}
}
var adjustsBackgroundImageWhenUserInterfaceStyleChanges = true
override init(frame: CGRect) {
@ -35,26 +41,44 @@ class PrimaryActionButton: UIButton {
}
extension PrimaryActionButton {
public enum Action {
case back
case next
}
}
extension PrimaryActionButton {
private func _init() {
titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
setTitleColor(.white, for: .normal)
setupBackgroundAppearance()
setupAppearance(action: action)
applyCornerRadius(radius: 10)
}
func setupBackgroundAppearance() {
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color), for: .normal)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlueDarken20.color), for: .highlighted)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
func setupAppearance(action: Action) {
switch action {
case .back:
setTitleColor(Asset.Colors.Label.primary.color, for: .normal)
setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationBackButtonBackground.color), for: .normal)
setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationBackButtonBackgroundHighlighted.color), for: .highlighted)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
case .next:
setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationNextButtonBackground.color), for: .normal)
setBackgroundImage(UIImage.placeholder(color: Asset.Scene.Onboarding.navigationNextButtonBackgroundHighlighted.color), for: .highlighted)
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if adjustsBackgroundImageWhenUserInterfaceStyleChanges {
setupBackgroundAppearance()
setupAppearance(action: action)
}
}

View File

@ -7,7 +7,7 @@
import UIKit
final class TouchBlockingView: UIView {
class TouchBlockingView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)

View File

@ -126,10 +126,10 @@ struct TimelineHeaderView_Previews: PreviewProvider {
static var previews: some View {
Group {
UIViewPreview(width: 375) {
let headerView = TimelineHeaderView()
headerView.iconImageView.image = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).iconImage
headerView.messageLabel.text = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).message
return headerView
let serverSectionHeaderView = TimelineHeaderView()
serverSectionHeaderView.iconImageView.image = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).iconImage
serverSectionHeaderView.messageLabel.text = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).message
return serverSectionHeaderView
}
.previewLayout(.fixed(width: 375, height: 400))
}