Display pinned statuses on profile

This commit is contained in:
Thomas Ricouard 2023-01-03 18:22:08 +01:00
parent e9991020ec
commit a1681c3f1b
8 changed files with 95 additions and 8 deletions

View file

@ -57,6 +57,9 @@ public struct AccountDetailView: View {
switch viewModel.tabState { switch viewModel.tabState {
case .statuses: case .statuses:
if viewModel.selectedTab == .statuses {
pinnedPostsView
}
StatusesListView(fetcher: viewModel) StatusesListView(fetcher: viewModel)
case let .followedTags(tags): case let .followedTags(tags):
makeTagsListView(tags: tags) makeTagsListView(tags: tags)
@ -295,6 +298,24 @@ public struct AccountDetailView: View {
Text("Enter the name for your list") Text("Enter the name for your list")
} }
} }
@ViewBuilder
private var pinnedPostsView: some View {
if !viewModel.pinned.isEmpty {
ForEach(viewModel.pinned) { status in
VStack(alignment: .leading) {
Label("Pinned post", systemImage: "pin.fill")
.font(.footnote)
.foregroundColor(.gray)
.fontWeight(.semibold)
StatusRowView(viewModel: .init(status: status))
}
.padding(.horizontal, .layoutPadding)
Divider()
.padding(.vertical, .dividerPadding)
}
}
}
} }
struct AccountDetailView_Previews: PreviewProvider { struct AccountDetailView_Previews: PreviewProvider {

View file

@ -59,6 +59,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
@Published var statusesState: StatusesState = .loading @Published var statusesState: StatusesState = .loading
@Published var relationship: Relationshionship? @Published var relationship: Relationshionship?
@Published var pinned: [Status] = []
@Published var favourites: [Status] = [] @Published var favourites: [Status] = []
private var favouritesNextPage: LinkHandler? private var favouritesNextPage: LinkHandler?
@Published var followedTags: [Tag] = [] @Published var followedTags: [Tag] = []
@ -137,7 +138,17 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
sinceId: nil, sinceId: nil,
tag: nil, tag: nil,
onlyMedia: selectedTab == .media ? true : nil, onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil)) excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
if selectedTab == .statuses {
pinned =
try await client.get(endpoint: Accounts.statuses(id: accountId,
sinceId: nil,
tag: nil,
onlyMedia: nil,
excludeReplies: nil,
pinned: true))
}
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))
} }
@ -159,7 +170,8 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
sinceId: lastId, sinceId: lastId,
tag: nil, tag: nil,
onlyMedia: selectedTab == .media ? true : nil, onlyMedia: selectedTab == .media ? true : nil,
excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil)) excludeReplies: selectedTab == .statuses && !isCurrentUser ? true : nil,
pinned: nil))
statuses.append(contentsOf: newStatuses) statuses.append(contentsOf: newStatuses)
tabState = .statuses(statusesState: .display(statuses: statuses, tabState = .statuses(statusesState: .display(statuses: statuses,
nextPageState: newStatuses.count < 20 ? .none : .hasNextPage)) nextPageState: newStatuses.count < 20 ? .none : .hasNextPage))

View file

@ -6,7 +6,12 @@ public enum Accounts: Endpoint {
case followedTags case followedTags
case featuredTags(id: String) case featuredTags(id: String)
case verifyCredentials case verifyCredentials
case statuses(id: String, sinceId: String?, tag: String?, onlyMedia: Bool?, excludeReplies: Bool?) case statuses(id: String,
sinceId: String?,
tag: String?,
onlyMedia: Bool?,
excludeReplies: Bool?,
pinned: Bool?)
case relationships(ids: [String]) case relationships(ids: [String])
case follow(id: String) case follow(id: String)
case unfollow(id: String) case unfollow(id: String)
@ -28,7 +33,7 @@ public enum Accounts: Endpoint {
return "accounts/\(id)/featured_tags" return "accounts/\(id)/featured_tags"
case .verifyCredentials: case .verifyCredentials:
return "accounts/verify_credentials" return "accounts/verify_credentials"
case .statuses(let id, _, _, _, _): case .statuses(let id, _, _, _, _, _):
return "accounts/\(id)/statuses" return "accounts/\(id)/statuses"
case .relationships: case .relationships:
return "accounts/relationships" return "accounts/relationships"
@ -51,7 +56,7 @@ public enum Accounts: Endpoint {
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
case .statuses(_, let sinceId, let tag, let onlyMedia, let excludeReplies): case .statuses(_, let sinceId, let tag, let onlyMedia, let excludeReplies, let 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))
@ -65,6 +70,9 @@ public enum Accounts: Endpoint {
if let excludeReplies { if let excludeReplies {
params.append(.init(name: "exclude_replies", value: excludeReplies ? "true" : "fals")) params.append(.init(name: "exclude_replies", value: excludeReplies ? "true" : "fals"))
} }
if let pinned {
params.append(.init(name: "pinned", value: pinned ? "true" : "false"))
}
return params return params
case let .relationships(ids): case let .relationships(ids):
return ids.map { return ids.map {

View file

@ -20,6 +20,8 @@ public enum Statuses: Endpoint {
case unreblog(id: String) case unreblog(id: String)
case rebloggedBy(id: String, maxId: String?) case rebloggedBy(id: String, maxId: String?)
case favouritedBy(id: String, maxId: String?) case favouritedBy(id: String, maxId: String?)
case pin(id: String)
case unpin(id: String)
public func path() -> String { public func path() -> String {
switch self { switch self {
@ -43,6 +45,10 @@ public enum Statuses: Endpoint {
return "statuses/\(id)/reblogged_by" return "statuses/\(id)/reblogged_by"
case .favouritedBy(let id, _): case .favouritedBy(let id, _):
return "statuses/\(id)/favourited_by" return "statuses/\(id)/favourited_by"
case let .pin(id):
return "statuses/\(id)/pin"
case let .unpin(id):
return "statuses/\(id)/unpin"
} }
} }

View file

@ -18,6 +18,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
StatusRowView(viewModel: .init(status: status, isCompact: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.shimmering() .shimmering()
.padding(.horizontal, .layoutPadding)
Divider() Divider()
.padding(.vertical, .dividerPadding) .padding(.vertical, .dividerPadding)
} }
@ -26,6 +27,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
case let .display(statuses, nextPageState): case let .display(statuses, nextPageState):
ForEach(statuses, id: \.viewId) { status in ForEach(statuses, id: \.viewId) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
.padding(.horizontal, .layoutPadding)
Divider() Divider()
.padding(.vertical, .dividerPadding) .padding(.vertical, .dividerPadding)
} }
@ -45,7 +47,6 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
} }
} }
} }
.padding(.horizontal, .layoutPadding)
} }
private var loadingRow: some View { private var loadingRow: some View {
@ -54,5 +55,6 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
ProgressView() ProgressView()
Spacer() Spacer()
} }
.padding(.horizontal, .layoutPadding)
} }
} }

View file

@ -251,6 +251,17 @@ public struct StatusRowView: View {
} }
if account.account?.id == viewModel.status.account.id { if account.account?.id == viewModel.status.account.id {
Button {
Task {
if viewModel.isPinned {
await viewModel.unPin()
} else {
await viewModel.pin()
}
}
} label: {
Label(viewModel.isPinned ? "Unpin": "Pin", systemImage: viewModel.isPinned ? "pin.fill" : "pin")
}
Button { Button {
routeurPath.presentedSheet = .editStatusEditor(status: viewModel.status) routeurPath.presentedSheet = .editStatusEditor(status: viewModel.status)
} label: { } label: {

View file

@ -11,6 +11,7 @@ public class StatusRowViewModel: ObservableObject {
@Published var favouritesCount: Int @Published var favouritesCount: Int
@Published var isFavourited: Bool @Published var isFavourited: Bool
@Published var isReblogged: Bool @Published var isReblogged: Bool
@Published var isPinned: 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?
@ -33,9 +34,11 @@ public class StatusRowViewModel: ObservableObject {
if let reblog = status.reblog { if let reblog = status.reblog {
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
} 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.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
@ -129,6 +132,28 @@ public class StatusRowViewModel: ObservableObject {
} }
} }
func pin() async {
guard let client, client.isAuth else { return }
isPinned = true
do {
let status: Status = try await client.post(endpoint: Statuses.pin(id: status.reblog?.id ?? status.id))
updateFromStatus(status: status)
} catch {
isPinned = false
}
}
func unPin() async {
guard let client, client.isAuth else { return }
isPinned = false
do {
let status: Status = try await client.post(endpoint: Statuses.unpin(id: status.reblog?.id ?? status.id))
updateFromStatus(status: status)
} catch {
isPinned = true
}
}
func delete() async { func delete() async {
guard let client else { return } guard let client else { return }
do { do {
@ -140,9 +165,11 @@ public class StatusRowViewModel: ObservableObject {
if let reblog = status.reblog { if let reblog = status.reblog {
isFavourited = reblog.favourited == true isFavourited = reblog.favourited == true
isReblogged = reblog.reblogged == true isReblogged = reblog.reblogged == true
isPinned = reblog.pinned == true
} else { } else {
isFavourited = status.favourited == true isFavourited = status.favourited == true
isReblogged = status.reblogged == true isReblogged = status.reblogged == true
isPinned = status.pinned == 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

View file

@ -61,7 +61,7 @@ public enum TimelineFilter: Hashable, Equatable {
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) return Accounts.statuses(id: accountId, sinceId: nil, tag: tag, onlyMedia: nil, excludeReplies: nil, pinned: nil)
} else { } else {
return Timelines.hashtag(tag: tag, maxId: maxId) return Timelines.hashtag(tag: tag, maxId: maxId)
} }