mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-06-08 16:29:24 +00:00
Fix #1836
This commit is contained in:
parent
e725b6be4d
commit
5d24c4d2e8
|
@ -63,9 +63,15 @@ public struct AccountDetailView: View {
|
||||||
ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs,
|
ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs,
|
||||||
id: \.self)
|
id: \.self)
|
||||||
{ tab in
|
{ tab in
|
||||||
Image(systemName: tab.iconName)
|
if tab == .boosts {
|
||||||
.tag(tab)
|
Image("Rocket")
|
||||||
.accessibilityLabel(tab.accessibilityLabel)
|
.tag(tab)
|
||||||
|
.accessibilityLabel(tab.accessibilityLabel)
|
||||||
|
} else {
|
||||||
|
Image(systemName: tab.iconName)
|
||||||
|
.tag(tab)
|
||||||
|
.accessibilityLabel(tab.accessibilityLabel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
|
@ -73,19 +79,12 @@ public struct AccountDetailView: View {
|
||||||
.applyAccountDetailsRowStyle(theme: theme)
|
.applyAccountDetailsRowStyle(theme: theme)
|
||||||
.id("status")
|
.id("status")
|
||||||
|
|
||||||
switch viewModel.tabState {
|
if viewModel.selectedTab == .statuses {
|
||||||
case .statuses:
|
pinnedPostsView
|
||||||
if viewModel.selectedTab == .statuses {
|
|
||||||
pinnedPostsView
|
|
||||||
}
|
|
||||||
StatusesListView(fetcher: viewModel,
|
|
||||||
client: client,
|
|
||||||
routerPath: routerPath)
|
|
||||||
case .followedTags:
|
|
||||||
tagsListView
|
|
||||||
case .lists:
|
|
||||||
listsListView
|
|
||||||
}
|
}
|
||||||
|
StatusesListView(fetcher: viewModel,
|
||||||
|
client: client,
|
||||||
|
routerPath: routerPath)
|
||||||
}
|
}
|
||||||
.environment(\.defaultMinListRowHeight, 1)
|
.environment(\.defaultMinListRowHeight, 1)
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
|
@ -236,56 +235,6 @@ public struct AccountDetailView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var tagsListView: some View {
|
|
||||||
Group {
|
|
||||||
ForEach(currentAccount.sortedTags) { tag in
|
|
||||||
HStack {
|
|
||||||
TagRowView(tag: tag)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
}
|
|
||||||
#if !os(visionOS)
|
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}.task {
|
|
||||||
await currentAccount.fetchFollowedTags()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var listsListView: some View {
|
|
||||||
Group {
|
|
||||||
ForEach(currentAccount.sortedLists) { list in
|
|
||||||
NavigationLink(value: RouterDestination.list(list: list)) {
|
|
||||||
Text(list.title)
|
|
||||||
.font(.scaledHeadline)
|
|
||||||
.foregroundColor(theme.labelColor)
|
|
||||||
}
|
|
||||||
#if !os(visionOS)
|
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
|
||||||
#endif
|
|
||||||
.contextMenu {
|
|
||||||
Button("account.list.delete", role: .destructive) {
|
|
||||||
Task {
|
|
||||||
await currentAccount.deleteList(list: list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button("account.list.create") {
|
|
||||||
routerPath.presentedSheet = .listCreate
|
|
||||||
}
|
|
||||||
.tint(theme.tintColor)
|
|
||||||
.buttonStyle(.borderless)
|
|
||||||
#if !os(visionOS)
|
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.task {
|
|
||||||
await currentAccount.fetchLists()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var pinnedPostsView: some View {
|
private var pinnedPostsView: some View {
|
||||||
if !viewModel.pinned.isEmpty {
|
if !viewModel.pinned.isEmpty {
|
||||||
|
|
|
@ -16,14 +16,14 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Tab: Int {
|
enum Tab: Int {
|
||||||
case statuses, favorites, bookmarks, followedTags, postsAndReplies, media, lists
|
case statuses, favorites, bookmarks, replies, boosts, media
|
||||||
|
|
||||||
static var currentAccountTabs: [Tab] {
|
static var currentAccountTabs: [Tab] {
|
||||||
[.statuses, .favorites, .bookmarks, .followedTags, .lists]
|
[.statuses, .replies, .boosts, .favorites, .bookmarks]
|
||||||
}
|
}
|
||||||
|
|
||||||
static var accountTabs: [Tab] {
|
static var accountTabs: [Tab] {
|
||||||
[.statuses, .postsAndReplies, .media]
|
[.statuses, .replies, .boosts, .media]
|
||||||
}
|
}
|
||||||
|
|
||||||
var iconName: String {
|
var iconName: String {
|
||||||
|
@ -31,10 +31,9 @@ import SwiftUI
|
||||||
case .statuses: "bubble.right"
|
case .statuses: "bubble.right"
|
||||||
case .favorites: "star"
|
case .favorites: "star"
|
||||||
case .bookmarks: "bookmark"
|
case .bookmarks: "bookmark"
|
||||||
case .followedTags: "tag"
|
case .replies: "bubble.left.and.bubble.right"
|
||||||
case .postsAndReplies: "bubble.left.and.bubble.right"
|
case .boosts: ""
|
||||||
case .media: "photo.on.rectangle.angled"
|
case .media: "photo.on.rectangle.angled"
|
||||||
case .lists: "list.bullet"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,34 +42,14 @@ import SwiftUI
|
||||||
case .statuses: "accessibility.tabs.profile.picker.statuses"
|
case .statuses: "accessibility.tabs.profile.picker.statuses"
|
||||||
case .favorites: "accessibility.tabs.profile.picker.favorites"
|
case .favorites: "accessibility.tabs.profile.picker.favorites"
|
||||||
case .bookmarks: "accessibility.tabs.profile.picker.bookmarks"
|
case .bookmarks: "accessibility.tabs.profile.picker.bookmarks"
|
||||||
case .followedTags: "accessibility.tabs.profile.picker.followed-tags"
|
case .replies: "accessibility.tabs.profile.picker.posts-and-replies"
|
||||||
case .postsAndReplies: "accessibility.tabs.profile.picker.posts-and-replies"
|
case .boosts: "accessibility.tabs.profile.picker.boosts"
|
||||||
case .media: "accessibility.tabs.profile.picker.media"
|
case .media: "accessibility.tabs.profile.picker.media"
|
||||||
case .lists: "accessibility.tabs.profile.picker.lists"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TabState {
|
|
||||||
case followedTags
|
|
||||||
case statuses(statusesState: StatusesState)
|
|
||||||
case lists
|
|
||||||
}
|
|
||||||
|
|
||||||
var accountState: AccountState = .loading
|
var accountState: AccountState = .loading
|
||||||
var tabState: TabState = .statuses(statusesState: .loading) {
|
|
||||||
didSet {
|
|
||||||
/// Forward viewModel tabState related to statusesState to statusesState property
|
|
||||||
/// for `StatusesFetcher` conformance as we wrap StatusesState in TabState
|
|
||||||
switch tabState {
|
|
||||||
case let .statuses(statusesState):
|
|
||||||
self.statusesState = statusesState
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusesState: StatusesState = .loading
|
var statusesState: StatusesState = .loading
|
||||||
|
|
||||||
var relationship: Relationship?
|
var relationship: Relationship?
|
||||||
|
@ -85,7 +64,7 @@ import SwiftUI
|
||||||
var selectedTab = Tab.statuses {
|
var selectedTab = Tab.statuses {
|
||||||
didSet {
|
didSet {
|
||||||
switch selectedTab {
|
switch selectedTab {
|
||||||
case .statuses, .postsAndReplies, .media:
|
case .statuses, .replies, .boosts, .media:
|
||||||
tabTask?.cancel()
|
tabTask?.cancel()
|
||||||
tabTask = Task {
|
tabTask = Task {
|
||||||
await fetchNewestStatuses(pullToRefresh: false)
|
await fetchNewestStatuses(pullToRefresh: false)
|
||||||
|
@ -105,6 +84,8 @@ import SwiftUI
|
||||||
private var tabTask: Task<Void, Never>?
|
private var tabTask: Task<Void, Never>?
|
||||||
|
|
||||||
private(set) var statuses: [Status] = []
|
private(set) var statuses: [Status] = []
|
||||||
|
|
||||||
|
var boosts: [Status] = []
|
||||||
|
|
||||||
/// When coming from a URL like a mention tap in a status.
|
/// When coming from a URL like a mention tap in a status.
|
||||||
init(accountId: String) {
|
init(accountId: String) {
|
||||||
|
@ -173,22 +154,28 @@ import SwiftUI
|
||||||
func fetchNewestStatuses(pullToRefresh: Bool) async {
|
func fetchNewestStatuses(pullToRefresh: Bool) async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
tabState = .statuses(statusesState: .loading)
|
statusesState = .loading
|
||||||
|
boosts = []
|
||||||
statuses =
|
statuses =
|
||||||
try await client.get(endpoint: Accounts.statuses(id: accountId,
|
try await client.get(endpoint: Accounts.statuses(id: accountId,
|
||||||
sinceId: nil,
|
sinceId: nil,
|
||||||
tag: nil,
|
tag: nil,
|
||||||
onlyMedia: selectedTab == .media ? true : nil,
|
onlyMedia: selectedTab == .media,
|
||||||
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
|
excludeReplies: selectedTab != .replies,
|
||||||
|
excludeReblogs: selectedTab != .boosts,
|
||||||
pinned: nil))
|
pinned: nil))
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
||||||
|
if selectedTab == .boosts {
|
||||||
|
boosts = statuses.filter{ $0.reblog != nil }
|
||||||
|
}
|
||||||
if selectedTab == .statuses {
|
if selectedTab == .statuses {
|
||||||
pinned =
|
pinned =
|
||||||
try await client.get(endpoint: Accounts.statuses(id: accountId,
|
try await client.get(endpoint: Accounts.statuses(id: accountId,
|
||||||
sinceId: nil,
|
sinceId: nil,
|
||||||
tag: nil,
|
tag: nil,
|
||||||
onlyMedia: nil,
|
onlyMedia: false,
|
||||||
excludeReplies: nil,
|
excludeReplies: false,
|
||||||
|
excludeReblogs: false,
|
||||||
pinned: true))
|
pinned: true))
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: pinned, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: pinned, client: client)
|
||||||
}
|
}
|
||||||
|
@ -200,7 +187,7 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
reloadTabState()
|
reloadTabState()
|
||||||
} catch {
|
} catch {
|
||||||
tabState = .statuses(statusesState: .error(error: error))
|
statusesState = .error(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,56 +195,66 @@ import SwiftUI
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
switch selectedTab {
|
switch selectedTab {
|
||||||
case .statuses, .postsAndReplies, .media:
|
case .statuses, .replies, .boosts, .media:
|
||||||
guard let lastId = statuses.last?.id else { return }
|
guard let lastId = statuses.last?.id else { return }
|
||||||
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .loadingNextPage))
|
if selectedTab == .boosts {
|
||||||
|
statusesState = .display(statuses: boosts, nextPageState: .loadingNextPage)
|
||||||
|
} else {
|
||||||
|
statusesState = .display(statuses: statuses, nextPageState: .loadingNextPage)
|
||||||
|
}
|
||||||
let newStatuses: [Status] =
|
let newStatuses: [Status] =
|
||||||
try await client.get(endpoint: Accounts.statuses(id: accountId,
|
try await client.get(endpoint: Accounts.statuses(id: accountId,
|
||||||
sinceId: lastId,
|
sinceId: lastId,
|
||||||
tag: nil,
|
tag: nil,
|
||||||
onlyMedia: selectedTab == .media ? true : nil,
|
onlyMedia: selectedTab == .media,
|
||||||
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
|
excludeReplies: selectedTab != .replies,
|
||||||
|
excludeReblogs: selectedTab != .boosts,
|
||||||
pinned: nil))
|
pinned: nil))
|
||||||
statuses.append(contentsOf: newStatuses)
|
statuses.append(contentsOf: newStatuses)
|
||||||
|
if selectedTab == .boosts {
|
||||||
|
let newBoosts = statuses.filter{ $0.reblog != nil }
|
||||||
|
self.boosts.append(contentsOf: newBoosts)
|
||||||
|
}
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
||||||
tabState = .statuses(statusesState: .display(statuses: statuses,
|
if selectedTab == .boosts {
|
||||||
nextPageState: newStatuses.count < 20 ? .none : .hasNextPage))
|
statusesState = .display(statuses: boosts,
|
||||||
|
nextPageState: newStatuses.count < 20 ? .none : .hasNextPage)
|
||||||
|
} else {
|
||||||
|
statusesState = .display(statuses: statuses,
|
||||||
|
nextPageState: newStatuses.count < 20 ? .none : .hasNextPage)
|
||||||
|
}
|
||||||
case .favorites:
|
case .favorites:
|
||||||
guard let nextPageId = favoritesNextPage?.maxId else { return }
|
guard let nextPageId = favoritesNextPage?.maxId else { return }
|
||||||
let newFavorites: [Status]
|
let newFavorites: [Status]
|
||||||
(newFavorites, favoritesNextPage) = try await client.getWithLink(endpoint: Accounts.favorites(sinceId: nextPageId))
|
(newFavorites, favoritesNextPage) = try await client.getWithLink(endpoint: Accounts.favorites(sinceId: nextPageId))
|
||||||
favorites.append(contentsOf: newFavorites)
|
favorites.append(contentsOf: newFavorites)
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newFavorites, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newFavorites, client: client)
|
||||||
tabState = .statuses(statusesState: .display(statuses: favorites, nextPageState: .hasNextPage))
|
statusesState = .display(statuses: favorites, nextPageState: .hasNextPage)
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
guard let nextPageId = bookmarksNextPage?.maxId else { return }
|
guard let nextPageId = bookmarksNextPage?.maxId else { return }
|
||||||
let newBookmarks: [Status]
|
let newBookmarks: [Status]
|
||||||
(newBookmarks, bookmarksNextPage) = try await client.getWithLink(endpoint: Accounts.bookmarks(sinceId: nextPageId))
|
(newBookmarks, bookmarksNextPage) = try await client.getWithLink(endpoint: Accounts.bookmarks(sinceId: nextPageId))
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newBookmarks, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newBookmarks, client: client)
|
||||||
bookmarks.append(contentsOf: newBookmarks)
|
bookmarks.append(contentsOf: newBookmarks)
|
||||||
tabState = .statuses(statusesState: .display(statuses: bookmarks, nextPageState: .hasNextPage))
|
statusesState = .display(statuses: bookmarks, nextPageState: .hasNextPage)
|
||||||
case .followedTags, .lists:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
tabState = .statuses(statusesState: .error(error: error))
|
statusesState = .error(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadTabState() {
|
private func reloadTabState() {
|
||||||
switch selectedTab {
|
switch selectedTab {
|
||||||
case .statuses, .postsAndReplies, .media:
|
case .statuses, .replies, .media:
|
||||||
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage))
|
statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage)
|
||||||
|
case .boosts:
|
||||||
|
statusesState = .display(statuses: boosts, nextPageState: statuses.count < 20 ? .none : .hasNextPage)
|
||||||
case .favorites:
|
case .favorites:
|
||||||
tabState = .statuses(statusesState: .display(statuses: favorites,
|
statusesState = .display(statuses: favorites,
|
||||||
nextPageState: favoritesNextPage != nil ? .hasNextPage : .none))
|
nextPageState: favoritesNextPage != nil ? .hasNextPage : .none)
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
tabState = .statuses(statusesState: .display(statuses: bookmarks,
|
statusesState = .display(statuses: bookmarks,
|
||||||
nextPageState: bookmarksNextPage != nil ? .hasNextPage : .none))
|
nextPageState: bookmarksNextPage != nil ? .hasNextPage : .none)
|
||||||
case .followedTags:
|
|
||||||
tabState = .followedTags
|
|
||||||
case .lists:
|
|
||||||
tabState = .lists
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,9 @@ public enum Accounts: Endpoint {
|
||||||
case statuses(id: String,
|
case statuses(id: String,
|
||||||
sinceId: String?,
|
sinceId: String?,
|
||||||
tag: String?,
|
tag: String?,
|
||||||
onlyMedia: Bool?,
|
onlyMedia: Bool,
|
||||||
excludeReplies: Bool?,
|
excludeReplies: Bool,
|
||||||
|
excludeReblogs: Bool,
|
||||||
pinned: Bool?)
|
pinned: Bool?)
|
||||||
case relationships(ids: [String])
|
case relationships(ids: [String])
|
||||||
case follow(id: String, notify: Bool, reblogs: Bool)
|
case follow(id: String, notify: Bool, reblogs: Bool)
|
||||||
|
@ -50,7 +51,7 @@ public enum Accounts: Endpoint {
|
||||||
"accounts/verify_credentials"
|
"accounts/verify_credentials"
|
||||||
case .updateCredentials, .updateCredentialsMedia:
|
case .updateCredentials, .updateCredentialsMedia:
|
||||||
"accounts/update_credentials"
|
"accounts/update_credentials"
|
||||||
case let .statuses(id, _, _, _, _, _):
|
case let .statuses(id, _, _, _, _, _, _):
|
||||||
"accounts/\(id)/statuses"
|
"accounts/\(id)/statuses"
|
||||||
case .relationships:
|
case .relationships:
|
||||||
"accounts/relationships"
|
"accounts/relationships"
|
||||||
|
@ -89,7 +90,7 @@ public enum Accounts: Endpoint {
|
||||||
return [
|
return [
|
||||||
.init(name: "acct", value: name),
|
.init(name: "acct", value: name),
|
||||||
]
|
]
|
||||||
case let .statuses(_, sinceId, tag, onlyMedia, excludeReplies, pinned):
|
case let .statuses(_, sinceId, tag, onlyMedia, excludeReplies, excludeReblogs, pinned):
|
||||||
var params: [URLQueryItem] = []
|
var params: [URLQueryItem] = []
|
||||||
if let tag {
|
if let tag {
|
||||||
params.append(.init(name: "tagged", value: tag))
|
params.append(.init(name: "tagged", value: tag))
|
||||||
|
@ -97,12 +98,11 @@ public enum Accounts: Endpoint {
|
||||||
if let sinceId {
|
if let sinceId {
|
||||||
params.append(.init(name: "max_id", value: sinceId))
|
params.append(.init(name: "max_id", value: sinceId))
|
||||||
}
|
}
|
||||||
if let onlyMedia {
|
|
||||||
params.append(.init(name: "only_media", value: onlyMedia ? "true" : "false"))
|
params.append(.init(name: "only_media", value: onlyMedia ? "true" : "false"))
|
||||||
}
|
params.append(.init(name: "exclude_replies", value: excludeReplies ? "true" : "false"))
|
||||||
if let excludeReplies {
|
params.append(.init(name: "exclude_reblogs", value: excludeReblogs ? "true" : "false"))
|
||||||
params.append(.init(name: "exclude_replies", value: excludeReplies ? "true" : "false"))
|
|
||||||
}
|
|
||||||
if let pinned {
|
if let pinned {
|
||||||
params.append(.init(name: "pinned", value: pinned ? "true" : "false"))
|
params.append(.init(name: "pinned", value: pinned ? "true" : "false"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,13 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable {
|
||||||
case let .list(list): return Timelines.list(listId: list.id, sinceId: sinceId, maxId: maxId, minId: minId)
|
case let .list(list): return Timelines.list(listId: list.id, sinceId: sinceId, maxId: maxId, minId: minId)
|
||||||
case let .hashtag(tag, accountId):
|
case let .hashtag(tag, accountId):
|
||||||
if let accountId {
|
if let accountId {
|
||||||
return Accounts.statuses(id: accountId, sinceId: nil, tag: tag, onlyMedia: nil, excludeReplies: nil, pinned: nil)
|
return Accounts.statuses(id: accountId,
|
||||||
|
sinceId: nil,
|
||||||
|
tag: tag,
|
||||||
|
onlyMedia: false,
|
||||||
|
excludeReplies: false,
|
||||||
|
excludeReblogs: false,
|
||||||
|
pinned: nil)
|
||||||
} else {
|
} else {
|
||||||
return Timelines.hashtag(tag: tag, additional: nil, maxId: maxId, minId: minId)
|
return Timelines.hashtag(tag: tag, additional: nil, maxId: maxId, minId: minId)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue