Post boosted by / favourited by

This commit is contained in:
Thomas Ricouard 2022-12-24 13:41:25 +01:00
parent 569aedeaeb
commit 70ee6e0d27
14 changed files with 143 additions and 68 deletions

View File

@ -18,9 +18,13 @@ extension View {
case let .hashTag(tag, accountId):
TimelineView(timeline: .hashtag(tag: tag, accountId: accountId))
case let .following(id):
AccountsListView(accountId: id, mode: .following)
AccountsListView(mode: .followers(accountId: id))
case let .followers(id):
AccountsListView(accountId: id, mode: .followers)
AccountsListView(mode: .followers(accountId: id))
case let .favouritedBy(id):
AccountsListView(mode: .favouritedBy(statusId: id))
case let .rebloggedBy(id):
AccountsListView(mode: .rebloggedBy(statusId: id))
}
}
}

View File

@ -96,7 +96,7 @@ struct AccountDetailHeaderView: View {
VStack(alignment: .leading, spacing: 0) {
account.displayNameWithEmojis
.font(.headline)
Text(account.acct)
Text("@\(account.acct)")
.font(.callout)
.foregroundColor(.gray)
}

View File

@ -38,7 +38,8 @@ public struct AccountsListRow: View {
.font(.footnote)
.foregroundColor(.gray)
Text(viewModel.account.note.asSafeAttributedString)
.font(.callout)
.font(.footnote)
.lineLimit(3)
.environment(\.openURL, OpenURLAction { url in
routeurPath.handle(url: url)
})

View File

@ -9,8 +9,8 @@ public struct AccountsListView: View {
@StateObject private var viewModel: AccountsListViewModel
@State private var didAppear: Bool = false
public init(accountId: String, mode: AccountsListMode) {
_viewModel = StateObject(wrappedValue: .init(accountId: accountId, mode: mode))
public init(mode: AccountsListMode) {
_viewModel = StateObject(wrappedValue: .init(mode: mode))
}
public var body: some View {
@ -50,7 +50,7 @@ public struct AccountsListView: View {
}
}
.listStyle(.plain)
.navigationTitle(viewModel.mode.rawValue.capitalized)
.navigationTitle(viewModel.mode.title)
.navigationBarTitleDisplayMode(.inline)
.task {
viewModel.client = client

View File

@ -2,15 +2,28 @@ import SwiftUI
import Models
import Network
public enum AccountsListMode: String {
case following, followers
public enum AccountsListMode {
case following(accountId: String), followers(accountId: String)
case favouritedBy(statusId: String), rebloggedBy(statusId: String)
var title: String {
switch self {
case .following:
return "Following"
case .followers:
return "Followers"
case .favouritedBy:
return "Favourited by"
case .rebloggedBy:
return "Boosted by"
}
}
}
@MainActor
class AccountsListViewModel: ObservableObject {
var client: Client?
let accountId: String
let mode: AccountsListMode
public enum State {
@ -31,8 +44,7 @@ class AccountsListViewModel: ObservableObject {
private var nextPageId: String?
init(accountId: String, mode: AccountsListMode) {
self.accountId = accountId
init(mode: AccountsListMode) {
self.mode = mode
}
@ -42,12 +54,18 @@ class AccountsListViewModel: ObservableObject {
state = .loading
let link: LinkHandler?
switch mode {
case .followers:
case let .followers(accountId):
(accounts, link) = try await client.getWithLink(endpoint: Accounts.followers(id: accountId,
sinceId: nil))
case .following:
maxId: nil))
case let .following(accountId):
(accounts, link) = try await client.getWithLink(endpoint: Accounts.following(id: accountId,
sinceId: nil))
maxId: nil))
case let .rebloggedBy(statusId):
(accounts, link) = try await client.getWithLink(endpoint: Statuses.rebloggedBy(id: statusId,
maxId: nil))
case let .favouritedBy(statusId):
(accounts, link) = try await client.getWithLink(endpoint: Statuses.favouritedBy(id: statusId,
maxId: nil))
}
nextPageId = link?.maxId
relationships = try await client.get(endpoint:
@ -65,12 +83,18 @@ class AccountsListViewModel: ObservableObject {
let newAccounts: [Account]
let link: LinkHandler?
switch mode {
case .followers:
case let .followers(accountId):
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.followers(id: accountId,
sinceId: nextPageId))
case .following:
maxId: nextPageId))
case let .following(accountId):
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.following(id: accountId,
sinceId: nextPageId))
maxId: nextPageId))
case let .rebloggedBy(statusId):
(newAccounts, link) = try await client.getWithLink(endpoint: Statuses.rebloggedBy(id: statusId,
maxId: nextPageId))
case let .favouritedBy(statusId):
(newAccounts, link) = try await client.getWithLink(endpoint: Statuses.favouritedBy(id: statusId,
maxId: nextPageId))
}
accounts.append(contentsOf: newAccounts)
let newRelationships: [Relationshionship] =

View File

@ -9,6 +9,8 @@ public enum RouteurDestinations: Hashable {
case hashTag(tag: String, account: String?)
case followers(id: String)
case following(id: String)
case favouritedBy(id: String)
case rebloggedBy(id: String)
}
public enum SheetDestinations: Identifiable {

View File

@ -12,8 +12,8 @@ public enum Accounts: Endpoint {
case unfollow(id: String)
case familiarFollowers(withAccount: String)
case suggestions
case followers(id: String, sinceId: String?)
case following(id: String, sinceId: String?)
case followers(id: String, maxId: String?)
case following(id: String, maxId: String?)
public func path() -> String {
switch self {
@ -63,12 +63,10 @@ public enum Accounts: Endpoint {
}
case let .familiarFollowers(withAccount):
return [.init(name: "id[]", value: withAccount)]
case let .followers(_, sinceId):
guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)]
case let .following(_, sinceId):
guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)]
case let .followers(_, maxId):
return makePaginationParam(sinceId: nil, maxId: maxId)
case let .following(_, maxId):
return makePaginationParam(sinceId: nil, maxId: maxId)
case let .favourites(sinceId):
guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)]

View File

@ -7,6 +7,8 @@ public enum Statuses: Endpoint {
case unfavourite(id: String)
case reblog(id: String)
case unreblog(id: String)
case rebloggedBy(id: String, maxId: String?)
case favouritedBy(id: String, maxId: String?)
public func path() -> String {
switch self {
@ -22,11 +24,19 @@ public enum Statuses: Endpoint {
return "statuses/\(id)/reblog"
case .unreblog(let id):
return "statuses/\(id)/unreblog"
case .rebloggedBy(let id, _):
return "statuses/\(id)/reblogged_by"
case .favouritedBy(let id, _):
return "statuses/\(id)/favourited_by"
}
}
public func queryItems() -> [URLQueryItem]? {
switch self {
case let .rebloggedBy(_, maxId):
return makePaginationParam(sinceId: nil, maxId: maxId)
case let .favouritedBy(_, maxId):
return makePaginationParam(sinceId: nil, maxId: maxId)
default:
return nil
}

View File

@ -14,7 +14,7 @@ struct NotificationRowView: View {
if let type = notification.supportedType {
HStack(alignment: .top, spacing: 8) {
makeAvatarView(type: type)
VStack(alignment: .leading, spacing: 0) {
VStack(alignment: .leading, spacing: 2) {
makeMainLabel(type: type)
makeContent(type: type)
}
@ -77,14 +77,14 @@ struct NotificationRowView: View {
)
.padding(.top, 8)
} else {
Text(notification.account.acct)
Text("@\(notification.account.acct)")
.font(.callout)
.foregroundColor(.gray)
if type == .follow {
Text(notification.account.note.asSafeAttributedString)
.lineLimit(3)
.font(.body)
.font(.callout)
.foregroundColor(.gray)
.environment(\.openURL, OpenURLAction { url in
routeurPath.handle(url: url)

View File

@ -34,11 +34,12 @@ public struct StatusDetailView: View {
.padding(.vertical, DS.Constants.dividerPadding)
}
}
StatusRowView(viewModel: .init(status: status, isEmbed: false))
StatusRowView(viewModel: .init(status: status,
isEmbed: false,
isFocused: true))
.id(status.id)
makeStatusInfoDetailView(status: status)
Divider()
.padding(.vertical, DS.Constants.dividerPadding * 2)
.padding(.bottom, DS.Constants.dividerPadding * 2)
if !context.descendants.isEmpty {
ForEach(context.descendants) { descendant in
StatusRowView(viewModel: .init(status: descendant, isEmbed: false))
@ -66,15 +67,4 @@ public struct StatusDetailView: View {
.navigationTitle(viewModel.title)
.navigationBarTitleDisplayMode(.inline)
}
@ViewBuilder
private func makeStatusInfoDetailView(status: Status) -> some View {
HStack {
Text(status.createdAt.asDate, style: .date)
Text(status.createdAt.asDate, style: .time)
Spacer()
Text(status.application?.name ?? "")
}
.font(.caption)
}
}

View File

@ -6,7 +6,7 @@ import Network
struct StatusActionsView: View {
@EnvironmentObject private var routeurPath: RouterPath
@ObservedObject var viewModel: StatusRowViewModel
@MainActor
enum Actions: CaseIterable {
case respond, boost, favourite, share
@ -39,32 +39,73 @@ struct StatusActionsView: View {
}
var body: some View {
HStack {
ForEach(Actions.allCases, id: \.self) { action in
if action == .share {
if let url = viewModel.status.reblog?.url ?? viewModel.status.url {
ShareLink(item: url) {
Image(systemName: action.iconName(viewModel: viewModel))
}
}
} else {
Button {
handleAction(action: action)
} label: {
HStack(spacing: 2) {
Image(systemName: action.iconName(viewModel: viewModel))
if let count = action.count(viewModel: viewModel) {
Text("\(count)")
.font(.footnote)
VStack(spacing: 12) {
HStack {
ForEach(Actions.allCases, id: \.self) { action in
if action == .share {
if let url = viewModel.status.reblog?.url ?? viewModel.status.url {
ShareLink(item: url) {
Image(systemName: action.iconName(viewModel: viewModel))
}
}
} else {
Button {
handleAction(action: action)
} label: {
HStack(spacing: 2) {
Image(systemName: action.iconName(viewModel: viewModel))
if let count = action.count(viewModel: viewModel) {
Text("\(count)")
.font(.footnote)
}
}
}
.buttonStyle(.borderless)
Spacer()
}
.buttonStyle(.borderless)
Spacer()
}
}
if viewModel.isFocused {
summaryView
}
}
}
@ViewBuilder
private var summaryView: some View {
HStack {
Text(viewModel.status.createdAt.asDate, style: .date)
Text(viewModel.status.createdAt.asDate, style: .time)
Spacer()
Text(viewModel.status.application?.name ?? "")
}
.font(.caption)
if viewModel.favouritesCount > 0 {
Divider()
Button {
routeurPath.navigate(to: .favouritedBy(id: viewModel.status.id))
} label: {
HStack {
Text("\(viewModel.favouritesCount) favorites")
Spacer()
Image(systemName: "chevron.right")
}
.font(.callout)
}
}
if viewModel.reblogsCount > 0 {
Divider()
Button {
routeurPath.navigate(to: .rebloggedBy(id: viewModel.status.id))
} label: {
HStack {
Text("\(viewModel.reblogsCount) boosts")
Spacer()
Image(systemName: "chevron.right")
}
.font(.callout)
}
}
.tint(.gray)
}
private func handleAction(action: Actions) {

View File

@ -24,6 +24,7 @@ public struct StatusRowView: View {
if !viewModel.isEmbed {
StatusActionsView(viewModel: viewModel)
.padding(.vertical, 8)
.tint(viewModel.isFocused ? .brand : .gray)
}
}
.onAppear {

View File

@ -6,6 +6,7 @@ import Network
public class StatusRowViewModel: ObservableObject {
let status: Status
let isEmbed: Bool
let isFocused: Bool
@Published var favouritesCount: Int
@Published var isFavourited: Bool
@ -15,9 +16,12 @@ public class StatusRowViewModel: ObservableObject {
var client: Client?
public init(status: Status, isEmbed: Bool) {
public init(status: Status,
isEmbed: Bool = false,
isFocused: Bool = false) {
self.status = status
self.isEmbed = isEmbed
self.isFocused = isFocused
if let reblog = status.reblog {
self.isFavourited = reblog.favourited == true
self.isReblogged = reblog.reblogged == true

View File

@ -53,7 +53,7 @@ public struct TimelineView: View {
VStack(alignment: .leading, spacing: 4) {
Text("#\(tag.name)")
.font(.headline)
Text("\(tag.totalUses) posts from \(tag.totalAccounts) participants")
Text("\(tag.totalUses) recent posts from \(tag.totalAccounts) participants")
.font(.footnote)
.foregroundColor(.gray)
}