From ea78f884ab5f8de1f307ae5bf2397ab459e52a1f Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sat, 12 Nov 2022 23:25:45 +0100 Subject: [PATCH] Download and show server list (#540) --- Mastodon.xcodeproj/project.pbxproj | 8 ++ Mastodon/Coordinator/SceneCoordinator.swift | 2 +- .../MastodonLoginServerTableViewCell.swift | 12 +++ .../Onboarding/Login/MastodonLoginView.swift | 1 - .../Login/MastodonLoginViewController.swift | 77 +++++++++++++++++-- .../Login/MastodonLoginViewModel.swift | 45 +++++++++++ .../Service/API/APIService+Onboarding.swift | 4 +- .../Entity/Mastodon+Entity+Server.swift | 4 +- 8 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift create mode 100644 Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 9755bb0c7..fca6eb8b5 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -89,6 +89,8 @@ 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; }; C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24C97022922F30500BAE8CB /* RefreshControl.swift */; }; D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; }; + D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; }; + D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */; }; D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; @@ -611,6 +613,8 @@ CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = ""; }; D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; }; + D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; }; + D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; }; D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = ""; }; DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = ""; }; @@ -1503,6 +1507,8 @@ children = ( D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */, D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */, + D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */, + D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */, ); path = Login; sourceTree = ""; @@ -3185,6 +3191,7 @@ DB5B54A32833BD1A00DEF8B2 /* UserListViewModel.swift in Sources */, DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */, DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */, + D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */, DB0FCB7E27958957006C02E2 /* StatusThreadRootTableViewCell+ViewModel.swift in Sources */, DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */, DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */, @@ -3307,6 +3314,7 @@ DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */, DB98EB5C27B10A730082E365 /* ReportSupplementaryViewModel.swift in Sources */, DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */, + D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */, DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */, 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index bf663b7d8..34ac09050 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -406,7 +406,7 @@ private extension SceneCoordinator { _viewController.viewModel = viewModel viewController = _viewController case .mastodonLogin: - let loginViewController = MastodonLoginViewController() + let loginViewController = MastodonLoginViewController(appContext: appContext, authenticationViewModel: AuthenticationViewModel(context: appContext, coordinator: self, isAuthenticationExist: false)) loginViewController.delegate = self viewController = loginViewController diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift new file mode 100644 index 000000000..2fb599015 --- /dev/null +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift @@ -0,0 +1,12 @@ +// +// MastodonLoginServerTableViewCell.swift +// Mastodon +// +// Created by Nathan Mattes on 11.11.22. +// + +import UIKit + +class MastodonLoginServerTableViewCell: UITableViewCell { + static let reuseIdentifier = "MastodonLoginServerTableViewCell" +} diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift index b62fe25c2..cb506a739 100644 --- a/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift @@ -49,7 +49,6 @@ class MastodonLoginView: UIView { tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.backgroundColor = .green //TODO: @zeitchlag Cell navigationActionView = NavigationActionView() diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift index 993aec13d..0c67c8705 100644 --- a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift @@ -6,25 +6,38 @@ // import UIKit +import MastodonSDK +import MastodonCore protocol MastodonLoginViewControllerDelegate: AnyObject { func backButtonPressed(_ viewController: MastodonLoginViewController) func nextButtonPressed(_ viewController: MastodonLoginViewController) } +enum MastodonLoginViewSection: Hashable { + case servers +} + class MastodonLoginViewController: UIViewController { - // back-button, next-button (enabled if user selectes a server or url is valid - // next-button does MastodonPickServerViewController.doSignIn() - weak var delegate: MastodonLoginViewControllerDelegate? + var dataSource: UITableViewDiffableDataSource? + let viewModel: MastodonLoginViewModel + let authenticationViewModel: AuthenticationViewModel + weak var appContext: AppContext? var contentView: MastodonLoginView { view as! MastodonLoginView } - init() { + init(appContext: AppContext, authenticationViewModel: AuthenticationViewModel) { + + viewModel = MastodonLoginViewModel(appContext: appContext) + self.authenticationViewModel = authenticationViewModel + self.appContext = appContext + super.init(nibName: nil, bundle: nil) + viewModel.delegate = self navigationItem.hidesBackButton = true } @@ -37,8 +50,9 @@ class MastodonLoginViewController: UIViewController { loginView.navigationActionView.nextButton.addTarget(self, action: #selector(MastodonLoginViewController.nextButtonPressed(_:)), for: .touchUpInside) loginView.navigationActionView.backButton.addTarget(self, action: #selector(MastodonLoginViewController.backButtonPressed(_:)), for: .touchUpInside) loginView.searchTextField.addTarget(self, action: #selector(MastodonLoginViewController.textfieldDidChange(_:)), for: .editingChanged) - - //TODO: Set tableView.delegate and tableView.dataSource + loginView.tableView.delegate = self + loginView.tableView.register(MastodonLoginServerTableViewCell.self, forCellReuseIdentifier: MastodonLoginServerTableViewCell.reuseIdentifier) + loginView.navigationActionView.nextButton.isEnabled = false view = loginView } @@ -46,10 +60,35 @@ class MastodonLoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + let dataSource = UITableViewDiffableDataSource(tableView: contentView.tableView) { [weak self] tableView, indexPath, itemIdentifier in + guard let cell = tableView.dequeueReusableCell(withIdentifier: MastodonLoginServerTableViewCell.reuseIdentifier, for: indexPath) as? MastodonLoginServerTableViewCell, + let self = self else { + fatalError("Wrong cell") + } + + let server = self.viewModel.serverList[indexPath.row] + var configuration = cell.defaultContentConfiguration() + configuration.text = server.domain + + cell.contentConfiguration = configuration + cell.accessoryType = .disclosureIndicator + + return cell + } + + contentView.tableView.dataSource = dataSource + self.dataSource = dataSource + defer { setupNavigationBarBackgroundView() } setupOnboardingAppearance() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewModel.updateServers() + } + //MARK: - Actions @objc func backButtonPressed(_ sender: Any) { @@ -108,10 +147,36 @@ class MastodonLoginViewController: UIViewController { @objc func textfieldDidChange(_ textField: UITextField) { print(textField.text ?? "---") + //TODO: @zeitschlag filter server-list, update tableview + + contentView.navigationActionView.nextButton.isEnabled = false } } // MARK: - OnboardingViewControllerAppearance extension MastodonLoginViewController: OnboardingViewControllerAppearance { } +//MARK: - UITableViewDelegate +extension MastodonLoginViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let server = viewModel.serverList[indexPath.row] + viewModel.selectedServer = server + contentView.searchTextField.text = server.domain + //TODO: @zeitschlag filter server list + + contentView.navigationActionView.nextButton.isEnabled = true + tableView.deselectRow(at: indexPath, animated: true) + } +} + +extension MastodonLoginViewController: MastodonLoginViewModelDelegate { + func serversUpdated(_ viewModel: MastodonLoginViewModel) { + var snapshot = NSDiffableDataSourceSnapshot() + + snapshot.appendSections([MastodonLoginViewSection.servers]) + snapshot.appendItems(viewModel.serverList) + + dataSource?.applySnapshot(snapshot, animated: true) + } +} diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift new file mode 100644 index 000000000..68810dded --- /dev/null +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift @@ -0,0 +1,45 @@ +// +// MastodonLoginViewModel.swift +// Mastodon +// +// Created by Nathan Mattes on 11.11.22. +// + +import Foundation +import MastodonSDK +import MastodonCore +import Combine + +protocol MastodonLoginViewModelDelegate: AnyObject { + func serversUpdated(_ viewModel: MastodonLoginViewModel) +} + +class MastodonLoginViewModel { + + var serverList: [Mastodon.Entity.Server] = [] + var selectedServer: Mastodon.Entity.Server? + + weak var appContext: AppContext? + weak var delegate: MastodonLoginViewModelDelegate? + var disposeBag = Set() + + init(appContext: AppContext) { + self.appContext = appContext + } + + func updateServers() { + appContext?.apiService.servers().sink(receiveCompletion: { [weak self] completion in + switch completion { + case .finished: + guard let self = self else { return } + + self.delegate?.serversUpdated(self) + case .failure(let error): + print(error) + } + }, receiveValue: { content in + let servers = content.value + self.serverList = servers + }).store(in: &disposeBag) + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift index 383763359..84104d838 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift @@ -12,8 +12,8 @@ import MastodonSDK extension APIService { public func servers( - language: String?, - category: String? + language: String? = nil, + category: String? = nil ) -> AnyPublisher, Error> { let query = Mastodon.API.Onboarding.ServersQuery(language: language, category: category) return Mastodon.API.Onboarding.servers(session: session, query: query) diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift index da1f0dfad..56b4cd327 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift @@ -9,7 +9,7 @@ import Foundation extension Mastodon.Entity { - public struct Server: Codable, Equatable { + public struct Server: Codable, Equatable, Hashable { public let domain: String public let version: String public let description: String @@ -22,7 +22,7 @@ extension Mastodon.Entity { public let approvalRequired: Bool public let language: String public let category: String - //TODO: @zeitschlag Is there a way to figure out in advance if a server accepts new registrations? Right now we'd have to query the server and it responts with a `AuthenticationViewModel.AuthenticationError.registrationClosed` + //TODO: @zeitschlag Is there a way to figure out in advance if a server accepts new registrations? Right now we'd have to query the server and it responds with a `AuthenticationViewModel.AuthenticationError.registrationClosed` enum CodingKeys: String, CodingKey { case domain