mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-01-03 04:48:50 +00:00
Favourite / Unfavourite status
This commit is contained in:
parent
60a963441c
commit
024d325291
8 changed files with 130 additions and 28 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
22
Packages/Network/Sources/Network/Endpoint/Statuses.swift
Normal file
22
Packages/Network/Sources/Network/Endpoint/Statuses.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
52
Packages/Status/Sources/Status/Row/StatusRowViewModel.swift
Normal file
52
Packages/Status/Sources/Status/Row/StatusRowViewModel.swift
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue