From 223049a3f538bff612df66cbfaa0418d9d0ef65b Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 4 Jan 2022 18:30:21 +0800 Subject: [PATCH] feat: update server pick scene UI --- Localization/app.json | 12 +- Mastodon.xcodeproj/project.pbxproj | 20 +- .../xcshareddata/swiftpm/Package.resolved | 9 + .../Deprecated/PickServerCategoriesCell.swift | 145 ++++++++ .../Deprecated/PickServerSearchCell.swift | 171 ++++++++++ .../Diffiable/Item/CategoryPickerItem.swift | 44 ++- Mastodon/Diffiable/Item/PickServerItem.swift | 10 - .../Onboarding/CategoryPickerSection.swift | 22 +- .../Onboarding/PickServerSection.swift | 123 +++---- Mastodon/Generated/Assets.swift | 9 + .../Label/primary.colorset/Contents.json | 12 +- .../primary.reverse.colorset/Contents.json | 38 +++ .../Label/secondary.colorset/Contents.json | 8 +- .../Scene/Onboarding/Contents.json | 9 + .../Contents.json | 38 +++ .../Contents.json | 38 +++ .../Contents.json | 38 +++ .../Contents.json | 38 +++ .../Contents.json | 38 +++ .../Contents.json | 38 +++ .../Contents.json | 6 +- .../Contents.json | 6 +- .../Contents.json | 6 +- ...PickServerCategoryCollectionViewCell.swift | 16 +- .../MastodonPickServerViewController.swift | 314 +++++++----------- ...MastodonPickServerViewModel+Diffable.swift | 83 ++++- .../MastodonPickServerViewModel.swift | 74 +---- .../PickServerCategoriesCell.swift | 145 -------- .../TableViewCell/PickServerCell.swift | 292 +++------------- .../PickServerLoaderTableViewCell.swift | 47 +-- .../TableViewCell/PickServerSearchCell.swift | 171 ---------- .../TableViewCell/PickServerTitleCell.swift | 47 +-- .../View/PickServerCategoryView.swift | 57 ++-- .../View/PickServerEmptyStateView.swift | 10 +- ...ckServerServerSectionTableHeaderView.swift | 204 ++++++++++++ .../Share/NavigationActionView.swift | 69 ++++ .../OnboardingNavigationController.swift | 3 +- .../OnboardingViewControllerAppearance.swift | 31 +- .../Welcome/View/GradientBorderView.swift | 6 +- .../View/Button/PrimaryActionButton.swift | 36 +- .../View/Container/TouchBlockingView.swift | 2 +- .../View/Content/TimelineHeaderView.swift | 8 +- 42 files changed, 1390 insertions(+), 1103 deletions(-) create mode 100644 Mastodon/Deprecated/PickServerCategoriesCell.swift create mode 100644 Mastodon/Deprecated/PickServerSearchCell.swift create mode 100644 Mastodon/Resources/Assets.xcassets/Colors/Label/primary.reverse.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Onboarding/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.highlighted.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.highlighted.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Onboarding/onboarding.background.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Onboarding/search.bar.background.colorset/Contents.json delete mode 100644 Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift delete mode 100644 Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift create mode 100644 Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift create mode 100644 Mastodon/Scene/Onboarding/Share/NavigationActionView.swift diff --git a/Localization/app.json b/Localization/app.json index 3b39a7130..5e74bc696 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -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": "Letโ€™s 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, youโ€™re 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" diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 8f0bfafaa..79bd67778 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -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 = ""; }; DB0617EA277EF3820030EE79 /* GradientBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientBorderView.swift; sourceTree = ""; }; DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = ""; }; + DB0617EE277F12720030EE79 /* NavigationActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationActionView.swift; sourceTree = ""; }; + DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerServerSectionTableHeaderView.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonUser+Property.swift"; sourceTree = ""; }; @@ -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 = ""; @@ -2097,6 +2100,15 @@ path = TableViewCell; sourceTree = ""; }; + DB0617F3278436360030EE79 /* Deprecated */ = { + isa = PBXGroup; + children = ( + 0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */, + 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */, + ); + path = Deprecated; + sourceTree = ""; + }; 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 = ""; @@ -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 */, diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 11dde7269..d933b5cd5 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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" + } } ] }, diff --git a/Mastodon/Deprecated/PickServerCategoriesCell.swift b/Mastodon/Deprecated/PickServerCategoriesCell.swift new file mode 100644 index 000000000..b2ca1cc7d --- /dev/null +++ b/Mastodon/Deprecated/PickServerCategoriesCell.swift @@ -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? +// +// 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 +// } +// +//} diff --git a/Mastodon/Deprecated/PickServerSearchCell.swift b/Mastodon/Deprecated/PickServerSearchCell.swift new file mode 100644 index 000000000..465e7ae29 --- /dev/null +++ b/Mastodon/Deprecated/PickServerSearchCell.swift @@ -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 +// } +//} diff --git a/Mastodon/Diffiable/Item/CategoryPickerItem.swift b/Mastodon/Diffiable/Item/CategoryPickerItem.swift index 0f2cdcc21..53f9c9abd 100644 --- a/Mastodon/Diffiable/Item/CategoryPickerItem.swift +++ b/Mastodon/Diffiable/Item/CategoryPickerItem.swift @@ -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: } } } diff --git a/Mastodon/Diffiable/Item/PickServerItem.swift b/Mastodon/Diffiable/Item/PickServerItem.swift index 7db2c958f..ba693ad78 100644 --- a/Mastodon/Diffiable/Item/PickServerItem.swift +++ b/Mastodon/Diffiable/Item/PickServerItem.swift @@ -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): diff --git a/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift b/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift index 732813c0a..525d77206 100644 --- a/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift +++ b/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift @@ -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) diff --git a/Mastodon/Diffiable/Section/Onboarding/PickServerSection.swift b/Mastodon/Diffiable/Section/Onboarding/PickServerSection.swift index 28b1ded3f..b2079aaba 100644 --- a/Mastodon/Diffiable/Section/Onboarding/PickServerSection.swift +++ b/Mastodon/Diffiable/Section/Onboarding/PickServerSection.swift @@ -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 { 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() - 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 { diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift index 5098d05f0..0761780c7 100644 --- a/Mastodon/Generated/Assets.swift +++ b/Mastodon/Generated/Assets.swift @@ -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") diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json index 202a1c04e..ee70bcc16 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json @@ -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" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.reverse.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.reverse.colorset/Contents.json new file mode 100644 index 000000000..8f42a585a --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.reverse.colorset/Contents.json @@ -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 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json index 70b1446d0..104dfd026 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json @@ -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" diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/Contents.json new file mode 100644 index 000000000..6e965652d --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.colorset/Contents.json new file mode 100644 index 000000000..b7d63ece2 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.colorset/Contents.json @@ -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 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.highlighted.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.highlighted.colorset/Contents.json new file mode 100644 index 000000000..7136040b0 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.back.button.background.highlighted.colorset/Contents.json @@ -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 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.colorset/Contents.json new file mode 100644 index 000000000..17ed9364b --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.colorset/Contents.json @@ -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 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.highlighted.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.highlighted.colorset/Contents.json new file mode 100644 index 000000000..706cd755b --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/navigation.next.button.background.highlighted.colorset/Contents.json @@ -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 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/onboarding.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/onboarding.background.colorset/Contents.json new file mode 100644 index 000000000..0b219c90c --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/onboarding.background.colorset/Contents.json @@ -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 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/search.bar.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/search.bar.background.colorset/Contents.json new file mode 100644 index 000000000..f16bb02fc --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Scene/Onboarding/search.bar.background.colorset/Contents.json @@ -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 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json index d211d7df9..c8aa45b5e 100644 --- a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/content.warning.overlay.background.colorset/Contents.json @@ -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" diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json index 77d24b11d..14441ef0c 100644 --- a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/secondary.system.background.colorset/Contents.json @@ -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" diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json index 370a745eb..daac70e02 100644 --- a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/system.grouped.background.colorset/Contents.json @@ -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" diff --git a/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift b/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift index 9793d40fb..89ca8267b 100644 --- a/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift @@ -8,14 +8,10 @@ import UIKit class PickServerCategoryCollectionViewCell: UICollectionViewCell { - + var observations = Set() - 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), ]) } } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift index f3570c6c5..1fbf204f1 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift @@ -14,6 +14,7 @@ import AuthenticationServices final class MastodonPickServerViewController: UIViewController, NeedsDependency { private var disposeBag = Set() + private var observations = Set() 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, 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 diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift index 9da0399e1..152786722 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift @@ -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() + 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() - 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() + 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) } } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift index 7a6480118..af38b110b 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift @@ -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() + + 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() - 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 } } + +// 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 + } +} diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift deleted file mode 100644 index 659317752..000000000 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift +++ /dev/null @@ -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? - - 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 - } - -} diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift index 2f60a5206..6dd0f1973 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift @@ -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() - - let expandMode = CurrentValueSubject(.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 - } - -} diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift index 945ecac6a..eb0b619df 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift @@ -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 diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift deleted file mode 100644 index 0a64103d2..000000000 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift +++ /dev/null @@ -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 - } -} diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift index f0d78eb41..161b15d09 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift @@ -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 - } - } } diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift index 6565fbcfa..f3bc39942 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift @@ -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) } } diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift index 1d2c17c76..c5682143c 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift @@ -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 diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift new file mode 100644 index 000000000..4afa31aad --- /dev/null +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift @@ -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? + + 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 + } + +} diff --git a/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift new file mode 100644 index 000000000..4b5bb124f --- /dev/null +++ b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift @@ -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), + ]) + } +} diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift index 07e58b58a..537102dc9 100644 --- a/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift +++ b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift @@ -34,7 +34,7 @@ extension OnboardingNavigationController { super.traitCollectionDidChange(previousTraitCollection) } - + } extension OnboardingNavigationController { @@ -47,4 +47,5 @@ extension OnboardingNavigationController { gradientBorderView.isHidden = false } } + } diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift index 2405f2a81..c4fae4dd7 100644 --- a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift +++ b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift @@ -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 }() diff --git a/Mastodon/Scene/Onboarding/Welcome/View/GradientBorderView.swift b/Mastodon/Scene/Onboarding/Welcome/View/GradientBorderView.swift index c9a4a0d7e..68e7968bf 100644 --- a/Mastodon/Scene/Onboarding/Welcome/View/GradientBorderView.swift +++ b/Mastodon/Scene/Onboarding/Welcome/View/GradientBorderView.swift @@ -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 diff --git a/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift b/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift index 326dfa122..3bda63dec 100644 --- a/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift +++ b/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift @@ -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) } } diff --git a/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift b/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift index b86137f1c..5a1518122 100644 --- a/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift +++ b/Mastodon/Scene/Share/View/Container/TouchBlockingView.swift @@ -7,7 +7,7 @@ import UIKit -final class TouchBlockingView: UIView { +class TouchBlockingView: UIView { override init(frame: CGRect) { super.init(frame: frame) diff --git a/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift b/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift index 2948af4cf..50518e59e 100644 --- a/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift +++ b/Mastodon/Scene/Share/View/Content/TimelineHeaderView.swift @@ -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)) }