Compare commits
No commits in common. "2229b332e094dc6a39112fb5e7cca9d30afa9049" and "addcc2daccb20f3bd9c9a0f0492840520587e367" have entirely different histories.
2229b332e0
...
addcc2dacc
|
@ -94,9 +94,6 @@ class ConversationCollectionViewController: UIViewController, CollectionViewCont
|
||||||
let expandThreadCell = UICollectionView.CellRegistration<ExpandThreadCollectionViewCell, ([ConversationNode], Bool)> { cell, indexPath, item in
|
let expandThreadCell = UICollectionView.CellRegistration<ExpandThreadCollectionViewCell, ([ConversationNode], Bool)> { cell, indexPath, item in
|
||||||
cell.updateUI(childThreads: item.0, inline: item.1)
|
cell.updateUI(childThreads: item.0, inline: item.1)
|
||||||
}
|
}
|
||||||
let loadingCell = UICollectionView.CellRegistration<LoadingCollectionViewCell, Void> { cell, indexPath, itemIdentifier in
|
|
||||||
cell.indicator.startAnimating()
|
|
||||||
}
|
|
||||||
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
|
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
|
||||||
switch itemIdentifier {
|
switch itemIdentifier {
|
||||||
case let .status(id: id, state: state, prevLink: prevLink, nextLink: nextLink):
|
case let .status(id: id, state: state, prevLink: prevLink, nextLink: nextLink):
|
||||||
|
@ -107,31 +104,17 @@ class ConversationCollectionViewController: UIViewController, CollectionViewCont
|
||||||
}
|
}
|
||||||
case .expandThread(childThreads: let childThreads, inline: let inline):
|
case .expandThread(childThreads: let childThreads, inline: let inline):
|
||||||
return collectionView.dequeueConfiguredReusableCell(using: expandThreadCell, for: indexPath, item: (childThreads, inline))
|
return collectionView.dequeueConfiguredReusableCell(using: expandThreadCell, for: indexPath, item: (childThreads, inline))
|
||||||
case .loadingIndicator:
|
|
||||||
return collectionView.dequeueConfiguredReusableCell(using: loadingCell, for: indexPath, item: ())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
|
|
||||||
clearSelectionOnAppear(animated: animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addMainStatus(_ status: StatusMO) {
|
func addMainStatus(_ status: StatusMO) {
|
||||||
loadViewIfNeeded()
|
loadViewIfNeeded()
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
snapshot.appendSections([.statuses])
|
snapshot.appendSections([.statuses])
|
||||||
|
|
||||||
if status.inReplyToID != nil {
|
|
||||||
snapshot.appendItems([.loadingIndicator], toSection: .statuses)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState, prevLink: status.inReplyToID != nil, nextLink: false)
|
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState, prevLink: status.inReplyToID != nil, nextLink: false)
|
||||||
snapshot.appendItems([mainStatusItem], toSection: .statuses)
|
snapshot.appendItems([mainStatusItem], toSection: .statuses)
|
||||||
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +125,6 @@ class ConversationCollectionViewController: UIViewController, CollectionViewCont
|
||||||
await mastodonController.persistentContainer.addAll(statuses: parentStatuses + context.descendants)
|
await mastodonController.persistentContainer.addAll(statuses: parentStatuses + context.descendants)
|
||||||
|
|
||||||
var snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
snapshot.deleteItems([.loadingIndicator])
|
|
||||||
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState, prevLink: mainStatus.inReplyToID != nil, nextLink: false)
|
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState, prevLink: mainStatus.inReplyToID != nil, nextLink: false)
|
||||||
let parentItems = parentIDs.enumerated().map { index, id in
|
let parentItems = parentIDs.enumerated().map { index, id in
|
||||||
Item.status(id: id, state: .unknown, prevLink: index > 0, nextLink: true)
|
Item.status(id: id, state: .unknown, prevLink: index > 0, nextLink: true)
|
||||||
|
@ -317,7 +299,6 @@ extension ConversationCollectionViewController {
|
||||||
enum Item: Hashable {
|
enum Item: Hashable {
|
||||||
case status(id: String, state: CollapseState, prevLink: Bool, nextLink: Bool)
|
case status(id: String, state: CollapseState, prevLink: Bool, nextLink: Bool)
|
||||||
case expandThread(childThreads: [ConversationNode], inline: Bool)
|
case expandThread(childThreads: [ConversationNode], inline: Bool)
|
||||||
case loadingIndicator
|
|
||||||
|
|
||||||
static func ==(lhs: Item, rhs: Item) -> Bool {
|
static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
|
@ -325,8 +306,6 @@ extension ConversationCollectionViewController {
|
||||||
return a == b && aPrev == bPrev && aNext == bNext
|
return a == b && aPrev == bPrev && aNext == bNext
|
||||||
case let (.expandThread(childThreads: a, inline: aInline), .expandThread(childThreads: b, inline: bInline)):
|
case let (.expandThread(childThreads: a, inline: aInline), .expandThread(childThreads: b, inline: bInline)):
|
||||||
return a.count == b.count && zip(a, b).allSatisfy { $0.status.id == $1.status.id } && aInline == bInline
|
return a.count == b.count && zip(a, b).allSatisfy { $0.status.id == $1.status.id } && aInline == bInline
|
||||||
case (.loadingIndicator, .loadingIndicator):
|
|
||||||
return true
|
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -345,8 +324,6 @@ extension ConversationCollectionViewController {
|
||||||
hasher.combine(thread.status.id)
|
hasher.combine(thread.status.id)
|
||||||
}
|
}
|
||||||
hasher.combine(inline)
|
hasher.combine(inline)
|
||||||
case .loadingIndicator:
|
|
||||||
hasher.combine(2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,13 +331,11 @@ extension ConversationCollectionViewController {
|
||||||
|
|
||||||
extension ConversationCollectionViewController: UICollectionViewDelegate {
|
extension ConversationCollectionViewController: UICollectionViewDelegate {
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
||||||
switch dataSource.itemIdentifier(for: indexPath) {
|
if case .status(id: let id, _, _, _) = dataSource.itemIdentifier(for: indexPath),
|
||||||
case .status(id: let id, state: _, prevLink: _, nextLink: _):
|
id == mainStatusID {
|
||||||
return id != mainStatusID
|
|
||||||
case .expandThread(childThreads: _, inline: _):
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,8 +343,6 @@ extension ConversationCollectionViewController: UICollectionViewDelegate {
|
||||||
switch dataSource.itemIdentifier(for: indexPath) {
|
switch dataSource.itemIdentifier(for: indexPath) {
|
||||||
case nil:
|
case nil:
|
||||||
break
|
break
|
||||||
case .loadingIndicator:
|
|
||||||
break
|
|
||||||
case .status(id: let id, state: let state, _, _):
|
case .status(id: let id, state: let state, _, _):
|
||||||
selected(status: id, state: state.copy())
|
selected(status: id, state: state.copy())
|
||||||
case .expandThread(childThreads: let childThreads, inline: _):
|
case .expandThread(childThreads: let childThreads, inline: _):
|
||||||
|
|
|
@ -8,16 +8,20 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import WebURL
|
|
||||||
import WebURLFoundationExtras
|
|
||||||
|
|
||||||
class ConversationViewController: UIViewController {
|
class ConversationViewController: UIViewController {
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
private(set) var mode: Mode
|
let mainStatusID: String
|
||||||
let mainStatusState: CollapseState
|
let mainStatusState: CollapseState
|
||||||
var statusIDToScrollToOnLoad: String?
|
var statusIDToScrollToOnLoad: String {
|
||||||
|
didSet {
|
||||||
|
if case .displaying(let vc) = state {
|
||||||
|
vc.statusIDToScrollToOnLoad = statusIDToScrollToOnLoad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var showStatusesAutomatically = false {
|
var showStatusesAutomatically = false {
|
||||||
didSet {
|
didSet {
|
||||||
if case .displaying(let vc) = state {
|
if case .displaying(let vc) = state {
|
||||||
|
@ -53,8 +57,6 @@ class ConversationViewController: UIViewController {
|
||||||
embedChild(vc)
|
embedChild(vc)
|
||||||
case .notFound:
|
case .notFound:
|
||||||
showMainStatusNotFound()
|
showMainStatusNotFound()
|
||||||
case .unableToResolve(let error):
|
|
||||||
showUnableToResolve(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVisibilityBarButtonItem()
|
updateVisibilityBarButtonItem()
|
||||||
|
@ -62,16 +64,9 @@ class ConversationViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(for mainStatusID: String, state mainStatusState: CollapseState, mastodonController: MastodonController) {
|
init(for mainStatusID: String, state mainStatusState: CollapseState, mastodonController: MastodonController) {
|
||||||
self.mode = .localID(mainStatusID)
|
self.mainStatusID = mainStatusID
|
||||||
self.mainStatusState = mainStatusState
|
self.mainStatusState = mainStatusState
|
||||||
self.mastodonController = mastodonController
|
self.statusIDToScrollToOnLoad = mainStatusID
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(resolving url: URL, mastodonController: MastodonController) {
|
|
||||||
self.mode = .resolve(url)
|
|
||||||
self.mainStatusState = .unknown
|
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
@ -126,8 +121,7 @@ class ConversationViewController: UIViewController {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
let accountID = mastodonController.accountInfo?.id,
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
userInfo["accountID"] as? String == accountID,
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String],
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
case .localID(let mainStatusID) = mode else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if statusIDs.contains(mainStatusID) {
|
if statusIDs.contains(mainStatusID) {
|
||||||
|
@ -143,10 +137,6 @@ class ConversationViewController: UIViewController {
|
||||||
// MARK: Loading
|
// MARK: Loading
|
||||||
|
|
||||||
private func loadMainStatus() async {
|
private func loadMainStatus() async {
|
||||||
guard let mainStatusID = await resolveStatusIfNecessary() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func doLoadMainStatus() async -> StatusMO? {
|
func doLoadMainStatus() async -> StatusMO? {
|
||||||
switch await FetchStatusService(statusID: mainStatusID, mastodonController: mastodonController).run() {
|
switch await FetchStatusService(statusID: mainStatusID, mastodonController: mastodonController).run() {
|
||||||
|
@ -179,37 +169,10 @@ class ConversationViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
private func resolveStatusIfNecessary() async -> String? {
|
|
||||||
switch mode {
|
|
||||||
case .localID(let id):
|
|
||||||
return id
|
|
||||||
case .resolve(let url):
|
|
||||||
let indicator = UIActivityIndicatorView(style: .medium)
|
|
||||||
indicator.startAnimating()
|
|
||||||
state = .loading(indicator)
|
|
||||||
|
|
||||||
let url = WebURL(url)!
|
|
||||||
let request = Client.search(query: url.serialized(), types: [.statuses], resolve: true)
|
|
||||||
do {
|
|
||||||
let (results, _) = try await mastodonController.run(request)
|
|
||||||
guard let status = results.statuses.first(where: { $0.url == url }) else {
|
|
||||||
throw UnableToResolveError()
|
|
||||||
}
|
|
||||||
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
|
|
||||||
mode = .localID(status.id)
|
|
||||||
return status.id
|
|
||||||
} catch {
|
|
||||||
state = .unableToResolve(error)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func mainStatusLoaded(_ mainStatus: StatusMO) async {
|
private func mainStatusLoaded(_ mainStatus: StatusMO) async {
|
||||||
let vc = ConversationCollectionViewController(for: mainStatus.id, state: mainStatusState, mastodonController: mastodonController)
|
let vc = ConversationCollectionViewController(for: mainStatusID, state: mainStatusState, mastodonController: mastodonController)
|
||||||
vc.statusIDToScrollToOnLoad = statusIDToScrollToOnLoad ?? mainStatus.id
|
vc.statusIDToScrollToOnLoad = statusIDToScrollToOnLoad
|
||||||
vc.showStatusesAutomatically = showStatusesAutomatically
|
vc.showStatusesAutomatically = showStatusesAutomatically
|
||||||
vc.addMainStatus(mainStatus)
|
vc.addMainStatus(mainStatus)
|
||||||
state = .displaying(vc)
|
state = .displaying(vc)
|
||||||
|
@ -264,66 +227,6 @@ class ConversationViewController: UIViewController {
|
||||||
self.showToast(configuration: config, animated: true)
|
self.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showUnableToResolve(_ error: Error) {
|
|
||||||
let image = UIImageView(image: UIImage(systemName: "exclamationmark.triangle.fill")!)
|
|
||||||
image.tintColor = .secondaryLabel
|
|
||||||
image.contentMode = .scaleAspectFit
|
|
||||||
|
|
||||||
let title = UILabel()
|
|
||||||
title.textColor = .secondaryLabel
|
|
||||||
title.font = .preferredFont(forTextStyle: .title1).withTraits(.traitBold)!
|
|
||||||
title.adjustsFontForContentSizeCategory = true
|
|
||||||
title.text = "Couldn't Load Post"
|
|
||||||
|
|
||||||
let subtitle = UILabel()
|
|
||||||
subtitle.textColor = .secondaryLabel
|
|
||||||
subtitle.font = .preferredFont(forTextStyle: .body)
|
|
||||||
subtitle.adjustsFontForContentSizeCategory = true
|
|
||||||
subtitle.numberOfLines = 0
|
|
||||||
subtitle.textAlignment = .center
|
|
||||||
if let error = error as? UnableToResolveError {
|
|
||||||
subtitle.text = error.localizedDescription
|
|
||||||
} else if let error = error as? Client.Error {
|
|
||||||
subtitle.text = error.localizedDescription
|
|
||||||
} else {
|
|
||||||
subtitle.text = error.localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = UIButton.Configuration.plain()
|
|
||||||
config.title = "Open in Safari"
|
|
||||||
config.image = UIImage(systemName: "safari")
|
|
||||||
config.imagePadding = 4
|
|
||||||
let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [unowned self] _ in
|
|
||||||
guard case .resolve(let url) = self.mode else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.selected(url: url, allowResolveStatuses: false)
|
|
||||||
}))
|
|
||||||
|
|
||||||
let stack = UIStackView(arrangedSubviews: [
|
|
||||||
image,
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
button,
|
|
||||||
])
|
|
||||||
stack.axis = .vertical
|
|
||||||
stack.alignment = .center
|
|
||||||
stack.spacing = 8
|
|
||||||
stack.isAccessibilityElement = true
|
|
||||||
stack.accessibilityLabel = "\(title.text!). \(subtitle.text!)"
|
|
||||||
|
|
||||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.addSubview(stack)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
image.widthAnchor.constraint(equalToConstant: 64),
|
|
||||||
image.heightAnchor.constraint(equalToConstant: 64),
|
|
||||||
|
|
||||||
stack.leadingAnchor.constraint(equalToSystemSpacingAfter: view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1),
|
|
||||||
view.safeAreaLayoutGuide.trailingAnchor.constraint(equalToSystemSpacingAfter: stack.trailingAnchor, multiplier: 1),
|
|
||||||
stack.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Interaction
|
// MARK: Interaction
|
||||||
|
|
||||||
@objc func toggleCollapseButtonPressed() {
|
@objc func toggleCollapseButtonPressed() {
|
||||||
|
@ -337,35 +240,15 @@ class ConversationViewController: UIViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConversationViewController {
|
|
||||||
enum Mode {
|
|
||||||
case localID(String)
|
|
||||||
case resolve(URL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ConversationViewController {
|
|
||||||
struct UnableToResolveError: Error {
|
|
||||||
var localizedDescription: String {
|
|
||||||
"Unable to resolve status from URL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ConversationViewController {
|
extension ConversationViewController {
|
||||||
enum State {
|
enum State {
|
||||||
case unloaded
|
case unloaded
|
||||||
case loading(UIActivityIndicatorView)
|
case loading(UIActivityIndicatorView)
|
||||||
case displaying(ConversationCollectionViewController)
|
case displaying(ConversationCollectionViewController)
|
||||||
case notFound
|
case notFound
|
||||||
case unableToResolve(Error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConversationViewController: TuskerNavigationDelegate {
|
|
||||||
var apiController: MastodonController! { mastodonController }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ConversationViewController: ToastableViewController {
|
extension ConversationViewController: ToastableViewController {
|
||||||
var toastScrollView: UIScrollView? {
|
var toastScrollView: UIScrollView? {
|
||||||
if case .displaying(let vc) = state {
|
if case .displaying(let vc) = state {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class ExpandThreadCollectionViewCell: UICollectionViewListCell {
|
class ExpandThreadCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
private var avatarContainerView: UIView!
|
private var avatarContainerView: UIView!
|
||||||
private var avatarContainerWidthConstraint: NSLayoutConstraint!
|
private var avatarContainerWidthConstraint: NSLayoutConstraint!
|
||||||
|
@ -46,6 +46,8 @@ class ExpandThreadCollectionViewCell: UICollectionViewListCell {
|
||||||
contentView.addSubview(hStack)
|
contentView.addSubview(hStack)
|
||||||
stackViewLeadingConstraint = hStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16)
|
stackViewLeadingConstraint = hStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16)
|
||||||
|
|
||||||
|
// TODO: separator
|
||||||
|
|
||||||
threadLinkView = UIView()
|
threadLinkView = UIView()
|
||||||
threadLinkView.backgroundColor = .tintColor.withAlphaComponent(0.5)
|
threadLinkView.backgroundColor = .tintColor.withAlphaComponent(0.5)
|
||||||
threadLinkView.layer.cornerRadius = 2.5
|
threadLinkView.layer.cornerRadius = 2.5
|
||||||
|
@ -140,19 +142,4 @@ class ExpandThreadCollectionViewCell: UICollectionViewListCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
|
||||||
var config = UIBackgroundConfiguration.listPlainCell()
|
|
||||||
if state.isSelected || state.isHighlighted {
|
|
||||||
var hue: CGFloat = 0
|
|
||||||
var saturation: CGFloat = 0
|
|
||||||
var brightness: CGFloat = 0
|
|
||||||
UIColor.secondarySystemBackground.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
|
|
||||||
let sign: CGFloat = traitCollection.userInterfaceStyle == .dark ? 1 : -1
|
|
||||||
config.backgroundColor = UIColor(hue: hue, saturation: saturation, brightness: max(0, brightness + sign * 0.1), alpha: 1)
|
|
||||||
} else {
|
|
||||||
config.backgroundColor = .secondarySystemBackground
|
|
||||||
}
|
|
||||||
backgroundConfiguration = config.updated(for: state)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ extension TuskerNavigationDelegate {
|
||||||
show(HashtagTimelineViewController(for: tag, mastodonController: apiController), sender: self)
|
show(HashtagTimelineViewController(for: tag, mastodonController: apiController), sender: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(url: URL, allowResolveStatuses: Bool = true, allowUniversalLinks: Bool = true) {
|
func selected(url: URL, allowUniversalLinks: Bool = true) {
|
||||||
func openSafari() {
|
func openSafari() {
|
||||||
if Preferences.shared.useInAppSafari,
|
if Preferences.shared.useInAppSafari,
|
||||||
url.scheme == "https" || url.scheme == "http" {
|
url.scheme == "https" || url.scheme == "http" {
|
||||||
|
@ -66,11 +66,7 @@ extension TuskerNavigationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowResolveStatuses,
|
if allowUniversalLinks && Preferences.shared.openLinksInApps,
|
||||||
isLikelyResolvableAsStatus(url) {
|
|
||||||
show(ConversationViewController(resolving: url, mastodonController: apiController))
|
|
||||||
} else if allowUniversalLinks,
|
|
||||||
Preferences.shared.openLinksInApps,
|
|
||||||
url.scheme == "https" || url.scheme == "http" {
|
url.scheme == "https" || url.scheme == "http" {
|
||||||
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { (success) in
|
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { (success) in
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
@ -221,21 +217,3 @@ enum PopoverSource {
|
||||||
.barButtonItem(WeakHolder(item))
|
.barButtonItem(WeakHolder(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let statusPathRegex = try! NSRegularExpression(
|
|
||||||
pattern:
|
|
||||||
"(^/@[a-z0-9_]+/\\d{18})" // mastodon
|
|
||||||
+ "|(^/notice/[a-z0-9]{18})" // pleroma
|
|
||||||
+ "|(^/p/[a-z0-9_]+/\\d{18})" // pixelfed
|
|
||||||
+ "|(^/i/web/post/\\d{18})" // pixelfed web frontend
|
|
||||||
+ "|(^/u/.+/h/[a-z0-9]{18})" // honk
|
|
||||||
+ "|(^/@.+/statuses/[a-z0-9]{26})" // gotosocial
|
|
||||||
,
|
|
||||||
options: .caseInsensitive
|
|
||||||
)
|
|
||||||
|
|
||||||
private func isLikelyResolvableAsStatus(_ url: URL) -> Bool {
|
|
||||||
let path = url.path
|
|
||||||
let range = NSRange(location: 0, length: path.utf16.count)
|
|
||||||
return statusPathRegex.numberOfMatches(in: path, range: range) == 1
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue