From df8e0dedd4de56cd5da21d1fa7fd988d1d29c33d Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 15 Sep 2019 15:01:35 -0400 Subject: [PATCH] Add instance selector --- Pachyderm/Utilities/InstanceSelector.swift | 81 ++++++++ Tusker.xcodeproj/project.pbxproj | 28 ++- Tusker/AppDelegate.swift | 2 +- .../InstanceSelectorTableViewController.swift | 194 ++++++++++++++++++ .../Onboarding/OnboardingViewController.swift | 46 ++--- .../Onboarding/OnboardingViewController.xib | 63 ------ .../Instance Cell/InstanceTableViewCell.swift | 89 ++++++++ .../Instance Cell/InstanceTableViewCell.xib | 82 ++++++++ 8 files changed, 486 insertions(+), 99 deletions(-) create mode 100644 Pachyderm/Utilities/InstanceSelector.swift create mode 100644 Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift delete mode 100644 Tusker/Screens/Onboarding/OnboardingViewController.xib create mode 100644 Tusker/Views/Instance Cell/InstanceTableViewCell.swift create mode 100644 Tusker/Views/Instance Cell/InstanceTableViewCell.xib diff --git a/Pachyderm/Utilities/InstanceSelector.swift b/Pachyderm/Utilities/InstanceSelector.swift new file mode 100644 index 00000000..e0341f45 --- /dev/null +++ b/Pachyderm/Utilities/InstanceSelector.swift @@ -0,0 +1,81 @@ +// +// InstanceSelector.swift +// Pachyderm +// +// Created by Shadowfacts on 9/15/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation + +public class InstanceSelector { + + private static let decoder = JSONDecoder() + + public static func getInstances(category: String?, completion: @escaping Client.Callback<[Instance]>) { + let url: URL + if let category = category { + url = URL(string: "https://api.joinmastodon.org/servers?category=\(category)")! + } else { + url = URL(string: "https://api.joinmastodon.org/servers")! + } + let request = URLRequest(url: url) + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + completion(.failure(error)) + return + } + guard let data = data, + let response = response as? HTTPURLResponse else { + completion(.failure(Client.Error.invalidResponse)) + return + } + guard response.statusCode == 200 else { + completion(.failure(Client.Error.unknownError)) + return + } + guard let result = try? decoder.decode([Instance].self, from: data) else { + completion(.failure(Client.Error.invalidModel)) + return + } + completion(.success(result, nil)) + } + task.resume() + } + +} + +public extension InstanceSelector { + struct Instance: Codable { + public let domain: String + public let description: String + public let proxiedThumbnailURL: URL + public let language: String + public let category: Category + + enum CodingKeys: String, CodingKey { + case domain + case description + case proxiedThumbnailURL = "proxied_thumbnail" + case language + case category + } + } +} + +public extension InstanceSelector { + enum Category: String, Codable { + // source: https://source.joinmastodon.org/mastodon/joinmastodon/blob/master/src/Wizard.js#L108 + case general + case regional + case art + case journalism + case activism + case lgbt + case games + case tech + case adult + case furry + case food + } +} diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index d5547754..278c9a4f 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -72,6 +72,10 @@ D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; }; D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; }; D6163F2C21AA0AF1008DAC41 /* MyProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6163F2B21AA0AF1008DAC41 /* MyProfileTableViewController.swift */; }; + D61AC1D3232E928600C54D2D /* InstanceSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D2232E928600C54D2D /* InstanceSelector.swift */; }; + D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; + D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; + D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF75217E923E00CC0648 /* DraftsManager.swift */; }; D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF78217E950100CC0648 /* DraftsTableViewController.xib */; }; D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */; }; @@ -157,7 +161,6 @@ D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */; }; D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC8D2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift */; }; D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; }; - D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; }; D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; }; D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */; }; D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; }; @@ -327,6 +330,10 @@ D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTableViewCell.swift; sourceTree = ""; }; D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HashtagTableViewCell.xib; sourceTree = ""; }; D6163F2B21AA0AF1008DAC41 /* MyProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileTableViewController.swift; sourceTree = ""; }; + D61AC1D2232E928600C54D2D /* InstanceSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelector.swift; sourceTree = ""; }; + D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = ""; }; + D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = ""; }; + D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = ""; }; D627FF75217E923E00CC0648 /* DraftsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsManager.swift; sourceTree = ""; }; D627FF78217E950100CC0648 /* DraftsTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftsTableViewController.xib; sourceTree = ""; }; D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsTableViewController.swift; sourceTree = ""; }; @@ -411,7 +418,6 @@ D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountTableViewCell.xib; sourceTree = ""; }; D6A3BC8D2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActionAccountListTableViewController.swift; sourceTree = ""; }; D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = ""; }; - D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = ""; }; D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = ""; }; D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMesasgeActivity.swift; sourceTree = ""; }; D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = ""; }; @@ -674,6 +680,15 @@ path = "Hashtag Cell"; sourceTree = ""; }; + D61AC1DA232EA43100C54D2D /* Instance Cell */ = { + isa = PBXGroup; + children = ( + D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */, + D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */, + ); + path = "Instance Cell"; + sourceTree = ""; + }; D627FF77217E94F200CC0648 /* Drafts */ = { isa = PBXGroup; children = ( @@ -737,8 +752,8 @@ D641C783213DD7FE004B4513 /* Onboarding */ = { isa = PBXGroup; children = ( - D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */, D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */, + D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */, ); path = Onboarding; sourceTree = ""; @@ -980,6 +995,7 @@ D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */, D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */, D667383B23299340000A2373 /* InstanceType.swift */, + D61AC1D2232E928600C54D2D /* InstanceSelector.swift */, ); path = Utilities; sourceTree = ""; @@ -1055,6 +1071,7 @@ D641C78C213DD937004B4513 /* Notifications */, D6A3BC872321F78000FD64D5 /* Account Cell */, D611C2CC232DC5FC00C86A49 /* Hashtag Cell */, + D61AC1DA232EA43100C54D2D /* Instance Cell */, ); path = Views; sourceTree = ""; @@ -1418,6 +1435,7 @@ buildActionMask = 2147483647; files = ( D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */, + D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */, D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */, D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */, D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */, @@ -1425,7 +1443,6 @@ D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */, D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */, D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */, - D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */, D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */, D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */, D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */, @@ -1478,6 +1495,7 @@ D6109A0D214599E100432DC2 /* RequestRange.swift in Sources */, D61099D92144B76400432DC2 /* Data.swift in Sources */, D61099EB2145661700432DC2 /* ConversationContext.swift in Sources */, + D61AC1D3232E928600C54D2D /* InstanceSelector.swift in Sources */, D61099C92144B13C00432DC2 /* Client.swift in Sources */, D61099D42144B32E00432DC2 /* Parameter.swift in Sources */, D61099CB2144B20500432DC2 /* Request.swift in Sources */, @@ -1587,6 +1605,7 @@ 04496BD721625361001F1B23 /* ContentLabel.swift in Sources */, D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */, D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */, + D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */, D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */, D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */, D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */, @@ -1610,6 +1629,7 @@ D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */, 0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */, D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */, + D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */, D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */, D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */, D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */, diff --git a/Tusker/AppDelegate.swift b/Tusker/AppDelegate.swift index 730fce2e..df476a2a 100644 --- a/Tusker/AppDelegate.swift +++ b/Tusker/AppDelegate.swift @@ -73,7 +73,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func showOnboardingUI() { let onboarding = OnboardingViewController() - onboarding.delegate = self + onboarding.onboardingDelegate = self window!.rootViewController = onboarding } diff --git a/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift b/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift new file mode 100644 index 00000000..ea03e883 --- /dev/null +++ b/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift @@ -0,0 +1,194 @@ +// +// InstanceSelectorTableViewController.swift +// Tusker +// +// Created by Shadowfacts on 9/15/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit +import Combine +import Pachyderm + +protocol InstanceSelectorTableViewControllerDelegate { + func didSelectInstance(url: URL) +} + +fileprivate let instanceCell = "instanceCell" + +class InstanceSelectorTableViewController: UITableViewController { + + var delegate: InstanceSelectorTableViewControllerDelegate? + + var dataSource: DataSource! + var searchController: UISearchController! + + var recommendedInstances: [InstanceSelector.Instance] = [] + + let urlCheckerSubject = PassthroughSubject() + var currentQuery: String? + + init() { + super.init(style: .grouped) + + title = NSLocalizedString("Choose Your Instance", comment: "onboarding screen title") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell) + + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 120 + + dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in + switch item { + case let .selected(instance): + let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell + cell.updateUI(instance: instance) + cell.delegate = self + return cell + case let .recommended(instance): + let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell + cell.updateUI(instance: instance) + cell.delegate = self + return cell + } + }) + + searchController = UISearchController(searchResultsController: nil) + searchController.searchResultsUpdater = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.searchTextField.autocapitalizationType = .none + navigationItem.searchController = searchController + definesPresentationContext = true + + _ = urlCheckerSubject + .debounce(for: .seconds(1), scheduler: RunLoop.main) + .compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) } + .sink(receiveValue: updateSpecificInstance) + + loadRecommendedInstances() + } + + private func updateSpecificInstance(domain: String) { + var components = URLComponents() + if domain.contains("://") { + let parts = domain.components(separatedBy: "://") + components.scheme = parts[0] + components.host = parts[1] + } else { + components.host = domain + } + if components.scheme != "https" && components.scheme != "http" { + components.scheme = "https" + } + components.path = "/" + + let client = Client(baseURL: components.url!) + let request = client.getInstance() + client.run(request) { (response) in + var snapshot = self.dataSource.snapshot() + snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .selected)) + + if case let .success(instance, _) = response { + if !snapshot.sectionIdentifiers.contains(.selected) { + snapshot.appendSections([.selected]) + } + snapshot.appendItems([.selected(instance)], toSection: .selected) + DispatchQueue.main.async { + self.dataSource.apply(snapshot) + } + } + } + } + + private func loadRecommendedInstances() { + InstanceSelector.getInstances(category: nil) { (response) in + guard case let .success(instances, _) = response else { fatalError() } + + self.recommendedInstances = instances + self.filterRecommendedResults() + } + } + + func filterRecommendedResults() { + let filteredInstances: [InstanceSelector.Instance] + if let currentQuery = currentQuery, !currentQuery.isEmpty { + filteredInstances = recommendedInstances.filter { + $0.domain.contains(currentQuery) || $0.description.lowercased().contains(currentQuery) + } + } else { + filteredInstances = recommendedInstances + } + + var snapshot = self.dataSource.snapshot() + snapshot.deleteSections([.recommendedInstances]) + snapshot.appendSections([.recommendedInstances]) + snapshot.appendItems(filteredInstances.map { Item.recommended($0) }, toSection: .recommendedInstances) + DispatchQueue.main.async { + self.dataSource.apply(snapshot) + } + } +} + +extension InstanceSelectorTableViewController { + enum Section { + case selected + case recommendedInstances + } + enum Item: Equatable, Hashable { + case selected(Instance) + case recommended(InstanceSelector.Instance) + + static func ==(lhs: Item, rhs: Item) -> Bool { + if case let .selected(instance) = lhs, + case let .selected(other) = rhs { + return instance.uri == other.uri + } else if case let .recommended(instance) = lhs, + case let .recommended(other) = rhs { + return instance.domain == other.domain + } + return false + } + + func hash(into hasher: inout Hasher) { + switch self { + case let .selected(instance): + hasher.combine(Section.selected) + hasher.combine(instance.uri) + case let .recommended(instance): + hasher.combine(Section.recommendedInstances) + hasher.combine(instance.domain) + } + } + } + class DataSource: UITableViewDiffableDataSource { + } +} + +extension InstanceSelectorTableViewController: UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + currentQuery = searchController.searchBar.text?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + filterRecommendedResults() + urlCheckerSubject.send(currentQuery) + } +} + +extension InstanceSelectorTableViewController: InstanceTableViewCellDelegate { + func didSelectInstance(_ instance: Instance) { + delegate?.didSelectInstance(url: URL(string: instance.uri)!) + } + func didSelectInstance(_ instance: InstanceSelector.Instance) { + var components = URLComponents() + components.scheme = "https" + components.host = instance.domain + components.path = "/" + delegate?.didSelectInstance(url: components.url!) + } +} diff --git a/Tusker/Screens/Onboarding/OnboardingViewController.swift b/Tusker/Screens/Onboarding/OnboardingViewController.swift index b2c7762c..6e25933f 100644 --- a/Tusker/Screens/Onboarding/OnboardingViewController.swift +++ b/Tusker/Screens/Onboarding/OnboardingViewController.swift @@ -10,12 +10,10 @@ import UIKit import AuthenticationServices protocol OnboardingViewControllerDelegate { - func didFinishOnboarding() - } -class OnboardingViewController: UIViewController { +class OnboardingViewController: UINavigationController { static var blocks: [NSRegularExpression] = { guard let path = Bundle.main.path(forResource: "DomainBlocks", ofType: "plist"), @@ -23,14 +21,14 @@ class OnboardingViewController: UIViewController { return array.compactMap { try? NSRegularExpression(pattern: $0, options: .caseInsensitive) } }() - var delegate: OnboardingViewControllerDelegate? + var onboardingDelegate: OnboardingViewControllerDelegate? - @IBOutlet weak var urlTextField: UITextField! + var instanceSelector = InstanceSelectorTableViewController() var authenticationSession: ASWebAuthenticationSession? init() { - super.init(nibName: "OnboardingViewController", bundle: nil) + super.init(rootViewController: instanceSelector) } required init?(coder aDecoder: NSCoder) { @@ -39,21 +37,14 @@ class OnboardingViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + + instanceSelector.delegate = self } - - @IBAction func loginPressed(_ sender: Any) { - guard let text = urlTextField.text, - let url = URL(string: text), - var components = URLComponents(string: text), - let host = components.host, - !OnboardingViewController.blocks.contains(where: { - $0.numberOfMatches(in: host, range: NSRange(host.startIndex.. 0 - }) else { - return - } - - - + +} + +extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate { + func didSelectInstance(url: URL) { LocalData.shared.instanceURL = url MastodonController.createClient() MastodonController.registerApp { @@ -61,6 +52,7 @@ class OnboardingViewController: UIViewController { let callbackURL = "tusker://oauth" + var components = URLComponents(url: url, resolvingAgainstBaseURL: false)! components.path = "/oauth/authorize" components.queryItems = [ URLQueryItem(name: "client_id", value: clientID), @@ -68,9 +60,9 @@ class OnboardingViewController: UIViewController { URLQueryItem(name: "scope", value: "read write follow"), URLQueryItem(name: "redirect_uri", value: callbackURL) ] - let url = components.url! + let authorizeURL = components.url! - self.authenticationSession = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL) { url, error in + self.authenticationSession = ASWebAuthenticationSession(url: authorizeURL, callbackURLScheme: callbackURL) { url, error in guard error == nil, let url = url, let components = URLComponents(url: url, resolvingAgainstBaseURL: true), @@ -79,7 +71,7 @@ class OnboardingViewController: UIViewController { MastodonController.authorize(authorizationCode: authCode) { DispatchQueue.main.async { - self.delegate?.didFinishOnboarding() + self.onboardingDelegate?.didFinishOnboarding() } } } @@ -89,14 +81,6 @@ class OnboardingViewController: UIViewController { } } } - - @IBAction func clearDataPressed(_ sender: Any) { - LocalData.shared.instanceURL = nil - LocalData.shared.clientID = nil - LocalData.shared.clientSecret = nil - LocalData.shared.accessToken = nil - } - } extension OnboardingViewController: ASWebAuthenticationPresentationContextProviding { diff --git a/Tusker/Screens/Onboarding/OnboardingViewController.xib b/Tusker/Screens/Onboarding/OnboardingViewController.xib deleted file mode 100644 index 2f746a76..00000000 --- a/Tusker/Screens/Onboarding/OnboardingViewController.xib +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tusker/Views/Instance Cell/InstanceTableViewCell.swift b/Tusker/Views/Instance Cell/InstanceTableViewCell.swift new file mode 100644 index 00000000..0ebcd05f --- /dev/null +++ b/Tusker/Views/Instance Cell/InstanceTableViewCell.swift @@ -0,0 +1,89 @@ +// +// InstanceTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 9/15/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +protocol InstanceTableViewCellDelegate { + func didSelectInstance(_ instance: Instance) + func didSelectInstance(_ instance: InstanceSelector.Instance) +} + +class InstanceTableViewCell: UITableViewCell { + + var delegate: InstanceTableViewCellDelegate? + + @IBOutlet weak var thumbnailImageView: UIImageView! + @IBOutlet weak var domainLabel: UILabel! + @IBOutlet weak var adultLabel: UILabel! + @IBOutlet weak var descriptionLabel: ContentLabel! + + var instance: Instance? + var selectorInstance: InstanceSelector.Instance? + + var thumbnailURL: URL? + + override func awakeFromNib() { + super.awakeFromNib() + + thumbnailImageView.layer.masksToBounds = true + thumbnailImageView.layer.cornerRadius = 5 + + adultLabel.layer.masksToBounds = true + adultLabel.layer.cornerRadius = 0.5 * adultLabel.bounds.height + } + + func updateUI(instance: InstanceSelector.Instance) { + self.selectorInstance = instance + self.instance = nil + + domainLabel.text = instance.domain + adultLabel.isHidden = instance.category != .adult + descriptionLabel.setTextFromHtml(instance.description) + updateThumbnail(url: instance.proxiedThumbnailURL) + } + + func updateUI(instance: Instance) { + self.instance = instance + self.selectorInstance = nil + + domainLabel.text = URLComponents(string: instance.uri)?.host ?? instance.uri + adultLabel.isHidden = true + descriptionLabel.setTextFromHtml(instance.description) + + if let thumbnail = instance.thumbnail { + updateThumbnail(url: thumbnail) + } else { + thumbnailImageView.image = nil + } + } + + private func updateThumbnail(url: URL) { + thumbnailImageView.image = nil + thumbnailURL = url + ImageCache.attachments.get(url) { (data) in + guard self.thumbnailURL == url, let data = data, let image = UIImage(data: data) else { return } + DispatchQueue.main.async { + self.thumbnailImageView.image = image + } + } + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + if selected, let delegate = delegate { + if let instance = instance { + delegate.didSelectInstance(instance) + } else if let instance = selectorInstance { + delegate.didSelectInstance(instance) + } + } + } + +} diff --git a/Tusker/Views/Instance Cell/InstanceTableViewCell.xib b/Tusker/Views/Instance Cell/InstanceTableViewCell.xib new file mode 100644 index 00000000..99a48b7f --- /dev/null +++ b/Tusker/Views/Instance Cell/InstanceTableViewCell.xib @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +