forked from shadowfacts/Tusker
Add Trending Links
This commit is contained in:
parent
240ccf23a4
commit
8473f32781
|
@ -388,6 +388,16 @@ public class Client {
|
|||
return Request(method: .get, path: "/api/v1/trends/statuses", queryParameters: parameters)
|
||||
}
|
||||
|
||||
public static func getTrendingLinks(limit: Int? = nil) -> Request<[Card]> {
|
||||
let parameters: [Parameter]
|
||||
if let limit = limit {
|
||||
parameters = ["limit" => limit]
|
||||
} else {
|
||||
parameters = []
|
||||
}
|
||||
return Request(method: .get, path: "/api/v1/trends/links", queryParameters: parameters)
|
||||
}
|
||||
|
||||
public static func getFeaturedProfiles(local: Bool, order: DirectoryOrder, offset: Int? = nil, limit: Int? = nil) -> Request<[Account]> {
|
||||
var parameters = [
|
||||
"order" => order.rawValue,
|
||||
|
|
|
@ -23,6 +23,8 @@ public class Card: Codable {
|
|||
public let width: Int?
|
||||
public let height: Int?
|
||||
public let blurhash: String?
|
||||
/// Only present when returned from the trending links endpoint
|
||||
public let history: [History]?
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
@ -40,6 +42,7 @@ public class Card: Codable {
|
|||
self.width = try? container.decodeIfPresent(Int.self, forKey: .width)
|
||||
self.height = try? container.decodeIfPresent(Int.self, forKey: .height)
|
||||
self.blurhash = try? container.decodeIfPresent(String.self, forKey: .blurhash)
|
||||
self.history = try? container.decodeIfPresent([History].self, forKey: .history)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
@ -67,6 +70,7 @@ public class Card: Codable {
|
|||
case width
|
||||
case height
|
||||
case blurhash
|
||||
case history
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import Foundation
|
|||
public class Hashtag: Codable {
|
||||
public let name: String
|
||||
public let url: URL
|
||||
/// Only present when returned from the trending hashtags endpoint
|
||||
public let history: [History]?
|
||||
|
||||
public init(name: String, url: URL) {
|
||||
|
@ -26,53 +27,6 @@ public class Hashtag: Codable {
|
|||
}
|
||||
}
|
||||
|
||||
extension Hashtag {
|
||||
public class History: Codable {
|
||||
public let day: Date
|
||||
public let uses: Int
|
||||
public let accounts: Int
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if let day = try? container.decode(Date.self, forKey: .day) {
|
||||
self.day = day
|
||||
} else if let unixTimestamp = try? container.decode(Double.self, forKey: .day) {
|
||||
self.day = Date(timeIntervalSince1970: unixTimestamp)
|
||||
} else if let str = try? container.decode(String.self, forKey: .day),
|
||||
let unixTimestamp = Double(str) {
|
||||
self.day = Date(timeIntervalSince1970: unixTimestamp)
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .day, in: container, debugDescription: "day must be either date or UNIX timestamp")
|
||||
}
|
||||
|
||||
if let uses = try? container.decode(Int.self, forKey: .uses) {
|
||||
self.uses = uses
|
||||
} else if let str = try? container.decode(String.self, forKey: .uses),
|
||||
let uses = Int(str) {
|
||||
self.uses = uses
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .uses, in: container, debugDescription: "uses must either be int or string containing int")
|
||||
}
|
||||
|
||||
if let accounts = try? container.decode(Int.self, forKey: .accounts) {
|
||||
self.accounts = accounts
|
||||
} else if let str = try? container.decode(String.self, forKey: .accounts),
|
||||
let accounts = Int(str) {
|
||||
self.accounts = accounts
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .accounts, in: container, debugDescription: "accounts must either be int or string containing int")
|
||||
}
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case day
|
||||
case uses
|
||||
case accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Hashtag: Equatable, Hashable {
|
||||
public static func ==(lhs: Hashtag, rhs: Hashtag) -> Bool {
|
||||
return lhs.name == rhs.name
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// History.swift
|
||||
// Pachyderm
|
||||
//
|
||||
// Created by Shadowfacts on 4/2/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class History: Codable {
|
||||
public let day: Date
|
||||
public let uses: Int
|
||||
public let accounts: Int
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if let day = try? container.decode(Date.self, forKey: .day) {
|
||||
self.day = day
|
||||
} else if let unixTimestamp = try? container.decode(Double.self, forKey: .day) {
|
||||
self.day = Date(timeIntervalSince1970: unixTimestamp)
|
||||
} else if let str = try? container.decode(String.self, forKey: .day),
|
||||
let unixTimestamp = Double(str) {
|
||||
self.day = Date(timeIntervalSince1970: unixTimestamp)
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .day, in: container, debugDescription: "day must be either date or UNIX timestamp")
|
||||
}
|
||||
|
||||
if let uses = try? container.decode(Int.self, forKey: .uses) {
|
||||
self.uses = uses
|
||||
} else if let str = try? container.decode(String.self, forKey: .uses),
|
||||
let uses = Int(str) {
|
||||
self.uses = uses
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .uses, in: container, debugDescription: "uses must either be int or string containing int")
|
||||
}
|
||||
|
||||
if let accounts = try? container.decode(Int.self, forKey: .accounts) {
|
||||
self.accounts = accounts
|
||||
} else if let str = try? container.decode(String.self, forKey: .accounts),
|
||||
let accounts = Int(str) {
|
||||
self.accounts = accounts
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .accounts, in: container, debugDescription: "accounts must either be int or string containing int")
|
||||
}
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case day
|
||||
case uses
|
||||
case accounts
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@
|
|||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; };
|
||||
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; };
|
||||
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; };
|
||||
D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */; };
|
||||
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */; };
|
||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; };
|
||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; };
|
||||
|
@ -72,6 +72,9 @@
|
|||
D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */; };
|
||||
D6114E0B27F3F6EA0080E273 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0A27F3F6EA0080E273 /* Endpoint.swift */; };
|
||||
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */; };
|
||||
D6114E0F27F897D70080E273 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0E27F897D70080E273 /* History.swift */; };
|
||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */; };
|
||||
D6114E1327F89B440080E273 /* TrendingLinkTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */; };
|
||||
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; };
|
||||
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; };
|
||||
D61AC1D3232E928600C54D2D /* InstanceSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D2232E928600C54D2D /* InstanceSelector.swift */; };
|
||||
|
@ -435,7 +438,7 @@
|
|||
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagSearchResultsViewController.swift; sourceTree = "<group>"; };
|
||||
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingHashtagTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagHistoryView.swift; sourceTree = "<group>"; };
|
||||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendHistoryView.swift; sourceTree = "<group>"; };
|
||||
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
|
||||
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D60E2F232442372B005F8713 /* StatusMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMO.swift; sourceTree = "<group>"; };
|
||||
|
@ -486,6 +489,9 @@
|
|||
D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; };
|
||||
D6114E0A27F3F6EA0080E273 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
|
||||
D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusesViewController.swift; sourceTree = "<group>"; };
|
||||
D6114E0E27F897D70080E273 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
|
||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinksViewController.swift; sourceTree = "<group>"; };
|
||||
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinkTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HashtagTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D61AC1D2232E928600C54D2D /* InstanceSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelector.swift; sourceTree = "<group>"; };
|
||||
|
@ -916,6 +922,7 @@
|
|||
D61099E22144C38900432DC2 /* Emoji.swift */,
|
||||
D61099EC2145664800432DC2 /* Filter.swift */,
|
||||
D6109A0021456B0800432DC2 /* Hashtag.swift */,
|
||||
D6114E0E27F897D70080E273 /* History.swift */,
|
||||
D61099EE214566C000432DC2 /* Instance.swift */,
|
||||
D61099F02145686D00432DC2 /* List.swift */,
|
||||
D6109A062145756700432DC2 /* LoginSettings.swift */,
|
||||
|
@ -944,7 +951,6 @@
|
|||
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */,
|
||||
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */,
|
||||
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */,
|
||||
D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */,
|
||||
);
|
||||
path = "Hashtag Cell";
|
||||
sourceTree = "<group>";
|
||||
|
@ -1027,6 +1033,8 @@
|
|||
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */,
|
||||
D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */,
|
||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
||||
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */,
|
||||
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
||||
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
||||
|
@ -1506,6 +1514,7 @@
|
|||
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */,
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
|
||||
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */,
|
||||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
||||
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
||||
|
@ -2082,6 +2091,7 @@
|
|||
D6114E0B27F3F6EA0080E273 /* Endpoint.swift in Sources */,
|
||||
D61099EF214566C000432DC2 /* Instance.swift in Sources */,
|
||||
D61099D22144B2E600432DC2 /* Body.swift in Sources */,
|
||||
D6114E0F27F897D70080E273 /* History.swift in Sources */,
|
||||
D623A53F2635F6910095BD04 /* Poll.swift in Sources */,
|
||||
D63569E023908A8D003DD353 /* StatusState.swift in Sources */,
|
||||
D6109A0121456B0800432DC2 /* Hashtag.swift in Sources */,
|
||||
|
@ -2115,7 +2125,7 @@
|
|||
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */,
|
||||
D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */,
|
||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
||||
D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */,
|
||||
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */,
|
||||
D6DEA0DE268400C300FE896A /* ConfirmLoadMoreTableViewCell.swift in Sources */,
|
||||
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */,
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
|
@ -2275,6 +2285,7 @@
|
|||
D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */,
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
||||
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
|
||||
D6114E1327F89B440080E273 /* TrendingLinkTableViewCell.swift in Sources */,
|
||||
D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */,
|
||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
||||
|
@ -2284,6 +2295,7 @@
|
|||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
|
||||
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
|
||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
|
||||
D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */,
|
||||
|
|
|
@ -156,7 +156,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
private func addDiscoverSection(to snapshot: inout NSDiffableDataSourceSnapshot<Section, Item>) {
|
||||
snapshot.insertSections([.discover], afterSection: .bookmarks)
|
||||
// todo: check version
|
||||
snapshot.appendItems([.trendingStatuses, .trendingTags, .profileDirectory], toSection: .discover)
|
||||
snapshot.appendItems([.trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory], toSection: .discover)
|
||||
}
|
||||
|
||||
private func ownInstanceLoaded(_ instance: Instance) {
|
||||
|
@ -300,6 +300,9 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
case .trendingTags:
|
||||
show(TrendingHashtagsViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .trendingLinks:
|
||||
show(TrendingLinksViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .profileDirectory:
|
||||
show(ProfileDirectoryViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
|
@ -381,6 +384,7 @@ extension ExploreViewController {
|
|||
case bookmarks
|
||||
case trendingStatuses
|
||||
case trendingTags
|
||||
case trendingLinks
|
||||
case profileDirectory
|
||||
case list(List)
|
||||
case addList
|
||||
|
@ -397,6 +401,8 @@ extension ExploreViewController {
|
|||
return NSLocalizedString("Trending Posts", comment: "trending statuses nav item title")
|
||||
case .trendingTags:
|
||||
return NSLocalizedString("Trending Hashtags", comment: "trending hashtags nav item title")
|
||||
case .trendingLinks:
|
||||
return NSLocalizedString("Trending Links", comment: "trending links nav item title")
|
||||
case .profileDirectory:
|
||||
return NSLocalizedString("Profile Directory", comment: "profile directory nav item title")
|
||||
case let .list(list):
|
||||
|
@ -422,7 +428,9 @@ extension ExploreViewController {
|
|||
case .trendingStatuses:
|
||||
name = "doc.text.image"
|
||||
case .trendingTags:
|
||||
name = "arrow.up.arrow.down"
|
||||
name = "number"
|
||||
case .trendingLinks:
|
||||
name = "link"
|
||||
case .profileDirectory:
|
||||
name = "person.2.fill"
|
||||
case .list(_):
|
||||
|
@ -447,6 +455,8 @@ extension ExploreViewController {
|
|||
return true
|
||||
case (.trendingTags, .trendingTags):
|
||||
return true
|
||||
case (.trendingLinks, .trendingLinks):
|
||||
return true
|
||||
case (.profileDirectory, .profileDirectory):
|
||||
return true
|
||||
case let (.list(a), .list(b)):
|
||||
|
@ -474,6 +484,8 @@ extension ExploreViewController {
|
|||
hasher.combine("trendingStatuses")
|
||||
case .trendingTags:
|
||||
hasher.combine("trendingTags")
|
||||
case .trendingLinks:
|
||||
hasher.combine("trendingLinks")
|
||||
case .profileDirectory:
|
||||
hasher.combine("profileDirectory")
|
||||
case let .list(list):
|
||||
|
|
|
@ -81,7 +81,6 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
} actionProvider: { (_) in
|
||||
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.tableView.cellForRow(at: indexPath)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
//
|
||||
// TrendingLinkTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/2/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class TrendingLinkTableViewCell: UITableViewCell {
|
||||
|
||||
private var card: Card?
|
||||
private var isGrayscale = false
|
||||
private var thumbnailRequest: ImageCache.Request?
|
||||
|
||||
private let thumbnailView = UIImageView()
|
||||
private let titleLabel = UILabel()
|
||||
private let providerLabel = UILabel()
|
||||
private let activityLabel = UILabel()
|
||||
private let historyView = TrendHistoryView()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
thumbnailView.contentMode = .scaleAspectFill
|
||||
thumbnailView.clipsToBounds = true
|
||||
|
||||
titleLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .headline).withSymbolicTraits(.traitBold)!, size: 0)
|
||||
titleLabel.numberOfLines = 2
|
||||
|
||||
providerLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
|
||||
activityLabel.font = .preferredFont(forTextStyle: .caption1)
|
||||
|
||||
let vStack = UIStackView(arrangedSubviews: [
|
||||
titleLabel,
|
||||
providerLabel,
|
||||
activityLabel,
|
||||
])
|
||||
vStack.axis = .vertical
|
||||
vStack.spacing = 4
|
||||
|
||||
let hStack = UIStackView(arrangedSubviews: [
|
||||
thumbnailView,
|
||||
vStack,
|
||||
historyView,
|
||||
])
|
||||
hStack.axis = .horizontal
|
||||
hStack.spacing = 4
|
||||
hStack.alignment = .center
|
||||
hStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(hStack)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
thumbnailView.heightAnchor.constraint(equalToConstant: 75),
|
||||
thumbnailView.widthAnchor.constraint(equalTo: thumbnailView.heightAnchor),
|
||||
|
||||
historyView.widthAnchor.constraint(equalToConstant: 75),
|
||||
historyView.heightAnchor.constraint(equalToConstant: 44),
|
||||
|
||||
hStack.leadingAnchor.constraint(equalToSystemSpacingAfter: safeAreaLayoutGuide.leadingAnchor, multiplier: 1),
|
||||
safeAreaLayoutGuide.trailingAnchor.constraint(equalToSystemSpacingAfter: hStack.trailingAnchor, multiplier: 1),
|
||||
hStack.topAnchor.constraint(equalToSystemSpacingBelow: topAnchor, multiplier: 1),
|
||||
bottomAnchor.constraint(equalToSystemSpacingBelow: hStack.bottomAnchor, multiplier: 1),
|
||||
])
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
thumbnailView.layer.cornerRadius = 0.05 * thumbnailView.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
|
||||
titleLabel.isHidden = title.isEmpty
|
||||
|
||||
let provider = card.providerName?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
providerLabel.text = provider
|
||||
providerLabel.isHidden = provider?.isEmpty ?? true
|
||||
|
||||
if let history = card.history {
|
||||
let sorted = 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, +)
|
||||
|
||||
let format = NSLocalizedString("trending hashtag info", comment: "trending hashtag posts and people")
|
||||
activityLabel.text = String.localizedStringWithFormat(format, accounts, uses)
|
||||
activityLabel.isHidden = false
|
||||
} else {
|
||||
activityLabel.isHidden = true
|
||||
}
|
||||
|
||||
historyView.setHistory(card.history)
|
||||
historyView.isHidden = card.history == nil || card.history!.count < 2
|
||||
}
|
||||
|
||||
@objc private func updateUIForPreferences() {
|
||||
}
|
||||
|
||||
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 = 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,
|
||||
self.card?.url == card.url,
|
||||
self.thumbnailView.image == nil else {
|
||||
return
|
||||
}
|
||||
self.thumbnailView.image = preview
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// TrendingLinksViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/2/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import SafariServices
|
||||
|
||||
class TrendingLinksViewController: EnhancedTableViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .grouped)
|
||||
|
||||
dragEnabled = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = NSLocalizedString("Trending Links", comment: "trending links screen title")
|
||||
|
||||
tableView.register(TrendingLinkTableViewCell.self, forCellReuseIdentifier: "trendingLinkCell")
|
||||
tableView.estimatedRowHeight = 100
|
||||
|
||||
dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingLinkCell", for: indexPath) as! TrendingLinkTableViewCell
|
||||
cell.updateUI(card: item.card)
|
||||
return cell
|
||||
})
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
let request = Client.getTrendingLinks()
|
||||
Task {
|
||||
guard let (links, _) = try? await mastodonController.run(request) else {
|
||||
return
|
||||
}
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.links])
|
||||
snapshot.appendItems(links.map(Item.init))
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
let url = URL(item.card.url) else {
|
||||
return
|
||||
}
|
||||
selected(url: url)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
let url = URL(item.card.url) else {
|
||||
return nil
|
||||
}
|
||||
return UIContextMenuConfiguration(identifier: nil) {
|
||||
return SFSafariViewController(url: url)
|
||||
} actionProvider: { _ in
|
||||
return UIMenu(children: self.actionsForTrendingLink(card: item.card))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension TrendingLinksViewController {
|
||||
enum Section {
|
||||
case links
|
||||
}
|
||||
struct Item: Hashable {
|
||||
let card: Card
|
||||
|
||||
static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||
return lhs.card.url == rhs.card.url
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(card.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TrendingLinksViewController: TuskerNavigationDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
}
|
||||
|
||||
extension TrendingLinksViewController: MenuPreviewProvider {
|
||||
var navigationDelegate: TuskerNavigationDelegate? { self }
|
||||
}
|
|
@ -209,6 +209,29 @@ extension MenuPreviewProvider {
|
|||
]
|
||||
}
|
||||
|
||||
func actionsForTrendingLink(card: Card) -> [UIMenuElement] {
|
||||
guard let url = URL(card.url) else {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
openInSafariAction(url: url),
|
||||
createAction(identifier: "postlink", title: "Post this Link", systemImageName: "square.and.pencil", handler: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
let draft = self.mastodonController!.createDraft()
|
||||
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !title.isEmpty {
|
||||
draft.text += title
|
||||
draft.text += ":\n"
|
||||
}
|
||||
draft.text += url.absoluteString
|
||||
// prevents the draft from being saved automatically until the user makes a change
|
||||
// also prevents it from being posted without being changed
|
||||
draft.initialText = draft.text
|
||||
self.navigationDelegate?.compose(editing: draft)
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
private func createAction(identifier: String, title: String, systemImageName: String?, handler: @escaping UIActionHandler) -> UIAction {
|
||||
let image: UIImage?
|
||||
if let name = systemImageName {
|
||||
|
|
|
@ -13,7 +13,7 @@ class TrendingHashtagTableViewCell: UITableViewCell {
|
|||
|
||||
@IBOutlet weak var hashtagLabel: UILabel!
|
||||
@IBOutlet weak var peopleTodayLabel: UILabel!
|
||||
@IBOutlet weak var historyView: HashtagHistoryView!
|
||||
@IBOutlet weak var historyView: TrendHistoryView!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -37,7 +37,7 @@
|
|||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Xrw-2v-ybZ" customClass="HashtagHistoryView" customModule="Tusker" customModuleProvider="target">
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Xrw-2v-ybZ" customClass="TrendHistoryView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="188" y="11" width="100" height="44"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// HashtagHistoryView.swift
|
||||
// TrendHistoryView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/24/21.
|
||||
|
@ -9,9 +9,9 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class HashtagHistoryView: UIView {
|
||||
class TrendHistoryView: UIView {
|
||||
|
||||
private var history: [Hashtag.History]?
|
||||
private var history: [History]?
|
||||
|
||||
private let curveRadius: CGFloat = 10
|
||||
|
||||
|
@ -30,7 +30,7 @@ class HashtagHistoryView: UIView {
|
|||
createLayers()
|
||||
}
|
||||
|
||||
func setHistory(_ history: [Hashtag.History]?) {
|
||||
func setHistory(_ history: [History]?) {
|
||||
if let history = history {
|
||||
self.history = history.sorted(by: { $0.day < $1.day })
|
||||
} else {
|
Loading…
Reference in New Issue