Compare commits

..

No commits in common. "13a4221fce021667f1d49b188b022f8d2acfe829" and "1e7bfac13ca39405b0a79cf5ca477728160d8a7b" have entirely different histories.

6 changed files with 27 additions and 115 deletions

View File

@ -46,9 +46,6 @@ class MastodonController: ObservableObject {
@Published private(set) var instance: Instance! @Published private(set) var instance: Instance!
private(set) var customEmojis: [Emoji]? private(set) var customEmojis: [Emoji]?
private var pendingOwnInstanceRequestCallbacks = [(Instance) -> Void]()
private var ownInstanceRequest: URLSessionTask?
var loggedIn: Bool { var loggedIn: Bool {
accountInfo != nil accountInfo != nil
} }
@ -118,56 +115,17 @@ class MastodonController: ObservableObject {
} }
} }
// todo: this should dedup requests
func getOwnInstance(completion: ((Instance) -> Void)? = nil) { func getOwnInstance(completion: ((Instance) -> Void)? = nil) {
getOwnInstanceInternal(retryAttempt: 0, completion: completion)
}
private func getOwnInstanceInternal(retryAttempt: Int, completion: ((Instance) -> Void)?) {
// this is main thread only to prevent concurrent access to ownInstanceRequest and pendingOwnInstanceRequestCallbacks
assert(Thread.isMainThread)
if let instance = self.instance { if let instance = self.instance {
completion?(instance) completion?(instance)
} else { } else {
if let completion = completion {
pendingOwnInstanceRequestCallbacks.append(completion)
}
if ownInstanceRequest == nil {
let request = Client.getInstance() let request = Client.getInstance()
ownInstanceRequest = run(request) { (response) in run(request) { (response) in
switch response { guard case let .success(instance, _) = response else { fatalError() }
case .failure(_):
let delay: DispatchTimeInterval
switch retryAttempt {
case 0:
delay = .seconds(1)
case 1:
delay = .seconds(5)
case 2:
delay = .seconds(30)
case 3:
delay = .seconds(60)
default:
// if we've failed four times, just give up :/
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
// completion is nil because in this invocation of getOwnInstanceInternal we've already added it to the pending callbacks array
self.getOwnInstanceInternal(retryAttempt: retryAttempt + 1, completion: nil)
}
case let .success(instance, _):
DispatchQueue.main.async { DispatchQueue.main.async {
self.ownInstanceRequest = nil
self.instance = instance self.instance = instance
completion?(instance)
for completion in self.pendingOwnInstanceRequestCallbacks {
completion(instance)
}
self.pendingOwnInstanceRequestCallbacks = []
}
}
} }
} }
} }

View File

@ -69,7 +69,6 @@ class AssetCollectionViewController: UICollectionViewController {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed)) navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
collectionView.alwaysBounceVertical = true collectionView.alwaysBounceVertical = true
collectionView.allowsMultipleSelection = true
collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier) collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier)
collectionView.register(UINib(nibName: "ShowCameraCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: cameraReuseIdentifier) collectionView.register(UINib(nibName: "ShowCameraCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: cameraReuseIdentifier)
@ -98,6 +97,19 @@ class AssetCollectionViewController: UICollectionViewController {
} }
}) })
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchResult = fetchAssets(with: options)
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.assets])
var items: [Item] = [.showCamera]
fetchResult.enumerateObjects { (asset, _, _) in
items.append(.asset(asset))
}
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: false)
collectionView.allowsMultipleSelection = true
setEditing(true, animated: false) setEditing(true, animated: false)
updateItemsSelectedCount() updateItemsSelectedCount()
@ -110,12 +122,6 @@ class AssetCollectionViewController: UICollectionViewController {
} }
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadAssets()
}
override func viewWillLayoutSubviews() { override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews() super.viewWillLayoutSubviews()
@ -131,40 +137,6 @@ class AssetCollectionViewController: UICollectionViewController {
} }
} }
private func loadAssets() {
switch PHPhotoLibrary.authorizationStatus(for: .readWrite) {
case .notDetermined:
PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in
self.loadAssets()
}
return
case .restricted, .denied:
// todo: better UI for this
return
case .authorized, .limited:
// todo: show "add more" button for limited access
break
@unknown default:
// who knows, just try anyways
break
}
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchResult = fetchAssets(with: options)
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.assets])
var items: [Item] = [.showCamera]
fetchResult.enumerateObjects { (asset, _, _) in
items.append(.asset(asset))
}
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: false)
}
open func fetchAssets(with options: PHFetchOptions) -> PHFetchResult<PHAsset> { open func fetchAssets(with options: PHFetchOptions) -> PHFetchResult<PHAsset> {
return PHAsset.fetchAssets(with: options) return PHAsset.fetchAssets(with: options)
} }

