forked from zelo72/mastodon-ios
feat: update server pick scene UI
This commit is contained in:
parent
cff048c2a3
commit
223049a3f5
|
@ -198,7 +198,9 @@
|
||||||
"log_in": "Log In"
|
"log_in": "Log In"
|
||||||
},
|
},
|
||||||
"server_picker": {
|
"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": {
|
"button": {
|
||||||
"category": {
|
"category": {
|
||||||
"all": "All",
|
"all": "All",
|
||||||
|
@ -225,7 +227,7 @@
|
||||||
"category": "CATEGORY"
|
"category": "CATEGORY"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"placeholder": "Find a server or join your own..."
|
"placeholder": "Search communities"
|
||||||
},
|
},
|
||||||
"empty_state": {
|
"empty_state": {
|
||||||
"finding_servers": "Finding available servers...",
|
"finding_servers": "Finding available servers...",
|
||||||
|
@ -234,7 +236,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"title": "Tell us about you.",
|
"title": "Let’s get you set up on %s",
|
||||||
"input": {
|
"input": {
|
||||||
"avatar": {
|
"avatar": {
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
|
@ -288,7 +290,7 @@
|
||||||
},
|
},
|
||||||
"server_rules": {
|
"server_rules": {
|
||||||
"title": "Some ground 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.",
|
"prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.",
|
||||||
"terms_of_service": "terms of service",
|
"terms_of_service": "terms of service",
|
||||||
"privacy_policy": "privacy policy",
|
"privacy_policy": "privacy policy",
|
||||||
|
@ -298,7 +300,7 @@
|
||||||
},
|
},
|
||||||
"confirm_email": {
|
"confirm_email": {
|
||||||
"title": "One last thing.",
|
"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": {
|
"button": {
|
||||||
"open_email_app": "Open Email App",
|
"open_email_app": "Open Email App",
|
||||||
"dont_receive_email": "I never got an email"
|
"dont_receive_email": "I never got an email"
|
||||||
|
|
|
@ -193,6 +193,8 @@
|
||||||
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; };
|
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; };
|
||||||
DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EA277EF3820030EE79 /* GradientBorderView.swift */; };
|
DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EA277EF3820030EE79 /* GradientBorderView.swift */; };
|
||||||
DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0617EC277F02C50030EE79 /* OnboardingNavigationController.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 */; };
|
DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; };
|
||||||
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
|
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
|
||||||
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; };
|
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; };
|
||||||
|
@ -973,6 +975,8 @@
|
||||||
DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = "<group>"; };
|
DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = "<group>"; };
|
||||||
DB0617EA277EF3820030EE79 /* GradientBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientBorderView.swift; sourceTree = "<group>"; };
|
DB0617EA277EF3820030EE79 /* GradientBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientBorderView.swift; sourceTree = "<group>"; };
|
||||||
DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = "<group>"; };
|
DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = "<group>"; };
|
||||||
|
DB0617EE277F12720030EE79 /* NavigationActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationActionView.swift; sourceTree = "<group>"; };
|
||||||
|
DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerServerSectionTableHeaderView.swift; sourceTree = "<group>"; };
|
||||||
DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
|
DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
|
||||||
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
|
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
|
||||||
DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonUser+Property.swift"; sourceTree = "<group>"; };
|
DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonUser+Property.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1595,8 +1599,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */,
|
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */,
|
||||||
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
|
|
||||||
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */,
|
|
||||||
0FB3D33725E6401400AAD544 /* PickServerCell.swift */,
|
0FB3D33725E6401400AAD544 /* PickServerCell.swift */,
|
||||||
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */,
|
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */,
|
||||||
);
|
);
|
||||||
|
@ -1608,6 +1610,7 @@
|
||||||
children = (
|
children = (
|
||||||
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */,
|
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */,
|
||||||
DB9282B125F3222800823B15 /* PickServerEmptyStateView.swift */,
|
DB9282B125F3222800823B15 /* PickServerEmptyStateView.swift */,
|
||||||
|
DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2097,6 +2100,15 @@
|
||||||
path = TableViewCell;
|
path = TableViewCell;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
DB0617F3278436360030EE79 /* Deprecated */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */,
|
||||||
|
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
|
||||||
|
);
|
||||||
|
path = Deprecated;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
|
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2228,6 +2240,7 @@
|
||||||
children = (
|
children = (
|
||||||
DB427DE325BAA00100D1B89D /* Info.plist */,
|
DB427DE325BAA00100D1B89D /* Info.plist */,
|
||||||
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
|
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
|
||||||
|
DB0617F3278436360030EE79 /* Deprecated */,
|
||||||
2D76319C25C151DE00929FB9 /* Diffiable */,
|
2D76319C25C151DE00929FB9 /* Diffiable */,
|
||||||
DB8AF52A25C13561002E6C99 /* State */,
|
DB8AF52A25C13561002E6C99 /* State */,
|
||||||
2D61335525C1886800CAE157 /* Service */,
|
2D61335525C1886800CAE157 /* Service */,
|
||||||
|
@ -2524,6 +2537,7 @@
|
||||||
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */,
|
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */,
|
||||||
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */,
|
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */,
|
||||||
DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */,
|
DB0617EC277F02C50030EE79 /* OnboardingNavigationController.swift */,
|
||||||
|
DB0617EE277F12720030EE79 /* NavigationActionView.swift */,
|
||||||
);
|
);
|
||||||
path = Share;
|
path = Share;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3972,6 +3986,7 @@
|
||||||
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */,
|
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */,
|
||||||
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
|
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
|
||||||
DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */,
|
DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */,
|
||||||
|
DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */,
|
||||||
DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */,
|
DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */,
|
||||||
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */,
|
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */,
|
||||||
DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */,
|
DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */,
|
||||||
|
@ -4132,6 +4147,7 @@
|
||||||
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
|
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
|
||||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
||||||
DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
|
DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
|
||||||
|
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
|
||||||
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
||||||
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
||||||
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */,
|
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */,
|
||||||
|
|
|
@ -216,6 +216,15 @@
|
||||||
"revision": "dad97167bf1be16aeecd109130900995dd01c515",
|
"revision": "dad97167bf1be16aeecd109130900995dd01c515",
|
||||||
"version": "2.6.0"
|
"version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "UITextView+Placeholder",
|
||||||
|
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "20f513ded04a040cdf5467f0891849b1763ede3b",
|
||||||
|
"version": "1.4.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
//
|
||||||
|
// PickServerCategoriesCell.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by BradGao on 2021/2/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
//import os.log
|
||||||
|
//import UIKit
|
||||||
|
//import MastodonSDK
|
||||||
|
//
|
||||||
|
//protocol PickServerCategoriesCellDelegate: AnyObject {
|
||||||
|
// func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//final class PickServerCategoriesCell: UITableViewCell {
|
||||||
|
//
|
||||||
|
// weak var delegate: PickServerCategoriesCellDelegate?
|
||||||
|
//
|
||||||
|
// var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
|
||||||
|
//
|
||||||
|
// let metricView = UIView()
|
||||||
|
//
|
||||||
|
// let collectionView: UICollectionView = {
|
||||||
|
// let flowLayout = UICollectionViewFlowLayout()
|
||||||
|
// flowLayout.scrollDirection = .horizontal
|
||||||
|
// let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
|
||||||
|
// view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
|
||||||
|
// view.backgroundColor = .clear
|
||||||
|
// view.showsHorizontalScrollIndicator = false
|
||||||
|
// view.showsVerticalScrollIndicator = false
|
||||||
|
// view.layer.masksToBounds = false
|
||||||
|
// view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
// return view
|
||||||
|
// }()
|
||||||
|
//
|
||||||
|
// override func prepareForReuse() {
|
||||||
|
// super.prepareForReuse()
|
||||||
|
//
|
||||||
|
// delegate = nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
// super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
// _init()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// required init?(coder: NSCoder) {
|
||||||
|
// super.init(coder: coder)
|
||||||
|
// _init()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//extension PickServerCategoriesCell {
|
||||||
|
//
|
||||||
|
// private func _init() {
|
||||||
|
// selectionStyle = .none
|
||||||
|
// backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
||||||
|
// configureMargin()
|
||||||
|
//
|
||||||
|
// metricView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
// contentView.addSubview(metricView)
|
||||||
|
// NSLayoutConstraint.activate([
|
||||||
|
// metricView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||||
|
// metricView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
||||||
|
// metricView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
|
// metricView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||||
|
// metricView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
|
||||||
|
// ])
|
||||||
|
//
|
||||||
|
// contentView.addSubview(collectionView)
|
||||||
|
// NSLayoutConstraint.activate([
|
||||||
|
// collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||||
|
// collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||||
|
// collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
|
||||||
|
// contentView.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20),
|
||||||
|
// collectionView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
|
||||||
|
// ])
|
||||||
|
//
|
||||||
|
// collectionView.delegate = self
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
// super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
//
|
||||||
|
// configureMargin()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override func layoutSubviews() {
|
||||||
|
// super.layoutSubviews()
|
||||||
|
//
|
||||||
|
// collectionView.collectionViewLayout.invalidateLayout()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//extension PickServerCategoriesCell {
|
||||||
|
// private func configureMargin() {
|
||||||
|
// switch traitCollection.horizontalSizeClass {
|
||||||
|
// case .regular:
|
||||||
|
// let margin = MastodonPickServerViewController.viewEdgeMargin
|
||||||
|
// contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
|
||||||
|
// default:
|
||||||
|
// contentView.layoutMargins = .zero
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// MARK: - UICollectionViewDelegateFlowLayout
|
||||||
|
//extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
|
||||||
|
//
|
||||||
|
// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||||
|
// collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
|
||||||
|
// delegate?.pickServerCategoriesCell(self, collectionView: collectionView, didSelectItemAt: indexPath)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||||
|
// layoutIfNeeded()
|
||||||
|
// return UIEdgeInsets(top: 0, left: metricView.frame.minX - collectionView.frame.minX, bottom: 0, right: collectionView.frame.maxX - metricView.frame.maxX)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
||||||
|
// return 16
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||||
|
// return CGSize(width: 60, height: 80)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//extension PickServerCategoriesCell {
|
||||||
|
//
|
||||||
|
// override func accessibilityElementCount() -> Int {
|
||||||
|
// guard let diffableDataSource = diffableDataSource else { return 0 }
|
||||||
|
// return diffableDataSource.snapshot().itemIdentifiers.count
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override func accessibilityElement(at index: Int) -> Any? {
|
||||||
|
// guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
|
||||||
|
// return item
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
|
@ -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
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -15,10 +15,11 @@ enum CategoryPickerItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CategoryPickerItem {
|
extension CategoryPickerItem {
|
||||||
var title: String {
|
|
||||||
|
var emoji: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .all:
|
case .all:
|
||||||
return L10n.Scene.ServerPicker.Button.Category.all
|
return "💬"
|
||||||
case .category(let category):
|
case .category(let category):
|
||||||
switch category.category {
|
switch category.category {
|
||||||
case .academia:
|
case .academia:
|
||||||
|
@ -32,7 +33,7 @@ extension CategoryPickerItem {
|
||||||
case .games:
|
case .games:
|
||||||
return "🕹"
|
return "🕹"
|
||||||
case .general:
|
case .general:
|
||||||
return "💬"
|
return "🐘"
|
||||||
case .journalism:
|
case .journalism:
|
||||||
return "📰"
|
return "📰"
|
||||||
case .lgbt:
|
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 {
|
var accessibilityDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -82,7 +118,7 @@ extension CategoryPickerItem {
|
||||||
case .tech:
|
case .tech:
|
||||||
return L10n.Scene.ServerPicker.Button.Category.tech
|
return L10n.Scene.ServerPicker.Button.Category.tech
|
||||||
case ._other:
|
case ._other:
|
||||||
return "❓" // FIXME:
|
return "-" // FIXME:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ import MastodonSDK
|
||||||
/// Note: update Equatable when change case
|
/// Note: update Equatable when change case
|
||||||
enum PickServerItem {
|
enum PickServerItem {
|
||||||
case header
|
case header
|
||||||
case categoryPicker(items: [CategoryPickerItem])
|
|
||||||
case search
|
|
||||||
case server(server: Mastodon.Entity.Server, attribute: ServerItemAttribute)
|
case server(server: Mastodon.Entity.Server, attribute: ServerItemAttribute)
|
||||||
case loader(attribute: LoaderItemAttribute)
|
case loader(attribute: LoaderItemAttribute)
|
||||||
}
|
}
|
||||||
|
@ -63,10 +61,6 @@ extension PickServerItem: Equatable {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.header, .header):
|
case (.header, .header):
|
||||||
return true
|
return true
|
||||||
case (.categoryPicker(let itemsLeft), .categoryPicker(let itemsRight)):
|
|
||||||
return itemsLeft == itemsRight
|
|
||||||
case (.search, .search):
|
|
||||||
return true
|
|
||||||
case (.server(let serverLeft, _), .server(let serverRight, _)):
|
case (.server(let serverLeft, _), .server(let serverRight, _)):
|
||||||
return serverLeft.domain == serverRight.domain
|
return serverLeft.domain == serverRight.domain
|
||||||
case (.loader(let attributeLeft), loader(let attributeRight)):
|
case (.loader(let attributeLeft), loader(let attributeRight)):
|
||||||
|
@ -82,10 +76,6 @@ extension PickServerItem: Hashable {
|
||||||
switch self {
|
switch self {
|
||||||
case .header:
|
case .header:
|
||||||
hasher.combine(String(describing: PickServerItem.header.self))
|
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, _):
|
case .server(let server, _):
|
||||||
hasher.combine(server.domain)
|
hasher.combine(server.domain)
|
||||||
case .loader(let attribute):
|
case .loader(let attribute):
|
||||||
|
|
|
@ -19,27 +19,11 @@ extension CategoryPickerSection {
|
||||||
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in
|
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||||
guard let _ = dependency else { return nil }
|
guard let _ = dependency else { return nil }
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
|
||||||
switch item {
|
cell.categoryView.emojiLabel.text = item.emoji
|
||||||
case .all:
|
|
||||||
cell.categoryView.titleLabel.font = .systemFont(ofSize: 17)
|
|
||||||
case .category:
|
|
||||||
cell.categoryView.titleLabel.font = .systemFont(ofSize: 28)
|
|
||||||
}
|
|
||||||
cell.categoryView.titleLabel.text = item.title
|
cell.categoryView.titleLabel.text = item.title
|
||||||
cell.observe(\.isSelected, options: [.initial, .new]) { cell, _ in
|
cell.observe(\.isSelected, options: [.initial, .new]) { cell, _ in
|
||||||
if cell.isSelected {
|
cell.categoryView.highlightedIndicatorView.alpha = cell.isSelected ? 1 : 0
|
||||||
cell.categoryView.bgView.backgroundColor = Asset.Colors.brandBlue.color
|
cell.categoryView.titleLabel.textColor = cell.isSelected ? Asset.Colors.Label.primary.color : Asset.Colors.Label.secondary.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.store(in: &cell.observations)
|
.store(in: &cell.observations)
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,6 @@ import AlamofireImage
|
||||||
|
|
||||||
enum PickServerSection: Equatable, Hashable {
|
enum PickServerSection: Equatable, Hashable {
|
||||||
case header
|
case header
|
||||||
case category
|
|
||||||
case search
|
|
||||||
case servers
|
case servers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,14 +19,10 @@ extension PickServerSection {
|
||||||
static func tableViewDiffableDataSource(
|
static func tableViewDiffableDataSource(
|
||||||
for tableView: UITableView,
|
for tableView: UITableView,
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency,
|
||||||
pickServerCategoriesCellDelegate: PickServerCategoriesCellDelegate,
|
|
||||||
pickServerSearchCellDelegate: PickServerSearchCellDelegate,
|
|
||||||
pickServerCellDelegate: PickServerCellDelegate
|
pickServerCellDelegate: PickServerCellDelegate
|
||||||
) -> UITableViewDiffableDataSource<PickServerSection, PickServerItem> {
|
) -> UITableViewDiffableDataSource<PickServerSection, PickServerItem> {
|
||||||
UITableViewDiffableDataSource(tableView: tableView) { [
|
UITableViewDiffableDataSource(tableView: tableView) { [
|
||||||
weak dependency,
|
weak dependency,
|
||||||
weak pickServerCategoriesCellDelegate,
|
|
||||||
weak pickServerSearchCellDelegate,
|
|
||||||
weak pickServerCellDelegate
|
weak pickServerCellDelegate
|
||||||
] tableView, indexPath, item -> UITableViewCell? in
|
] tableView, indexPath, item -> UITableViewCell? in
|
||||||
guard let dependency = dependency else { return nil }
|
guard let dependency = dependency else { return nil }
|
||||||
|
@ -36,22 +30,6 @@ extension PickServerSection {
|
||||||
case .header:
|
case .header:
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerTitleCell.self), for: indexPath) as! PickServerTitleCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerTitleCell.self), for: indexPath) as! PickServerTitleCell
|
||||||
return cell
|
return cell
|
||||||
case .categoryPicker(let items):
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCategoriesCell.self), for: indexPath) as! PickServerCategoriesCell
|
|
||||||
cell.delegate = pickServerCategoriesCellDelegate
|
|
||||||
cell.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
|
|
||||||
for: cell.collectionView,
|
|
||||||
dependency: dependency
|
|
||||||
)
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<CategoryPickerSection, CategoryPickerItem>()
|
|
||||||
snapshot.appendSections([.main])
|
|
||||||
snapshot.appendItems(items, toSection: .main)
|
|
||||||
cell.diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
|
||||||
return cell
|
|
||||||
case .search:
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerSearchCell.self), for: indexPath) as! PickServerSearchCell
|
|
||||||
cell.delegate = pickServerSearchCellDelegate
|
|
||||||
return cell
|
|
||||||
case .server(let server, let attribute):
|
case .server(let server, let attribute):
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell
|
||||||
PickServerSection.configure(cell: cell, server: server, attribute: attribute)
|
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) {
|
static func configure(cell: PickServerCell, server: Mastodon.Entity.Server, attribute: PickServerItem.ServerItemAttribute) {
|
||||||
cell.domainLabel.text = server.domain
|
cell.domainLabel.text = server.domain
|
||||||
cell.descriptionLabel.text = {
|
cell.descriptionLabel.attributedText = {
|
||||||
guard let html = try? HTML(html: server.description, encoding: .utf8) else {
|
let content: String = {
|
||||||
return server.description
|
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.attributedText = {
|
||||||
cell.usersValueLabel.text = parseUsersCount(server.totalUsers)
|
let attributedString = NSMutableAttributedString()
|
||||||
cell.categoryValueLabel.text = server.category.uppercased()
|
let attachment = NSTextAttachment(image: UIImage(systemName: "person.2.fill")!)
|
||||||
|
let attachmentAttributedString = NSAttributedString(attachment: attachment)
|
||||||
cell.updateExpandMode(mode: attribute.isExpand ? .expand : .collapse)
|
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
|
attribute.isLast
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak cell] isLast in
|
.sink { [weak cell] isLast in
|
||||||
|
@ -101,41 +123,6 @@ extension PickServerSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
.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 {
|
private static func parseUsersCount(_ usersCount: Int) -> String {
|
||||||
|
|
|
@ -47,6 +47,7 @@ internal enum Asset {
|
||||||
}
|
}
|
||||||
internal enum Label {
|
internal enum Label {
|
||||||
internal static let primary = ColorAsset(name: "Colors/Label/primary")
|
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 secondary = ColorAsset(name: "Colors/Label/secondary")
|
||||||
internal static let tertiary = ColorAsset(name: "Colors/Label/tertiary")
|
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 static let faceSmilingAdaptive = ImageAsset(name: "Human/face.smiling.adaptive")
|
||||||
}
|
}
|
||||||
internal enum Scene {
|
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 Profile {
|
||||||
internal enum Banner {
|
internal enum Banner {
|
||||||
internal static let bioEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/bio.edit.background.gray")
|
internal static let bioEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/bio.edit.background.gray")
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0x00",
|
"blue" : "0.216",
|
||||||
"green" : "0x00",
|
"green" : "0.173",
|
||||||
"red" : "0x00"
|
"red" : "0.157"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
@ -23,9 +23,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "1.000",
|
"blue" : "0xEE",
|
||||||
"green" : "1.000",
|
"green" : "0xEE",
|
||||||
"red" : "1.000"
|
"red" : "0xEE"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,10 +22,10 @@
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "0.600",
|
"alpha" : "1.000",
|
||||||
"blue" : "0xF5",
|
"blue" : "0xAD",
|
||||||
"green" : "0xEB",
|
"green" : "0x9D",
|
||||||
"red" : "0xEB"
|
"red" : "0x97"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,9 +5,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.922",
|
"blue" : "0xEB",
|
||||||
"green" : "0.898",
|
"green" : "0xE4",
|
||||||
"red" : "0.867"
|
"red" : "0xDD"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.910",
|
"blue" : "0xE8",
|
||||||
"green" : "0.882",
|
"green" : "0xE0",
|
||||||
"red" : "0.851"
|
"red" : "0xD9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.910",
|
"blue" : "0xE8",
|
||||||
"green" : "0.882",
|
"green" : "0xE0",
|
||||||
"red" : "0.851"
|
"red" : "0xD9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
@ -8,14 +8,10 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
|
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
var observations = Set<NSKeyValueObservation>()
|
var observations = Set<NSKeyValueObservation>()
|
||||||
|
|
||||||
var categoryView: PickServerCategoryView = {
|
var categoryView = PickServerCategoryView()
|
||||||
let view = PickServerCategoryView()
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
@ -35,13 +31,15 @@ class PickServerCategoryCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
extension PickServerCategoryCollectionViewCell {
|
extension PickServerCategoryCollectionViewCell {
|
||||||
private func configure() {
|
private func configure() {
|
||||||
contentView.addSubview(categoryView)
|
backgroundColor = .clear
|
||||||
|
|
||||||
|
categoryView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
contentView.addSubview(categoryView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
categoryView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
categoryView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
categoryView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||||
categoryView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
categoryView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||||
categoryView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
|
contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor),
|
||||||
contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor, constant: 10),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import AuthenticationServices
|
||||||
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
|
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
private var disposeBag = Set<AnyCancellable>()
|
private var disposeBag = Set<AnyCancellable>()
|
||||||
|
private var observations = Set<NSKeyValueObservation>()
|
||||||
private var tableViewObservation: NSKeyValueObservation?
|
private var tableViewObservation: NSKeyValueObservation?
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
@ -31,21 +32,16 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
|
||||||
private let emptyStateView = PickServerEmptyStateView()
|
private let emptyStateView = PickServerEmptyStateView()
|
||||||
private var emptyStateViewLeadingLayoutConstraint: NSLayoutConstraint!
|
private var emptyStateViewLeadingLayoutConstraint: NSLayoutConstraint!
|
||||||
private var emptyStateViewTrailingLayoutConstraint: 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: UITableView = {
|
||||||
let tableView = ControlContainableTableView()
|
let tableView = ControlContainableTableView()
|
||||||
tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
|
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(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self))
|
||||||
tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self))
|
tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self))
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
tableView.keyboardDismissMode = .onDrag
|
tableView.keyboardDismissMode = .onDrag
|
||||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude
|
tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude
|
||||||
} else {
|
} else {
|
||||||
|
@ -54,14 +50,11 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let buttonContainer = UIView()
|
let navigationActionView: NavigationActionView = {
|
||||||
let nextStepButton: PrimaryActionButton = {
|
let navigationActionView = NavigationActionView()
|
||||||
let button = PrimaryActionButton()
|
navigationActionView.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
|
||||||
button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal)
|
return navigationActionView
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return button
|
|
||||||
}()
|
}()
|
||||||
var buttonContainerBottomLayoutConstraint: NSLayoutConstraint!
|
|
||||||
|
|
||||||
var mastodonAuthenticationController: MastodonAuthenticationController?
|
var mastodonAuthenticationController: MastodonAuthenticationController?
|
||||||
|
|
||||||
|
@ -72,16 +65,15 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MastodonPickServerViewController {
|
extension MastodonPickServerViewController {
|
||||||
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem()
|
||||||
|
|
||||||
setupOnboardingAppearance()
|
setupOnboardingAppearance()
|
||||||
defer { setupNavigationBarBackgroundView() }
|
defer { setupNavigationBarBackgroundView() }
|
||||||
configureTitleLabel()
|
|
||||||
configureMargin()
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: nil, action: nil)
|
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)
|
navigationItem.rightBarButtonItem?.menu = UIMenu(title: "Debug Tool", image: nil, identifier: nil, options: [], children: children)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
buttonContainer.translatesAutoresizingMaskIntoConstraints = false
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
buttonContainer.preservesSuperviewLayoutMargins = true
|
view.addSubview(tableView)
|
||||||
view.addSubview(buttonContainer)
|
|
||||||
buttonContainerBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor, constant: 0).priority(.defaultHigh)
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
buttonContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
buttonContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
view.safeAreaLayoutGuide.bottomAnchor.constraint(greaterThanOrEqualTo: buttonContainer.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
|
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
buttonContainerBottomLayoutConstraint,
|
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
view.addSubview(nextStepButton)
|
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.addSubview(navigationActionView)
|
||||||
|
defer {
|
||||||
|
view.bringSubviewToFront(navigationActionView)
|
||||||
|
}
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
nextStepButton.topAnchor.constraint(equalTo: buttonContainer.topAnchor),
|
navigationActionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
nextStepButton.leadingAnchor.constraint(equalTo: buttonContainer.layoutMarginsGuide.leadingAnchor),
|
navigationActionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
buttonContainer.layoutMarginsGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor),
|
view.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor),
|
||||||
nextStepButton.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor),
|
|
||||||
nextStepButton.heightAnchor.constraint(equalToConstant: MastodonPickServerViewController.actionButtonHeight).priority(.defaultHigh),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
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
|
// fix AutoLayout warning when observe before view appear
|
||||||
viewModel.viewWillAppear
|
viewModel.viewWillAppear
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
@ -125,26 +127,7 @@ extension MastodonPickServerViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.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
|
emptyStateView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(emptyStateView)
|
view.addSubview(emptyStateView)
|
||||||
emptyStateViewLeadingLayoutConstraint = emptyStateView.leadingAnchor.constraint(equalTo: tableView.leadingAnchor)
|
emptyStateViewLeadingLayoutConstraint = emptyStateView.leadingAnchor.constraint(equalTo: tableView.leadingAnchor)
|
||||||
|
@ -153,64 +136,24 @@ extension MastodonPickServerViewController {
|
||||||
emptyStateView.topAnchor.constraint(equalTo: view.topAnchor),
|
emptyStateView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
emptyStateViewLeadingLayoutConstraint,
|
emptyStateViewLeadingLayoutConstraint,
|
||||||
emptyStateViewTrailingLayoutConstraint,
|
emptyStateViewTrailingLayoutConstraint,
|
||||||
buttonContainer.topAnchor.constraint(equalTo: emptyStateView.bottomAnchor, constant: 21),
|
navigationActionView.topAnchor.constraint(equalTo: emptyStateView.bottomAnchor, constant: 21),
|
||||||
])
|
])
|
||||||
view.sendSubviewToBack(emptyStateView)
|
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
|
tableView.delegate = self
|
||||||
viewModel.setupDiffableDataSource(
|
viewModel.setupDiffableDataSource(
|
||||||
for: tableView,
|
for: tableView,
|
||||||
dependency: self,
|
dependency: self,
|
||||||
pickServerCategoriesCellDelegate: self,
|
pickServerServerSectionTableHeaderViewDelegate: self,
|
||||||
pickServerSearchCellDelegate: self,
|
|
||||||
pickServerCellDelegate: self
|
pickServerCellDelegate: self
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModel
|
viewModel
|
||||||
.selectedServer
|
.selectedServer
|
||||||
.map { $0 != nil }
|
.map { $0 != nil }
|
||||||
.assign(to: \.isEnabled, on: nextStepButton)
|
.assign(to: \.isEnabled, on: navigationActionView.nextButton)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
Publishers.Merge(
|
Publishers.Merge(
|
||||||
viewModel.error,
|
viewModel.error,
|
||||||
authenticationViewModel.error
|
authenticationViewModel.error
|
||||||
|
@ -229,7 +172,7 @@ extension MastodonPickServerViewController {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
authenticationViewModel
|
authenticationViewModel
|
||||||
.authenticated
|
.authenticated
|
||||||
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
|
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
|
||||||
|
@ -249,17 +192,17 @@ extension MastodonPickServerViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
authenticationViewModel.isAuthenticating
|
authenticationViewModel.isAuthenticating
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isAuthenticating in
|
.sink { [weak self] isAuthenticating in
|
||||||
guard let self = self else { return }
|
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)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.emptyStateViewState
|
viewModel.emptyStateViewState
|
||||||
.receive(on: RunLoop.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] state in
|
.sink { [weak self] state in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
switch state {
|
switch state {
|
||||||
|
@ -284,6 +227,9 @@ extension MastodonPickServerViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.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) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
@ -296,38 +242,20 @@ extension MastodonPickServerViewController {
|
||||||
|
|
||||||
setupNavigationBarAppearance()
|
setupNavigationBarAppearance()
|
||||||
updateEmptyStateViewLayout()
|
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 {
|
extension MastodonPickServerViewController {
|
||||||
|
|
||||||
@objc
|
@objc private func backButtonDidPressed(_ sender: UIButton) {
|
||||||
private func nextStepButtonDidClicked(_ sender: UIButton) {
|
navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func nextButtonDidPressed(_ sender: UIButton) {
|
||||||
switch viewModel.mode {
|
switch viewModel.mode {
|
||||||
case .signIn:
|
case .signIn: doSignIn()
|
||||||
doSignIn()
|
case .signUp: doSignUp()
|
||||||
case .signUp:
|
|
||||||
doSignUp()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,16 +386,6 @@ extension MastodonPickServerViewController {
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
extension MastodonPickServerViewController: 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? {
|
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
|
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) 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 }
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||||
|
|
||||||
switch item {
|
switch item {
|
||||||
case .categoryPicker:
|
// case .categoryPicker:
|
||||||
guard let cell = cell as? PickServerCategoriesCell else { return }
|
// guard let cell = cell as? PickServerCategoriesCell else { return }
|
||||||
guard let diffableDataSource = cell.diffableDataSource else { return }
|
// guard let diffableDataSource = cell.diffableDataSource else { return }
|
||||||
let snapshot = diffableDataSource.snapshot()
|
// let snapshot = diffableDataSource.snapshot()
|
||||||
|
//
|
||||||
let item = viewModel.selectCategoryItem.value
|
// let item = viewModel.selectCategoryItem.value
|
||||||
guard let section = snapshot.indexOfSection(.main),
|
// guard let section = snapshot.indexOfSection(.main),
|
||||||
let row = snapshot.indexOfItem(item) else { return }
|
// let row = snapshot.indexOfItem(item) else { return }
|
||||||
cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally)
|
// cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally)
|
||||||
case .search:
|
// case .search:
|
||||||
guard let cell = cell as? PickServerSearchCell else { return }
|
// guard let cell = cell as? PickServerSearchCell else { return }
|
||||||
cell.searchTextField.text = viewModel.searchText.value
|
// cell.searchTextField.text = viewModel.searchText.value
|
||||||
default:
|
default:
|
||||||
break
|
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 {
|
extension MastodonPickServerViewController {
|
||||||
private func updateEmptyStateViewLayout() {
|
private func updateEmptyStateViewLayout() {
|
||||||
guard let diffableDataSource = self.viewModel.diffableDataSource else { return }
|
// guard let diffableDataSource = self.viewModel.diffableDataSource else { return }
|
||||||
guard let indexPath = diffableDataSource.indexPath(for: .search) else { return }
|
// guard let indexPath = diffableDataSource.indexPath(for: .search) else { return }
|
||||||
let rectInTableView = tableView.rectForRow(at: indexPath)
|
// let rectInTableView = tableView.rectForRow(at: indexPath)
|
||||||
|
//
|
||||||
emptyStateView.topPaddingViewTopLayoutConstraint.constant = rectInTableView.maxY
|
// emptyStateView.topPaddingViewTopLayoutConstraint.constant = rectInTableView.maxY
|
||||||
|
//
|
||||||
switch traitCollection.horizontalSizeClass {
|
// switch traitCollection.horizontalSizeClass {
|
||||||
case .regular:
|
// case .regular:
|
||||||
emptyStateViewLeadingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
|
// emptyStateViewLeadingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
|
||||||
emptyStateViewTrailingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
|
// emptyStateViewTrailingLayoutConstraint.constant = MastodonPickServerViewController.viewEdgeMargin
|
||||||
default:
|
// default:
|
||||||
let margin = tableView.layoutMarginsGuide.layoutFrame.origin.x
|
// let margin = tableView.layoutMarginsGuide.layoutFrame.origin.x
|
||||||
emptyStateViewLeadingLayoutConstraint.constant = margin
|
// emptyStateViewLeadingLayoutConstraint.constant = margin
|
||||||
emptyStateViewTrailingLayoutConstraint.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - PickServerCategoriesCellDelegate
|
// MARK: - PickServerServerSectionTableHeaderViewDelegate
|
||||||
extension MastodonPickServerViewController: PickServerCategoriesCellDelegate {
|
extension MastodonPickServerViewController: PickServerServerSectionTableHeaderViewDelegate {
|
||||||
func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
guard let diffableDataSource = cell.diffableDataSource else { return }
|
guard let diffableDataSource = headerView.diffableDataSource else { return }
|
||||||
let item = diffableDataSource.itemIdentifier(for: indexPath)
|
let item = diffableDataSource.itemIdentifier(for: indexPath)
|
||||||
viewModel.selectCategoryItem.value = item ?? .all
|
viewModel.selectCategoryItem.value = item ?? .all
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, searchTextDidChange searchText: String?) {
|
||||||
// MARK: - PickServerSearchCellDelegate
|
|
||||||
extension MastodonPickServerViewController: PickServerSearchCellDelegate {
|
|
||||||
func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?) {
|
|
||||||
viewModel.searchText.send(searchText ?? "")
|
viewModel.searchText.send(searchText ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - PickServerCellDelegate
|
// MARK: - PickServerCellDelegate
|
||||||
extension MastodonPickServerViewController: 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
|
// MARK: - OnboardingViewControllerAppearance
|
||||||
|
|
|
@ -6,32 +6,101 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
extension MastodonPickServerViewModel {
|
extension MastodonPickServerViewModel {
|
||||||
|
|
||||||
func setupDiffableDataSource(
|
func setupDiffableDataSource(
|
||||||
for tableView: UITableView,
|
for tableView: UITableView,
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency,
|
||||||
pickServerCategoriesCellDelegate: PickServerCategoriesCellDelegate,
|
pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate,
|
||||||
pickServerSearchCellDelegate: PickServerSearchCellDelegate,
|
|
||||||
pickServerCellDelegate: PickServerCellDelegate
|
pickServerCellDelegate: PickServerCellDelegate
|
||||||
) {
|
) {
|
||||||
|
// set section header
|
||||||
|
serverSectionHeaderView.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource(
|
||||||
|
for: serverSectionHeaderView.collectionView,
|
||||||
|
dependency: dependency
|
||||||
|
)
|
||||||
|
var sectionHeaderSnapshot = NSDiffableDataSourceSnapshot<CategoryPickerSection, CategoryPickerItem>()
|
||||||
|
sectionHeaderSnapshot.appendSections([.main])
|
||||||
|
sectionHeaderSnapshot.appendItems(categoryPickerItems, toSection: .main)
|
||||||
|
serverSectionHeaderView.delegate = pickServerServerSectionTableHeaderViewDelegate
|
||||||
|
serverSectionHeaderView.diffableDataSource?.applySnapshot(sectionHeaderSnapshot, animated: false)
|
||||||
|
|
||||||
|
// set tableView
|
||||||
diffableDataSource = PickServerSection.tableViewDiffableDataSource(
|
diffableDataSource = PickServerSection.tableViewDiffableDataSource(
|
||||||
for: tableView,
|
for: tableView,
|
||||||
dependency: dependency,
|
dependency: dependency,
|
||||||
pickServerCategoriesCellDelegate: pickServerCategoriesCellDelegate,
|
|
||||||
pickServerSearchCellDelegate: pickServerSearchCellDelegate,
|
|
||||||
pickServerCellDelegate: pickServerCellDelegate
|
pickServerCellDelegate: pickServerCellDelegate
|
||||||
)
|
)
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
|
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
|
||||||
snapshot.appendSections([.header, .category, .search, .servers])
|
snapshot.appendSections([.header, .servers])
|
||||||
snapshot.appendItems([.header], toSection: .header)
|
snapshot.appendItems([.header], toSection: .header)
|
||||||
snapshot.appendItems([.categoryPicker(items: categoryPickerItems)], toSection: .category)
|
|
||||||
snapshot.appendItems([.search], toSection: .search)
|
|
||||||
diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
diffableDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||||
|
|
||||||
loadIndexedServerStateMachine.enter(LoadIndexedServerState.Loading.self)
|
loadIndexedServerStateMachine.enter(LoadIndexedServerState.Loading.self)
|
||||||
|
|
||||||
|
Publishers.CombineLatest(
|
||||||
|
filteredIndexedServers,
|
||||||
|
unindexedServers
|
||||||
|
)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink(receiveValue: { [weak self] indexedServers, unindexedServers in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||||
|
|
||||||
|
let oldSnapshot = diffableDataSource.snapshot()
|
||||||
|
var oldSnapshotServerItemAttributeDict: [String : PickServerItem.ServerItemAttribute] = [:]
|
||||||
|
for item in oldSnapshot.itemIdentifiers {
|
||||||
|
guard case let .server(server, attribute) = item else { continue }
|
||||||
|
oldSnapshotServerItemAttributeDict[server.domain] = attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
|
||||||
|
snapshot.appendSections([.header, .servers])
|
||||||
|
snapshot.appendItems([.header], toSection: .header)
|
||||||
|
|
||||||
|
// TODO: handle filter
|
||||||
|
var serverItems: [PickServerItem] = []
|
||||||
|
for server in indexedServers {
|
||||||
|
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
|
||||||
|
attribute.isLast.value = false
|
||||||
|
let item = PickServerItem.server(server: server, attribute: attribute)
|
||||||
|
guard !serverItems.contains(item) else { continue }
|
||||||
|
serverItems.append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let unindexedServers = unindexedServers {
|
||||||
|
if !unindexedServers.isEmpty {
|
||||||
|
for server in unindexedServers {
|
||||||
|
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
|
||||||
|
attribute.isLast.value = false
|
||||||
|
let item = PickServerItem.server(server: server, attribute: attribute)
|
||||||
|
guard !serverItems.contains(item) else { continue }
|
||||||
|
serverItems.append(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if indexedServers.isEmpty && !self.isLoadingIndexedServers.value {
|
||||||
|
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: true)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: false)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .server(_, attribute) = serverItems.last {
|
||||||
|
attribute.isLast.value = true
|
||||||
|
}
|
||||||
|
if case let .loader(attribute) = serverItems.last {
|
||||||
|
attribute.isLast = true
|
||||||
|
}
|
||||||
|
snapshot.appendItems(serverItems, toSection: .servers)
|
||||||
|
|
||||||
|
diffableDataSource.defaultRowAnimation = .fade
|
||||||
|
diffableDataSource.apply(snapshot, animatingDifferences: true, completion: nil)
|
||||||
|
})
|
||||||
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import GameplayKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import OrderedCollections
|
import OrderedCollections
|
||||||
|
import Tabman
|
||||||
|
|
||||||
class MastodonPickServerViewModel: NSObject {
|
class MastodonPickServerViewModel: NSObject {
|
||||||
|
|
||||||
|
@ -27,6 +28,8 @@ class MastodonPickServerViewModel: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
let serverSectionHeaderView = PickServerServerSectionTableHeaderView()
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let mode: PickServerMode
|
let mode: PickServerMode
|
||||||
|
@ -82,68 +85,6 @@ class MastodonPickServerViewModel: NSObject {
|
||||||
extension MastodonPickServerViewModel {
|
extension MastodonPickServerViewModel {
|
||||||
|
|
||||||
private func configure() {
|
private func configure() {
|
||||||
Publishers.CombineLatest(
|
|
||||||
filteredIndexedServers,
|
|
||||||
unindexedServers
|
|
||||||
)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink(receiveValue: { [weak self] indexedServers, unindexedServers in
|
|
||||||
guard let self = self else { return }
|
|
||||||
guard let diffableDataSource = self.diffableDataSource else { return }
|
|
||||||
|
|
||||||
let oldSnapshot = diffableDataSource.snapshot()
|
|
||||||
var oldSnapshotServerItemAttributeDict: [String : PickServerItem.ServerItemAttribute] = [:]
|
|
||||||
for item in oldSnapshot.itemIdentifiers {
|
|
||||||
guard case let .server(server, attribute) = item else { continue }
|
|
||||||
oldSnapshotServerItemAttributeDict[server.domain] = attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<PickServerSection, PickServerItem>()
|
|
||||||
snapshot.appendSections([.header, .category, .search, .servers])
|
|
||||||
snapshot.appendItems([.header], toSection: .header)
|
|
||||||
snapshot.appendItems([.categoryPicker(items: self.categoryPickerItems)], toSection: .category)
|
|
||||||
snapshot.appendItems([.search], toSection: .search)
|
|
||||||
// TODO: handle filter
|
|
||||||
var serverItems: [PickServerItem] = []
|
|
||||||
for server in indexedServers {
|
|
||||||
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
|
|
||||||
attribute.isLast.value = false
|
|
||||||
let item = PickServerItem.server(server: server, attribute: attribute)
|
|
||||||
guard !serverItems.contains(item) else { continue }
|
|
||||||
serverItems.append(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let unindexedServers = unindexedServers {
|
|
||||||
if !unindexedServers.isEmpty {
|
|
||||||
for server in unindexedServers {
|
|
||||||
let attribute = oldSnapshotServerItemAttributeDict[server.domain] ?? PickServerItem.ServerItemAttribute(isLast: false, isExpand: false)
|
|
||||||
attribute.isLast.value = false
|
|
||||||
let item = PickServerItem.server(server: server, attribute: attribute)
|
|
||||||
guard !serverItems.contains(item) else { continue }
|
|
||||||
serverItems.append(item)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if indexedServers.isEmpty && !self.isLoadingIndexedServers.value {
|
|
||||||
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: true)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
serverItems.append(.loader(attribute: PickServerItem.LoaderItemAttribute(isLast: false, isEmptyResult: false)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if case let .server(_, attribute) = serverItems.last {
|
|
||||||
attribute.isLast.value = true
|
|
||||||
}
|
|
||||||
if case let .loader(attribute) = serverItems.last {
|
|
||||||
attribute.isLast = true
|
|
||||||
}
|
|
||||||
snapshot.appendItems(serverItems, toSection: .servers)
|
|
||||||
|
|
||||||
diffableDataSource.defaultRowAnimation = .fade
|
|
||||||
diffableDataSource.apply(snapshot, animatingDifferences: true, completion: nil)
|
|
||||||
})
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
isLoadingIndexedServers,
|
isLoadingIndexedServers,
|
||||||
loadingIndexedServersError
|
loadingIndexedServersError
|
||||||
|
@ -301,3 +242,12 @@ extension MastodonPickServerViewModel {
|
||||||
let applicationToken: Mastodon.Response.Content<Mastodon.Entity.Token>
|
let applicationToken: Mastodon.Response.Content<Mastodon.Entity.Token>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - TMBarDataSource
|
||||||
|
extension MastodonPickServerViewModel: TMBarDataSource {
|
||||||
|
func barItem(for bar: TMBar, at index: Int) -> TMBarItemable {
|
||||||
|
let item = categoryPickerItems[index]
|
||||||
|
let barItem = TMBarItem(title: item.title)
|
||||||
|
return barItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
//
|
|
||||||
// PickServerCategoriesCell.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by BradGao on 2021/2/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import UIKit
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
protocol PickServerCategoriesCellDelegate: AnyObject {
|
|
||||||
func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
final class PickServerCategoriesCell: UITableViewCell {
|
|
||||||
|
|
||||||
weak var delegate: PickServerCategoriesCellDelegate?
|
|
||||||
|
|
||||||
var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
|
|
||||||
|
|
||||||
let metricView = UIView()
|
|
||||||
|
|
||||||
let collectionView: UICollectionView = {
|
|
||||||
let flowLayout = UICollectionViewFlowLayout()
|
|
||||||
flowLayout.scrollDirection = .horizontal
|
|
||||||
let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
|
|
||||||
view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
view.showsHorizontalScrollIndicator = false
|
|
||||||
view.showsVerticalScrollIndicator = false
|
|
||||||
view.layer.masksToBounds = false
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
override func prepareForReuse() {
|
|
||||||
super.prepareForReuse()
|
|
||||||
|
|
||||||
delegate = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
||||||
_init()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
_init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PickServerCategoriesCell {
|
|
||||||
|
|
||||||
private func _init() {
|
|
||||||
selectionStyle = .none
|
|
||||||
backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
|
||||||
configureMargin()
|
|
||||||
|
|
||||||
metricView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
contentView.addSubview(metricView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
metricView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
|
||||||
metricView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
|
||||||
metricView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
|
||||||
metricView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
|
||||||
metricView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
|
|
||||||
])
|
|
||||||
|
|
||||||
contentView.addSubview(collectionView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
|
||||||
collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
|
||||||
collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
|
|
||||||
contentView.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20),
|
|
||||||
collectionView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
|
|
||||||
])
|
|
||||||
|
|
||||||
collectionView.delegate = self
|
|
||||||
}
|
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
|
||||||
|
|
||||||
configureMargin()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
|
|
||||||
collectionView.collectionViewLayout.invalidateLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PickServerCategoriesCell {
|
|
||||||
private func configureMargin() {
|
|
||||||
switch traitCollection.horizontalSizeClass {
|
|
||||||
case .regular:
|
|
||||||
let margin = MastodonPickServerViewController.viewEdgeMargin
|
|
||||||
contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
|
|
||||||
default:
|
|
||||||
contentView.layoutMargins = .zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - UICollectionViewDelegateFlowLayout
|
|
||||||
extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
|
||||||
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
|
|
||||||
delegate?.pickServerCategoriesCell(self, collectionView: collectionView, didSelectItemAt: indexPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
|
||||||
layoutIfNeeded()
|
|
||||||
return UIEdgeInsets(top: 0, left: metricView.frame.minX - collectionView.frame.minX, bottom: 0, right: collectionView.frame.maxX - metricView.frame.maxX)
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
|
||||||
return 16
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
|
||||||
return CGSize(width: 60, height: 80)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PickServerCategoriesCell {
|
|
||||||
|
|
||||||
override func accessibilityElementCount() -> Int {
|
|
||||||
guard let diffableDataSource = diffableDataSource else { return 0 }
|
|
||||||
return diffableDataSource.snapshot().itemIdentifiers.count
|
|
||||||
}
|
|
||||||
|
|
||||||
override func accessibilityElement(at index: Int) -> Any? {
|
|
||||||
guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -13,7 +13,7 @@ import AlamofireImage
|
||||||
import Kanna
|
import Kanna
|
||||||
|
|
||||||
protocol PickServerCellDelegate: AnyObject {
|
protocol PickServerCellDelegate: AnyObject {
|
||||||
func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
|
// func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
class PickServerCell: UITableViewCell {
|
class PickServerCell: UITableViewCell {
|
||||||
|
@ -21,20 +21,17 @@ class PickServerCell: UITableViewCell {
|
||||||
weak var delegate: PickServerCellDelegate?
|
weak var delegate: PickServerCellDelegate?
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
let expandMode = CurrentValueSubject<ExpandMode, Never>(.collapse)
|
let containerView: UIStackView = {
|
||||||
|
let view = UIStackView()
|
||||||
let containerView: UIView = {
|
view.axis = .vertical
|
||||||
let view = UIView()
|
view.spacing = 4
|
||||||
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
|
|
||||||
view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let domainLabel: UILabel = {
|
let domainLabel: UILabel = {
|
||||||
let label = 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.textColor = Asset.Colors.Label.primary.color
|
||||||
label.adjustsFontForContentSizeCategory = true
|
label.adjustsFontForContentSizeCategory = true
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -52,7 +49,7 @@ class PickServerCell: UITableViewCell {
|
||||||
|
|
||||||
let descriptionLabel: UILabel = {
|
let descriptionLabel: UILabel = {
|
||||||
let label = 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.numberOfLines = 0
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
label.textColor = Asset.Colors.Label.primary.color
|
||||||
label.adjustsFontForContentSizeCategory = true
|
label.adjustsFontForContentSizeCategory = true
|
||||||
|
@ -60,112 +57,33 @@ class PickServerCell: UITableViewCell {
|
||||||
return label
|
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 infoStackView: UIStackView = {
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
stackView.axis = .horizontal
|
stackView.axis = .horizontal
|
||||||
stackView.alignment = .fill
|
stackView.spacing = 16
|
||||||
stackView.distribution = .fillEqually
|
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return stackView
|
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 separator: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
view.backgroundColor = Asset.Theme.System.separator.color
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let langValueLabel: UILabel = {
|
let langValueLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
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.textAlignment = .center
|
||||||
label.adjustsFontForContentSizeCategory = true
|
label.adjustsFontForContentSizeCategory = true
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let usersValueLabel: UILabel = {
|
let usersValueLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
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.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
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -175,9 +93,6 @@ class PickServerCell: UITableViewCell {
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
|
||||||
thumbnailImageView.isHidden = false
|
|
||||||
thumbnailImageView.af.cancelImageRequest()
|
|
||||||
thumbnailActivityIndicator.stopAnimating()
|
|
||||||
disposeBag.removeAll()
|
disposeBag.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,172 +112,55 @@ class PickServerCell: UITableViewCell {
|
||||||
extension PickServerCell {
|
extension PickServerCell {
|
||||||
private func _init() {
|
private func _init() {
|
||||||
selectionStyle = .none
|
selectionStyle = .none
|
||||||
backgroundColor = .clear
|
backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
|
||||||
configureMargin()
|
|
||||||
|
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)
|
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([
|
NSLayoutConstraint.activate([
|
||||||
// Set background view
|
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11),
|
||||||
containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
containerView.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 22),
|
||||||
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
containerView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
||||||
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 11),
|
||||||
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
|
||||||
|
|
||||||
// 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),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
thumbnailActivityIndicator.translatesAutoresizingMaskIntoConstraints = false
|
containerView.addArrangedSubview(domainLabel)
|
||||||
thumbnailImageView.addSubview(thumbnailActivityIndicator)
|
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([
|
NSLayoutConstraint.activate([
|
||||||
thumbnailActivityIndicator.centerXAnchor.constraint(equalTo: thumbnailImageView.centerXAnchor),
|
separator.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||||
thumbnailActivityIndicator.centerYAnchor.constraint(equalTo: thumbnailImageView.centerYAnchor),
|
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) {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
super.setSelected(selected, animated: animated)
|
super.setSelected(selected, animated: animated)
|
||||||
if selected {
|
if selected {
|
||||||
checkbox.image = UIImage(systemName: "checkmark.circle.fill")
|
checkbox.image = UIImage(systemName: "checkmark.circle.fill")
|
||||||
|
checkbox.tintColor = Asset.Colors.Label.primary.color
|
||||||
} else {
|
} else {
|
||||||
checkbox.image = UIImage(systemName: "circle")
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,15 +13,7 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||||
let containerView: UIView = {
|
let containerView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
|
view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16)
|
||||||
view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
|
view.backgroundColor = .clear
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
let seperator: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -30,30 +22,22 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||||
label.text = L10n.Scene.ServerPicker.EmptyState.noResults
|
label.text = L10n.Scene.ServerPicker.EmptyState.noResults
|
||||||
label.textColor = Asset.Colors.Label.secondary.color
|
label.textColor = Asset.Colors.Label.secondary.color
|
||||||
label.textAlignment = .center
|
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
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
override func _init() {
|
override func _init() {
|
||||||
super._init()
|
super._init()
|
||||||
|
|
||||||
configureMargin()
|
|
||||||
|
|
||||||
contentView.addSubview(containerView)
|
|
||||||
contentView.addSubview(seperator)
|
|
||||||
|
|
||||||
|
// Set background view
|
||||||
|
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
contentView.addSubview(containerView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
// Set background view
|
|
||||||
containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||||
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
||||||
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 1),
|
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||||
|
|
||||||
// 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),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
emptyStatusLabel.translatesAutoresizingMaskIntoConstraints = false
|
emptyStatusLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -69,24 +53,7 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell {
|
||||||
activityIndicatorView.isHidden = false
|
activityIndicatorView.isHidden = false
|
||||||
startAnimating()
|
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
|
#if canImport(SwiftUI) && DEBUG
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,17 +11,24 @@ final class PickServerTitleCell: UITableViewCell {
|
||||||
|
|
||||||
let titleLabel: UILabel = {
|
let titleLabel: UILabel = {
|
||||||
let label = 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.textColor = Asset.Colors.Label.primary.color
|
||||||
label.text = L10n.Scene.ServerPicker.title
|
label.text = L10n.Scene.ServerPicker.title
|
||||||
label.adjustsFontForContentSizeCategory = true
|
label.adjustsFontForContentSizeCategory = true
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
return label
|
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?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
_init()
|
_init()
|
||||||
|
@ -37,46 +44,22 @@ extension PickServerTitleCell {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
selectionStyle = .none
|
selectionStyle = .none
|
||||||
backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
|
||||||
|
|
||||||
let container = UIStackView()
|
let container = UIStackView()
|
||||||
container.axis = .vertical
|
container.axis = .vertical
|
||||||
|
container.spacing = 16
|
||||||
container.translatesAutoresizingMaskIntoConstraints = false
|
container.translatesAutoresizingMaskIntoConstraints = false
|
||||||
containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: .leastNonzeroMagnitude)
|
|
||||||
contentView.addSubview(container)
|
contentView.addSubview(container)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
container.topAnchor.constraint(equalTo: contentView.topAnchor),
|
container.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
container.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
container.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||||
container.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
container.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
||||||
container.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
contentView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 11),
|
||||||
])
|
])
|
||||||
|
|
||||||
container.addArrangedSubview(titleLabel)
|
container.addArrangedSubview(titleLabel)
|
||||||
|
container.addArrangedSubview(subTitleLabel)
|
||||||
configureTitleLabelDisplay()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,24 +10,24 @@ import MastodonSDK
|
||||||
|
|
||||||
class PickServerCategoryView: UIView {
|
class PickServerCategoryView: UIView {
|
||||||
|
|
||||||
var bgShadowView: UIView = {
|
let highlightedIndicatorView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.backgroundColor = Asset.Colors.Label.primary.color
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var bgView: UIView = {
|
let emojiLabel: UILabel = {
|
||||||
let view = UIView()
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.layer.masksToBounds = true
|
|
||||||
view.layer.cornerRadius = 30
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
var titleLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textAlignment = .center
|
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
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -45,20 +45,27 @@ class PickServerCategoryView: UIView {
|
||||||
extension PickServerCategoryView {
|
extension PickServerCategoryView {
|
||||||
|
|
||||||
private func configure() {
|
private func configure() {
|
||||||
addSubview(bgView)
|
let container = UIStackView()
|
||||||
addSubview(titleLabel)
|
container.axis = .vertical
|
||||||
|
container.distribution = .fillProportionally
|
||||||
bgView.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
|
|
||||||
|
container.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(container)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
bgView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
container.topAnchor.constraint(equalTo: topAnchor),
|
||||||
bgView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
container.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
bgView.topAnchor.constraint(equalTo: self.topAnchor),
|
container.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
bgView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
container.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
|
||||||
titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
|
||||||
titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,13 +44,7 @@ final class PickServerEmptyStateView: UIView {
|
||||||
extension PickServerEmptyStateView {
|
extension PickServerEmptyStateView {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
|
backgroundColor = .clear
|
||||||
layer.maskedCorners = [
|
|
||||||
.layerMinXMaxYCorner,
|
|
||||||
.layerMaxXMaxYCorner
|
|
||||||
]
|
|
||||||
layer.cornerCurve = .continuous
|
|
||||||
layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
|
|
||||||
|
|
||||||
let topPaddingView = UIView()
|
let topPaddingView = UIView()
|
||||||
topPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
topPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -101,7 +95,7 @@ extension PickServerEmptyStateView {
|
||||||
])
|
])
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
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
|
activityIndicatorView.hidesWhenStopped = true
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
//
|
||||||
|
// PickServerServerSectionTableHeaderView.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK on 2022-1-4.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import Tabman
|
||||||
|
|
||||||
|
protocol PickServerServerSectionTableHeaderViewDelegate: AnyObject {
|
||||||
|
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||||
|
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, searchTextDidChange searchText: String?)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PickServerServerSectionTableHeaderView: UIView {
|
||||||
|
|
||||||
|
static let collectionViewHeight: CGFloat = 88
|
||||||
|
static let searchTextFieldHeight: CGFloat = 38
|
||||||
|
static let spacing: CGFloat = 11
|
||||||
|
|
||||||
|
static let height: CGFloat = collectionViewHeight + spacing + searchTextFieldHeight + spacing
|
||||||
|
|
||||||
|
weak var delegate: PickServerServerSectionTableHeaderViewDelegate?
|
||||||
|
|
||||||
|
var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
|
||||||
|
|
||||||
|
static func createCollectionViewLayout() -> UICollectionViewLayout {
|
||||||
|
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(88), heightDimension: .absolute(PickServerServerSectionTableHeaderView.collectionViewHeight))
|
||||||
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
|
||||||
|
let groupSize = NSCollectionLayoutSize(widthDimension: itemSize.widthDimension, heightDimension: itemSize.heightDimension)
|
||||||
|
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
|
||||||
|
|
||||||
|
let section = NSCollectionLayoutSection(group: group)
|
||||||
|
section.orthogonalScrollingBehavior = .continuous
|
||||||
|
section.contentInsetsReference = .readableContent
|
||||||
|
section.interGroupSpacing = 16
|
||||||
|
|
||||||
|
return UICollectionViewCompositionalLayout(section: section)
|
||||||
|
}
|
||||||
|
|
||||||
|
let collectionView: UICollectionView = {
|
||||||
|
let collectionViewLayout = PickServerServerSectionTableHeaderView.createCollectionViewLayout()
|
||||||
|
let view = ControlContainableCollectionView(
|
||||||
|
frame: CGRect(origin: .zero, size: CGSize(width: 100, height: PickServerServerSectionTableHeaderView.collectionViewHeight)),
|
||||||
|
collectionViewLayout: collectionViewLayout
|
||||||
|
)
|
||||||
|
view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
view.alwaysBounceVertical = false
|
||||||
|
view.showsHorizontalScrollIndicator = false
|
||||||
|
view.showsVerticalScrollIndicator = false
|
||||||
|
view.layer.masksToBounds = false
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
let searchTextField: UITextField = {
|
||||||
|
let textField = UITextField()
|
||||||
|
textField.backgroundColor = Asset.Scene.Onboarding.searchBarBackground.color
|
||||||
|
textField.leftView = {
|
||||||
|
let imageView = UIImageView(
|
||||||
|
image: UIImage(
|
||||||
|
systemName: "magnifyingglass",
|
||||||
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
imageView.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)
|
||||||
|
|
||||||
|
let containerView = UIView()
|
||||||
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
containerView.addSubview(imageView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
|
||||||
|
imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8),
|
||||||
|
imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
let paddingView = UIView()
|
||||||
|
paddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
containerView.addSubview(paddingView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
|
||||||
|
paddingView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor),
|
||||||
|
paddingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
||||||
|
paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||||
|
paddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
|
||||||
|
])
|
||||||
|
return containerView
|
||||||
|
}()
|
||||||
|
textField.leftViewMode = .always
|
||||||
|
textField.font = .systemFont(ofSize: 15, weight: .regular)
|
||||||
|
textField.tintColor = Asset.Colors.Label.primary.color
|
||||||
|
textField.textColor = Asset.Colors.Label.primary.color
|
||||||
|
textField.adjustsFontForContentSizeCategory = true
|
||||||
|
textField.attributedPlaceholder =
|
||||||
|
NSAttributedString(
|
||||||
|
string: L10n.Scene.ServerPicker.Input.placeholder,
|
||||||
|
attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
|
||||||
|
.foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
|
||||||
|
textField.clearButtonMode = .whileEditing
|
||||||
|
textField.autocapitalizationType = .none
|
||||||
|
textField.autocorrectionType = .no
|
||||||
|
textField.returnKeyType = .done
|
||||||
|
textField.keyboardType = .URL
|
||||||
|
textField.borderStyle = .none
|
||||||
|
|
||||||
|
textField.layer.masksToBounds = true
|
||||||
|
textField.layer.cornerRadius = 10
|
||||||
|
textField.layer.cornerCurve = .continuous
|
||||||
|
|
||||||
|
return textField
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
collectionView.invalidateIntrinsicContentSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerServerSectionTableHeaderView {
|
||||||
|
private func _init() {
|
||||||
|
preservesSuperviewLayoutMargins = true
|
||||||
|
backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
|
||||||
|
|
||||||
|
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
collectionView.preservesSuperviewLayoutMargins = true
|
||||||
|
addSubview(collectionView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
collectionView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
collectionView.heightAnchor.constraint(equalToConstant: PickServerServerSectionTableHeaderView.collectionViewHeight).priority(.required - 1),
|
||||||
|
])
|
||||||
|
|
||||||
|
searchTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(searchTextField)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
searchTextField.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: PickServerServerSectionTableHeaderView.spacing),
|
||||||
|
searchTextField.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||||
|
searchTextField.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||||
|
bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: PickServerServerSectionTableHeaderView.spacing),
|
||||||
|
searchTextField.heightAnchor.constraint(equalToConstant: PickServerServerSectionTableHeaderView.searchTextFieldHeight).priority(.required - 1),
|
||||||
|
])
|
||||||
|
|
||||||
|
collectionView.delegate = self
|
||||||
|
searchTextField.delegate = self
|
||||||
|
searchTextField.addTarget(self, action: #selector(PickServerServerSectionTableHeaderView.textFieldDidChange(_:)), for: .editingChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerServerSectionTableHeaderView {
|
||||||
|
@objc private func textFieldDidChange(_ textField: UITextField) {
|
||||||
|
delegate?.pickServerServerSectionTableHeaderView(self, searchTextDidChange: textField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UICollectionViewDelegate
|
||||||
|
extension PickServerServerSectionTableHeaderView: UICollectionViewDelegate {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||||
|
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
|
||||||
|
delegate?.pickServerServerSectionTableHeaderView(self, collectionView: collectionView, didSelectItemAt: indexPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PickServerServerSectionTableHeaderView {
|
||||||
|
|
||||||
|
override func accessibilityElementCount() -> Int {
|
||||||
|
guard let diffableDataSource = diffableDataSource else { return 0 }
|
||||||
|
return diffableDataSource.snapshot().itemIdentifiers.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func accessibilityElement(at index: Int) -> Any? {
|
||||||
|
guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UITextFieldDelegate
|
||||||
|
extension PickServerServerSectionTableHeaderView: UITextFieldDelegate {
|
||||||
|
|
||||||
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
textField.resignFirstResponder()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ extension OnboardingNavigationController {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OnboardingNavigationController {
|
extension OnboardingNavigationController {
|
||||||
|
@ -47,4 +47,5 @@ extension OnboardingNavigationController {
|
||||||
gradientBorderView.isHidden = false
|
gradientBorderView.isHidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ extension OnboardingViewControllerAppearance {
|
||||||
static var viewBottomPaddingHeightExtend: CGFloat { return 22 }
|
static var viewBottomPaddingHeightExtend: CGFloat { return 22 }
|
||||||
|
|
||||||
func setupOnboardingAppearance() {
|
func setupOnboardingAppearance() {
|
||||||
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
view.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
|
||||||
|
|
||||||
setupNavigationBarAppearance()
|
setupNavigationBarAppearance()
|
||||||
|
|
||||||
|
@ -39,31 +39,22 @@ extension OnboardingViewControllerAppearance {
|
||||||
// use TransparentBackground so view push / dismiss will be more visual nature
|
// use TransparentBackground so view push / dismiss will be more visual nature
|
||||||
// please add opaque background for status bar manually if needs
|
// please add opaque background for status bar manually if needs
|
||||||
|
|
||||||
switch traitCollection.userInterfaceIdiom {
|
let barAppearance = UINavigationBarAppearance()
|
||||||
case .pad:
|
barAppearance.configureWithTransparentBackground()
|
||||||
if traitCollection.horizontalSizeClass == .regular {
|
navigationItem.standardAppearance = barAppearance
|
||||||
// do nothing
|
navigationItem.compactAppearance = barAppearance
|
||||||
} else {
|
navigationItem.scrollEdgeAppearance = barAppearance
|
||||||
fallthrough
|
if #available(iOS 15.0, *) {
|
||||||
}
|
navigationItem.compactScrollEdgeAppearance = barAppearance
|
||||||
default:
|
} else {
|
||||||
let barAppearance = UINavigationBarAppearance()
|
// Fallback on earlier versions
|
||||||
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() {
|
func setupNavigationBarBackgroundView() {
|
||||||
let navigationBarBackgroundView: UIView = {
|
let navigationBarBackgroundView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
view.backgroundColor = Asset.Scene.Onboarding.onboardingBackground.color
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,10 @@ final class GradientBorderView: UIView {
|
||||||
let gradientLayer = CAGradientLayer()
|
let gradientLayer = CAGradientLayer()
|
||||||
let maskLayer = CAShapeLayer()
|
let maskLayer = CAShapeLayer()
|
||||||
|
|
||||||
|
var cornerRadius: CGFloat = 9 {
|
||||||
|
didSet { setNeedsLayout() }
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
_init()
|
_init()
|
||||||
|
@ -48,7 +52,7 @@ extension GradientBorderView {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
let bezierPath = UIBezierPath(rect: bounds)
|
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.fillRule = .evenOdd
|
||||||
maskLayer.path = bezierPath.cgPath
|
maskLayer.path = bezierPath.cgPath
|
||||||
|
|
|
@ -21,6 +21,12 @@ class PrimaryActionButton: UIButton {
|
||||||
|
|
||||||
private var originalButtonTitle: String?
|
private var originalButtonTitle: String?
|
||||||
|
|
||||||
|
var action: Action = .next {
|
||||||
|
didSet {
|
||||||
|
setupAppearance(action: action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var adjustsBackgroundImageWhenUserInterfaceStyleChanges = true
|
var adjustsBackgroundImageWhenUserInterfaceStyleChanges = true
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
|
@ -35,26 +41,44 @@ class PrimaryActionButton: UIButton {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension PrimaryActionButton {
|
||||||
|
|
||||||
|
public enum Action {
|
||||||
|
case back
|
||||||
|
case next
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extension PrimaryActionButton {
|
extension PrimaryActionButton {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
|
titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
|
||||||
setTitleColor(.white, for: .normal)
|
setTitleColor(.white, for: .normal)
|
||||||
setupBackgroundAppearance()
|
setupAppearance(action: action)
|
||||||
applyCornerRadius(radius: 10)
|
applyCornerRadius(radius: 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupBackgroundAppearance() {
|
func setupAppearance(action: Action) {
|
||||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlue.color), for: .normal)
|
switch action {
|
||||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.brandBlueDarken20.color), for: .highlighted)
|
case .back:
|
||||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.disabled.color), for: .disabled)
|
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?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
|
||||||
if adjustsBackgroundImageWhenUserInterfaceStyleChanges {
|
if adjustsBackgroundImageWhenUserInterfaceStyleChanges {
|
||||||
setupBackgroundAppearance()
|
setupAppearance(action: action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
final class TouchBlockingView: UIView {
|
class TouchBlockingView: UIView {
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
|
@ -126,10 +126,10 @@ struct TimelineHeaderView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
Group {
|
Group {
|
||||||
UIViewPreview(width: 375) {
|
UIViewPreview(width: 375) {
|
||||||
let headerView = TimelineHeaderView()
|
let serverSectionHeaderView = TimelineHeaderView()
|
||||||
headerView.iconImageView.image = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).iconImage
|
serverSectionHeaderView.iconImageView.image = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).iconImage
|
||||||
headerView.messageLabel.text = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).message
|
serverSectionHeaderView.messageLabel.text = Item.EmptyStateHeaderAttribute.Reason.blocking(name: nil).message
|
||||||
return headerView
|
return serverSectionHeaderView
|
||||||
}
|
}
|
||||||
.previewLayout(.fixed(width: 375, height: 400))
|
.previewLayout(.fixed(width: 375, height: 400))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue