Download and show server list (#540)

This commit is contained in:
Nathan Mattes 2022-11-12 23:25:45 +01:00
parent cc6ec42c5c
commit ea78f884ab
8 changed files with 141 additions and 12 deletions

View File

@ -89,6 +89,8 @@
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; }; 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24C97022922F30500BAE8CB /* RefreshControl.swift */; }; C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24C97022922F30500BAE8CB /* RefreshControl.swift */; };
D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.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 */; }; 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, ); }; }; 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 */; }; 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; }; 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 = "<group>"; }; 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 = "<group>"; };
D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = "<group>"; }; D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = "<group>"; };
D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = "<group>"; };
D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = "<group>"; };
D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = "<group>"; }; D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = "<group>"; };
DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = "<group>"; }; DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = "<group>"; };
DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = "<group>"; }; DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = "<group>"; };
@ -1503,6 +1507,8 @@
children = ( children = (
D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */, D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */,
D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */, D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */,
D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */,
D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */,
); );
path = Login; path = Login;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3185,6 +3191,7 @@
DB5B54A32833BD1A00DEF8B2 /* UserListViewModel.swift in Sources */, DB5B54A32833BD1A00DEF8B2 /* UserListViewModel.swift in Sources */,
DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */, DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */,
DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */, DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */,
D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */,
DB0FCB7E27958957006C02E2 /* StatusThreadRootTableViewCell+ViewModel.swift in Sources */, DB0FCB7E27958957006C02E2 /* StatusThreadRootTableViewCell+ViewModel.swift in Sources */,
DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */, DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */,
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */, DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */,
@ -3307,6 +3314,7 @@
DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */, DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */,
DB98EB5C27B10A730082E365 /* ReportSupplementaryViewModel.swift in Sources */, DB98EB5C27B10A730082E365 /* ReportSupplementaryViewModel.swift in Sources */,
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */, DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */,
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */, DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,

View File

@ -406,7 +406,7 @@ private extension SceneCoordinator {
_viewController.viewModel = viewModel _viewController.viewModel = viewModel
viewController = _viewController viewController = _viewController
case .mastodonLogin: case .mastodonLogin:
let loginViewController = MastodonLoginViewController() let loginViewController = MastodonLoginViewController(appContext: appContext, authenticationViewModel: AuthenticationViewModel(context: appContext, coordinator: self, isAuthenticationExist: false))
loginViewController.delegate = self loginViewController.delegate = self
viewController = loginViewController viewController = loginViewController

View File

@ -0,0 +1,12 @@
//
// MastodonLoginServerTableViewCell.swift
// Mastodon
//
// Created by Nathan Mattes on 11.11.22.
//
import UIKit
class MastodonLoginServerTableViewCell: UITableViewCell {
static let reuseIdentifier = "MastodonLoginServerTableViewCell"
}

View File

@ -49,7 +49,6 @@ class MastodonLoginView: UIView {
tableView = UITableView() tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .green
//TODO: @zeitchlag Cell //TODO: @zeitchlag Cell
navigationActionView = NavigationActionView() navigationActionView = NavigationActionView()

View File

@ -6,25 +6,38 @@
// //
import UIKit import UIKit
import MastodonSDK
import MastodonCore
protocol MastodonLoginViewControllerDelegate: AnyObject { protocol MastodonLoginViewControllerDelegate: AnyObject {
func backButtonPressed(_ viewController: MastodonLoginViewController) func backButtonPressed(_ viewController: MastodonLoginViewController)
func nextButtonPressed(_ viewController: MastodonLoginViewController) func nextButtonPressed(_ viewController: MastodonLoginViewController)
} }
enum MastodonLoginViewSection: Hashable {
case servers
}
class MastodonLoginViewController: UIViewController { 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? weak var delegate: MastodonLoginViewControllerDelegate?
var dataSource: UITableViewDiffableDataSource<MastodonLoginViewSection, Mastodon.Entity.Server>?
let viewModel: MastodonLoginViewModel
let authenticationViewModel: AuthenticationViewModel
weak var appContext: AppContext?
var contentView: MastodonLoginView { var contentView: MastodonLoginView {
view as! 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) super.init(nibName: nil, bundle: nil)
viewModel.delegate = self
navigationItem.hidesBackButton = true navigationItem.hidesBackButton = true
} }
@ -37,8 +50,9 @@ class MastodonLoginViewController: UIViewController {
loginView.navigationActionView.nextButton.addTarget(self, action: #selector(MastodonLoginViewController.nextButtonPressed(_:)), for: .touchUpInside) loginView.navigationActionView.nextButton.addTarget(self, action: #selector(MastodonLoginViewController.nextButtonPressed(_:)), for: .touchUpInside)
loginView.navigationActionView.backButton.addTarget(self, action: #selector(MastodonLoginViewController.backButtonPressed(_:)), for: .touchUpInside) loginView.navigationActionView.backButton.addTarget(self, action: #selector(MastodonLoginViewController.backButtonPressed(_:)), for: .touchUpInside)
loginView.searchTextField.addTarget(self, action: #selector(MastodonLoginViewController.textfieldDidChange(_:)), for: .editingChanged) loginView.searchTextField.addTarget(self, action: #selector(MastodonLoginViewController.textfieldDidChange(_:)), for: .editingChanged)
loginView.tableView.delegate = self
//TODO: Set tableView.delegate and tableView.dataSource loginView.tableView.register(MastodonLoginServerTableViewCell.self, forCellReuseIdentifier: MastodonLoginServerTableViewCell.reuseIdentifier)
loginView.navigationActionView.nextButton.isEnabled = false
view = loginView view = loginView
} }
@ -46,10 +60,35 @@ class MastodonLoginViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
let dataSource = UITableViewDiffableDataSource<MastodonLoginViewSection, Mastodon.Entity.Server>(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() } defer { setupNavigationBarBackgroundView() }
setupOnboardingAppearance() setupOnboardingAppearance()
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
viewModel.updateServers()
}
//MARK: - Actions //MARK: - Actions
@objc func backButtonPressed(_ sender: Any) { @objc func backButtonPressed(_ sender: Any) {
@ -108,10 +147,36 @@ class MastodonLoginViewController: UIViewController {
@objc func textfieldDidChange(_ textField: UITextField) { @objc func textfieldDidChange(_ textField: UITextField) {
print(textField.text ?? "---") print(textField.text ?? "---")
//TODO: @zeitschlag filter server-list, update tableview
contentView.navigationActionView.nextButton.isEnabled = false
} }
} }
// MARK: - OnboardingViewControllerAppearance // MARK: - OnboardingViewControllerAppearance
extension MastodonLoginViewController: 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<MastodonLoginViewSection, Mastodon.Entity.Server>()
snapshot.appendSections([MastodonLoginViewSection.servers])
snapshot.appendItems(viewModel.serverList)
dataSource?.applySnapshot(snapshot, animated: true)
}
}

View File

@ -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<AnyCancellable>()
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)
}
}

View File

@ -12,8 +12,8 @@ import MastodonSDK
extension APIService { extension APIService {
public func servers( public func servers(
language: String?, language: String? = nil,
category: String? category: String? = nil
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Server]>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Server]>, Error> {
let query = Mastodon.API.Onboarding.ServersQuery(language: language, category: category) let query = Mastodon.API.Onboarding.ServersQuery(language: language, category: category)
return Mastodon.API.Onboarding.servers(session: session, query: query) return Mastodon.API.Onboarding.servers(session: session, query: query)

View File

@ -9,7 +9,7 @@ import Foundation
extension Mastodon.Entity { extension Mastodon.Entity {
public struct Server: Codable, Equatable { public struct Server: Codable, Equatable, Hashable {
public let domain: String public let domain: String
public let version: String public let version: String
public let description: String public let description: String
@ -22,7 +22,7 @@ extension Mastodon.Entity {
public let approvalRequired: Bool public let approvalRequired: Bool
public let language: String public let language: String
public let category: 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 { enum CodingKeys: String, CodingKey {
case domain case domain