Add Profile Directory
This commit is contained in:
parent
6a927e4092
commit
bbb8707cb7
|
@ -323,7 +323,7 @@ public class Client {
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Trends
|
// MARK: - Instance
|
||||||
public static func getTrends(limit: Int? = nil) -> Request<[Hashtag]> {
|
public static func getTrends(limit: Int? = nil) -> Request<[Hashtag]> {
|
||||||
let parameters: [Parameter]
|
let parameters: [Parameter]
|
||||||
if let limit = limit {
|
if let limit = limit {
|
||||||
|
@ -334,6 +334,20 @@ public class Client {
|
||||||
return Request<[Hashtag]>(method: .get, path: "/api/v1/trends", queryParameters: parameters)
|
return Request<[Hashtag]>(method: .get, path: "/api/v1/trends", queryParameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func getFeaturedProfiles(local: Bool, order: DirectoryOrder, offset: Int? = nil, limit: Int? = nil) -> Request<[Account]> {
|
||||||
|
var parameters = [
|
||||||
|
"order" => order.rawValue,
|
||||||
|
"local" => local,
|
||||||
|
]
|
||||||
|
if let offset = offset {
|
||||||
|
parameters.append("offset" => offset)
|
||||||
|
}
|
||||||
|
if let limit = limit {
|
||||||
|
parameters.append("limit" => limit)
|
||||||
|
}
|
||||||
|
return Request<[Account]>(method: .get, path: "/api/v1/directory", queryParameters: parameters)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Client {
|
extension Client {
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
//
|
||||||
|
// DirectoryOrder.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 2/6/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum DirectoryOrder: String {
|
||||||
|
case active
|
||||||
|
case new
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||||
|
D600613E25D07E170067FAD6 /* ProfileDirectoryFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D600613D25D07E170067FAD6 /* ProfileDirectoryFilterView.swift */; };
|
||||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; };
|
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; };
|
||||||
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; };
|
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; };
|
||||||
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; };
|
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; };
|
||||||
|
@ -197,6 +198,10 @@
|
||||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
||||||
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */; };
|
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */; };
|
||||||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
|
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
|
||||||
|
D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */; };
|
||||||
|
D693A72C25CF8D15003A14E2 /* DirectoryOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72B25CF8D15003A14E2 /* DirectoryOrder.swift */; };
|
||||||
|
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */; };
|
||||||
|
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */; };
|
||||||
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
||||||
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5823FE24300061E07D /* InteractivePushTransition.swift */; };
|
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5823FE24300061E07D /* InteractivePushTransition.swift */; };
|
||||||
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedDataManager.swift */; };
|
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedDataManager.swift */; };
|
||||||
|
@ -381,6 +386,7 @@
|
||||||
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
||||||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||||
|
D600613D25D07E170067FAD6 /* ProfileDirectoryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDirectoryFilterView.swift; sourceTree = "<group>"; };
|
||||||
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagSearchResultsViewController.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingHashtagTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
@ -564,6 +570,10 @@
|
||||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
||||||
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Helpers.swift"; sourceTree = "<group>"; };
|
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Helpers.swift"; sourceTree = "<group>"; };
|
||||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
|
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDirectoryViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D693A72B25CF8D15003A14E2 /* DirectoryOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryOrder.swift; sourceTree = "<group>"; };
|
||||||
|
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FeaturedProfileCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
||||||
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = "<group>"; };
|
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = "<group>"; };
|
||||||
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedDataManager.swift; sourceTree = "<group>"; };
|
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedDataManager.swift; sourceTree = "<group>"; };
|
||||||
|
@ -818,6 +828,7 @@
|
||||||
D61099E6214561FF00432DC2 /* Attachment.swift */,
|
D61099E6214561FF00432DC2 /* Attachment.swift */,
|
||||||
D61099E82145658300432DC2 /* Card.swift */,
|
D61099E82145658300432DC2 /* Card.swift */,
|
||||||
D61099EA2145661700432DC2 /* ConversationContext.swift */,
|
D61099EA2145661700432DC2 /* ConversationContext.swift */,
|
||||||
|
D693A72B25CF8D15003A14E2 /* DirectoryOrder.swift */,
|
||||||
D61099E22144C38900432DC2 /* Emoji.swift */,
|
D61099E22144C38900432DC2 /* Emoji.swift */,
|
||||||
D61099EC2145664800432DC2 /* Filter.swift */,
|
D61099EC2145664800432DC2 /* Filter.swift */,
|
||||||
D6109A0021456B0800432DC2 /* Hashtag.swift */,
|
D6109A0021456B0800432DC2 /* Hashtag.swift */,
|
||||||
|
@ -917,6 +928,10 @@
|
||||||
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */,
|
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */,
|
||||||
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */,
|
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */,
|
||||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
||||||
|
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
||||||
|
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
||||||
|
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
||||||
|
D600613D25D07E170067FAD6 /* ProfileDirectoryFilterView.swift */,
|
||||||
);
|
);
|
||||||
path = Explore;
|
path = Explore;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1740,6 +1755,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
||||||
|
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */,
|
||||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
||||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
||||||
D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */,
|
D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */,
|
||||||
|
@ -1834,6 +1850,7 @@
|
||||||
D61099D02144B2D700432DC2 /* Method.swift in Sources */,
|
D61099D02144B2D700432DC2 /* Method.swift in Sources */,
|
||||||
D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */,
|
D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */,
|
||||||
D61099FB214569F600432DC2 /* Report.swift in Sources */,
|
D61099FB214569F600432DC2 /* Report.swift in Sources */,
|
||||||
|
D693A72C25CF8D15003A14E2 /* DirectoryOrder.swift in Sources */,
|
||||||
D61099F92145698900432DC2 /* Relationship.swift in Sources */,
|
D61099F92145698900432DC2 /* Relationship.swift in Sources */,
|
||||||
D61099E12144C1DC00432DC2 /* Account.swift in Sources */,
|
D61099E12144C1DC00432DC2 /* Account.swift in Sources */,
|
||||||
D61099E92145658300432DC2 /* Card.swift in Sources */,
|
D61099E92145658300432DC2 /* Card.swift in Sources */,
|
||||||
|
@ -1876,6 +1893,7 @@
|
||||||
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */,
|
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */,
|
||||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */,
|
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */,
|
||||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
||||||
|
D600613E25D07E170067FAD6 /* ProfileDirectoryFilterView.swift in Sources */,
|
||||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
||||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||||
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */,
|
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */,
|
||||||
|
@ -1969,7 +1987,9 @@
|
||||||
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */,
|
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */,
|
||||||
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
|
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
|
||||||
D6D3FDE024F41B8400FF50A5 /* ComposeContainerView.swift in Sources */,
|
D6D3FDE024F41B8400FF50A5 /* ComposeContainerView.swift in Sources */,
|
||||||
|
D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */,
|
||||||
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */,
|
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */,
|
||||||
|
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */,
|
||||||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
|
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
|
||||||
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
|
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
|
||||||
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */,
|
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */,
|
||||||
|
|
|
@ -134,7 +134,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
snapshot.appendSections(Section.allCases)
|
snapshot.appendSections(Section.allCases)
|
||||||
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
||||||
snapshot.appendItems([.trendingTags], toSection: .discover)
|
snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover)
|
||||||
snapshot.appendItems([.addList], toSection: .lists)
|
snapshot.appendItems([.addList], toSection: .lists)
|
||||||
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags)
|
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags)
|
||||||
snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags)
|
snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags)
|
||||||
|
@ -259,6 +259,9 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||||
case .trendingTags:
|
case .trendingTags:
|
||||||
show(TrendingHashtagsViewController(mastodonController: mastodonController), sender: nil)
|
show(TrendingHashtagsViewController(mastodonController: mastodonController), sender: nil)
|
||||||
|
|
||||||
|
case .profileDirectory:
|
||||||
|
show(ProfileDirectoryViewController(mastodonController: mastodonController), sender: nil)
|
||||||
|
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
show(ListTimelineViewController(for: list, mastodonController: mastodonController), sender: nil)
|
show(ListTimelineViewController(for: list, mastodonController: mastodonController), sender: nil)
|
||||||
|
|
||||||
|
@ -336,6 +339,7 @@ extension ExploreViewController {
|
||||||
enum Item: Hashable {
|
enum Item: Hashable {
|
||||||
case bookmarks
|
case bookmarks
|
||||||
case trendingTags
|
case trendingTags
|
||||||
|
case profileDirectory
|
||||||
case list(List)
|
case list(List)
|
||||||
case addList
|
case addList
|
||||||
case savedHashtag(Hashtag)
|
case savedHashtag(Hashtag)
|
||||||
|
@ -349,6 +353,8 @@ extension ExploreViewController {
|
||||||
return NSLocalizedString("Bookmarks", comment: "bookmarks nav item title")
|
return NSLocalizedString("Bookmarks", comment: "bookmarks nav item title")
|
||||||
case .trendingTags:
|
case .trendingTags:
|
||||||
return NSLocalizedString("Trending Hashtags", comment: "trending hashtags nav item title")
|
return NSLocalizedString("Trending Hashtags", comment: "trending hashtags nav item title")
|
||||||
|
case .profileDirectory:
|
||||||
|
return NSLocalizedString("Profile Directory", comment: "profile directory nav item title")
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
return list.title
|
return list.title
|
||||||
case .addList:
|
case .addList:
|
||||||
|
@ -371,6 +377,8 @@ extension ExploreViewController {
|
||||||
name = "bookmark.fill"
|
name = "bookmark.fill"
|
||||||
case .trendingTags:
|
case .trendingTags:
|
||||||
name = "arrow.up.arrow.down"
|
name = "arrow.up.arrow.down"
|
||||||
|
case .profileDirectory:
|
||||||
|
name = "person.2.fill"
|
||||||
case .list(_):
|
case .list(_):
|
||||||
name = "list.bullet"
|
name = "list.bullet"
|
||||||
case .addList, .addSavedHashtag:
|
case .addList, .addSavedHashtag:
|
||||||
|
@ -391,6 +399,8 @@ extension ExploreViewController {
|
||||||
return true
|
return true
|
||||||
case (.trendingTags, .trendingTags):
|
case (.trendingTags, .trendingTags):
|
||||||
return true
|
return true
|
||||||
|
case (.profileDirectory, .profileDirectory):
|
||||||
|
return true
|
||||||
case let (.list(a), .list(b)):
|
case let (.list(a), .list(b)):
|
||||||
return a.id == b.id
|
return a.id == b.id
|
||||||
case (.addList, .addList):
|
case (.addList, .addList):
|
||||||
|
@ -414,6 +424,8 @@ extension ExploreViewController {
|
||||||
hasher.combine("bookmarks")
|
hasher.combine("bookmarks")
|
||||||
case .trendingTags:
|
case .trendingTags:
|
||||||
hasher.combine("trendingTags")
|
hasher.combine("trendingTags")
|
||||||
|
case .profileDirectory:
|
||||||
|
hasher.combine("profileDirectory")
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
hasher.combine("list")
|
hasher.combine("list")
|
||||||
hasher.combine(list.id)
|
hasher.combine(list.id)
|
||||||
|
@ -468,7 +480,7 @@ extension ExploreViewController: UICollectionViewDragDelegate {
|
||||||
case let .savedInstance(url):
|
case let .savedInstance(url):
|
||||||
provider = NSItemProvider(object: url as NSURL)
|
provider = NSItemProvider(object: url as NSURL)
|
||||||
// todo: should dragging public timelines into new windows be supported?
|
// todo: should dragging public timelines into new windows be supported?
|
||||||
case .trendingTags, .addList, .addSavedHashtag, .findInstance:
|
case .trendingTags, .profileDirectory, .addList, .addSavedHashtag, .findInstance:
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return [UIDragItem(itemProvider: provider)]
|
return [UIDragItem(itemProvider: provider)]
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// FeaturedProfileCollectionViewCell.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 2/6/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
@IBOutlet weak var headerImageView: UIImageView!
|
||||||
|
@IBOutlet weak var avatarContainerView: UIView!
|
||||||
|
@IBOutlet weak var avatarImageView: UIImageView!
|
||||||
|
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
||||||
|
@IBOutlet weak var noteTextView: StatusContentTextView!
|
||||||
|
|
||||||
|
var account: Account?
|
||||||
|
|
||||||
|
private var avatarRequest: ImageCache.Request?
|
||||||
|
private var headerRequest: ImageCache.Request?
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView)
|
||||||
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
|
|
||||||
|
noteTextView.defaultFont = .systemFont(ofSize: 16)
|
||||||
|
noteTextView.textContainer.lineBreakMode = .byTruncatingTail
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUI(account: Account) {
|
||||||
|
self.account = account
|
||||||
|
|
||||||
|
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||||
|
|
||||||
|
noteTextView.setTextFromHtml(account.note)
|
||||||
|
noteTextView.setEmojis(account.emojis)
|
||||||
|
|
||||||
|
avatarImageView.image = nil
|
||||||
|
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in
|
||||||
|
defer {
|
||||||
|
self?.avatarRequest = nil
|
||||||
|
}
|
||||||
|
guard let self = self,
|
||||||
|
let image = image,
|
||||||
|
self.account?.id == account.id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.avatarImageView.image = image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerImageView.image = nil
|
||||||
|
if let header = account.header {
|
||||||
|
headerRequest = ImageCache.headers.get(header) { [weak self] (_, image) in
|
||||||
|
defer {
|
||||||
|
self?.headerRequest = nil
|
||||||
|
}
|
||||||
|
guard let self = self,
|
||||||
|
let image = image,
|
||||||
|
self.account?.id == account.id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.headerImageView.image = image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func preferencesChanged() {
|
||||||
|
avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView)
|
||||||
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
|
|
||||||
|
if let account = account {
|
||||||
|
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18121" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18091"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="System colors in document resources" 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="gTV-IL-0wX" customClass="FeaturedProfileCollectionViewCell" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="400" height="200"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="400" height="200"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="bo4-Sd-caI">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="400" height="66"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemGray5Color"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="66" id="9Aa-Up-chJ"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RQe-uE-TEv">
|
||||||
|
<rect key="frame" x="8" y="34" width="64" height="64"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4wd-wq-Sh2">
|
||||||
|
<rect key="frame" x="2" y="2" width="60" height="60"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="60" id="Xyl-Ry-J3r"/>
|
||||||
|
<constraint firstAttribute="width" secondItem="4wd-wq-Sh2" secondAttribute="height" multiplier="1:1" id="YEc-fT-FRB"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" secondItem="RQe-uE-TEv" secondAttribute="height" multiplier="1:1" id="4vR-IF-yS8"/>
|
||||||
|
<constraint firstAttribute="width" secondItem="4wd-wq-Sh2" secondAttribute="width" constant="4" id="52Q-zq-k28"/>
|
||||||
|
<constraint firstItem="4wd-wq-Sh2" firstAttribute="centerY" secondItem="RQe-uE-TEv" secondAttribute="centerY" id="Ped-H7-QtP"/>
|
||||||
|
<constraint firstItem="4wd-wq-Sh2" firstAttribute="centerX" secondItem="RQe-uE-TEv" secondAttribute="centerX" id="bRk-uJ-JGg"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="voW-Is-1b2" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="76" y="72" width="316" height="24"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="749" scrollEnabled="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bvj-F0-ggC" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="8" y="102" width="384" height="94"/>
|
||||||
|
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
|
||||||
|
<color key="textColor" systemColor="labelColor"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="bvj-F0-ggC" firstAttribute="top" secondItem="RQe-uE-TEv" secondAttribute="bottom" constant="4" id="8Nc-FF-kRX"/>
|
||||||
|
<constraint firstItem="bo4-Sd-caI" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="CJ1-Be-L45"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="bvj-F0-ggC" secondAttribute="bottom" constant="4" id="Hza-qE-Agk"/>
|
||||||
|
<constraint firstItem="voW-Is-1b2" firstAttribute="bottom" secondItem="4wd-wq-Sh2" secondAttribute="bottom" id="N0l-fE-AAX"/>
|
||||||
|
<constraint firstItem="RQe-uE-TEv" firstAttribute="centerY" secondItem="bo4-Sd-caI" secondAttribute="bottom" id="Ngh-DO-Q0X"/>
|
||||||
|
<constraint firstItem="bvj-F0-ggC" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="8" id="Rjq-1i-PV2"/>
|
||||||
|
<constraint firstItem="voW-Is-1b2" firstAttribute="leading" secondItem="RQe-uE-TEv" secondAttribute="trailing" constant="4" id="WUb-3i-BFe"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="bvj-F0-ggC" secondAttribute="trailing" constant="8" id="ZrT-Wa-pbY"/>
|
||||||
|
<constraint firstItem="voW-Is-1b2" firstAttribute="top" relation="greaterThanOrEqual" secondItem="bo4-Sd-caI" secondAttribute="bottom" id="g4l-yF-2wH"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="bo4-Sd-caI" secondAttribute="trailing" id="geb-Qa-zZp"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="voW-Is-1b2" secondAttribute="trailing" constant="8" id="l91-F6-kAL"/>
|
||||||
|
<constraint firstItem="bo4-Sd-caI" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="tUr-Oy-nXN"/>
|
||||||
|
<constraint firstItem="RQe-uE-TEv" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="8" id="uZI-LM-bZW"/>
|
||||||
|
</constraints>
|
||||||
|
<connections>
|
||||||
|
<outlet property="avatarContainerView" destination="RQe-uE-TEv" id="tBI-fT-26P"/>
|
||||||
|
<outlet property="avatarImageView" destination="4wd-wq-Sh2" id="rba-cv-8fb"/>
|
||||||
|
<outlet property="displayNameLabel" destination="voW-Is-1b2" id="XVS-4d-PKx"/>
|
||||||
|
<outlet property="headerImageView" destination="bo4-Sd-caI" id="YkL-Wi-BXb"/>
|
||||||
|
<outlet property="noteTextView" destination="bvj-F0-ggC" id="Bbm-ai-bu1"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="535" y="428"/>
|
||||||
|
</collectionViewCell>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<systemColor name="labelColor">
|
||||||
|
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
|
<systemColor name="systemGray5Color">
|
||||||
|
<color red="0.89803921568627454" green="0.89803921568627454" blue="0.91764705882352937" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</systemColor>
|
||||||
|
</resources>
|
||||||
|
</document>
|
|
@ -0,0 +1,132 @@
|
||||||
|
//
|
||||||
|
// ProfileDirectoryFilterView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 2/7/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class ProfileDirectoryFilterView: UICollectionReusableView {
|
||||||
|
|
||||||
|
var onFilterChanged: ((Scope, DirectoryOrder) -> Void)?
|
||||||
|
|
||||||
|
private var scope: UISegmentedControl!
|
||||||
|
private var sort: UISegmentedControl!
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
commonInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
|
||||||
|
commonInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func commonInit() {
|
||||||
|
scope = UISegmentedControl(items: ["Instance", NSLocalizedString("Everywhere", comment: "everywhere profile directory scope")])
|
||||||
|
scope.selectedSegmentIndex = 0
|
||||||
|
scope.addTarget(self, action: #selector(filterChanged), for: .valueChanged)
|
||||||
|
|
||||||
|
sort = UISegmentedControl(items: [
|
||||||
|
NSLocalizedString("Active", comment: "active profile directory sort"),
|
||||||
|
NSLocalizedString("New", comment: "new profile directory sort"),
|
||||||
|
])
|
||||||
|
sort.selectedSegmentIndex = 0
|
||||||
|
sort.addTarget(self, action: #selector(filterChanged), for: .valueChanged)
|
||||||
|
|
||||||
|
let fromLabel = UILabel()
|
||||||
|
fromLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
fromLabel.text = NSLocalizedString("From", comment: "profile directory scope label")
|
||||||
|
fromLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||||
|
|
||||||
|
let sortLabel = UILabel()
|
||||||
|
sortLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
sortLabel.text = NSLocalizedString("Sort By", comment: "profile directory sort label")
|
||||||
|
sortLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||||
|
|
||||||
|
let labelContainer = UIView()
|
||||||
|
labelContainer.addSubview(sortLabel)
|
||||||
|
labelContainer.addSubview(fromLabel)
|
||||||
|
|
||||||
|
let controlStack = UIStackView(arrangedSubviews: [sort, scope])
|
||||||
|
controlStack.axis = .vertical
|
||||||
|
controlStack.spacing = 8
|
||||||
|
|
||||||
|
let blurEffect = UIBlurEffect(style: .systemChromeMaterial)
|
||||||
|
let blurView = UIVisualEffectView(effect: blurEffect)
|
||||||
|
blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(blurView)
|
||||||
|
|
||||||
|
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect, style: .label)
|
||||||
|
let vibrancyView = UIVisualEffectView(effect: vibrancyEffect)
|
||||||
|
vibrancyView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
blurView.contentView.addSubview(vibrancyView)
|
||||||
|
|
||||||
|
let filterStack = UIStackView(arrangedSubviews: [
|
||||||
|
labelContainer,
|
||||||
|
controlStack,
|
||||||
|
])
|
||||||
|
filterStack.axis = .horizontal
|
||||||
|
filterStack.spacing = 8
|
||||||
|
filterStack.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
vibrancyView.contentView.addSubview(filterStack)
|
||||||
|
|
||||||
|
let separator = UIView()
|
||||||
|
separator.backgroundColor = .separator
|
||||||
|
separator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(separator)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
fromLabel.leadingAnchor.constraint(equalTo: labelContainer.leadingAnchor),
|
||||||
|
fromLabel.trailingAnchor.constraint(equalTo: labelContainer.trailingAnchor),
|
||||||
|
fromLabel.centerYAnchor.constraint(equalTo: scope.centerYAnchor),
|
||||||
|
|
||||||
|
sortLabel.leadingAnchor.constraint(equalTo: labelContainer.leadingAnchor),
|
||||||
|
sortLabel.trailingAnchor.constraint(equalTo: labelContainer.trailingAnchor),
|
||||||
|
sortLabel.centerYAnchor.constraint(equalTo: sort.centerYAnchor),
|
||||||
|
|
||||||
|
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
blurView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
|
||||||
|
vibrancyView.leadingAnchor.constraint(equalTo: blurView.contentView.leadingAnchor),
|
||||||
|
vibrancyView.trailingAnchor.constraint(equalTo: blurView.contentView.trailingAnchor),
|
||||||
|
vibrancyView.topAnchor.constraint(equalTo: blurView.contentView.topAnchor),
|
||||||
|
vibrancyView.bottomAnchor.constraint(equalTo: blurView.contentView.bottomAnchor),
|
||||||
|
|
||||||
|
filterStack.leadingAnchor.constraint(equalToSystemSpacingAfter: vibrancyView.contentView.leadingAnchor, multiplier: 1),
|
||||||
|
vibrancyView.contentView.trailingAnchor.constraint(equalToSystemSpacingAfter: filterStack.trailingAnchor, multiplier: 1),
|
||||||
|
filterStack.topAnchor.constraint(equalToSystemSpacingBelow: vibrancyView.contentView.topAnchor, multiplier: 1),
|
||||||
|
vibrancyView.contentView.bottomAnchor.constraint(equalToSystemSpacingBelow: filterStack.bottomAnchor, multiplier: 1),
|
||||||
|
|
||||||
|
separator.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
separator.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
separator.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
separator.heightAnchor.constraint(equalToConstant: 0.5),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUI(mastodonController: MastodonController) {
|
||||||
|
scope.setTitle(mastodonController.accountInfo!.instanceURL.host!, forSegmentAt: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func filterChanged() {
|
||||||
|
let scope = Scope(rawValue: scope.selectedSegmentIndex)!
|
||||||
|
let order = sort.selectedSegmentIndex == 0 ? DirectoryOrder.active : .new
|
||||||
|
onFilterChanged?(scope, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProfileDirectoryFilterView {
|
||||||
|
enum Scope: Int, Equatable {
|
||||||
|
case instance, everywhere
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
//
|
||||||
|
// ProfileDirectoryViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 2/6/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class ProfileDirectoryViewController: UIViewController {
|
||||||
|
|
||||||
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
|
private var collectionView: UICollectionView!
|
||||||
|
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||||
|
|
||||||
|
init(mastodonController: MastodonController) {
|
||||||
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
title = NSLocalizedString("Profile Directory", comment: "profile directory title")
|
||||||
|
|
||||||
|
let configuration = UICollectionViewCompositionalLayoutConfiguration()
|
||||||
|
configuration.boundarySupplementaryItems = [
|
||||||
|
NSCollectionLayoutBoundarySupplementaryItem(
|
||||||
|
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100)),
|
||||||
|
elementKind: "filter",
|
||||||
|
alignment: .top
|
||||||
|
)
|
||||||
|
]
|
||||||
|
let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, layoutEnvironment) in
|
||||||
|
let itemHeight = NSCollectionLayoutDimension.absolute(200)
|
||||||
|
let itemWidth: NSCollectionLayoutDimension
|
||||||
|
if case .compact = layoutEnvironment.traitCollection.horizontalSizeClass {
|
||||||
|
itemWidth = .fractionalWidth(1)
|
||||||
|
} else {
|
||||||
|
itemWidth = .absolute((layoutEnvironment.container.contentSize.width - 12) / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemSize = NSCollectionLayoutSize(widthDimension: itemWidth, heightDimension: itemHeight)
|
||||||
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
let itemB = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
|
||||||
|
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemHeight)
|
||||||
|
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, itemB])
|
||||||
|
group.interItemSpacing = .flexible(4)
|
||||||
|
|
||||||
|
let section = NSCollectionLayoutSection(group: group)
|
||||||
|
section.interGroupSpacing = 4
|
||||||
|
if case .compact = layoutEnvironment.traitCollection.horizontalSizeClass {
|
||||||
|
section.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0)
|
||||||
|
} else {
|
||||||
|
section.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)
|
||||||
|
}
|
||||||
|
return section
|
||||||
|
}, configuration: configuration)
|
||||||
|
|
||||||
|
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
|
||||||
|
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
collectionView.backgroundColor = .secondarySystemBackground
|
||||||
|
collectionView.register(UINib(nibName: "FeaturedProfileCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: "featuredProfileCell")
|
||||||
|
collectionView.register(ProfileDirectoryFilterView.self, forSupplementaryViewOfKind: "filter", withReuseIdentifier: "filter")
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dragDelegate = self
|
||||||
|
view.addSubview(collectionView)
|
||||||
|
|
||||||
|
dataSource = createDataSource()
|
||||||
|
updateProfiles(local: true, order: .active)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { (collectionView, indexPath, item) in
|
||||||
|
guard case let .account(account) = item else { fatalError() }
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "featuredProfileCell", for: indexPath) as! FeaturedProfileCollectionViewCell
|
||||||
|
cell.updateUI(account: account)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in
|
||||||
|
guard elementKind == "filter" else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let filterView = collectionView.dequeueReusableSupplementaryView(ofKind: "filter", withReuseIdentifier: "filter", for: indexPath) as! ProfileDirectoryFilterView
|
||||||
|
filterView.updateUI(mastodonController: self.mastodonController)
|
||||||
|
filterView.onFilterChanged = { [weak self] (scope, order) in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.dataSource.apply(.init())
|
||||||
|
self.updateProfiles(local: scope == .instance, order: order)
|
||||||
|
}
|
||||||
|
return filterView
|
||||||
|
}
|
||||||
|
return dataSource
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateProfiles(local: Bool, order: DirectoryOrder) {
|
||||||
|
let request = Client.getFeaturedProfiles(local: local, order: order)
|
||||||
|
mastodonController.run(request) { (response) in
|
||||||
|
guard case let .success(accounts, _) = response else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
|
snapshot.appendSections([.featuredProfiles])
|
||||||
|
snapshot.appendItems(accounts.map { .account($0) })
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.dataSource.apply(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProfileDirectoryViewController {
|
||||||
|
enum Section {
|
||||||
|
case featuredProfiles
|
||||||
|
}
|
||||||
|
enum Item: Hashable {
|
||||||
|
case account(Account)
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
guard case let .account(account) = self else { return }
|
||||||
|
hasher.combine(account.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProfileDirectoryViewController: TuskerNavigationDelegate {
|
||||||
|
var apiController: MastodonController { mastodonController }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProfileDirectoryViewController: MenuPreviewProvider {
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProfileDirectoryViewController: UICollectionViewDelegate {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||||
|
case let .account(account) = item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
show(ProfileViewController(accountID: account.id, mastodonController: mastodonController), sender: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
|
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||||
|
case let .account(account) = item else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
|
return ProfileViewController(accountID: account.id, mastodonController: self.mastodonController)
|
||||||
|
} actionProvider: { (_) in
|
||||||
|
let actions = self.actionsForProfile(accountID: account.id, sourceView: self.collectionView.cellForItem(at: indexPath))
|
||||||
|
return UIMenu(children: actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
|
if let viewController = animator.previewViewController {
|
||||||
|
animator.preferredCommitStyle = .pop
|
||||||
|
animator.addCompletion {
|
||||||
|
self.show(viewController, sender: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProfileDirectoryViewController: UICollectionViewDragDelegate {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||||
|
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||||
|
case let .account(account) = item,
|
||||||
|
let currentAccountID = mastodonController.accountInfo?.id else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let provider = NSItemProvider(object: account.url as NSURL)
|
||||||
|
let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: currentAccountID)
|
||||||
|
provider.registerObject(activity, visibility: .all)
|
||||||
|
return [UIDragItem(itemProvider: provider)]
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ class MainSidebarViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
var exploreTabItems: [Item] {
|
var exploreTabItems: [Item] {
|
||||||
var items: [Item] = [.search, .bookmarks, .trendingTags]
|
var items: [Item] = [.search, .bookmarks, .trendingTags, .profileDirectory]
|
||||||
let snapshot = dataSource.snapshot()
|
let snapshot = dataSource.snapshot()
|
||||||
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
|
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
|
||||||
items.append(.list(list))
|
items.append(.list(list))
|
||||||
|
@ -143,6 +143,7 @@ class MainSidebarViewController: UIViewController {
|
||||||
], toSection: .compose)
|
], toSection: .compose)
|
||||||
snapshot.appendItems([
|
snapshot.appendItems([
|
||||||
.trendingTags,
|
.trendingTags,
|
||||||
|
.profileDirectory,
|
||||||
], toSection: .discover)
|
], toSection: .discover)
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
|
||||||
|
@ -283,7 +284,7 @@ extension MainSidebarViewController {
|
||||||
enum Item: Hashable {
|
enum Item: Hashable {
|
||||||
case tab(MainTabBarViewController.Tab)
|
case tab(MainTabBarViewController.Tab)
|
||||||
case search, bookmarks
|
case search, bookmarks
|
||||||
case trendingTags
|
case trendingTags, profileDirectory
|
||||||
case listsHeader, list(List), addList
|
case listsHeader, list(List), addList
|
||||||
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
|
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
|
||||||
case savedInstancesHeader, savedInstance(URL), addSavedInstance
|
case savedInstancesHeader, savedInstance(URL), addSavedInstance
|
||||||
|
@ -298,6 +299,8 @@ extension MainSidebarViewController {
|
||||||
return "Bookmarks"
|
return "Bookmarks"
|
||||||
case .trendingTags:
|
case .trendingTags:
|
||||||
return "Trending Hashtags"
|
return "Trending Hashtags"
|
||||||
|
case .profileDirectory:
|
||||||
|
return "Profile Directory"
|
||||||
case .listsHeader:
|
case .listsHeader:
|
||||||
return "Lists"
|
return "Lists"
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
|
@ -329,6 +332,8 @@ extension MainSidebarViewController {
|
||||||
return "bookmark"
|
return "bookmark"
|
||||||
case .trendingTags:
|
case .trendingTags:
|
||||||
return "arrow.up.arrow.down"
|
return "arrow.up.arrow.down"
|
||||||
|
case .profileDirectory:
|
||||||
|
return "person.2.fill"
|
||||||
case .list(_):
|
case .list(_):
|
||||||
return "list.bullet"
|
return "list.bullet"
|
||||||
case .savedHashtag(_):
|
case .savedHashtag(_):
|
||||||
|
|
|
@ -203,7 +203,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
|
|
||||||
tabBarViewController.select(tab: .explore)
|
tabBarViewController.select(tab: .explore)
|
||||||
|
|
||||||
case .bookmarks, .trendingTags, .list(_), .savedHashtag(_), .savedInstance(_):
|
case .bookmarks, .trendingTags, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||||
tabBarViewController.select(tab: .explore)
|
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
|
// 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.
|
// in compact mode and performing a search.
|
||||||
|
@ -279,6 +279,8 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
exploreItem = .savedInstance(instanceVC.instanceURL)
|
exploreItem = .savedInstance(instanceVC.instanceURL)
|
||||||
} else if tabNavigationStack[1] is TrendingHashtagsViewController {
|
} else if tabNavigationStack[1] is TrendingHashtagsViewController {
|
||||||
exploreItem = .trendingTags
|
exploreItem = .trendingTags
|
||||||
|
} else if tabNavigationStack[1] is ProfileDirectoryViewController {
|
||||||
|
exploreItem = .profileDirectory
|
||||||
}
|
}
|
||||||
transferNavigationStack(from: tabNavController, to: exploreItem!, skipFirst: 1, prepend: toPrepend)
|
transferNavigationStack(from: tabNavController, to: exploreItem!, skipFirst: 1, prepend: toPrepend)
|
||||||
|
|
||||||
|
@ -332,6 +334,8 @@ fileprivate extension MainSidebarViewController.Item {
|
||||||
return BookmarksTableViewController(mastodonController: mastodonController)
|
return BookmarksTableViewController(mastodonController: mastodonController)
|
||||||
case .trendingTags:
|
case .trendingTags:
|
||||||
return TrendingHashtagsViewController(mastodonController: mastodonController)
|
return TrendingHashtagsViewController(mastodonController: mastodonController)
|
||||||
|
case .profileDirectory:
|
||||||
|
return ProfileDirectoryViewController(mastodonController: mastodonController)
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
return ListTimelineViewController(for: list, mastodonController: mastodonController)
|
return ListTimelineViewController(for: list, mastodonController: mastodonController)
|
||||||
case let .savedHashtag(hashtag):
|
case let .savedHashtag(hashtag):
|
||||||
|
|
Loading…
Reference in New Issue