From eb7a33932eadd533df178736c2517f8ddf57fd06 Mon Sep 17 00:00:00 2001 From: jk234ert Date: Tue, 23 Feb 2021 22:14:10 +0800 Subject: [PATCH] feat: implement pick server view category select --- Mastodon.xcodeproj/project.pbxproj | 45 +++++ Mastodon/Coordinator/SceneCoordinator.swift | 5 + Mastodon/Extension/UIFont.swift | 2 +- Mastodon/Extension/UIView.swift | 24 +++ Mastodon/Generated/Strings.swift | 6 + .../Resources/en.lproj/Localizable.strings | 1 + ...PickServerCategoryCollectionViewCell.swift | 52 ++++++ .../PickServer/PickServerViewController.swift | 72 +++++++- .../PickServer/PickServerViewModel.swift | 165 ++++++++++++++++++ .../PickServerCategoriesCell.swift | 110 ++++++++++++ .../TableViewCell/PickServerTitleCell.swift | 48 +++++ .../View/PickServerCategoryView.swift | 107 ++++++++++++ .../View/Button/PrimaryActionButton.swift | 2 +- .../Scene/Welcome/WelcomeViewController.swift | 18 +- 14 files changed, 650 insertions(+), 7 deletions(-) create mode 100644 Mastodon/Scene/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift create mode 100644 Mastodon/Scene/PickServer/PickServerViewModel.swift create mode 100644 Mastodon/Scene/PickServer/TableViewCell/PickServerCategoriesCell.swift create mode 100644 Mastodon/Scene/PickServer/TableViewCell/PickServerTitleCell.swift create mode 100644 Mastodon/Scene/PickServer/View/PickServerCategoryView.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 821dc8a6..795aa80a 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -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 = ""; }; 0FAA101B25E10E760017CCDE /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; 0FAA102625E1126A0017CCDE /* PickServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerViewController.swift; sourceTree = ""; }; + 0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerViewModel.swift; sourceTree = ""; }; + 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerTitleCell.swift; sourceTree = ""; }; + 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoriesCell.swift; sourceTree = ""; }; + 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = ""; }; + 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryCollectionViewCell.swift; sourceTree = ""; }; 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = ""; }; 2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; }; 2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; @@ -414,11 +424,40 @@ 0FAA102525E1125D0017CCDE /* PickServer */ = { isa = PBXGroup; children = ( + 0FB3D31825E525DE00AAD544 /* CollectionViewCell */, + 0FB3D30D25E525C000AAD544 /* View */, + 0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */, 0FAA102625E1126A0017CCDE /* PickServerViewController.swift */, + 0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */, ); path = PickServer; sourceTree = ""; }; + 0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */ = { + isa = PBXGroup; + children = ( + 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */, + 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */, + ); + path = TableViewCell; + sourceTree = ""; + }; + 0FB3D30D25E525C000AAD544 /* View */ = { + isa = PBXGroup; + children = ( + 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */, + ); + path = View; + sourceTree = ""; + }; + 0FB3D31825E525DE00AAD544 /* CollectionViewCell */ = { + isa = PBXGroup; + children = ( + 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */, + ); + path = CollectionViewCell; + sourceTree = ""; + }; 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; diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index c48c60d7..bac8b6e3 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -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 diff --git a/Mastodon/Extension/UIFont.swift b/Mastodon/Extension/UIFont.swift index 63f70866..eec4f89e 100644 --- a/Mastodon/Extension/UIFont.swift +++ b/Mastodon/Extension/UIFont.swift @@ -2,7 +2,7 @@ // UIFont.swift // Mastodon // -// Created by 高原 on 2021/2/20. +// Created by BradGao on 2021/2/20. // import UIKit diff --git a/Mastodon/Extension/UIView.swift b/Mastodon/Extension/UIView.swift index 7e1ba379..48752526 100644 --- a/Mastodon/Extension/UIView.swift +++ b/Mastodon/Extension/UIView.swift @@ -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 + } } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 7b9d9eca..4c53c7fa 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -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") diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 75dc3999..a0e04cff 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -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 %@."; diff --git a/Mastodon/Scene/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift b/Mastodon/Scene/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift new file mode 100644 index 00000000..587ffbae --- /dev/null +++ b/Mastodon/Scene/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift @@ -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), + ]) + } +} diff --git a/Mastodon/Scene/PickServer/PickServerViewController.swift b/Mastodon/Scene/PickServer/PickServerViewController.swift index b05fe745..a2a4a407 100644 --- a/Mastodon/Scene/PickServer/PickServerViewController.swift +++ b/Mastodon/Scene/PickServer/PickServerViewController.swift @@ -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() + + 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 + } } diff --git a/Mastodon/Scene/PickServer/PickServerViewModel.swift b/Mastodon/Scene/PickServer/PickServerViewModel.swift new file mode 100644 index 00000000..3215462a --- /dev/null +++ b/Mastodon/Scene/PickServer/PickServerViewModel.swift @@ -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(0) + + let searchText = CurrentValueSubject(nil) + + let allServers = CurrentValueSubject<[Mastodon.Entity.Instance], Error>([]) + let searchedServers = CurrentValueSubject<[Mastodon.Entity.Instance], Error>([]) + + let nextButtonEnable = CurrentValueSubject(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) + } +} diff --git a/Mastodon/Scene/PickServer/TableViewCell/PickServerCategoriesCell.swift b/Mastodon/Scene/PickServer/TableViewCell/PickServerCategoriesCell.swift new file mode 100644 index 00000000..b324fe83 --- /dev/null +++ b/Mastodon/Scene/PickServer/TableViewCell/PickServerCategoriesCell.swift @@ -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 + } + + +} + diff --git a/Mastodon/Scene/PickServer/TableViewCell/PickServerTitleCell.swift b/Mastodon/Scene/PickServer/TableViewCell/PickServerTitleCell.swift new file mode 100644 index 00000000..0cee127a --- /dev/null +++ b/Mastodon/Scene/PickServer/TableViewCell/PickServerTitleCell.swift @@ -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), + ]) + } +} diff --git a/Mastodon/Scene/PickServer/View/PickServerCategoryView.swift b/Mastodon/Scene/PickServer/View/PickServerCategoryView.swift new file mode 100644 index 00000000..6fbabdf8 --- /dev/null +++ b/Mastodon/Scene/PickServer/View/PickServerCategoryView.swift @@ -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 + } + } + } +} diff --git a/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift b/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift index 5a68a800..1082a0ac 100644 --- a/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift +++ b/Mastodon/Scene/Share/View/Button/PrimaryActionButton.swift @@ -2,7 +2,7 @@ // PrimaryActionButton.swift // Mastodon // -// Created by 高原 on 2021/2/20. +// Created by BradGao on 2021/2/20. // import UIKit diff --git a/Mastodon/Scene/Welcome/WelcomeViewController.swift b/Mastodon/Scene/Welcome/WelcomeViewController.swift index 99aa89f9..dfe60283 100644 --- a/Mastodon/Scene/Welcome/WelcomeViewController.swift +++ b/Mastodon/Scene/Welcome/WelcomeViewController.swift @@ -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) + } +}