View File

@ -118,7 +118,7 @@ class ProfileDirectoryFilterView: UICollectionReusableView {
} }
@objc private func filterChanged() { @objc private func filterChanged() {
let scope = Scope(rawValue: self.scope.selectedSegmentIndex)! let scope = Scope(rawValue: scope.selectedSegmentIndex)!
let order = sort.selectedSegmentIndex == 0 ? DirectoryOrder.active : .new let order = sort.selectedSegmentIndex == 0 ? DirectoryOrder.active : .new
onFilterChanged?(scope, order) onFilterChanged?(scope, order)
} }

View File

@ -94,11 +94,11 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
loadingVC = LoadingViewController() loadingVC = LoadingViewController()
embedChild(loadingVC!) embedChild(loadingVC!)
imageRequest = cache.get(url, loadOriginal: true) { [weak self] (data, image) in imageRequest = cache.get(url, loadOriginal: true) { [weak self] (data, image) in
guard let self = self, let image = image else { return } guard let self = self else { return }
self.imageRequest = nil self.imageRequest = nil
DispatchQueue.main.async { DispatchQueue.main.async {
self.loadingVC?.removeViewAndController() self.loadingVC?.removeViewAndController()
self.createLargeImage(data: data, image: image, url: self.url) self.createLargeImage(data: data!, image: image!, url: self.url)
} }
} }
} }

View File

@ -72,10 +72,7 @@ class TimelineTableViewController: TimelineLikeTableViewController<TimelineEntry
let request = Client.getStatuses(timeline: timeline) let request = Client.getStatuses(timeline: timeline)
mastodonController?.run(request) { (response) in mastodonController?.run(request) { (response) in
guard case let .success(statuses, pagination) = response else { guard case let .success(statuses, pagination) = response else { fatalError() }
completion([])
return
}
self.newer = pagination?.newer self.newer = pagination?.newer
self.older = pagination?.older self.older = pagination?.older
@ -95,10 +92,7 @@ class TimelineTableViewController: TimelineLikeTableViewController<TimelineEntry
let request = Client.getStatuses(timeline: timeline, range: older) let request = Client.getStatuses(timeline: timeline, range: older)
mastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(statuses, pagination) = response else { guard case let .success(statuses, pagination) = response else { fatalError() }
completion([])
return
}
self.older = pagination?.older self.older = pagination?.older
@ -117,10 +111,7 @@ class TimelineTableViewController: TimelineLikeTableViewController<TimelineEntry
let request = Client.getStatuses(timeline: timeline, range: newer) let request = Client.getStatuses(timeline: timeline, range: newer)
mastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(statuses, pagination) = response else { guard case let .success(statuses, pagination) = response else { fatalError() }
completion([])
return
}
// if there are no new statuses, pagination is nil // if there are no new statuses, pagination is nil
// if we were to then overwrite self.newer, future refreshes would fail // if we were to then overwrite self.newer, future refreshes would fail

View File

@ -65,18 +65,11 @@ class TimelineLikeTableViewController<Item>: EnhancedTableViewController, Refres
func loadInitial() { func loadInitial() {
guard !loaded else { return } guard !loaded else { return }
// set loaded immediately so we don't trigger another request while the current one is running
loaded = true loaded = true
loadInitialItems() { (items) in loadInitialItems() { (items) in
guard items.count > 0 else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
guard items.count > 0 else {
// set loaded back to false so the next time the VC appears, we try to load again
// todo: this should probably retry automatically
self.loaded = false
return
}
if self.sections.count < self.headerSectionsCount() { if self.sections.count < self.headerSectionsCount() {
self.sections.insert(contentsOf: Array(repeating: [], count: self.headerSectionsCount() - self.sections.count), at: 0) self.sections.insert(contentsOf: Array(repeating: [], count: self.headerSectionsCount() - self.sections.count), at: 0)
} }
@ -104,8 +97,6 @@ class TimelineLikeTableViewController<Item>: EnhancedTableViewController, Refres
return "Refresh" return "Refresh"
} }
// todo: these three should use Result<[Item], Client.Error> so we can differentiate between failed requests and there actually being no results
func loadInitialItems(completion: @escaping ([Item]) -> Void) { func loadInitialItems(completion: @escaping ([Item]) -> Void) {
fatalError("loadInitialItems(completion:) must be implemented by subclasses") fatalError("loadInitialItems(completion:) must be implemented by subclasses")
} }