Fix crash when fetching recommended instances fails

This commit is contained in:
Shadowfacts 2021-08-12 19:36:28 -04:00
parent 1d79918a94
commit 85e1e131f6
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
1 changed files with 72 additions and 21 deletions

View File

@ -50,6 +50,11 @@ class InstanceSelectorTableViewController: UITableViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// disable transparent background when scrolled to top because it gets weird with animating table items in and out
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
navigationItem.scrollEdgeAppearance = appearance
tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell) tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell)
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
@ -120,14 +125,18 @@ class InstanceSelectorTableViewController: UITableViewController {
client.run(request) { (response) in client.run(request) { (response) in
var snapshot = self.dataSource.snapshot() var snapshot = self.dataSource.snapshot()
if snapshot.indexOfSection(.selected) != nil { if snapshot.indexOfSection(.selected) != nil {
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .selected)) snapshot.deleteSections([.selected])
} }
if case let .success(instance, _) = response { if case let .success(instance, _) = response {
if !snapshot.sectionIdentifiers.contains(.selected) { if snapshot.indexOfSection(.recommendedInstances) != nil {
snapshot.insertSections([.selected], beforeSection: .recommendedInstances)
} else {
snapshot.appendSections([.selected]) snapshot.appendSections([.selected])
} }
snapshot.appendItems([.selected(url, instance)], toSection: .selected) snapshot.appendItems([.selected(url, instance)], toSection: .selected)
DispatchQueue.main.async { DispatchQueue.main.async {
self.dataSource.apply(snapshot) self.dataSource.apply(snapshot)
} }
@ -137,14 +146,47 @@ class InstanceSelectorTableViewController: UITableViewController {
private func loadRecommendedInstances() { private func loadRecommendedInstances() {
InstanceSelector.getInstances(category: nil) { (response) in InstanceSelector.getInstances(category: nil) { (response) in
guard case let .success(instances, _) = response else { fatalError() } DispatchQueue.main.async {
switch response {
self.recommendedInstances = instances case let .failure(error):
self.filterRecommendedResults() self.showRecommendationsError(error)
case let .success(instances, _):
self.recommendedInstances = instances
self.filterRecommendedResults()
}
}
} }
} }
func filterRecommendedResults() { private func showRecommendationsError(_ error: Client.Error) {
let footer = UITableViewHeaderFooterView()
footer.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .secondaryLabel
label.textAlignment = .center
label.font = .boldSystemFont(ofSize: 17)
label.numberOfLines = 0
label.text = "Could not fetch suggested instances: \(error.localizedDescription)"
footer.contentView.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalToSystemSpacingAfter: footer.contentView.leadingAnchor, multiplier: 1),
footer.contentView.trailingAnchor.constraint(equalToSystemSpacingAfter: label.trailingAnchor, multiplier: 1),
label.topAnchor.constraint(equalTo: footer.contentView.topAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: footer.contentView.bottomAnchor, constant: 8),
])
let fittingSize = CGSize(width: tableView.bounds.width - (tableView.safeAreaInsets.left + tableView.safeAreaInsets.right), height: 0)
let size = footer.systemLayoutSizeFitting(fittingSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
footer.frame = CGRect(origin: .zero, size: size)
tableView.tableFooterView = footer
}
private func filterRecommendedResults() {
let filteredInstances: [InstanceSelector.Instance] let filteredInstances: [InstanceSelector.Instance]
if let currentQuery = currentQuery, !currentQuery.isEmpty { if let currentQuery = currentQuery, !currentQuery.isEmpty {
filteredInstances = recommendedInstances.filter { filteredInstances = recommendedInstances.filter {
@ -155,12 +197,20 @@ class InstanceSelectorTableViewController: UITableViewController {
} }
var snapshot = self.dataSource.snapshot() var snapshot = self.dataSource.snapshot()
snapshot.deleteSections([.recommendedInstances]) if snapshot.indexOfSection(.recommendedInstances) != nil {
snapshot.appendSections([.recommendedInstances]) let toRemove = snapshot.itemIdentifiers(inSection: .recommendedInstances).filter {
snapshot.appendItems(filteredInstances.map { Item.recommended($0) }, toSection: .recommendedInstances) if case .recommended(_) = $0 {
DispatchQueue.main.async { return true
self.dataSource.apply(snapshot) } else {
return false
}
}
snapshot.deleteItems(toRemove)
} else {
snapshot.appendSections([.recommendedInstances])
} }
snapshot.appendItems(filteredInstances.map { Item.recommended($0) }, toSection: .recommendedInstances)
self.dataSource.apply(snapshot)
} }
// MARK: - Table view delegate // MARK: - Table view delegate
@ -194,29 +244,30 @@ extension InstanceSelectorTableViewController {
case recommended(InstanceSelector.Instance) case recommended(InstanceSelector.Instance)
static func ==(lhs: Item, rhs: Item) -> Bool { static func ==(lhs: Item, rhs: Item) -> Bool {
if case let .selected(url, instance) = lhs, switch (lhs, rhs) {
case let .selected(otherUrl, other) = rhs { case let (.selected(urlA, instanceA), .selected(urlB, instanceB)):
return url == otherUrl && instance.uri == other.uri return urlA == urlB && instanceA.uri == instanceB.uri
} else if case let .recommended(instance) = lhs, case let (.recommended(a), .recommended(b)):
case let .recommended(other) = rhs { return a.domain == b.domain
return instance.domain == other.domain default:
return false
} }
return false
} }
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
switch self { switch self {
case let .selected(url, instance): case let .selected(url, instance):
hasher.combine(Section.selected) hasher.combine(0)
hasher.combine(url) hasher.combine(url)
hasher.combine(instance.uri) hasher.combine(instance.uri)
case let .recommended(instance): case let .recommended(instance):
hasher.combine(Section.recommendedInstances) hasher.combine(1)
hasher.combine(instance.domain) hasher.combine(instance.domain)
} }
} }
} }
class DataSource: UITableViewDiffableDataSource<Section, Item> { class DataSource: UITableViewDiffableDataSource<Section, Item> {
} }
} }