New views for trending accounts and tags.
This commit is contained in:
parent
3658e8b19c
commit
07f271dd10
|
@ -85,6 +85,9 @@ public struct Account: Codable {
|
|||
/// NULLABLE String (ISO 8601 Date), or null if no statuses
|
||||
public let lastStatusAt: String?
|
||||
|
||||
/// Recent photos send by the user.
|
||||
public let recentPosts: [Status]?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case username
|
||||
|
@ -111,6 +114,7 @@ public struct Account: Codable {
|
|||
case suspended
|
||||
case limited
|
||||
case lastStatusAt = "last_status_at"
|
||||
case recentPosts = "recent_posts"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Information about trending hashtag.
|
||||
public struct TagTrend: Codable {
|
||||
|
||||
/// Id number of tag.
|
||||
public let id: Int
|
||||
|
||||
/// The value of the hashtag.
|
||||
public let name: String
|
||||
|
||||
/// The value of the hashtag after the # sign.
|
||||
public let hashtag: String
|
||||
|
||||
/// A link to the hashtag on the instance.
|
||||
public let url: URL?
|
||||
|
||||
/// Total uses of hashtag.
|
||||
public let total: Int
|
||||
}
|
|
@ -31,6 +31,6 @@ public struct TagHistory: Codable {
|
|||
/// The counted usage of the tag within that day.
|
||||
public let uses: String
|
||||
|
||||
/// he total of accounts using the tag within that day (cast from an integer).
|
||||
/// The total of accounts using the tag within that day (cast from an integer).
|
||||
public let accounts: String
|
||||
}
|
||||
|
|
|
@ -17,4 +17,24 @@ public extension PixelfedClientAuthenticated {
|
|||
|
||||
return try await downloadJson([Status].self, request: request)
|
||||
}
|
||||
|
||||
func tagsTrends() async throws -> [TagTrend] {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Pixelfed.Trends.tags(nil, nil, nil),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
return try await downloadJson([TagTrend].self, request: request)
|
||||
}
|
||||
|
||||
func accountsTrends() async throws -> [Account] {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Pixelfed.Trends.accounts(nil, nil, nil),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
return try await downloadJson([Account].self, request: request)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@
|
|||
F8864CEF29ACE90B0020C534 /* UIFont+Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8864CEE29ACE90B0020C534 /* UIFont+Font.swift */; };
|
||||
F8864CF129ACFFB80020C534 /* View+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8864CF029ACFFB80020C534 /* View+Keyboard.swift */; };
|
||||
F886F257297859E300879356 /* CacheImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F886F256297859E300879356 /* CacheImageService.swift */; };
|
||||
F88AB05329B3613900345EDE /* PhotoUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88AB05229B3613900345EDE /* PhotoUrl.swift */; };
|
||||
F88AB05529B3626300345EDE /* ImageGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88AB05429B3626300345EDE /* ImageGrid.swift */; };
|
||||
F88AB05829B36B8200345EDE /* TrendingAccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88AB05729B36B8200345EDE /* TrendingAccountsView.swift */; };
|
||||
F88AB05D29B371B500345EDE /* AccountImagesGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88AB05B29B371B500345EDE /* AccountImagesGridView.swift */; };
|
||||
F88ABD9229686F1C004EF61E /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88ABD9129686F1C004EF61E /* MemoryCache.swift */; };
|
||||
F88ABD9429687CA4004EF61E /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88ABD9329687CA4004EF61E /* ComposeView.swift */; };
|
||||
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C246B295C37B80006098B /* VernissageApp.swift */; };
|
||||
|
@ -127,6 +131,8 @@
|
|||
F89D6C4A297196FF001DA3D4 /* ImagesViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C49297196FF001DA3D4 /* ImagesViewer.swift */; };
|
||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
|
||||
F8AD061329A565620042F111 /* String+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AD061229A565620042F111 /* String+Random.swift */; };
|
||||
F8AFF7C129B259150087D083 /* TrendingTagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF7C029B259150087D083 /* TrendingTagsView.swift */; };
|
||||
F8AFF7C429B25EF40087D083 /* TagImagesGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF7C329B25EF40087D083 /* TagImagesGridView.swift */; };
|
||||
F8B0885E29942E31002AB40A /* ThirdPartyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885D29942E31002AB40A /* ThirdPartyView.swift */; };
|
||||
F8B0886029943498002AB40A /* OtherSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885F29943498002AB40A /* OtherSectionView.swift */; };
|
||||
F8B08862299435C9002AB40A /* SupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B08861299435C9002AB40A /* SupportView.swift */; };
|
||||
|
@ -227,6 +233,10 @@
|
|||
F8864CEE29ACE90B0020C534 /* UIFont+Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Font.swift"; sourceTree = "<group>"; };
|
||||
F8864CF029ACFFB80020C534 /* View+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Keyboard.swift"; sourceTree = "<group>"; };
|
||||
F886F256297859E300879356 /* CacheImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheImageService.swift; sourceTree = "<group>"; };
|
||||
F88AB05229B3613900345EDE /* PhotoUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoUrl.swift; sourceTree = "<group>"; };
|
||||
F88AB05429B3626300345EDE /* ImageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrid.swift; sourceTree = "<group>"; };
|
||||
F88AB05729B36B8200345EDE /* TrendingAccountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingAccountsView.swift; sourceTree = "<group>"; };
|
||||
F88AB05B29B371B500345EDE /* AccountImagesGridView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountImagesGridView.swift; sourceTree = "<group>"; };
|
||||
F88ABD9129686F1C004EF61E /* MemoryCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = "<group>"; };
|
||||
F88ABD9329687CA4004EF61E /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; };
|
||||
F88ABD9529687D4D004EF61E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
|
@ -275,6 +285,8 @@
|
|||
F89F0605299139F6003DC875 /* Vernissage-002.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-002.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
|
||||
F8AD061229A565620042F111 /* String+Random.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Random.swift"; sourceTree = "<group>"; };
|
||||
F8AFF7C029B259150087D083 /* TrendingTagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingTagsView.swift; sourceTree = "<group>"; };
|
||||
F8AFF7C329B25EF40087D083 /* TagImagesGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagImagesGridView.swift; sourceTree = "<group>"; };
|
||||
F8B0885D29942E31002AB40A /* ThirdPartyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyView.swift; sourceTree = "<group>"; };
|
||||
F8B0885F29943498002AB40A /* OtherSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSectionView.swift; sourceTree = "<group>"; };
|
||||
F8B08861299435C9002AB40A /* SupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportView.swift; sourceTree = "<group>"; };
|
||||
|
@ -362,6 +374,8 @@
|
|||
F8341F93295C63E2009C8EE6 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F88AB05629B36B7700345EDE /* TrendingAccountsView */,
|
||||
F8AFF7BF29B258FC0087D083 /* TrendingTagsView */,
|
||||
F89D6C4029717FC0001DA3D4 /* SettingsView */,
|
||||
F89D6C4729718822001DA3D4 /* UserProfileView */,
|
||||
F808641229756583009F035C /* NotificationsView */,
|
||||
|
@ -418,6 +432,7 @@
|
|||
F8FA9918299FA35A007AB130 /* PhotoAttachment.swift */,
|
||||
F89AC00429A1F9B500F4159F /* AppMetadata.swift */,
|
||||
F8CEEDF929ABAFD200DBED66 /* ImageFileTranseferable.swift */,
|
||||
F88AB05229B3613900345EDE /* PhotoUrl.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -461,6 +476,7 @@
|
|||
F85D497C29640D5900751DF7 /* InteractionRow.swift */,
|
||||
F897978729681B9C00B22335 /* UserAvatar.swift */,
|
||||
F89797892968314A00B22335 /* LoadingIndicator.swift */,
|
||||
F88AB05429B3626300345EDE /* ImageGrid.swift */,
|
||||
F86B7217296C27C100EE59EC /* ActionButton.swift */,
|
||||
F86B721D296C458700EE59EC /* BlurredImage.swift */,
|
||||
F86B7222296C4BF500EE59EC /* ContentWarning.swift */,
|
||||
|
@ -549,6 +565,23 @@
|
|||
path = TextView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F88AB05629B36B7700345EDE /* TrendingAccountsView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F88AB05929B3719300345EDE /* Subviews */,
|
||||
F88AB05729B36B8200345EDE /* TrendingAccountsView.swift */,
|
||||
);
|
||||
path = TrendingAccountsView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F88AB05929B3719300345EDE /* Subviews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F88AB05B29B371B500345EDE /* AccountImagesGridView.swift */,
|
||||
);
|
||||
path = Subviews;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F88ABD9029686F00004EF61E /* Cache */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -674,6 +707,23 @@
|
|||
path = StatusView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F8AFF7BF29B258FC0087D083 /* TrendingTagsView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8AFF7C229B25ED60087D083 /* Subviews */,
|
||||
F8AFF7C029B259150087D083 /* TrendingTagsView.swift */,
|
||||
);
|
||||
path = TrendingTagsView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F8AFF7C229B25ED60087D083 /* Subviews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8AFF7C329B25EF40087D083 /* TagImagesGridView.swift */,
|
||||
);
|
||||
path = Subviews;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F8B9B354298D4B88009CC69C /* EnvironmentObjects */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -799,6 +849,7 @@
|
|||
files = (
|
||||
F85D497729640A5200751DF7 /* ImageRow.swift in Sources */,
|
||||
F8210DDF2966CFC7001D9973 /* AttachmentData+Attachment.swift in Sources */,
|
||||
F88AB05529B3626300345EDE /* ImageGrid.swift in Sources */,
|
||||
F87AEB922986C44E00434FB6 /* AuthorizationSession.swift in Sources */,
|
||||
F88E4D44297E82EB0057491A /* Status+MediaAttachmentType.swift in Sources */,
|
||||
F86A4301299A97F500DF7645 /* ProductIdentifiers.swift in Sources */,
|
||||
|
@ -866,17 +917,20 @@
|
|||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */,
|
||||
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
||||
F89AC00729A208CC00F4159F /* PlaceSelectorView.swift in Sources */,
|
||||
F8AFF7C429B25EF40087D083 /* TagImagesGridView.swift in Sources */,
|
||||
F86B721E296C458700EE59EC /* BlurredImage.swift in Sources */,
|
||||
F8B9B349298D4AA2009CC69C /* Client+Timeline.swift in Sources */,
|
||||
F8FA9919299FA35A007AB130 /* PhotoAttachment.swift in Sources */,
|
||||
F8B9B34B298D4ACE009CC69C /* Client+Tags.swift in Sources */,
|
||||
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
|
||||
F8AD061329A565620042F111 /* String+Random.swift in Sources */,
|
||||
F8AFF7C129B259150087D083 /* TrendingTagsView.swift in Sources */,
|
||||
F898DE7229728CB2004B4A6A /* CommentModel.swift in Sources */,
|
||||
F89A46DE296EABA20062125F /* StatusPlaceholderView.swift in Sources */,
|
||||
F88C2482295C3A4F0006098B /* StatusView.swift in Sources */,
|
||||
F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */,
|
||||
F8996DEB2971D29D0043EEC6 /* View+Transition.swift in Sources */,
|
||||
F88AB05D29B371B500345EDE /* AccountImagesGridView.swift in Sources */,
|
||||
F876418B298AC1B80057D362 /* NoDataView.swift in Sources */,
|
||||
F89D6C4629718193001DA3D4 /* ThemeSectionView.swift in Sources */,
|
||||
F85D497F296416C800751DF7 /* CommentsSectionView.swift in Sources */,
|
||||
|
@ -905,8 +959,10 @@
|
|||
F8CEEDFA29ABAFD200DBED66 /* ImageFileTranseferable.swift in Sources */,
|
||||
F802884F297AEED5000BDD51 /* DatabaseError.swift in Sources */,
|
||||
F86A4307299AA5E900DF7645 /* ThanksView.swift in Sources */,
|
||||
F88AB05829B36B8200345EDE /* TrendingAccountsView.swift in Sources */,
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
|
||||
F8B9B356298D4C1E009CC69C /* Client+Instance.swift in Sources */,
|
||||
F88AB05329B3613900345EDE /* PhotoUrl.swift in Sources */,
|
||||
F88E4D56297EAD6E0057491A /* AppRouteur.swift in Sources */,
|
||||
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */,
|
||||
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */,
|
||||
|
@ -1059,7 +1115,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 42;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1096,7 +1152,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 42;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
|
@ -38,6 +38,10 @@ extension View {
|
|||
ThirdPartyView()
|
||||
case .photoEditor(let photoAttachment):
|
||||
PhotoEditorView(photoAttachment: photoAttachment)
|
||||
case .trendingTags:
|
||||
TrendingTagsView()
|
||||
case .trendingAccounts:
|
||||
TrendingAccountsView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,13 @@ extension Client {
|
|||
public func statuses(range: Pixelfed.Trends.TrendRange) async throws -> [Status] {
|
||||
return try await pixelfedClient.statusesTrends(range: range)
|
||||
}
|
||||
|
||||
public func tags() async throws -> [TagTrend] {
|
||||
return try await pixelfedClient.tagsTrends()
|
||||
}
|
||||
|
||||
public func accounts() async throws -> [Account] {
|
||||
return try await pixelfedClient.accountsTrends()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class PhotoUrl: ObservableObject, Identifiable {
|
||||
public var id: String
|
||||
@Published public var url: URL?
|
||||
@Published public var blurhash: String?
|
||||
|
||||
init(id: String) {
|
||||
self.id = id
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@ enum RouteurDestinations: Hashable {
|
|||
case signIn
|
||||
case thirdParty
|
||||
case photoEditor(photoAttachment: PhotoAttachment)
|
||||
case trendingTags
|
||||
case trendingAccounts
|
||||
}
|
||||
|
||||
enum SheetDestinations: Identifiable {
|
||||
|
|
|
@ -27,7 +27,7 @@ struct MainView: View {
|
|||
@FetchRequest(sortDescriptors: [SortDescriptor(\.acct, order: .forward)]) var dbAccounts: FetchedResults<AccountData>
|
||||
|
||||
private enum ViewMode {
|
||||
case home, local, federated, profile, notifications, trending
|
||||
case home, local, federated, profile, notifications, trendingPhotos, trendingTags, trendingAccounts
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
@ -55,9 +55,15 @@ struct MainView: View {
|
|||
case .home:
|
||||
HomeFeedView(accountId: applicationState.account?.id ?? String.empty())
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
case .trending:
|
||||
case .trendingPhotos:
|
||||
TrendStatusesView(accountId: applicationState.account?.id ?? String.empty())
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
case .trendingTags:
|
||||
TrendingTagsView()
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
case .trendingAccounts:
|
||||
TrendingAccountsView()
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
case .local:
|
||||
StatusesView(listType: .local)
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
|
@ -115,12 +121,39 @@ struct MainView: View {
|
|||
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
HapticService.shared.fireHaptic(of: .tabSelection)
|
||||
viewMode = .trending
|
||||
Menu {
|
||||
Button {
|
||||
HapticService.shared.fireHaptic(of: .tabSelection)
|
||||
viewMode = .trendingPhotos
|
||||
} label: {
|
||||
HStack {
|
||||
Text(self.getViewTitle(viewMode: .trendingPhotos))
|
||||
Image(systemName: "photo.stack")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
HapticService.shared.fireHaptic(of: .tabSelection)
|
||||
viewMode = .trendingTags
|
||||
} label: {
|
||||
HStack {
|
||||
Text(self.getViewTitle(viewMode: .trendingTags))
|
||||
Image(systemName: "tag")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
HapticService.shared.fireHaptic(of: .tabSelection)
|
||||
viewMode = .trendingAccounts
|
||||
} label: {
|
||||
HStack {
|
||||
Text(self.getViewTitle(viewMode: .trendingAccounts))
|
||||
Image(systemName: "person.3")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text(self.getViewTitle(viewMode: .trending))
|
||||
Text("Trending")
|
||||
Image(systemName: "chart.line.uptrend.xyaxis")
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +242,7 @@ struct MainView: View {
|
|||
|
||||
@ToolbarContentBuilder
|
||||
private func getTrailingToolbar() -> some ToolbarContent {
|
||||
if viewMode == .local || viewMode == .home || viewMode == .federated || viewMode == .trending {
|
||||
if viewMode == .local || viewMode == .home || viewMode == .federated || viewMode == .trendingPhotos {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
HapticService.shared.fireHaptic(of: .buttonPress)
|
||||
|
@ -228,8 +261,12 @@ struct MainView: View {
|
|||
switch viewMode {
|
||||
case .home:
|
||||
return "Home"
|
||||
case .trending:
|
||||
return "Trending"
|
||||
case .trendingPhotos:
|
||||
return "Photos"
|
||||
case .trendingTags:
|
||||
return "Tags"
|
||||
case .trendingAccounts:
|
||||
return "Accounts"
|
||||
case .local:
|
||||
return "Local"
|
||||
case .federated:
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PixelfedKit
|
||||
import NukeUI
|
||||
|
||||
struct AccountImagesGridView: View {
|
||||
@EnvironmentObject var client: Client
|
||||
@EnvironmentObject var routerPath: RouterPath
|
||||
|
||||
private let account: Account
|
||||
private var photoUrls: [PhotoUrl]
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
self.photoUrls = [
|
||||
PhotoUrl(id: UUID().uuidString),
|
||||
PhotoUrl(id: UUID().uuidString),
|
||||
PhotoUrl(id: UUID().uuidString)
|
||||
]
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum:140))]) {
|
||||
ForEach(self.photoUrls) { photoUrl in
|
||||
ImageGrid(photoUrl: photoUrl)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.frame(width: 140, height: 140)
|
||||
.id(photoUrl.id)
|
||||
}
|
||||
|
||||
Button {
|
||||
self.routerPath.navigate(to: .userProfile(accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct))
|
||||
} label: {
|
||||
Text("more...")
|
||||
}
|
||||
}
|
||||
.onFirstAppear {
|
||||
self.loadData()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() {
|
||||
if let statuses = self.account.recentPosts {
|
||||
let statusesWithImages = statuses.getStatusesWithImagesOnly()
|
||||
|
||||
var index = 0
|
||||
for status in statusesWithImages {
|
||||
if let mediaAttachment = status.getAllImageMediaAttachments().first {
|
||||
self.photoUrls[index].url = mediaAttachment.url
|
||||
self.photoUrls[index].blurhash = mediaAttachment.blurhash
|
||||
|
||||
index = index + 1
|
||||
}
|
||||
|
||||
if index == 3 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PixelfedKit
|
||||
import Foundation
|
||||
|
||||
struct TrendingAccountsView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
@EnvironmentObject var client: Client
|
||||
@EnvironmentObject var routerPath: RouterPath
|
||||
|
||||
@State private var accounts: [Account] = []
|
||||
@State private var state: ViewState = .loading
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
.navigationTitle("Tags")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mainBody() -> some View {
|
||||
switch state {
|
||||
case .loading:
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
await self.loadData()
|
||||
}
|
||||
case .loaded:
|
||||
if self.accounts.isEmpty {
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "Unfortunately, there is no one here.")
|
||||
} else {
|
||||
List {
|
||||
ForEach(self.accounts, id: \.id) { account in
|
||||
Section {
|
||||
AccountImagesGridView(account: account)
|
||||
} header: {
|
||||
HStack {
|
||||
UsernameRow(
|
||||
accountId: account.id,
|
||||
accountAvatar: account.avatar,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUsername: account.acct)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
self.state = .loading
|
||||
|
||||
self.accounts = []
|
||||
await self.loadData()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
try await self.loadAccounts()
|
||||
self.state = .loaded
|
||||
} catch NetworkError.notSuccessResponse(let response) {
|
||||
// TODO: This code can be removed when other Pixelfed server will support trending accounts.
|
||||
if response.statusCode() == HTTPStatusCode.notFound {
|
||||
self.accounts = []
|
||||
self.state = .loaded
|
||||
}
|
||||
} catch {
|
||||
if !Task.isCancelled {
|
||||
ErrorService.shared.handle(error, message: "Accounts not retrieved.", showToastr: true)
|
||||
self.state = .error(error)
|
||||
} else {
|
||||
ErrorService.shared.handle(error, message: "Accounts not retrieved.", showToastr: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAccounts() async throws {
|
||||
let accountsFromApi = try await self.client.trends?.accounts()
|
||||
self.accounts = accountsFromApi ?? []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PixelfedKit
|
||||
import NukeUI
|
||||
|
||||
struct TagImagesGridView: View {
|
||||
@EnvironmentObject var client: Client
|
||||
@EnvironmentObject var routerPath: RouterPath
|
||||
|
||||
private let hashtag: String
|
||||
private let photoUrls: [PhotoUrl]
|
||||
|
||||
init(hashtag: String) {
|
||||
self.hashtag = hashtag
|
||||
self.photoUrls = [
|
||||
PhotoUrl(id: UUID().uuidString),
|
||||
PhotoUrl(id: UUID().uuidString),
|
||||
PhotoUrl(id: UUID().uuidString),
|
||||
PhotoUrl(id: UUID().uuidString),
|
||||
PhotoUrl(id: UUID().uuidString)
|
||||
]
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum:80))]) {
|
||||
ForEach(self.photoUrls) { photoUrl in
|
||||
ImageGrid(photoUrl: photoUrl)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.frame(width: 80, height: 80)
|
||||
.id(photoUrl.id)
|
||||
}
|
||||
|
||||
Button {
|
||||
self.routerPath.navigate(to: .tag(hashTag: hashtag))
|
||||
} label: {
|
||||
Text("more...")
|
||||
}
|
||||
}
|
||||
.onFirstAppear {
|
||||
Task {
|
||||
await self.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
let statusesFromApi = try await self.client.publicTimeline?.getTagStatuses(
|
||||
tag: self.hashtag,
|
||||
local: true,
|
||||
remote: false,
|
||||
limit: 10) ?? []
|
||||
|
||||
let statusesWithImages = statusesFromApi.getStatusesWithImagesOnly()
|
||||
|
||||
var index = 0
|
||||
for status in statusesWithImages {
|
||||
if let mediaAttachment = status.getAllImageMediaAttachments().first {
|
||||
self.photoUrls[index].url = mediaAttachment.url
|
||||
self.photoUrls[index].blurhash = mediaAttachment.blurhash
|
||||
|
||||
index = index + 1
|
||||
}
|
||||
|
||||
if index == 5 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "Loading tags failed.", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PixelfedKit
|
||||
import Foundation
|
||||
|
||||
struct TrendingTagsView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
@EnvironmentObject var client: Client
|
||||
@EnvironmentObject var routerPath: RouterPath
|
||||
|
||||
@State private var tags: [TagTrend] = []
|
||||
@State private var state: ViewState = .loading
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
.navigationTitle("Tags")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mainBody() -> some View {
|
||||
switch state {
|
||||
case .loading:
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
await self.loadData()
|
||||
}
|
||||
case .loaded:
|
||||
if self.tags.isEmpty {
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "Unfortunately, there is no one here.")
|
||||
} else {
|
||||
List {
|
||||
ForEach(self.tags, id: \.id) { tag in
|
||||
Section(header: Text(tag.name).font(.headline)) {
|
||||
TagImagesGridView(hashtag: tag.hashtag)
|
||||
.id(UUID().uuidString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
self.state = .loading
|
||||
|
||||
self.tags = []
|
||||
await self.loadData()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
try await self.loadTags()
|
||||
self.state = .loaded
|
||||
} catch {
|
||||
if !Task.isCancelled {
|
||||
ErrorService.shared.handle(error, message: "Tags not retrieved.", showToastr: true)
|
||||
self.state = .error(error)
|
||||
} else {
|
||||
ErrorService.shared.handle(error, message: "Tags not retrieved.", showToastr: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadTags() async throws {
|
||||
let tagsFromApi = try await self.client.trends?.tags()
|
||||
self.tags = tagsFromApi ?? []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import NukeUI
|
||||
|
||||
struct ImageGrid: View {
|
||||
@StateObject var photoUrl: PhotoUrl
|
||||
|
||||
var body: some View {
|
||||
if let url = photoUrl.url {
|
||||
LazyImage(url: url) { state in
|
||||
if let image = state.image {
|
||||
image
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} else if state.isLoading {
|
||||
placeholder()
|
||||
} else {
|
||||
placeholder()
|
||||
}
|
||||
}
|
||||
.priority(.high)
|
||||
} else {
|
||||
self.placeholder()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func placeholder() -> some View {
|
||||
if let imageBlurhash = photoUrl.blurhash, let uiImage = UIImage(blurHash: imageBlurhash, size: CGSize(width: 32, height: 32)) {
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
} else {
|
||||
Rectangle()
|
||||
.fill(Color.placeholderText)
|
||||
.redacted(reason: .placeholder)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue