feat: implement pick server view category select
This commit is contained in:
parent
738ba832a9
commit
eb7a33932e
@ -11,6 +11,11 @@
|
||||
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */; };
|
||||
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101B25E10E760017CCDE /* UIFont.swift */; };
|
||||
0FAA102725E1126A0017CCDE /* PickServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA102625E1126A0017CCDE /* PickServerViewController.swift */; };
|
||||
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */; };
|
||||
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */; };
|
||||
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */; };
|
||||
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */; };
|
||||
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */; };
|
||||
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
|
||||
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; };
|
||||
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* StatusView.swift */; };
|
||||
@ -204,6 +209,11 @@
|
||||
0FAA101125E105390017CCDE /* PrimaryActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryActionButton.swift; sourceTree = "<group>"; };
|
||||
0FAA101B25E10E760017CCDE /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
|
||||
0FAA102625E1126A0017CCDE /* PickServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerViewController.swift; sourceTree = "<group>"; };
|
||||
0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerViewModel.swift; sourceTree = "<group>"; };
|
||||
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerTitleCell.swift; sourceTree = "<group>"; };
|
||||
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoriesCell.swift; sourceTree = "<group>"; };
|
||||
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = "<group>"; };
|
||||
0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = "<group>"; };
|
||||
2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
||||
2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
|
||||
@ -414,11 +424,40 @@
|
||||
0FAA102525E1125D0017CCDE /* PickServer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0FB3D31825E525DE00AAD544 /* CollectionViewCell */,
|
||||
0FB3D30D25E525C000AAD544 /* View */,
|
||||
0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */,
|
||||
0FAA102625E1126A0017CCDE /* PickServerViewController.swift */,
|
||||
0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */,
|
||||
);
|
||||
path = PickServer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */,
|
||||
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */,
|
||||
);
|
||||
path = TableViewCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0FB3D30D25E525C000AAD544 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0FB3D31825E525DE00AAD544 /* CollectionViewCell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */,
|
||||
);
|
||||
path = CollectionViewCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1EBA4F56E920856A3FC84ACB /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1328,6 +1367,7 @@
|
||||
0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */,
|
||||
DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */,
|
||||
2D7631B325C159F700929FB9 /* Item.swift in Sources */,
|
||||
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */,
|
||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
||||
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||
@ -1361,6 +1401,7 @@
|
||||
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */,
|
||||
2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */,
|
||||
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */,
|
||||
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
|
||||
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift in Sources */,
|
||||
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */,
|
||||
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
|
||||
@ -1375,6 +1416,7 @@
|
||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
||||
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
|
||||
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
|
||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
||||
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */,
|
||||
@ -1417,7 +1459,10 @@
|
||||
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */,
|
||||
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */,
|
||||
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */,
|
||||
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */,
|
||||
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */,
|
||||
2D5A3D1125CF87AA002347D6 /* AvatarBarButtonItem.swift in Sources */,
|
||||
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
|
||||
DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -39,6 +39,7 @@ extension SceneCoordinator {
|
||||
|
||||
enum Scene {
|
||||
case welcome
|
||||
case pickServer(viewMode: PickServerViewModel)
|
||||
case authentication(viewModel: AuthenticationViewModel)
|
||||
case mastodonPinBasedAuthentication(viewModel: MastodonPinBasedAuthenticationViewModel)
|
||||
case mastodonRegister(viewModel: MastodonRegisterViewModel)
|
||||
@ -136,6 +137,10 @@ private extension SceneCoordinator {
|
||||
case .welcome:
|
||||
let _viewController = WelcomeViewController()
|
||||
viewController = _viewController
|
||||
case .pickServer(let viewModel):
|
||||
let _viewController = PickServerViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .authentication(let viewModel):
|
||||
let _viewController = AuthenticationViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
|
@ -2,7 +2,7 @@
|
||||
// UIFont.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by 高原 on 2021/2/20.
|
||||
// Created by BradGao on 2021/2/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
@ -35,4 +35,28 @@ extension UIView {
|
||||
layer.cornerCurve = .continuous
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func applyShadow(
|
||||
color: UIColor,
|
||||
alpha: Float,
|
||||
x: CGFloat,
|
||||
y: CGFloat,
|
||||
blur: CGFloat,
|
||||
spread: CGFloat = 0) -> Self
|
||||
{
|
||||
layer.masksToBounds = false
|
||||
layer.shadowColor = color.cgColor
|
||||
layer.shadowOpacity = alpha
|
||||
layer.shadowOffset = CGSize(width: x, height: y)
|
||||
layer.shadowRadius = blur / 2.0
|
||||
if spread == 0 {
|
||||
layer.shadowPath = nil
|
||||
} else {
|
||||
let dx = -spread
|
||||
let rect = bounds.insetBy(dx: dx, dy: dx)
|
||||
layer.shadowPath = UIBezierPath(rect: rect).cgPath
|
||||
}
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,12 @@ internal enum L10n {
|
||||
internal enum ServerPicker {
|
||||
/// Pick a Server,\nany server.
|
||||
internal static let title = L10n.tr("Localizable", "Scene.ServerPicker.Title")
|
||||
internal enum Button {
|
||||
internal enum Category {
|
||||
/// All
|
||||
internal static let all = L10n.tr("Localizable", "Scene.ServerPicker.Button.Category.All")
|
||||
}
|
||||
}
|
||||
internal enum Input {
|
||||
/// Find a server or join your own...
|
||||
internal static let placeholder = L10n.tr("Localizable", "Scene.ServerPicker.Input.Placeholder")
|
||||
|
@ -32,6 +32,7 @@
|
||||
"Scene.ServerPicker.Input.Placeholder" = "Find a server or join your own...";
|
||||
"Scene.ServerPicker.Title" = "Pick a Server,
|
||||
any server.";
|
||||
"Scene.ServerPicker.Button.Category.All" = "All";
|
||||
"Scene.ServerRules.Button.Confirm" = "I Agree";
|
||||
"Scene.ServerRules.Prompt" = "By continuing, you're subject to the terms of service and privacy policy for %@.";
|
||||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||
|
@ -0,0 +1,52 @@
|
||||
//
|
||||
// PickServerCategoryCollectionViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var category: PickServerViewModel.Category? {
|
||||
didSet {
|
||||
categoryView.category = category
|
||||
}
|
||||
}
|
||||
|
||||
var categoryView: PickServerCategoryView = {
|
||||
let view = PickServerCategoryView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
}()
|
||||
|
||||
override var isSelected: Bool {
|
||||
didSet {
|
||||
categoryView.selected = isSelected
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
configure()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
configure()
|
||||
}
|
||||
}
|
||||
|
||||
extension PickServerCategoryCollectionViewCell {
|
||||
private func configure() {
|
||||
contentView.addSubview(categoryView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
categoryView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
categoryView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
categoryView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
|
||||
contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor, constant: 10),
|
||||
])
|
||||
}
|
||||
}
|
@ -2,12 +2,21 @@
|
||||
// PickServerViewController.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by 高原 on 2021/2/20.
|
||||
// Created by BradGao on 2021/2/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
class PickServerViewController: UIViewController {
|
||||
final class PickServerViewController: UIViewController, NeedsDependency {
|
||||
|
||||
private var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var viewModel: PickServerViewModel!
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .boldSystemFont(ofSize: 34)
|
||||
@ -18,4 +27,63 @@ class PickServerViewController: UIViewController {
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
let tableView: UITableView = {
|
||||
let tableView = ControlContainableTableView()
|
||||
tableView.register(PickServerTitleCell.self, forCellReuseIdentifier: String(describing: PickServerTitleCell.self))
|
||||
tableView.register(PickServerCategoriesCell.self, forCellReuseIdentifier: String(describing: PickServerCategoriesCell.self))
|
||||
// tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .clear
|
||||
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
return tableView
|
||||
}()
|
||||
|
||||
let nextStepButton: PrimaryActionButton = {
|
||||
let button = PrimaryActionButton(type: .system)
|
||||
button.setTitle(L10n.Button.signUp, for: .normal)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
return button
|
||||
}()
|
||||
}
|
||||
|
||||
extension PickServerViewController {
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .darkContent
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = Asset.Colors.Background.onboardingBackground.color
|
||||
|
||||
view.addSubview(nextStepButton)
|
||||
NSLayoutConstraint.activate([
|
||||
nextStepButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 12),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor, constant: 12),
|
||||
view.bottomAnchor.constraint(equalTo: nextStepButton.bottomAnchor, constant: 34),
|
||||
])
|
||||
|
||||
view.addSubview(tableView)
|
||||
NSLayoutConstraint.activate([
|
||||
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
nextStepButton.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 7)
|
||||
])
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
tableView.delegate = viewModel
|
||||
tableView.dataSource = viewModel
|
||||
}
|
||||
}
|
||||
|
165
Mastodon/Scene/PickServer/PickServerViewModel.swift
Normal file
165
Mastodon/Scene/PickServer/PickServerViewModel.swift
Normal file
@ -0,0 +1,165 @@
|
||||
//
|
||||
// PickServerViewModel.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
|
||||
class PickServerViewModel: NSObject {
|
||||
enum PickServerMode {
|
||||
case SignUp
|
||||
case SignIn
|
||||
}
|
||||
|
||||
enum Section: CaseIterable {
|
||||
case title
|
||||
case categories
|
||||
case serverList
|
||||
}
|
||||
|
||||
enum Category {
|
||||
// `All` means search for all categories
|
||||
case All
|
||||
// `Some` means search for specific category
|
||||
case Some(Mastodon.Entity.Category)
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .All:
|
||||
return L10n.Scene.ServerPicker.Button.Category.all
|
||||
case .Some(let masCategory):
|
||||
switch masCategory.category {
|
||||
case .academia:
|
||||
return "AC"
|
||||
case .activism:
|
||||
return "AT"
|
||||
case .food:
|
||||
return "F"
|
||||
case .furry:
|
||||
return "FU"
|
||||
case .games:
|
||||
return "G"
|
||||
case .general:
|
||||
return "GE"
|
||||
case .journalism:
|
||||
return "JO"
|
||||
case .lgbt:
|
||||
return "LG"
|
||||
case .regional:
|
||||
return "📍"
|
||||
case .art:
|
||||
return "🎨"
|
||||
case .music:
|
||||
return "🎼"
|
||||
case .tech:
|
||||
return "📱"
|
||||
case ._other:
|
||||
return "UN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mode: PickServerMode
|
||||
let context: AppContext
|
||||
|
||||
var categories = [Category]()
|
||||
let selectCategoryIndex = CurrentValueSubject<Int, Never>(0)
|
||||
|
||||
let searchText = CurrentValueSubject<String?, Never>(nil)
|
||||
|
||||
let allServers = CurrentValueSubject<[Mastodon.Entity.Instance], Error>([])
|
||||
let searchedServers = CurrentValueSubject<[Mastodon.Entity.Instance], Error>([])
|
||||
|
||||
let nextButtonEnable = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
init(context: AppContext, mode: PickServerMode) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
super.init()
|
||||
|
||||
configure()
|
||||
}
|
||||
|
||||
private func configure() {
|
||||
let masCategories = context.apiService.stubCategories()
|
||||
categories.append(.All)
|
||||
categories.append(contentsOf: masCategories.map { Category.Some($0) })
|
||||
}
|
||||
}
|
||||
|
||||
extension PickServerViewModel: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
if section == 0 {
|
||||
return 20
|
||||
}
|
||||
else if section == 1 {
|
||||
return 10
|
||||
}
|
||||
else {
|
||||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PickServerViewModel: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
return UIView()
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return Self.Section.allCases.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
let section = Self.Section.allCases[section]
|
||||
switch section {
|
||||
case .title,
|
||||
.categories:
|
||||
return 1
|
||||
case .serverList:
|
||||
return searchedServers.value.count
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let section = Self.Section.allCases[indexPath.section]
|
||||
switch section {
|
||||
case .title:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerTitleCell.self), for: indexPath) as! PickServerTitleCell
|
||||
return cell
|
||||
case .categories:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCategoriesCell.self), for: indexPath) as! PickServerCategoriesCell
|
||||
cell.dataSource = self
|
||||
cell.delegate = self
|
||||
return cell
|
||||
case .serverList:
|
||||
return UITableViewCell(style: .default, reuseIdentifier: "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PickServerViewModel: PickServerCategoriesDataSource, PickServerCategoriesDelegate {
|
||||
func numberOfCategories() -> Int {
|
||||
return categories.count
|
||||
}
|
||||
|
||||
func category(at index: Int) -> Category {
|
||||
return categories[index]
|
||||
}
|
||||
|
||||
func selectedIndex() -> Int {
|
||||
return selectCategoryIndex.value
|
||||
}
|
||||
|
||||
func pickServerCategoriesCell(didSelect index: Int) {
|
||||
selectCategoryIndex.send(index)
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
//
|
||||
// PickServerCategoriesCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
|
||||
protocol PickServerCategoriesDataSource: class {
|
||||
func numberOfCategories() -> Int
|
||||
func category(at index: Int) -> PickServerViewModel.Category
|
||||
func selectedIndex() -> Int
|
||||
}
|
||||
|
||||
protocol PickServerCategoriesDelegate: class {
|
||||
func pickServerCategoriesCell(didSelect index: Int)
|
||||
}
|
||||
|
||||
final class PickServerCategoriesCell: UITableViewCell {
|
||||
|
||||
weak var dataSource: PickServerCategoriesDataSource!
|
||||
weak var delegate: PickServerCategoriesDelegate!
|
||||
|
||||
let collectionView: UICollectionView = {
|
||||
let flowLayout = UICollectionViewFlowLayout()
|
||||
flowLayout.scrollDirection = .horizontal
|
||||
let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
|
||||
view.backgroundColor = .clear
|
||||
view.showsHorizontalScrollIndicator = false
|
||||
view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
|
||||
view.showsVerticalScrollIndicator = false
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
}()
|
||||
|
||||
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() {
|
||||
self.selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
|
||||
contentView.addSubview(collectionView)
|
||||
NSLayoutConstraint.activate([
|
||||
collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
collectionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
|
||||
collectionView.heightAnchor.constraint(equalToConstant: 80),
|
||||
])
|
||||
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
}
|
||||
|
||||
extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
|
||||
delegate.pickServerCategoriesCell(didSelect: indexPath.row)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||
return UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
|
||||
}
|
||||
|
||||
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: UICollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return dataSource.numberOfCategories()
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let category = dataSource.category(at: indexPath.row)
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell
|
||||
cell.category = category
|
||||
|
||||
// Select the default category by default
|
||||
if indexPath.row == dataSource.selectedIndex() {
|
||||
// Use `[]` as the scrollPosition to avoid contentOffset change
|
||||
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
|
||||
cell.isSelected = true
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
//
|
||||
// PickServerTitleCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
final class PickServerTitleCell: UITableViewCell {
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.boldSystemFont(ofSize: 34))
|
||||
label.textColor = Asset.Colors.Label.black.color
|
||||
label.text = L10n.Scene.ServerPicker.title
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
}
|
||||
|
||||
extension PickServerTitleCell {
|
||||
|
||||
private func _init() {
|
||||
self.selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
|
||||
contentView.addSubview(titleLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
titleLabel.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor),
|
||||
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
}
|
||||
}
|
107
Mastodon/Scene/PickServer/View/PickServerCategoryView.swift
Normal file
107
Mastodon/Scene/PickServer/View/PickServerCategoryView.swift
Normal file
@ -0,0 +1,107 @@
|
||||
//
|
||||
// PickServerCategoryView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
|
||||
class PickServerCategoryView: UIView {
|
||||
var category: PickServerViewModel.Category? {
|
||||
didSet {
|
||||
updateCategory()
|
||||
}
|
||||
}
|
||||
var selected: Bool = false {
|
||||
didSet {
|
||||
updateSelectStatus()
|
||||
}
|
||||
}
|
||||
|
||||
var bgShadowView: UIView = {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
}()
|
||||
|
||||
var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.layer.masksToBounds = true
|
||||
view.layer.cornerRadius = 30
|
||||
return view
|
||||
}()
|
||||
|
||||
var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textAlignment = .center
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
return label
|
||||
}()
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
configure()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
configure()
|
||||
}
|
||||
}
|
||||
|
||||
extension PickServerCategoryView {
|
||||
private func configure() {
|
||||
// bgShadowView.backgroundColor = nil
|
||||
// addSubview(bgShadowView)
|
||||
// bgShadowView.addSubview(bgView)
|
||||
addSubview(bgView)
|
||||
addSubview(titleLabel)
|
||||
|
||||
bgView.backgroundColor = .white
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
// bgShadowView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
||||
// bgShadowView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
||||
// bgShadowView.topAnchor.constraint(equalTo: self.topAnchor),
|
||||
// bgShadowView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
||||
|
||||
bgView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
||||
bgView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
||||
bgView.topAnchor.constraint(equalTo: self.topAnchor),
|
||||
bgView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
||||
|
||||
titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
||||
titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
private func updateCategory() {
|
||||
guard let category = category else { return }
|
||||
titleLabel.text = category.title
|
||||
switch category {
|
||||
case .All:
|
||||
titleLabel.font = UIFont.systemFont(ofSize: 17)
|
||||
case .Some:
|
||||
titleLabel.font = UIFont.systemFont(ofSize: 28)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSelectStatus() {
|
||||
if selected {
|
||||
bgView.backgroundColor = Asset.Colors.lightBrandBlue.color
|
||||
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 1, x: 0, y: 0, blur: 4.0)
|
||||
if case .All = category {
|
||||
titleLabel.textColor = .white
|
||||
}
|
||||
} else {
|
||||
bgView.backgroundColor = .white
|
||||
bgView.applyShadow(color: Asset.Colors.lightBrandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0)
|
||||
if case .All = category {
|
||||
titleLabel.textColor = Asset.Colors.lightBackground.color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
// PrimaryActionButton.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by 高原 on 2021/2/20.
|
||||
// Created by BradGao on 2021/2/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
@ -2,7 +2,7 @@
|
||||
// WelcomeViewController.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by 高原 on 2021/2/20.
|
||||
// Created by BradGao on 2021/2/20.
|
||||
//
|
||||
|
||||
import os.log
|
||||
@ -88,8 +88,8 @@ extension WelcomeViewController {
|
||||
signInButton.topAnchor.constraint(equalTo: signUpButton.bottomAnchor, constant: 5)
|
||||
])
|
||||
|
||||
signInButton.addTarget(self, action: #selector(WelcomeViewController.signInButtonPressed(_:)), for: .touchUpInside)
|
||||
signUpButton.addTarget(self, action: #selector(WelcomeViewController.signUpButtonPressed(_:)), for: .touchUpInside)
|
||||
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
|
||||
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
@ -119,3 +119,15 @@ extension WelcomeViewController {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension WelcomeViewController {
|
||||
@objc
|
||||
private func signUpButtonDidClicked(_ sender: UIButton) {
|
||||
coordinator.present(scene: .pickServer(viewMode: PickServerViewModel(context: context, mode: .SignUp)), from: self, transition: .show)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func signInButtonDidClicked(_ sender: UIButton) {
|
||||
coordinator.present(scene: .pickServer(viewMode: PickServerViewModel(context: context, mode: .SignIn)), from: self, transition: .show)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user