diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index e2d94c60..b537a473 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -41,19 +41,15 @@ public struct AccountDetailView: View { featuredTagsView .offset(y: -36) Group { - if isCurrentUser { - Picker("", selection: $viewModel.selectedTab) { - ForEach(AccountDetailViewModel.Tab.allCases, id: \.self) { tab in - Text(tab.title).tag(tab) - } + Picker("", selection: $viewModel.selectedTab) { + ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs, + id: \.self) { tab in + Text(tab.title).tag(tab) } - .pickerStyle(.segmented) - .padding(.horizontal, DS.Constants.layoutPadding) - .offset(y: -20) - } else { - Divider() - .offset(y: -20) } + .pickerStyle(.segmented) + .padding(.horizontal, DS.Constants.layoutPadding) + .offset(y: -20) } .id("status") diff --git a/Packages/Account/Sources/Account/AccountDetailViewModel.swift b/Packages/Account/Sources/Account/AccountDetailViewModel.swift index 59e0fd13..16c9f575 100644 --- a/Packages/Account/Sources/Account/AccountDetailViewModel.swift +++ b/Packages/Account/Sources/Account/AccountDetailViewModel.swift @@ -14,14 +14,24 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { case loading, data(account: Account), error(error: Error) } - enum Tab: Int, CaseIterable { - case statuses, favourites, followedTags + enum Tab: Int { + case statuses, favourites, followedTags, postsAndReplies, media + + static var currentAccountTabs: [Tab] { + [.statuses, .favourites, .followedTags] + } + + static var accountTabs: [Tab] { + [.statuses, .postsAndReplies, .media] + } var title: String { switch self { case .statuses: return "Posts" case .favourites: return "Favourites" case .followedTags: return "Followed Tags" + case .postsAndReplies: return "Posts & Replies" + case .media: return "Media" } } } @@ -55,11 +65,20 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { @Published var familliarFollowers: [Account] = [] @Published var selectedTab = Tab.statuses { didSet { - reloadTabState() + switch selectedTab { + case .statuses, .postsAndReplies, .media: + tabTask?.cancel() + tabTask = Task { + await fetchStatuses() + } + default: + reloadTabState() + } } } private var account: Account? + private var tabTask: Task? private(set) var statuses: [Status] = [] @@ -109,7 +128,12 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { guard let client else { return } do { tabState = .statuses(statusesState: .loading) - statuses = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: nil, tag: nil)) + statuses = + try await client.get(endpoint: Accounts.statuses(id: accountId, + sinceId: nil, + tag: nil, + onlyMedia: selectedTab == .media ? true : nil, + excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil)) if isCurrentUser { (favourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nil)) } @@ -123,10 +147,15 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { guard let client else { return } do { switch selectedTab { - case .statuses: + case .statuses, .postsAndReplies, .media: guard let lastId = statuses.last?.id else { return } tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .loadingNextPage)) - let newStatuses: [Status] = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: lastId, tag: nil)) + let newStatuses: [Status] = + try await client.get(endpoint: Accounts.statuses(id: accountId, + sinceId: lastId, + tag: nil, + onlyMedia: selectedTab == .media ? true : nil, + excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil)) statuses.append(contentsOf: newStatuses) tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: newStatuses.count < 20 ? .none : .hasNextPage)) @@ -164,7 +193,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { private func reloadTabState() { switch selectedTab { - case .statuses: + case .statuses, .postsAndReplies, .media: tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage)) case .favourites: tabState = .statuses(statusesState: .display(statuses: favourites, diff --git a/Packages/Network/Sources/Network/Endpoint/Accounts.swift b/Packages/Network/Sources/Network/Endpoint/Accounts.swift index e4edaa4e..2327dac6 100644 --- a/Packages/Network/Sources/Network/Endpoint/Accounts.swift +++ b/Packages/Network/Sources/Network/Endpoint/Accounts.swift @@ -6,7 +6,7 @@ public enum Accounts: Endpoint { case followedTags case featuredTags(id: String) case verifyCredentials - case statuses(id: String, sinceId: String?, tag: String?) + case statuses(id: String, sinceId: String?, tag: String?, onlyMedia: Bool?, excludeReplies: Bool?) case relationships(ids: [String]) case follow(id: String) case unfollow(id: String) @@ -27,7 +27,7 @@ public enum Accounts: Endpoint { return "accounts/\(id)/featured_tags" case .verifyCredentials: return "accounts/verify_credentials" - case .statuses(let id, _, _): + case .statuses(let id, _, _, _, _): return "accounts/\(id)/statuses" case .relationships: return "accounts/relationships" @@ -48,7 +48,7 @@ public enum Accounts: Endpoint { public func queryItems() -> [URLQueryItem]? { switch self { - case .statuses(_, let sinceId, let tag): + case .statuses(_, let sinceId, let tag, let onlyMedia, let excludeReplies): var params: [URLQueryItem] = [] if let tag { params.append(.init(name: "tagged", value: tag)) @@ -56,6 +56,12 @@ public enum Accounts: Endpoint { if let sinceId { params.append(.init(name: "max_id", value: sinceId)) } + if let onlyMedia { + params.append(.init(name: "only_media", value: onlyMedia ? "true" : "false")) + } + if let excludeReplies { + params.append(.init(name: "exclude_replies", value: excludeReplies ? "true" : "fals")) + } return params case let .relationships(ids): return ids.map { diff --git a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift index f86a7e99..50a86de6 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift @@ -47,7 +47,7 @@ public enum TimelineFilter: Hashable, Equatable { case .home: return Timelines.home(sinceId: sinceId, maxId: maxId, minId: minId) case let .hashtag(tag, accountId): if let accountId { - return Accounts.statuses(id: accountId, sinceId: nil, tag: tag) + return Accounts.statuses(id: accountId, sinceId: nil, tag: tag, onlyMedia: nil, excludeReplies: nil) } else { return Timelines.hashtag(tag: tag, maxId: maxId) } diff --git a/README.md b/README.md index 895dcefd..2085795c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For contributors and myself, here is a todo list of features that could be added - [ ] Editor: Post image alts - [ ] Editor: Add / Edit polls - [ ] Editor: Support video types -- [ ] Editor: Open camera +- [ ] Editor: Add photos from camera - [X] Editor: Autocomplete for mentions / hashtags - [ ] Better settings tab - [ ] Edit profile @@ -30,6 +30,10 @@ For contributors and myself, here is a todo list of features that could be added - [ ] Open remote status locally - [ ] More context menu everywhere - [ ] Support pinned posts +- [ ] Support IceCubesApp://any mastodon links +- [ ] Add a share sheet +- [ ] Proper iPad support +- [ ] macOS support IceCubesApp is an open source application for accessing the decentralized social network Mastodon! It's built entirely in SwiftUI, making it fast, lightweight, and easy to use.