Favourite / Unfavourite status

This commit is contained in:
Thomas Ricouard 2022-12-20 20:33:45 +01:00
parent 60a963441c
commit 024d325291
8 changed files with 130 additions and 28 deletions

View file

@ -6,6 +6,7 @@ import Shimmer
import DesignSystem
public struct AccountDetailView: View {
@Environment(\.redactionReasons) private var reasons
@EnvironmentObject private var client: Client
@StateObject private var viewModel: AccountDetailViewModel
@State private var scrollOffset: CGFloat = 0
@ -35,6 +36,7 @@ public struct AccountDetailView: View {
}
}
.task {
guard reasons != .placeholder else { return }
viewModel.client = client
await viewModel.fetchAccount()
if viewModel.statuses.isEmpty {

View file

@ -11,6 +11,8 @@ public protocol AnyStatus {
var reblogsCount: Int { get }
var favouritesCount: Int { get }
var card: Card? { get }
var favourited: Bool { get }
var reblogged: Bool { get }
}
public struct Status: AnyStatus, Codable, Identifiable {
@ -25,6 +27,8 @@ public struct Status: AnyStatus, Codable, Identifiable {
public let reblogsCount: Int
public let favouritesCount: Int
public let card: Card?
public let favourited: Bool
public let reblogged: Bool
public static func placeholder() -> Status {
.init(id: UUID().uuidString,
@ -37,7 +41,9 @@ public struct Status: AnyStatus, Codable, Identifiable {
repliesCount: 0,
reblogsCount: 0,
favouritesCount: 0,
card: nil)
card: nil,
favourited: false,
reblogged: false)
}
public static func placeholders() -> [Status] {
@ -56,4 +62,6 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable {
public let reblogsCount: Int
public let favouritesCount: Int
public let card: Card?
public let favourited: Bool
public let reblogged: Bool
}

View file

@ -0,0 +1,22 @@
import Foundation
public enum Statuses: Endpoint {
case favourite(id: String)
case unfavourite(id: String)
public func path() -> String {
switch self {
case .favourite(let id):
return "statuses/\(id)/favourite"
case .unfavourite(let id):
return "statuses/\(id)/unfavourite"
}
}
public func queryItems() -> [URLQueryItem]? {
switch self {
default:
return nil
}
}
}

View file

@ -36,7 +36,7 @@ struct NotificationRowView: View {
}
}
if let status = notification.status {
StatusRowView(status: status, isEmbed: true)
StatusRowView(viewModel: .init(status: status, isEmbed: true))
} else {
Text(notification.account.acct)
.font(.callout)

View file

@ -15,7 +15,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
switch fetcher.statusesState {
case .loading:
ForEach(Status.placeholders()) { status in
StatusRowView(status: status)
StatusRowView(viewModel: .init(status: status, isEmbed: false))
.redacted(reason: .placeholder)
.shimmering()
Divider()
@ -25,7 +25,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
Text(error.localizedDescription)
case let .display(statuses, nextPageState):
ForEach(statuses) { status in
StatusRowView(status: status)
StatusRowView(viewModel: .init(status: status, isEmbed: false))
Divider()
.padding(.vertical, DS.Constants.dividerPadding)
}

View file

@ -1,34 +1,36 @@
import SwiftUI
import Models
import Routeur
import Network
struct StatusActionsView: View {
let status: Status
@ObservedObject var viewModel: StatusRowViewModel
@MainActor
enum Actions: CaseIterable {
case respond, boost, favourite, share
var iconName: String {
func iconName(viewModel: StatusRowViewModel) -> String {
switch self {
case .respond:
return "bubble.right"
case .boost:
return "arrow.left.arrow.right.circle"
case .favourite:
return "star"
return viewModel.isFavourited ? "star.fill" : "star"
case .share:
return "square.and.arrow.up"
}
}
func count(status: Status) -> Int? {
func count(viewModel: StatusRowViewModel) -> Int? {
switch self {
case .respond:
return status.repliesCount
return viewModel.status.repliesCount
case .favourite:
return status.favouritesCount
return viewModel.favouritesCount
case .boost:
return status.reblogsCount
return viewModel.status.reblogsCount
case .share:
return nil
}
@ -39,11 +41,11 @@ struct StatusActionsView: View {
HStack {
ForEach(Actions.allCases, id: \.self) { action in
Button {
handleAction(action: action)
} label: {
HStack(spacing: 2) {
Image(systemName: action.iconName)
if let count = action.count(status: status) {
Image(systemName: action.iconName(viewModel: viewModel))
if let count = action.count(viewModel: viewModel) {
Text("\(count)")
.font(.footnote)
}
@ -53,6 +55,22 @@ struct StatusActionsView: View {
Spacer()
}
}
}.tint(.gray)
}
.tint(.gray)
}
private func handleAction(action: Actions) {
Task {
switch action {
case .favourite:
if viewModel.isFavourited {
await viewModel.unFavourite()
} else {
await viewModel.favourite()
}
default:
break
}
}
}
}

View file

@ -8,30 +8,30 @@ public struct StatusRowView: View {
@Environment(\.redactionReasons) private var reasons
@EnvironmentObject private var client: Client
@EnvironmentObject private var routeurPath: RouterPath
@StateObject var viewModel: StatusRowViewModel
private let status: Status
private let isEmbed: Bool
public init(status: Status, isEmbed: Bool = false) {
self.status = status
self.isEmbed = isEmbed
public init(viewModel: StatusRowViewModel) {
_viewModel = StateObject(wrappedValue: viewModel)
}
public var body: some View {
VStack(alignment: .leading) {
reblogView
statusView
StatusActionsView(status: status)
StatusActionsView(viewModel: viewModel)
.padding(.vertical, 8)
}
.onAppear {
viewModel.client = client
}
}
@ViewBuilder
private var reblogView: some View {
if status.reblog != nil {
if viewModel.status.reblog != nil {
HStack(spacing: 2) {
Image(systemName:"arrow.left.arrow.right.circle")
Text("\(status.account.displayName) reblogged")
Text("\(viewModel.status.account.displayName) reblogged")
}
.font(.footnote)
.foregroundColor(.gray)
@ -41,8 +41,8 @@ public struct StatusRowView: View {
private var statusView: some View {
VStack(alignment: .leading, spacing: 8) {
if let status: AnyStatus = status.reblog ?? status {
if !isEmbed {
if let status: AnyStatus = viewModel.status.reblog ?? viewModel.status {
if !viewModel.isEmbed {
Button {
routeurPath.navigate(to: .accountDetailWithAccount(account: status.account))
} label: {

View file

@ -0,0 +1,52 @@
import SwiftUI
import Models
import Network
@MainActor
public class StatusRowViewModel: ObservableObject {
let status: Status
let isEmbed: Bool
@Published var favouritesCount: Int
@Published var isFavourited: Bool
var client: Client?
public init(status: Status, isEmbed: Bool) {
self.status = status
self.isEmbed = isEmbed
self.isFavourited = status.favourited
self.favouritesCount = status.favouritesCount
}
func favourite() async {
guard let client else { return }
isFavourited = true
favouritesCount += 1
do {
let status: Status = try await client.post(endpoint: Statuses.favourite(id: status.id))
updateFromStatus(status: status)
} catch {
isFavourited = false
favouritesCount -= 1
}
}
func unFavourite() async {
guard let client else { return }
isFavourited = false
favouritesCount -= 1
do {
let status: Status = try await client.post(endpoint: Statuses.unfavourite(id: status.id))
updateFromStatus(status: status)
} catch {
isFavourited = true
favouritesCount += 1
}
}
private func updateFromStatus(status: Status) {
isFavourited = status.favourited
favouritesCount = status.favouritesCount
}
}