From 515b3d476797362d4fb9c0786166304cc69381ac Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 20 Dec 2022 22:54:36 +0100 Subject: [PATCH] [WIP] Add language-selection to onboarding (#690) Consider this WIP, for now, languages are hard-coded --- Mastodon.xcodeproj/project.pbxproj | 4 - .../PickServer/CategoryPickerSection.swift | 26 +++-- ...PickServerCategoryCollectionViewCell.swift | 99 ++++++++++++++----- .../MastodonPickServerViewController.swift | 7 +- ...MastodonPickServerViewModel+Diffable.swift | 3 +- .../MastodonPickServerViewModel.swift | 43 ++++++-- .../View/PickServerCategoryView.swift | 78 --------------- 7 files changed, 137 insertions(+), 123 deletions(-) delete mode 100644 Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 8a6f8d6f8..dc8f2f6ae 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101B25E10E760017CCDE /* UIFont.swift */; }; 0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */; }; 0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */; }; - 0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */; }; 0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D31D25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift */; }; 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; }; 164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; }; @@ -544,7 +543,6 @@ 0FAA101B25E10E760017CCDE /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; 0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerViewController.swift; sourceTree = ""; }; 0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerViewModel.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 = ""; }; 0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = ""; }; 164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = ""; }; @@ -1224,7 +1222,6 @@ isa = PBXGroup; children = ( D8363B1529469CE200A74079 /* OnboardingNextView.swift */, - 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */, DB9282B125F3222800823B15 /* PickServerEmptyStateView.swift */, DB0617F0278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift */, ); @@ -3487,7 +3484,6 @@ DB3E6FFA2807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift in Sources */, DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */, DBB45B6027B50A4F002DC5A7 /* RecommendAccountItem.swift in Sources */, - 0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */, DB6180E626391B550018D199 /* MediaPreviewTransitionController.swift in Sources */, DB0FCB922796DE19006C02E2 /* TrendSectionHeaderCollectionReusableView.swift in Sources */, DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */, diff --git a/Mastodon/Scene/Onboarding/PickServer/CategoryPickerSection.swift b/Mastodon/Scene/Onboarding/PickServer/CategoryPickerSection.swift index 8f7480a84..84e73d98e 100644 --- a/Mastodon/Scene/Onboarding/PickServer/CategoryPickerSection.swift +++ b/Mastodon/Scene/Onboarding/PickServer/CategoryPickerSection.swift @@ -16,15 +16,25 @@ enum CategoryPickerSection: Equatable, Hashable { extension CategoryPickerSection { static func collectionViewDiffableDataSource( for collectionView: UICollectionView, - dependency: NeedsDependency + dependency: NeedsDependency, + buttonDelegate: PickServerCategoryCollectionViewCellDelegate? ) -> UICollectionViewDiffableDataSource { UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in guard let _ = dependency else { return nil } - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell - cell.categoryView.titleLabel.text = item.title + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PickServerCategoryCollectionViewCell.reuseIdentifier, for: indexPath) as! PickServerCategoryCollectionViewCell + + cell.titleLabel.text = item.title + cell.delegate = buttonDelegate let isLanguage = (item == .language(language: nil)) - cell.categoryView.chevron.isHidden = (isLanguage == false) + if isLanguage { + cell.chevron.isHidden = false + cell.menuButton.isUserInteractionEnabled = true + + } else { + cell.chevron.isHidden = true + cell.menuButton.isUserInteractionEnabled = false + } cell.observe(\.isSelected, options: [.initial, .new]) { cell, _ in @@ -42,10 +52,10 @@ extension CategoryPickerSection { borderColor = .separator } - cell.categoryView.backgroundColor = backgroundColor - cell.categoryView.titleLabel.textColor = textColor - cell.categoryView.layer.borderColor = borderColor.cgColor - cell.categoryView.chevron.tintColor = textColor + cell.backgroundColor = backgroundColor + cell.titleLabel.textColor = textColor + cell.layer.borderColor = borderColor.cgColor + cell.chevron.tintColor = textColor } .store(in: &cell.observations) diff --git a/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift b/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift index 89ca8267b..cdbf713bc 100644 --- a/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/CollectionViewCell/PickServerCategoryCollectionViewCell.swift @@ -6,13 +6,51 @@ // import UIKit +import MastodonSDK +import MastodonAsset +import MastodonUI +import MastodonLocalization + +protocol PickServerCategoryCollectionViewCellDelegate: AnyObject { + func didPressMenuButton(in cell: PickServerCategoryCollectionViewCell) //TODO: Add item +} class PickServerCategoryCollectionViewCell: UICollectionViewCell { + + static let reuseIdentifier = "PickServerCategoryCollectionViewCell" + weak var delegate: PickServerCategoryCollectionViewCellDelegate? + + let titleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.font = .systemFont(ofSize: 15, weight: .regular) + label.textColor = Asset.Colors.Label.secondary.color + return label + }() + + let chevron: UIImageView = { + let chevron = UIImageView(image: UIImage(systemName: "chevron.down")) + chevron.translatesAutoresizingMaskIntoConstraints = false + return chevron + }() + + let menuButton: UIButton = { + let menuButton = UIButton() + menuButton.translatesAutoresizingMaskIntoConstraints = false + return menuButton + }() + + private let container: UIStackView = { + let container = UIStackView() + container.translatesAutoresizingMaskIntoConstraints = false + + container.axis = .horizontal + container.spacing = 4 + container.distribution = .fillProportionally + return container + }() var observations = Set() - - var categoryView = PickServerCategoryView() - override func prepareForReuse() { super.prepareForReuse() observations.removeAll() @@ -20,26 +58,41 @@ class PickServerCategoryCollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: .zero) - configure() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - configure() - } -} -extension PickServerCategoryCollectionViewCell { - private func configure() { - backgroundColor = .clear - - categoryView.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(categoryView) - NSLayoutConstraint.activate([ - categoryView.topAnchor.constraint(equalTo: contentView.topAnchor), - categoryView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - categoryView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: categoryView.bottomAnchor), - ]) + container.addArrangedSubview(titleLabel) + container.addArrangedSubview(chevron) + + menuButton.addTarget(self, action: #selector(PickServerCategoryCollectionViewCell.didPressButton(_:)), for: .touchUpInside) + + layer.borderColor = UIColor.black.cgColor + layer.borderWidth = 1.0 + applyCornerRadius(radius: 15) + + contentView.addSubview(container) + contentView.addSubview(menuButton) + + setupConstraints() } + + private func setupConstraints() { + + var constraints = [ + container.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6), + container.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12), + contentView.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 12), + contentView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 6), + ] + + constraints.append(contentsOf: menuButton.pinToParent()) + NSLayoutConstraint.activate(constraints) + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + //MARK: - Actions + + @objc func didPressButton(_ sender: Any) { + delegate?.didPressMenuButton(in: self) + } + } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift index ca3aa9d06..3b7d9d0c7 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift @@ -392,7 +392,12 @@ extension MastodonPickServerViewController: PickServerServerSectionTableHeaderVi guard let diffableDataSource = headerView.diffableDataSource else { return } let item = diffableDataSource.itemIdentifier(for: indexPath) //TODO: @zeitschlag Consider language etc. also: show menu - viewModel.selectCategoryItem.value = item ?? .all + + if case let .language(_) = item { + + } else { + viewModel.selectCategoryItem.value = item ?? .all + } } } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift index 4bc31d1d6..2a22ebbbc 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift @@ -18,7 +18,8 @@ extension MastodonPickServerViewModel { // set section header serverSectionHeaderView.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource( for: serverSectionHeaderView.collectionView, - dependency: dependency + dependency: dependency, + buttonDelegate: self ) var sectionHeaderSnapshot = NSDiffableDataSourceSnapshot() sectionHeaderSnapshot.appendSections([.main]) diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift index 544bf804f..cec246cdd 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift @@ -15,6 +15,7 @@ import OrderedCollections import Tabman import MastodonCore import MastodonUI +import MastodonLocalization class MastodonPickServerViewModel: NSObject { @@ -39,6 +40,7 @@ class MastodonPickServerViewModel: NSObject { }() let selectCategoryItem = CurrentValueSubject(.all) let searchText = CurrentValueSubject("") + let selectedLanguage = CurrentValueSubject(nil) let indexedServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([]) let unindexedServers = CurrentValueSubject<[Mastodon.Entity.Server]?, Never>([]) // set nil when loading let viewWillAppear = PassthroughSubject() @@ -101,12 +103,13 @@ extension MastodonPickServerViewModel { .assign(to: \.value, on: emptyStateViewState) .store(in: &disposeBag) - Publishers.CombineLatest3( + Publishers.CombineLatest4( indexedServers.eraseToAnyPublisher(), selectCategoryItem.eraseToAnyPublisher(), - searchText.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main).removeDuplicates() + searchText.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main).removeDuplicates(), + selectedLanguage.eraseToAnyPublisher() ) - .map { indexedServers, selectCategoryItem, searchText -> [Mastodon.Entity.Server] in + .map { indexedServers, selectCategoryItem, searchText, selectedLanguage -> [Mastodon.Entity.Server] in // ignore approval required servers when sign-up var indexedServers = indexedServers indexedServers = indexedServers.filter { !$0.approvalRequired } @@ -156,12 +159,11 @@ extension MastodonPickServerViewModel { // Filter the indexed servers by category or search text switch selectCategoryItem { case .all: - return MastodonPickServerViewModel.filterServers(servers: indexedServers, category: nil, searchText: searchText) - case .language(let language): - //TODO: @zeitschlag Cache selected language - return MastodonPickServerViewModel.filterServers(servers: indexedServers, language: "de", category: nil, searchText: searchText) + return MastodonPickServerViewModel.filterServers(servers: indexedServers, language: selectedLanguage, category: nil, searchText: searchText) + case .language(_): + return MastodonPickServerViewModel.filterServers(servers: indexedServers, language: selectedLanguage, category: nil, searchText: searchText) case .category(let category): - return MastodonPickServerViewModel.filterServers(servers: indexedServers, category: category.category.rawValue, searchText: searchText) + return MastodonPickServerViewModel.filterServers(servers: indexedServers, language: selectedLanguage, category: category.category.rawValue, searchText: searchText) } } .assign(to: \.value, on: filteredIndexedServers) @@ -266,3 +268,28 @@ extension MastodonPickServerViewModel: TMBarDataSource { return barItem } } + +extension MastodonPickServerViewModel: PickServerCategoryCollectionViewCellDelegate { + func didPressMenuButton(in cell: PickServerCategoryCollectionViewCell) { + + let allLanguagesAction = UIAction(title: "All") { _ in + self.selectedLanguage.value = nil + cell.titleLabel.text = L10n.Scene.ServerPicker.Button.language + } + + let languageActions = ["de", "en"].compactMap { language in + UIAction(title: language) { action in + self.selectedLanguage.value = language + cell.titleLabel.text = language + } + } + + var allActions = [allLanguagesAction] + allActions.append(contentsOf: languageActions) + + let languageMenu = UIMenu(title: L10n.Scene.ServerPicker.Button.language, + children: allActions) + + cell.menuButton.menu = languageMenu + } +} diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift deleted file mode 100644 index 88c76c5f7..000000000 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// PickServerCategoryView.swift -// Mastodon -// -// Created by BradGao on 2021/2/23. -// - -import UIKit -import MastodonSDK -import MastodonAsset -import MastodonUI -import MastodonLocalization - -class PickServerCategoryView: UIView { - - let titleLabel: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.font = .systemFont(ofSize: 15, weight: .regular) - label.textColor = Asset.Colors.Label.secondary.color - return label - }() - - let chevron: UIImageView = { - let chevron = UIImageView(image: UIImage(systemName: "chevron.down")) - chevron.translatesAutoresizingMaskIntoConstraints = false - return chevron - }() - - init() { - super.init(frame: .zero) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - - private func _init() { - - let container = UIStackView() - container.axis = .horizontal - container.spacing = 4 - container.distribution = .fillProportionally - container.addArrangedSubview(titleLabel) - container.addArrangedSubview(chevron) - - container.translatesAutoresizingMaskIntoConstraints = false - addSubview(container) - let constraints = [ - container.topAnchor.constraint(equalTo: topAnchor, constant: 6), - container.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), - trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 12), - bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 6), - ] - - NSLayoutConstraint.activate(constraints) - - - layer.borderColor = UIColor.black.cgColor - layer.borderWidth = 1.0 - applyCornerRadius(radius: 15) - } - -} - -#if DEBUG && canImport(SwiftUI) -import SwiftUI - -struct PickServerCategoryView_Previews: PreviewProvider { - static var previews: some View { - UIViewPreview { - PickServerCategoryView() - } - } -} -#endif