mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Webfingering
This commit is contained in:
parent
cf342bf3ae
commit
eab12976cd
7 changed files with 224 additions and 42 deletions
9
Mastodon/Sources/Mastodon/Entities/Results.swift
Normal file
9
Mastodon/Sources/Mastodon/Entities/Results.swift
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Results: Codable {
|
||||||
|
public let accounts: [Account]
|
||||||
|
public let statuses: [Status]
|
||||||
|
public let hashtags: [Tag]
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import HTTP
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
public enum ResultsEndpoint {
|
||||||
|
case search(query: String, resolve: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ResultsEndpoint: Endpoint {
|
||||||
|
public typealias ResultType = Results
|
||||||
|
|
||||||
|
public var APIVersion: String {
|
||||||
|
switch self {
|
||||||
|
case .search:
|
||||||
|
return "v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var pathComponentsInContext: [String] {
|
||||||
|
switch self {
|
||||||
|
case .search:
|
||||||
|
return ["search"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var method: HTTPMethod {
|
||||||
|
switch self {
|
||||||
|
case .search:
|
||||||
|
return .get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var queryParameters: [String: String]? {
|
||||||
|
switch self {
|
||||||
|
case let .search(query, resolve):
|
||||||
|
var params = ["q": query]
|
||||||
|
|
||||||
|
if resolve {
|
||||||
|
params["resolve"] = String(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@
|
||||||
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
|
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
|
||||||
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
||||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
|
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
|
||||||
|
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */; };
|
||||||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
||||||
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
||||||
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
@ -139,6 +140,7 @@
|
||||||
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
|
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
|
||||||
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
||||||
|
D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebfingerIndicatorView.swift; sourceTree = "<group>"; };
|
||||||
D0E2C1CF24FD8BA400854680 /* ViewModels */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ViewModels; sourceTree = "<group>"; };
|
D0E2C1CF24FD8BA400854680 /* ViewModels */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ViewModels; sourceTree = "<group>"; };
|
||||||
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||||
|
@ -288,6 +290,7 @@
|
||||||
D0625E55250F086B00502611 /* Status */,
|
D0625E55250F086B00502611 /* Status */,
|
||||||
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */,
|
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */,
|
||||||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */,
|
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */,
|
||||||
|
D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -545,6 +548,7 @@
|
||||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
||||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
||||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||||
|
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
||||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
||||||
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
|
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
|
||||||
|
|
|
@ -10,6 +10,8 @@ public enum Navigation {
|
||||||
case url(URL)
|
case url(URL)
|
||||||
case statusList(StatusListService)
|
case statusList(StatusListService)
|
||||||
case accountStatuses(AccountStatusesService)
|
case accountStatuses(AccountStatusesService)
|
||||||
|
case webfingerStart
|
||||||
|
case webfingerEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct NavigationService {
|
public struct NavigationService {
|
||||||
|
@ -46,7 +48,11 @@ public extension NavigationService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Just(.url(url)).eraseToAnyPublisher()
|
if url.shouldWebfinger {
|
||||||
|
return webfinger(url: url)
|
||||||
|
} else {
|
||||||
|
return Just(.url(url)).eraseToAnyPublisher()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextStatusListService(id: String) -> StatusListService {
|
func contextStatusListService(id: String) -> StatusListService {
|
||||||
|
@ -88,6 +94,37 @@ private extension NavigationService {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func webfinger(url: URL) -> AnyPublisher<Navigation, Never> {
|
||||||
|
let navigationSubject = PassthroughSubject<Navigation, Never>()
|
||||||
|
|
||||||
|
let request = mastodonAPIClient.request(ResultsEndpoint.search(query: url.absoluteString, resolve: true))
|
||||||
|
.handleEvents(
|
||||||
|
receiveSubscription: { _ in navigationSubject.send(.webfingerStart) },
|
||||||
|
receiveCompletion: { _ in navigationSubject.send(.webfingerEnd) })
|
||||||
|
.map { results -> Navigation in
|
||||||
|
if let tag = results.hashtags.first {
|
||||||
|
return .statusList(
|
||||||
|
StatusListService(
|
||||||
|
timeline: .tag(tag.name),
|
||||||
|
mastodonAPIClient: mastodonAPIClient,
|
||||||
|
contentDatabase: contentDatabase))
|
||||||
|
} else if let account = results.accounts.first {
|
||||||
|
return .accountStatuses(accountStatusesService(id: account.id))
|
||||||
|
} else if let status = results.statuses.first {
|
||||||
|
return .statusList(
|
||||||
|
StatusListService(
|
||||||
|
statusID: status.id,
|
||||||
|
mastodonAPIClient: mastodonAPIClient,
|
||||||
|
contentDatabase: contentDatabase))
|
||||||
|
} else {
|
||||||
|
return .url(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.replaceError(with: .url(url))
|
||||||
|
|
||||||
|
return navigationSubject.merge(with: request).eraseToAnyPublisher()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension URL {
|
private extension URL {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import ViewModels
|
||||||
class CollectionViewController: UITableViewController {
|
class CollectionViewController: UITableViewController {
|
||||||
private let viewModel: CollectionViewModel
|
private let viewModel: CollectionViewModel
|
||||||
private let loadingTableFooterView = LoadingTableFooterView()
|
private let loadingTableFooterView = LoadingTableFooterView()
|
||||||
|
private let webfingerIndicatorView = WebfingerIndicatorView()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]()
|
private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]()
|
||||||
private let dataSourceQueue =
|
private let dataSourceQueue =
|
||||||
|
@ -57,49 +58,15 @@ class CollectionViewController: UITableViewController {
|
||||||
tableView.cellLayoutMarginsFollowReadableWidth = true
|
tableView.cellLayoutMarginsFollowReadableWidth = true
|
||||||
tableView.tableFooterView = UIView()
|
tableView.tableFooterView = UIView()
|
||||||
|
|
||||||
viewModel.title.sink { [weak self] in self?.navigationItem.title = $0 }.store(in: &cancellables)
|
view.addSubview(webfingerIndicatorView)
|
||||||
|
webfingerIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
viewModel.collectionItems
|
NSLayoutConstraint.activate([
|
||||||
.sink { [weak self] in self?.update(items: $0) }
|
webfingerIndicatorView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
|
||||||
.store(in: &cancellables)
|
webfingerIndicatorView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
viewModel.navigationEvents.sink { [weak self] in
|
setupViewModelBindings()
|
||||||
guard let self = self else { return }
|
|
||||||
switch $0 {
|
|
||||||
case let .share(url):
|
|
||||||
self.share(url: url)
|
|
||||||
case let .collectionNavigation(collectionViewModel):
|
|
||||||
self.show(CollectionViewController(viewModel: collectionViewModel), sender: self)
|
|
||||||
case let .urlNavigation(url):
|
|
||||||
self.present(SFSafariViewController(url: url), animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
viewModel.loading
|
|
||||||
.receive(on: RunLoop.main)
|
|
||||||
.sink { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
self.tableView.tableFooterView = $0 ? self.loadingTableFooterView : UIView()
|
|
||||||
self.sizeTableHeaderFooterViews()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
if let accountsStatusesViewModel = viewModel as? AccountStatusesViewModel {
|
|
||||||
// Initial size is to avoid unsatisfiable constraint warning
|
|
||||||
let accountHeaderView = AccountHeaderView(
|
|
||||||
frame: .init(
|
|
||||||
origin: .zero,
|
|
||||||
size: .init(width: 100, height: 100)))
|
|
||||||
accountHeaderView.viewModel = accountsStatusesViewModel
|
|
||||||
accountsStatusesViewModel.$account.dropFirst().receive(on: DispatchQueue.main).sink { [weak self] _ in
|
|
||||||
accountHeaderView.viewModel = accountsStatusesViewModel
|
|
||||||
self?.sizeTableHeaderFooterViews()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
tableView.tableHeaderView = accountHeaderView
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
@ -158,6 +125,57 @@ extension CollectionViewController: UITableViewDataSourcePrefetching {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension CollectionViewController {
|
private extension CollectionViewController {
|
||||||
|
func setupViewModelBindings() {
|
||||||
|
viewModel.title.sink { [weak self] in self?.navigationItem.title = $0 }.store(in: &cancellables)
|
||||||
|
|
||||||
|
viewModel.collectionItems
|
||||||
|
.sink { [weak self] in self?.update(items: $0) }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
viewModel.navigationEvents.receive(on: DispatchQueue.main).sink { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
switch $0 {
|
||||||
|
case let .share(url):
|
||||||
|
self.share(url: url)
|
||||||
|
case let .collectionNavigation(collectionViewModel):
|
||||||
|
self.show(CollectionViewController(viewModel: collectionViewModel), sender: self)
|
||||||
|
case let .urlNavigation(url):
|
||||||
|
self.present(SFSafariViewController(url: url), animated: true)
|
||||||
|
case .webfingerStart:
|
||||||
|
self.webfingerIndicatorView.startAnimating()
|
||||||
|
case .webfingerEnd:
|
||||||
|
self.webfingerIndicatorView.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
viewModel.loading
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.tableView.tableFooterView = $0 ? self.loadingTableFooterView : UIView()
|
||||||
|
self.sizeTableHeaderFooterViews()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
if let accountsStatusesViewModel = viewModel as? AccountStatusesViewModel {
|
||||||
|
// Initial size is to avoid unsatisfiable constraint warning
|
||||||
|
let accountHeaderView = AccountHeaderView(
|
||||||
|
frame: .init(
|
||||||
|
origin: .zero,
|
||||||
|
size: .init(width: 100, height: 100)))
|
||||||
|
accountHeaderView.viewModel = accountsStatusesViewModel
|
||||||
|
accountsStatusesViewModel.$account.dropFirst().receive(on: DispatchQueue.main).sink { [weak self] _ in
|
||||||
|
accountHeaderView.viewModel = accountsStatusesViewModel
|
||||||
|
self?.sizeTableHeaderFooterViews()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
tableView.tableHeaderView = accountHeaderView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(items: [[CollectionItem]]) {
|
func update(items: [[CollectionItem]]) {
|
||||||
var offsetFromNavigationBar: CGFloat?
|
var offsetFromNavigationBar: CGFloat?
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ public enum NavigationEvent {
|
||||||
case collectionNavigation(CollectionViewModel)
|
case collectionNavigation(CollectionViewModel)
|
||||||
case urlNavigation(URL)
|
case urlNavigation(URL)
|
||||||
case share(URL)
|
case share(URL)
|
||||||
|
case webfingerStart
|
||||||
|
case webfingerEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NavigationEvent {
|
extension NavigationEvent {
|
||||||
|
@ -21,6 +23,10 @@ extension NavigationEvent {
|
||||||
self = .collectionNavigation(StatusListViewModel(statusListService: statusListService))
|
self = .collectionNavigation(StatusListViewModel(statusListService: statusListService))
|
||||||
case let .accountStatuses(accountStatusesService):
|
case let .accountStatuses(accountStatusesService):
|
||||||
self = .collectionNavigation(AccountStatusesViewModel(accountStatusesService: accountStatusesService))
|
self = .collectionNavigation(AccountStatusesViewModel(accountStatusesService: accountStatusesService))
|
||||||
|
case .webfingerStart:
|
||||||
|
self = .webfingerStart
|
||||||
|
case .webfingerEnd:
|
||||||
|
self = .webfingerEnd
|
||||||
}
|
}
|
||||||
case let .accountListNavigation(accountListViewModel):
|
case let .accountListNavigation(accountListViewModel):
|
||||||
self = .collectionNavigation(accountListViewModel)
|
self = .collectionNavigation(accountListViewModel)
|
||||||
|
|
61
Views/WebfingerIndicatorView.swift
Normal file
61
Views/WebfingerIndicatorView.swift
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class WebfingerIndicatorView: UIVisualEffectView {
|
||||||
|
private let activityIndicatorView = UIActivityIndicatorView()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(effect: nil)
|
||||||
|
|
||||||
|
clipsToBounds = true
|
||||||
|
layer.cornerRadius = 8
|
||||||
|
|
||||||
|
contentView.addSubview(activityIndicatorView)
|
||||||
|
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
activityIndicatorView.style = .large
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
trailingAnchor.constraint(
|
||||||
|
equalTo: activityIndicatorView.trailingAnchor, constant: 8),
|
||||||
|
bottomAnchor.constraint(
|
||||||
|
equalTo: activityIndicatorView.bottomAnchor, constant: 8),
|
||||||
|
activityIndicatorView.topAnchor.constraint(
|
||||||
|
equalTo: topAnchor, constant: 8),
|
||||||
|
activityIndicatorView.leadingAnchor.constraint(
|
||||||
|
equalTo: leadingAnchor, constant: 8),
|
||||||
|
activityIndicatorView.centerXAnchor.constraint(
|
||||||
|
equalTo: contentView.safeAreaLayoutGuide.centerXAnchor),
|
||||||
|
activityIndicatorView.centerYAnchor.constraint(
|
||||||
|
equalTo: contentView.safeAreaLayoutGuide.centerYAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WebfingerIndicatorView {
|
||||||
|
func startAnimating() {
|
||||||
|
isHidden = false
|
||||||
|
activityIndicatorView.startAnimating()
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.5) {
|
||||||
|
self.effect = UIBlurEffect(style: .systemUltraThinMaterial)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopAnimating() {
|
||||||
|
activityIndicatorView.stopAnimating()
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.5) {
|
||||||
|
self.effect = nil
|
||||||
|
} completion: { _ in
|
||||||
|
self.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue