Bookmarks support close #48

This commit is contained in:
Thomas Ricouard 2023-01-09 19:26:56 +01:00
parent 1c1ca7ba0f
commit 662f4be29d
8 changed files with 89 additions and 13 deletions

View file

@ -46,7 +46,8 @@ public struct AccountDetailView: View {
Picker("", selection: $viewModel.selectedTab) { Picker("", selection: $viewModel.selectedTab) {
ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs, ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs,
id: \.self) { tab in id: \.self) { tab in
Text(tab.title).tag(tab) Image(systemName: tab.iconName)
.tag(tab)
} }
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)

View file

@ -15,24 +15,25 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
} }
enum Tab: Int { enum Tab: Int {
case statuses, favourites, followedTags, postsAndReplies, media, lists case statuses, favourites, bookmarks, followedTags, postsAndReplies, media, lists
static var currentAccountTabs: [Tab] { static var currentAccountTabs: [Tab] {
[.statuses, .favourites, .followedTags, .lists] [.statuses, .favourites, .bookmarks, .followedTags, .lists]
} }
static var accountTabs: [Tab] { static var accountTabs: [Tab] {
[.statuses, .postsAndReplies, .media] [.statuses, .postsAndReplies, .media]
} }
var title: String { var iconName: String {
switch self { switch self {
case .statuses: return "Posts" case .statuses: return "bubble.right"
case .favourites: return "Favorites" case .favourites: return "star"
case .followedTags: return "Tags" case .bookmarks: return "bookmark"
case .postsAndReplies: return "Posts & Replies" case .followedTags: return "tag"
case .media: return "Media" case .postsAndReplies: return "bubble.left.and.bubble.right"
case .lists: return "Lists" case .media: return "photo.on.rectangle.angled"
case .lists: return "list.bullet"
} }
} }
} }
@ -61,7 +62,9 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
@Published var relationship: Relationshionship? @Published var relationship: Relationshionship?
@Published var pinned: [Status] = [] @Published var pinned: [Status] = []
@Published var favourites: [Status] = [] @Published var favourites: [Status] = []
@Published var bookmarks: [Status] = []
private var favouritesNextPage: LinkHandler? private var favouritesNextPage: LinkHandler?
private var bookmarksNextPage: LinkHandler?
@Published var featuredTags: [FeaturedTag] = [] @Published var featuredTags: [FeaturedTag] = []
@Published var fields: [Account.Field] = [] @Published var fields: [Account.Field] = []
@Published var familliarFollowers: [Account] = [] @Published var familliarFollowers: [Account] = []
@ -145,6 +148,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
} }
if isCurrentUser { if isCurrentUser {
(favourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nil)) (favourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nil))
(bookmarks, bookmarksNextPage) = try await client.getWithLink(endpoint: Accounts.bookmarks(sinceId: nil))
} }
reloadTabState() reloadTabState()
} catch { } catch {
@ -175,6 +179,12 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
(newFavourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nextPageId)) (newFavourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nextPageId))
favourites.append(contentsOf: newFavourites) favourites.append(contentsOf: newFavourites)
tabState = .statuses(statusesState: .display(statuses: favourites, nextPageState: .hasNextPage)) tabState = .statuses(statusesState: .display(statuses: favourites, nextPageState: .hasNextPage))
case .bookmarks:
guard let nextPageId = bookmarksNextPage?.maxId else { return }
let newBookmarks: [Status]
(newBookmarks, bookmarksNextPage) = try await client.getWithLink(endpoint: Accounts.bookmarks(sinceId: nextPageId))
bookmarks.append(contentsOf: newBookmarks)
tabState = .statuses(statusesState: .display(statuses: bookmarks, nextPageState: .hasNextPage))
case .followedTags, .lists: case .followedTags, .lists:
break break
} }
@ -208,6 +218,9 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
case .favourites: case .favourites:
tabState = .statuses(statusesState: .display(statuses: favourites, tabState = .statuses(statusesState: .display(statuses: favourites,
nextPageState: favouritesNextPage != nil ? .hasNextPage : .none)) nextPageState: favouritesNextPage != nil ? .hasNextPage : .none))
case .bookmarks:
tabState = .statuses(statusesState: .display(statuses: bookmarks,
nextPageState: bookmarksNextPage != nil ? .hasNextPage : .none))
case .followedTags: case .followedTags:
tabState = .followedTags tabState = .followedTags
case .lists: case .lists:

View file

@ -40,6 +40,7 @@ public protocol AnyStatus {
var favourited: Bool? { get } var favourited: Bool? { get }
var reblogged: Bool? { get } var reblogged: Bool? { get }
var pinned: Bool? { get } var pinned: Bool? { get }
var bookmarked: Bool? { get }
var emojis: [Emoji] { get } var emojis: [Emoji] { get }
var url: URL? { get } var url: URL? { get }
var application: Application? { get } var application: Application? { get }
@ -71,6 +72,7 @@ public struct Status: AnyStatus, Codable, Identifiable {
public let favourited: Bool? public let favourited: Bool?
public let reblogged: Bool? public let reblogged: Bool?
public let pinned: Bool? public let pinned: Bool?
public let bookmarked: Bool?
public let emojis: [Emoji] public let emojis: [Emoji]
public let url: URL? public let url: URL?
public let application: Application? public let application: Application?
@ -96,6 +98,7 @@ public struct Status: AnyStatus, Codable, Identifiable {
favourited: false, favourited: false,
reblogged: false, reblogged: false,
pinned: false, pinned: false,
bookmarked: false,
emojis: [], emojis: [],
url: nil, url: nil,
application: nil, application: nil,
@ -130,6 +133,7 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable {
public let favourited: Bool? public let favourited: Bool?
public let reblogged: Bool? public let reblogged: Bool?
public let pinned: Bool? public let pinned: Bool?
public let bookmarked: Bool?
public let emojis: [Emoji] public let emojis: [Emoji]
public let url: URL? public let url: URL?
public var application: Application? public var application: Application?

View file

@ -3,6 +3,7 @@ import Foundation
public enum Accounts: Endpoint { public enum Accounts: Endpoint {
case accounts(id: String) case accounts(id: String)
case favourites(sinceId: String?) case favourites(sinceId: String?)
case bookmarks(sinceId: String?)
case followedTags case followedTags
case featuredTags(id: String) case featuredTags(id: String)
case verifyCredentials case verifyCredentials
@ -27,6 +28,8 @@ public enum Accounts: Endpoint {
return "accounts/\(id)" return "accounts/\(id)"
case .favourites: case .favourites:
return "favourites" return "favourites"
case .bookmarks:
return "bookmarks"
case .followedTags: case .followedTags:
return "followed_tags" return "followed_tags"
case .featuredTags(let id): case .featuredTags(let id):
@ -87,6 +90,9 @@ public enum Accounts: Endpoint {
case let .favourites(sinceId): case let .favourites(sinceId):
guard let sinceId else { return nil } guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)] return [.init(name: "max_id", value: sinceId)]
case let .bookmarks(sinceId):
guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)]
default: default:
return nil return nil
} }

View file

@ -22,6 +22,8 @@ public enum Statuses: Endpoint {
case favouritedBy(id: String, maxId: String?) case favouritedBy(id: String, maxId: String?)
case pin(id: String) case pin(id: String)
case unpin(id: String) case unpin(id: String)
case bookmark(id: String)
case unbookmark(id: String)
public func path() -> String { public func path() -> String {
switch self { switch self {
@ -49,6 +51,10 @@ public enum Statuses: Endpoint {
return "statuses/\(id)/pin" return "statuses/\(id)/pin"
case let .unpin(id): case let .unpin(id):
return "statuses/\(id)/unpin" return "statuses/\(id)/unpin"
case let .bookmark(id):
return "statuses/\(id)/bookmark"
case let .unbookmark(id):
return "statuses/\(id)/unbookmark"
} }
} }

View file

@ -14,7 +14,7 @@ struct StatusActionsView: View {
@MainActor @MainActor
enum Actions: CaseIterable { enum Actions: CaseIterable {
case respond, boost, favourite, share case respond, boost, favourite, bookmark, share
func iconName(viewModel: StatusRowViewModel) -> String { func iconName(viewModel: StatusRowViewModel) -> String {
switch self { switch self {
@ -24,6 +24,8 @@ struct StatusActionsView: View {
return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "arrow.left.arrow.right.circle" return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "arrow.left.arrow.right.circle"
case .favourite: case .favourite:
return viewModel.isFavourited ? "star.fill" : "star" return viewModel.isFavourited ? "star.fill" : "star"
case .bookmark:
return viewModel.isBookmarked ? "bookmark.fill" : "bookmark"
case .share: case .share:
return "square.and.arrow.up" return "square.and.arrow.up"
} }
@ -40,7 +42,7 @@ struct StatusActionsView: View {
return viewModel.favouritesCount return viewModel.favouritesCount
case .boost: case .boost:
return viewModel.reblogsCount return viewModel.reblogsCount
case .share: case .share, .bookmark:
return nil return nil
} }
} }
@ -51,6 +53,8 @@ struct StatusActionsView: View {
return nil return nil
case .favourite: case .favourite:
return viewModel.isFavourited ? .yellow : nil return viewModel.isFavourited ? .yellow : nil
case .bookmark:
return viewModel.isBookmarked ? .pink : nil
case .boost: case .boost:
return viewModel.isReblogged ? theme.tintColor : nil return viewModel.isReblogged ? theme.tintColor : nil
} }
@ -150,6 +154,12 @@ struct StatusActionsView: View {
} else { } else {
await viewModel.favourite() await viewModel.favourite()
} }
case .bookmark:
if viewModel.isBookmarked {
await viewModel.unbookmark()
} else {
await viewModel.bookmark()
}
case .boost: case .boost:
if viewModel.isReblogged { if viewModel.isReblogged {
await viewModel.unReblog() await viewModel.unReblog()

View file

@ -30,7 +30,16 @@ struct StatusRowContextMenu: View {
} } label: { } } label: {
Label(viewModel.isReblogged ? "Unboost" : "Boost", systemImage: "arrow.left.arrow.right.circle") Label(viewModel.isReblogged ? "Unboost" : "Boost", systemImage: "arrow.left.arrow.right.circle")
} }
Button { Task {
if viewModel.isBookmarked {
await viewModel.unbookmark()
} else {
await viewModel.bookmark()
}
} } label: {
Label(viewModel.isReblogged ? "Unbookmark" : "Bookmark",
systemImage: "bookmark")
}
} }
if viewModel.status.visibility == .pub, !viewModel.isRemote { if viewModel.status.visibility == .pub, !viewModel.isRemote {

View file

@ -14,6 +14,7 @@ public class StatusRowViewModel: ObservableObject {
@Published var isFavourited: Bool @Published var isFavourited: Bool
@Published var isReblogged: Bool @Published var isReblogged: Bool
@Published var isPinned: Bool @Published var isPinned: Bool
@Published var isBookmarked: Bool
@Published var reblogsCount: Int @Published var reblogsCount: Int
@Published var repliesCount: Int @Published var repliesCount: Int
@Published var embededStatus: Status? @Published var embededStatus: Status?
@ -39,10 +40,12 @@ public class StatusRowViewModel: ObservableObject {
self.isFavourited = reblog.favourited == true self.isFavourited = reblog.favourited == true
self.isReblogged = reblog.reblogged == true self.isReblogged = reblog.reblogged == true
self.isPinned = reblog.pinned == true self.isPinned = reblog.pinned == true
self.isBookmarked = reblog.bookmarked == true
} else { } else {
self.isFavourited = status.favourited == true self.isFavourited = status.favourited == true
self.isReblogged = status.reblogged == true self.isReblogged = status.reblogged == true
self.isPinned = status.pinned == true self.isPinned = status.pinned == true
self.isBookmarked = status.bookmarked == true
} }
self.favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount self.favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
@ -166,6 +169,28 @@ public class StatusRowViewModel: ObservableObject {
} }
} }
func bookmark() async {
guard let client, client.isAuth else { return }
isBookmarked = true
do {
let status: Status = try await client.post(endpoint: Statuses.bookmark(id: status.reblog?.id ?? status.id))
updateFromStatus(status: status)
} catch {
isBookmarked = false
}
}
func unbookmark() async {
guard let client, client.isAuth else { return }
isBookmarked = false
do {
let status: Status = try await client.post(endpoint: Statuses.unbookmark(id: status.reblog?.id ?? status.id))
updateFromStatus(status: status)
} catch {
isBookmarked = true
}
}
func delete() async { func delete() async {
guard let client else { return } guard let client else { return }
do { do {
@ -178,10 +203,12 @@ public class StatusRowViewModel: ObservableObject {
isFavourited = reblog.favourited == true isFavourited = reblog.favourited == true
isReblogged = reblog.reblogged == true isReblogged = reblog.reblogged == true
isPinned = reblog.pinned == true isPinned = reblog.pinned == true
isBookmarked = reblog.bookmarked == true
} else { } else {
isFavourited = status.favourited == true isFavourited = status.favourited == true
isReblogged = status.reblogged == true isReblogged = status.reblogged == true
isPinned = status.pinned == true isPinned = status.pinned == true
isBookmarked = status.bookmarked == true
} }
favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount