forked from shadowfacts/Tusker
Move trending hashtags/links to Explore tab on iPad
This commit is contained in:
parent
700cc2c67c
commit
5cb25c8c1f
|
@ -289,6 +289,8 @@
|
|||
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; };
|
||||
D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D6E57FA525C26FAB00341037 /* Localizable.stringsdict */; };
|
||||
D6E77D09286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E77D08286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift */; };
|
||||
D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */; };
|
||||
D6E77D0D286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */; };
|
||||
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E9CDA7281A427800BBC98E /* PostService.swift */; };
|
||||
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */; };
|
||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
||||
|
@ -641,6 +643,8 @@
|
|||
D6E4885C24A2890C0011C13E /* Tusker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tusker.entitlements; sourceTree = "<group>"; };
|
||||
D6E57FA425C26FAB00341037 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
D6E77D08286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinkCardCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingLinkCardCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
D6E9CDA7281A427800BBC98E /* PostService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostService.swift; sourceTree = "<group>"; };
|
||||
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = "<group>"; };
|
||||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
||||
|
@ -794,6 +798,8 @@
|
|||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
||||
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */,
|
||||
D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */,
|
||||
D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */,
|
||||
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
||||
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
||||
|
@ -1624,6 +1630,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
||||
D6E77D0D286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib in Resources */,
|
||||
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */,
|
||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
||||
|
@ -1912,6 +1919,7 @@
|
|||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
||||
D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */,
|
||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
|
||||
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
|
||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
|
||||
|
|
|
@ -48,7 +48,7 @@ class MastodonController: ObservableObject {
|
|||
@Published private(set) var instanceFeatures = InstanceFeatures()
|
||||
private(set) var customEmojis: [Emoji]?
|
||||
|
||||
private var pendingOwnInstanceRequestCallbacks = [(Instance) -> Void]()
|
||||
private var pendingOwnInstanceRequestCallbacks = [(Result<Instance, Client.Error>) -> Void]()
|
||||
private var ownInstanceRequest: URLSessionTask?
|
||||
|
||||
var loggedIn: Bool {
|
||||
|
@ -159,15 +159,28 @@ class MastodonController: ObservableObject {
|
|||
}
|
||||
|
||||
func getOwnInstance(completion: ((Instance) -> Void)? = nil) {
|
||||
getOwnInstanceInternal(retryAttempt: 0, completion: completion)
|
||||
getOwnInstanceInternal(retryAttempt: 0) {
|
||||
if case let .success(instance) = $0 {
|
||||
completion?(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getOwnInstanceInternal(retryAttempt: Int, completion: ((Instance) -> Void)?) {
|
||||
@MainActor
|
||||
func getOwnInstance() async throws -> Instance {
|
||||
return try await withCheckedThrowingContinuation({ continuation in
|
||||
getOwnInstanceInternal(retryAttempt: 0) { result in
|
||||
continuation.resume(with: result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getOwnInstanceInternal(retryAttempt: Int, completion: ((Result<Instance, Client.Error>) -> Void)?) {
|
||||
// this is main thread only to prevent concurrent access to ownInstanceRequest and pendingOwnInstanceRequestCallbacks
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
if let instance = self.instance {
|
||||
completion?(instance)
|
||||
completion?(.success(instance))
|
||||
} else {
|
||||
if let completion = completion {
|
||||
pendingOwnInstanceRequestCallbacks.append(completion)
|
||||
|
@ -177,7 +190,7 @@ class MastodonController: ObservableObject {
|
|||
let request = Client.getInstance()
|
||||
ownInstanceRequest = run(request) { (response) in
|
||||
switch response {
|
||||
case .failure(_):
|
||||
case .failure(let error):
|
||||
let delay: DispatchTimeInterval
|
||||
switch retryAttempt {
|
||||
case 0:
|
||||
|
@ -190,6 +203,10 @@ class MastodonController: ObservableObject {
|
|||
delay = .seconds(60)
|
||||
default:
|
||||
// if we've failed four times, just give up :/
|
||||
for completion in self.pendingOwnInstanceRequestCallbacks {
|
||||
completion(.failure(error))
|
||||
}
|
||||
self.pendingOwnInstanceRequestCallbacks = []
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
|
@ -204,7 +221,7 @@ class MastodonController: ObservableObject {
|
|||
self.instanceFeatures.update(instance: instance, nodeInfo: self.nodeInfo)
|
||||
|
||||
for completion in self.pendingOwnInstanceRequestCallbacks {
|
||||
completion(instance)
|
||||
completion(.success(instance))
|
||||
}
|
||||
self.pendingOwnInstanceRequestCallbacks = []
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ struct MenuController {
|
|||
let data: Any
|
||||
if case let .tab(tab) = item {
|
||||
data = tab.rawValue
|
||||
} else if case .search = item {
|
||||
} else if case .explore = item {
|
||||
data = "search"
|
||||
} else if case .bookmarks = item {
|
||||
data = "bookmarks"
|
||||
|
@ -42,7 +42,7 @@ struct MenuController {
|
|||
static let sidebarItemKeyCommands: [UIKeyCommand] = [
|
||||
sidebarCommand(item: .tab(.timelines), command: "1"),
|
||||
sidebarCommand(item: .tab(.notifications), command: "2"),
|
||||
sidebarCommand(item: .search, command: "3"),
|
||||
sidebarCommand(item: .explore, command: "3"),
|
||||
sidebarCommand(item: .bookmarks, command: "4"),
|
||||
sidebarCommand(item: .tab(.myProfile), command: "5"),
|
||||
]
|
||||
|
|
|
@ -34,6 +34,10 @@ struct InstanceFeatures {
|
|||
instanceType != .pixelfed
|
||||
}
|
||||
|
||||
var trends: Bool {
|
||||
instanceType == .mastodon
|
||||
}
|
||||
|
||||
var trendingStatusesAndLinks: Bool {
|
||||
instanceType == .mastodon && version != nil && version! >= Version(3, 5, 0)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// TrendingLinkCardCollectionViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/29/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
private var card: Card?
|
||||
private var isGrayscale = false
|
||||
private var thumbnailRequest: ImageCache.Request?
|
||||
|
||||
@IBOutlet weak var thumbnailView: UIImageView!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var providerLabel: UILabel!
|
||||
@IBOutlet weak var activityLabel: UILabel!
|
||||
@IBOutlet weak var historyView: TrendHistoryView!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
layer.shadowOpacity = 0.2
|
||||
layer.shadowRadius = 8
|
||||
layer.shadowOffset = .zero
|
||||
layer.masksToBounds = false
|
||||
updateLayerColors()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
contentView.layer.cornerRadius = 0.05 * bounds.width
|
||||
thumbnailView.layer.cornerRadius = 0.05 * bounds.width
|
||||
}
|
||||
|
||||
func updateUI(card: Card) {
|
||||
self.card = card
|
||||
self.thumbnailView.image = nil
|
||||
|
||||
updateGrayscaleableUI(card: card)
|
||||
updateUIForPreferences()
|
||||
|
||||
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
titleLabel.text = title
|
||||
|
||||
let provider = card.providerName!.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
providerLabel.text = provider
|
||||
|
||||
let sorted = card.history!.sorted(by: { $0.day < $1.day })
|
||||
let lastTwo = sorted[(sorted.count - 2)...]
|
||||
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
||||
let uses = lastTwo.map(\.uses).reduce(0, +)
|
||||
|
||||
// U+2009 THIN SPACE
|
||||
let activityStr = NSMutableAttributedString(string: "\(accounts.formatted())\u{2009}")
|
||||
activityStr.append(NSAttributedString(attachment: NSTextAttachment(image: UIImage(systemName: "person")!)))
|
||||
activityStr.append(NSAttributedString(string: ", \(uses.formatted())\u{2009}"))
|
||||
activityStr.append(NSAttributedString(attachment: NSTextAttachment(image: UIImage(systemName: "square.text.square")!)))
|
||||
activityLabel.attributedText = activityStr
|
||||
|
||||
historyView.setHistory(card.history)
|
||||
historyView.isHidden = card.history == nil || card.history!.count < 2
|
||||
}
|
||||
|
||||
@objc private func updateUIForPreferences() {
|
||||
if isGrayscale != Preferences.shared.grayscaleImages,
|
||||
let card {
|
||||
updateGrayscaleableUI(card: card)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateGrayscaleableUI(card: Card) {
|
||||
isGrayscale = Preferences.shared.grayscaleImages
|
||||
|
||||
if let imageURL = card.image,
|
||||
let url = URL(imageURL) {
|
||||
thumbnailRequest = ImageCache.attachments.get(url, completion: { _, image in
|
||||
guard let image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: url, image: image) else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.thumbnailView.image = transformedImage
|
||||
}
|
||||
})
|
||||
if thumbnailRequest != nil {
|
||||
loadBlurHash(card: card)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadBlurHash(card: Card) {
|
||||
guard let hash = card.blurhash else {
|
||||
return
|
||||
}
|
||||
let imageViewSize = self.thumbnailView.bounds.size
|
||||
AttachmentView.queue.async { [weak self] in
|
||||
let size: CGSize
|
||||
if let width = card.width, let height = card.height {
|
||||
size = CGSize(width: width, height: height)
|
||||
} else {
|
||||
size = imageViewSize
|
||||
}
|
||||
|
||||
guard let preview = UIImage(blurHash: hash, size: size) else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self,
|
||||
self.card?.url == card.url,
|
||||
self.thumbnailView.image == nil else {
|
||||
return
|
||||
}
|
||||
self.thumbnailView.image = preview
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
updateLayerColors()
|
||||
}
|
||||
|
||||
private func updateLayerColors() {
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
// clippingView.layer.borderColor = UIColor.darkGray.withAlphaComponent(0.5).cgColor
|
||||
layer.shadowColor = UIColor.darkGray.cgColor
|
||||
} else {
|
||||
// clippingView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21179.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21169.4"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="collection view cell content view" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="izA-ZZ-g7F" customClass="TrendingLinkCardCollectionViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="400"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="Zb0-aW-Sen">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="400"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="h3b-Mf-lD6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="225"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="h3b-Mf-lD6" secondAttribute="height" multiplier="4:3" id="QDY-8a-LYC"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ho3-cU-IGi">
|
||||
<rect key="frame" x="16" y="330.66666666666674" width="268" height="20.333333333333314"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Provider" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="O9r-10-LDD">
|
||||
<rect key="frame" x="16.000000000000004" y="355" width="57.333333333333343" height="18"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Activity" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ULe-Gd-t1S">
|
||||
<rect key="frame" x="16" y="377" width="43" height="15"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LZj-Ii-63i" customClass="TrendHistoryView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="200" y="355" width="100" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="cUc-p7-aLH"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="ULe-Gd-t1S" secondAttribute="bottom" id="6UL-8b-Aia"/>
|
||||
<constraint firstItem="h3b-Mf-lD6" firstAttribute="top" secondItem="Zb0-aW-Sen" secondAttribute="top" id="EFg-Yr-vdt"/>
|
||||
<constraint firstItem="Ho3-cU-IGi" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leadingMargin" id="Ga8-LQ-f4N"/>
|
||||
<constraint firstItem="ULe-Gd-t1S" firstAttribute="top" secondItem="O9r-10-LDD" secondAttribute="bottom" constant="4" id="HPD-qN-k3z"/>
|
||||
<constraint firstAttribute="bottom" secondItem="LZj-Ii-63i" secondAttribute="bottom" constant="1" id="HWu-In-Uem"/>
|
||||
<constraint firstItem="O9r-10-LDD" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leadingMargin" id="Hz8-Bw-jpl"/>
|
||||
<constraint firstAttribute="trailing" secondItem="LZj-Ii-63i" secondAttribute="trailing" id="J9c-CF-3EF"/>
|
||||
<constraint firstItem="ULe-Gd-t1S" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leadingMargin" id="KEj-En-StX"/>
|
||||
<constraint firstItem="Ho3-cU-IGi" firstAttribute="top" secondItem="h3b-Mf-lD6" secondAttribute="bottom" constant="4" id="PjW-V1-oDs"/>
|
||||
<constraint firstItem="LZj-Ii-63i" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="O9r-10-LDD" secondAttribute="trailing" id="WNr-ZP-o9a"/>
|
||||
<constraint firstItem="LZj-Ii-63i" firstAttribute="top" secondItem="Ho3-cU-IGi" secondAttribute="bottom" constant="4" id="fpM-Hp-Oyf"/>
|
||||
<constraint firstAttribute="trailing" secondItem="h3b-Mf-lD6" secondAttribute="trailing" id="kBD-1R-bh7"/>
|
||||
<constraint firstItem="LZj-Ii-63i" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="ULe-Gd-t1S" secondAttribute="trailing" id="ruZ-p8-n0x"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Ho3-cU-IGi" secondAttribute="trailing" id="ubj-f6-bXE"/>
|
||||
<constraint firstItem="h3b-Mf-lD6" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leading" id="wF1-Gm-nVQ"/>
|
||||
<constraint firstItem="O9r-10-LDD" firstAttribute="top" secondItem="Ho3-cU-IGi" secondAttribute="bottom" constant="4" id="yPq-dT-uib"/>
|
||||
</constraints>
|
||||
</collectionViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="activityLabel" destination="ULe-Gd-t1S" id="wqe-G6-IB3"/>
|
||||
<outlet property="historyView" destination="LZj-Ii-63i" id="MVF-az-uyA"/>
|
||||
<outlet property="providerLabel" destination="O9r-10-LDD" id="xAF-NW-ymm"/>
|
||||
<outlet property="thumbnailView" destination="h3b-Mf-lD6" id="4mF-bJ-ALY"/>
|
||||
<outlet property="titleLabel" destination="Ho3-cU-IGi" id="ltu-ey-chT"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="0.0" y="-13.507109004739336"/>
|
||||
</collectionViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
|
@ -113,6 +113,10 @@ class TrendingLinkTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@objc private func updateUIForPreferences() {
|
||||
if isGrayscale != Preferences.shared.grayscaleImages,
|
||||
let card {
|
||||
updateGrayscaleableUI(card: card)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateGrayscaleableUI(card: Card) {
|
||||
|
|
|
@ -37,7 +37,7 @@ class MainSidebarViewController: UIViewController {
|
|||
}
|
||||
|
||||
var exploreTabItems: [Item] {
|
||||
var items: [Item] = [.search, .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory]
|
||||
var items: [Item] = [.explore, .bookmarks, .trendingStatuses, .profileDirectory]
|
||||
let snapshot = dataSource.snapshot()
|
||||
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
|
||||
items.append(.list(list))
|
||||
|
@ -154,7 +154,7 @@ class MainSidebarViewController: UIViewController {
|
|||
snapshot.appendItems([
|
||||
.tab(.timelines),
|
||||
.tab(.notifications),
|
||||
.search,
|
||||
.explore,
|
||||
.bookmarks,
|
||||
.tab(.myProfile)
|
||||
], toSection: .tabs)
|
||||
|
@ -177,12 +177,10 @@ class MainSidebarViewController: UIViewController {
|
|||
var discoverSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||
discoverSnapshot.append([.discoverHeader])
|
||||
discoverSnapshot.append([
|
||||
.trendingTags,
|
||||
.profileDirectory,
|
||||
], to: .discoverHeader)
|
||||
if mastodonController.instanceFeatures.trendingStatusesAndLinks {
|
||||
discoverSnapshot.insert([.trendingStatuses], before: .trendingTags)
|
||||
discoverSnapshot.insert([.trendingLinks], after: .trendingTags)
|
||||
discoverSnapshot.insert([.trendingStatuses], before: .profileDirectory)
|
||||
}
|
||||
dataSource.apply(discoverSnapshot, to: .discover)
|
||||
}
|
||||
|
@ -345,7 +343,7 @@ class MainSidebarViewController: UIViewController {
|
|||
return UserActivityManager.checkNotificationsActivity(mode: Preferences.shared.defaultNotificationsMode)
|
||||
case .tab(.compose):
|
||||
return UserActivityManager.newPostActivity(accountID: id)
|
||||
case .search:
|
||||
case .explore:
|
||||
return UserActivityManager.searchActivity()
|
||||
case .bookmarks:
|
||||
return UserActivityManager.bookmarksActivity()
|
||||
|
@ -384,8 +382,8 @@ extension MainSidebarViewController {
|
|||
}
|
||||
enum Item: Hashable {
|
||||
case tab(MainTabBarViewController.Tab)
|
||||
case search, bookmarks
|
||||
case discoverHeader, trendingStatuses, trendingTags, trendingLinks, profileDirectory
|
||||
case explore, bookmarks
|
||||
case discoverHeader, trendingStatuses, profileDirectory
|
||||
case listsHeader, list(List), addList
|
||||
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
|
||||
case savedInstancesHeader, savedInstance(URL), addSavedInstance
|
||||
|
@ -394,18 +392,14 @@ extension MainSidebarViewController {
|
|||
switch self {
|
||||
case let .tab(tab):
|
||||
return tab.title
|
||||
case .search:
|
||||
return "Search"
|
||||
case .explore:
|
||||
return "Explore"
|
||||
case .bookmarks:
|
||||
return "Bookmarks"
|
||||
case .discoverHeader:
|
||||
return "Discover"
|
||||
case .trendingStatuses:
|
||||
return "Trending Posts"
|
||||
case .trendingTags:
|
||||
return "Trending Hashtags"
|
||||
case .trendingLinks:
|
||||
return "Trending Links"
|
||||
case .profileDirectory:
|
||||
return "Profile Directory"
|
||||
case .listsHeader:
|
||||
|
@ -433,16 +427,12 @@ extension MainSidebarViewController {
|
|||
switch self {
|
||||
case let .tab(tab):
|
||||
return tab.imageName
|
||||
case .search:
|
||||
case .explore:
|
||||
return "magnifyingglass"
|
||||
case .bookmarks:
|
||||
return "bookmark"
|
||||
case .trendingStatuses:
|
||||
return "doc.text.image"
|
||||
case .trendingTags:
|
||||
return "number"
|
||||
case .trendingLinks:
|
||||
return "link"
|
||||
return "square.text.square"
|
||||
case .profileDirectory:
|
||||
return "person.2.fill"
|
||||
case .list(_):
|
||||
|
|
|
@ -100,7 +100,7 @@ class MainSplitViewController: UISplitViewController {
|
|||
item = .tab(MainTabBarViewController.Tab(rawValue: index)!)
|
||||
} else if let str = command.propertyList as? String {
|
||||
if str == "search" {
|
||||
item = .search
|
||||
item = .explore
|
||||
} else if str == "bookmarks" {
|
||||
item = .bookmarks
|
||||
} else {
|
||||
|
@ -171,7 +171,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
$0.1 > $1.1
|
||||
}
|
||||
if let mostRecentExploreItem = mostRecentExploreItem?.0,
|
||||
mostRecentExploreItem != .search {
|
||||
mostRecentExploreItem != .explore {
|
||||
let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController
|
||||
// Pop back to root, so we're appending to the Explore VC instead of some other VC
|
||||
exploreNav.popToRootViewController(animated: false)
|
||||
|
@ -188,7 +188,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
// sidebar items that map 1 <-> 1 can be transferred directly
|
||||
tabBarViewController.select(tab: tab)
|
||||
|
||||
case .search:
|
||||
case .explore:
|
||||
// Search sidebar item maps to the Explore tab with the search controller/results visible
|
||||
// The nav stack can't be copied directly, since the split VC uses a different SearchViewController
|
||||
// so that explore items aren't shown multiple times.
|
||||
|
@ -217,11 +217,11 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
explore.resultsController.loadResults(from: search.resultsController)
|
||||
|
||||
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
|
||||
transferNavigationStack(from: .search, to: exploreNav, dropFirst: true, append: true)
|
||||
transferNavigationStack(from: .explore, to: exploreNav, dropFirst: true, append: true)
|
||||
|
||||
tabBarViewController.select(tab: .explore)
|
||||
|
||||
case .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||
case .bookmarks, .trendingStatuses, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||
tabBarViewController.select(tab: .explore)
|
||||
// Make sure the Explore VC doesn't show it's search bar when it appears, in case the user was previously
|
||||
// in compact mode and performing a search.
|
||||
|
@ -272,7 +272,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
// For other items, the 2nd VC in the nav stack determines which sidebar item they map to.
|
||||
// Search screen has special considerations, all others can be transferred directly.
|
||||
if tabNavigationStack.count == 1 || ((tabNavigationStack.first as? ExploreViewController)?.searchController?.isActive ?? false) {
|
||||
exploreItem = .search
|
||||
exploreItem = .explore
|
||||
let searchVC = SearchViewController(mastodonController: mastodonController)
|
||||
searchVC.loadViewIfNeeded()
|
||||
let explore = tabNavigationStack.first as! ExploreViewController
|
||||
|
@ -300,9 +300,9 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
case is TrendingStatusesViewController:
|
||||
exploreItem = .trendingStatuses
|
||||
case is TrendingHashtagsViewController:
|
||||
exploreItem = .trendingTags
|
||||
exploreItem = .explore
|
||||
case is TrendingLinksViewController:
|
||||
exploreItem = .trendingLinks
|
||||
exploreItem = .explore
|
||||
case is ProfileDirectoryViewController:
|
||||
exploreItem = .profileDirectory
|
||||
default:
|
||||
|
@ -354,16 +354,12 @@ fileprivate extension MainSidebarViewController.Item {
|
|||
switch self {
|
||||
case let .tab(tab):
|
||||
return tab.createViewController(mastodonController)
|
||||
case .search:
|
||||
case .explore:
|
||||
return SearchViewController(mastodonController: mastodonController)
|
||||
case .bookmarks:
|
||||
return BookmarksTableViewController(mastodonController: mastodonController)
|
||||
case .trendingStatuses:
|
||||
return TrendingStatusesViewController(mastodonController: mastodonController)
|
||||
case .trendingTags:
|
||||
return TrendingHashtagsViewController(mastodonController: mastodonController)
|
||||
case .trendingLinks:
|
||||
return TrendingLinksViewController(mastodonController: mastodonController)
|
||||
case .profileDirectory:
|
||||
return ProfileDirectoryViewController(mastodonController: mastodonController)
|
||||
case let .list(list):
|
||||
|
@ -435,8 +431,8 @@ extension MainSplitViewController: TuskerRootViewController {
|
|||
return
|
||||
}
|
||||
|
||||
if sidebar.selectedItem != .search {
|
||||
select(item: .search)
|
||||
if sidebar.selectedItem != .explore {
|
||||
select(item: .explore)
|
||||
}
|
||||
|
||||
guard let searchViewController = secondaryNavController.viewControllers.first as? SearchViewController else {
|
||||
|
|
|
@ -7,11 +7,16 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
|
||||
class SearchViewController: UIViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
private var collectionView: UICollectionView!
|
||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||
|
||||
var resultsController: SearchResultsViewController!
|
||||
var searchController: UISearchController!
|
||||
|
||||
|
@ -22,7 +27,7 @@ class SearchViewController: UIViewController {
|
|||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
title = NSLocalizedString("Search", comment: "search tab title")
|
||||
title = NSLocalizedString("Explore", comment: "explore tab title")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -32,12 +37,46 @@ class SearchViewController: UIViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = .systemBackground
|
||||
let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
|
||||
let sectionIdentifier = self.dataSource.snapshot().sectionIdentifiers[sectionIndex]
|
||||
switch sectionIdentifier {
|
||||
case .trendingHashtags:
|
||||
var listConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
|
||||
listConfig.headerMode = .supplementary
|
||||
return .list(using: listConfig, layoutEnvironment: environment)
|
||||
|
||||
case .trendingLinks:
|
||||
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
// todo: i really wish i could just say the height is automatic and let autolayout figure out what it needs to be
|
||||
// using .estimated(whatever) constrains the height to exactly whatever
|
||||
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(250), heightDimension: .estimated(280))
|
||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
|
||||
group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .fixed(8), top: nil, trailing: .fixed(8), bottom: nil)
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
|
||||
section.boundarySupplementaryItems = [
|
||||
NSCollectionLayoutBoundarySupplementaryItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(12)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .topLeading)
|
||||
]
|
||||
return section
|
||||
|
||||
default:
|
||||
fatalError("unimplemented")
|
||||
}
|
||||
}
|
||||
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
|
||||
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
collectionView.delegate = self
|
||||
collectionView.dragDelegate = self
|
||||
collectionView.backgroundColor = .secondarySystemBackground
|
||||
view.addSubview(collectionView)
|
||||
|
||||
dataSource = createDataSource()
|
||||
|
||||
resultsController = SearchResultsViewController(mastodonController: mastodonController)
|
||||
resultsController.exploreNavigationController = self.navigationController
|
||||
searchController = UISearchController(searchResultsController: resultsController)
|
||||
searchController.obscuresBackgroundDuringPresentation = false
|
||||
searchController.obscuresBackgroundDuringPresentation = true
|
||||
searchController.searchBar.autocapitalizationType = .none
|
||||
searchController.searchBar.delegate = resultsController
|
||||
searchController.hidesNavigationBarDuringPresentation = false
|
||||
|
@ -48,6 +87,18 @@ class SearchViewController: UIViewController {
|
|||
if #available(iOS 16.0, *) {
|
||||
navigationItem.preferredSearchBarPlacement = .stacked
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
Task(priority: .userInitiated) {
|
||||
if (try? await mastodonController.getOwnInstance()) != nil {
|
||||
await applySnapshot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
@ -62,4 +113,213 @@ class SearchViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
let sectionHeaderCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { [unowned self] (headerView, collectionView, indexPath) in
|
||||
let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
|
||||
var config = UIListContentConfiguration.groupedHeader()
|
||||
config.text = section.title
|
||||
headerView.contentConfiguration = config
|
||||
}
|
||||
|
||||
let trendingHashtagCell = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { (cell, indexPath, hashtag) in
|
||||
cell.updateUI(hashtag: hashtag)
|
||||
}
|
||||
let trendingLinkCell = UICollectionView.CellRegistration<TrendingLinkCardCollectionViewCell, Card>(cellNib: UINib(nibName: "TrendingLinkCardCollectionViewCell", bundle: .main)) { (cell, indexPath, card) in
|
||||
cell.updateUI(card: card)
|
||||
}
|
||||
|
||||
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: trendingHashtagCell, for: indexPath, item: hashtag)
|
||||
|
||||
case let .link(card):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: trendingLinkCell, for: indexPath, item: card)
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in
|
||||
if elementKind == UICollectionView.elementKindSectionHeader {
|
||||
return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderCell, for: indexPath)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dataSource
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func applySnapshot() async {
|
||||
guard mastodonController.instanceFeatures.trends,
|
||||
!Preferences.shared.hideDiscover else {
|
||||
await dataSource.apply(NSDiffableDataSourceSnapshot())
|
||||
return
|
||||
}
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
|
||||
let hashtagsReq = Client.getTrendingHashtags(limit: 5)
|
||||
async let hashtags = try? mastodonController.run(hashtagsReq).0
|
||||
let linksReq = Client.getTrendingLinks(limit: 10)
|
||||
async let links = try? mastodonController.run(linksReq).0
|
||||
|
||||
if let hashtags = await hashtags {
|
||||
snapshot.appendSections([.trendingHashtags])
|
||||
snapshot.appendItems(hashtags.map { .tag($0) }, toSection: .trendingHashtags)
|
||||
}
|
||||
|
||||
if let links = await links {
|
||||
snapshot.appendSections([.trendingLinks])
|
||||
snapshot.appendItems(links.map { .link($0) }, toSection: .trendingLinks)
|
||||
}
|
||||
|
||||
await dataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
@objc private func preferencesChanged() {
|
||||
Task {
|
||||
await applySnapshot()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SearchViewController {
|
||||
enum Section {
|
||||
case trendingHashtags
|
||||
case trendingLinks
|
||||
case trendingStatuses
|
||||
case profileSuggestions
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .trendingHashtags:
|
||||
return "Trending Hashtags"
|
||||
case .trendingLinks:
|
||||
return "Trending Links"
|
||||
case .trendingStatuses:
|
||||
return "Trending Statuses"
|
||||
case .profileSuggestions:
|
||||
return "Suggested Accounts"
|
||||
}
|
||||
}
|
||||
}
|
||||
enum Item: Equatable, Hashable {
|
||||
case status(String)
|
||||
case tag(Hashtag)
|
||||
case link(Card)
|
||||
|
||||
static func == (lhs: SearchViewController.Item, rhs: SearchViewController.Item) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.status(a), .status(b)):
|
||||
return a == b
|
||||
case let (.tag(a), .tag(b)):
|
||||
return a == b
|
||||
case let (.link(a), .link(b)):
|
||||
return a.url == b.url
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case let .status(id):
|
||||
hasher.combine("status")
|
||||
hasher.combine(id)
|
||||
case let .tag(tag):
|
||||
hasher.combine("tag")
|
||||
hasher.combine(tag.name)
|
||||
case let .link(card):
|
||||
hasher.combine("link")
|
||||
hasher.combine(card.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case let .link(card):
|
||||
if let url = URL(card.url) {
|
||||
selected(url: url)
|
||||
}
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
return UIContextMenuConfiguration(identifier: nil) {
|
||||
HashtagTimelineViewController(for: hashtag, mastodonController: self.mastodonController)
|
||||
} actionProvider: { (_) in
|
||||
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.collectionView.cellForItem(at: indexPath)))
|
||||
}
|
||||
|
||||
case let .link(card):
|
||||
guard let url = URL(card.url) else {
|
||||
return nil
|
||||
}
|
||||
return UIContextMenuConfiguration {
|
||||
SFSafariViewController(url: url)
|
||||
} actionProvider: { _ in
|
||||
UIMenu(children: self.actionsForTrendingLink(card: card))
|
||||
}
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController: UICollectionViewDragDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return []
|
||||
}
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
let provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) {
|
||||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
}
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
|
||||
case let .link(card):
|
||||
guard let url = URL(card.url) else {
|
||||
return []
|
||||
}
|
||||
return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))]
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController: TuskerNavigationDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
}
|
||||
|
||||
extension SearchViewController: ToastableViewController {
|
||||
}
|
||||
|
||||
extension SearchViewController: MenuActionProvider {
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue