mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Favoriting
This commit is contained in:
parent
8ca6f43610
commit
f340569295
6 changed files with 81 additions and 10 deletions
|
@ -7,6 +7,8 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
D002A0FB24F3362100E8AEBB /* StatusEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D002A0FA24F3362100E8AEBB /* StatusEndpoint.swift */; };
|
||||
D002A0FC24F3362100E8AEBB /* StatusEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D002A0FA24F3362100E8AEBB /* StatusEndpoint.swift */; };
|
||||
D0091B6824DC10B30040E8D2 /* PostingReadingPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0091B6724DC10B30040E8D2 /* PostingReadingPreferencesView.swift */; };
|
||||
D0091B6924DC10B30040E8D2 /* PostingReadingPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0091B6724DC10B30040E8D2 /* PostingReadingPreferencesView.swift */; };
|
||||
D0091B6B24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0091B6A24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift */; };
|
||||
|
@ -277,6 +279,7 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
D002A0FA24F3362100E8AEBB /* StatusEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusEndpoint.swift; sourceTree = "<group>"; };
|
||||
D0091B6724DC10B30040E8D2 /* PostingReadingPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostingReadingPreferencesView.swift; sourceTree = "<group>"; };
|
||||
D0091B6A24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostingReadingPreferencesViewModel.swift; sourceTree = "<group>"; };
|
||||
D0091B6D24DD68090040E8D2 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||
|
@ -510,6 +513,7 @@
|
|||
D019E6E024DF72E700697C7D /* InstanceEndpoint.swift */,
|
||||
D019E6DD24DF72E700697C7D /* PreferencesEndpoint.swift */,
|
||||
D0EC8DED24E2704D00A08489 /* PushSubscriptionEndpoint.swift */,
|
||||
D002A0FA24F3362100E8AEBB /* StatusEndpoint.swift */,
|
||||
D05494F624EA49F7008B00A5 /* TimelinesEndpoint.swift */,
|
||||
);
|
||||
path = Endpoints;
|
||||
|
@ -1082,6 +1086,7 @@
|
|||
D019E6ED24DF7BF300697C7D /* IdentityDatabase.swift in Sources */,
|
||||
D081A40524D0F1A8001B016E /* String+Extensions.swift in Sources */,
|
||||
D019E6E724DF72E700697C7D /* AccessTokenEndpoint.swift in Sources */,
|
||||
D002A0FB24F3362100E8AEBB /* StatusEndpoint.swift in Sources */,
|
||||
D02D870524EFBB79004583CC /* String+UIKitExtensions.swift in Sources */,
|
||||
D0BEC93824C9632800E864C4 /* RootViewModel.swift in Sources */,
|
||||
D02D86EC24EF9CA3004583CC /* CodingUserInfoKey+Extensions.swift in Sources */,
|
||||
|
@ -1247,6 +1252,7 @@
|
|||
D04FD73D24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
|
||||
D0DC175C24D0154F00A75C65 /* MastodonAPI.swift in Sources */,
|
||||
D020F51524ECBA60005AB084 /* LazyView.swift in Sources */,
|
||||
D002A0FC24F3362100E8AEBB /* StatusEndpoint.swift in Sources */,
|
||||
D0ED1BD224CF779B00B4899C /* MastodonTarget.swift in Sources */,
|
||||
D0EC8DC624DF842700A08489 /* KeychainService.swift in Sources */,
|
||||
D020F50C24EC9F1D005AB084 /* ContextService.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
enum StatusEndpoint {
|
||||
case favourite(id: String)
|
||||
case unfavourite(id: String)
|
||||
}
|
||||
|
||||
extension StatusEndpoint: MastodonEndpoint {
|
||||
typealias ResultType = Status
|
||||
|
||||
var context: [String] {
|
||||
defaultContext + ["statuses"]
|
||||
}
|
||||
|
||||
var pathComponentsInContext: [String] {
|
||||
switch self {
|
||||
case let .favourite(id):
|
||||
return [id, "favourite"]
|
||||
case let .unfavourite(id):
|
||||
return [id, "unfavourite"]
|
||||
}
|
||||
}
|
||||
|
||||
var method: HTTPMethod {
|
||||
switch self {
|
||||
case .favourite, .unfavourite:
|
||||
return .post
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,3 +14,14 @@ struct StatusService {
|
|||
self.contentDatabase = contentDatabase
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusService {
|
||||
func toggleFavorited() -> AnyPublisher<Void, Error> {
|
||||
networkClient.request(status.favourited
|
||||
? StatusEndpoint.unfavourite(id: status.id)
|
||||
: StatusEndpoint.favourite(id: status.id))
|
||||
.map { ([$0], nil) }
|
||||
.flatMap(contentDatabase.insert(statuses:collection:))
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,10 @@ struct StatusViewModel {
|
|||
var isReplyInContext = false
|
||||
var hasReplyFollowing = false
|
||||
var sensitiveContentToggled = false
|
||||
let events: AnyPublisher<AnyPublisher<Void, Error>, Never>
|
||||
|
||||
private let statusService: StatusService
|
||||
private let eventsInput = PassthroughSubject<AnyPublisher<Void, Error>, Never>()
|
||||
|
||||
init(statusService: StatusService) {
|
||||
self.statusService = statusService
|
||||
|
@ -38,6 +40,7 @@ struct StatusViewModel {
|
|||
rebloggedByDisplayNameEmoji = statusService.status.account.emojis
|
||||
pollOptionTitles = statusService.status.displayStatus.poll?.options.map { $0.title } ?? []
|
||||
pollEmoji = statusService.status.displayStatus.poll?.emojis ?? []
|
||||
events = eventsInput.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,9 +77,9 @@ extension StatusViewModel {
|
|||
|
||||
var favoritesCount: Int { statusService.status.displayStatus.favouritesCount }
|
||||
|
||||
var reblogged: Bool { statusService.status.displayStatus.reblogged ?? false }
|
||||
var reblogged: Bool { statusService.status.displayStatus.reblogged }
|
||||
|
||||
var favorited: Bool { statusService.status.displayStatus.favourited ?? false }
|
||||
var favorited: Bool { statusService.status.displayStatus.favourited }
|
||||
|
||||
var sensitive: Bool { statusService.status.displayStatus.sensitive }
|
||||
|
||||
|
@ -98,6 +101,10 @@ extension StatusViewModel {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func toggleFavorited() {
|
||||
eventsInput.send(statusService.toggleFavorited())
|
||||
}
|
||||
}
|
||||
|
||||
private extension StatusViewModel {
|
||||
|
|
|
@ -9,13 +9,17 @@ class StatusesViewModel: ObservableObject {
|
|||
@Published private(set) var loading = false
|
||||
private(set) var maintainScrollPositionOfStatusID: String?
|
||||
private let statusListService: StatusListService
|
||||
private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(statusListService: StatusListService) {
|
||||
self.statusListService = statusListService
|
||||
|
||||
statusListService.statusSections
|
||||
.handleEvents(receiveOutput: determineIfScrollPositionShouldBeMaintained(newStatusSections:))
|
||||
.handleEvents(receiveOutput: { [weak self] in
|
||||
self?.determineIfScrollPositionShouldBeMaintained(newStatusSections: $0)
|
||||
self?.cleanViewModelCache(newStatusSections: $0)
|
||||
})
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.assign(to: &$statusSections)
|
||||
}
|
||||
|
@ -35,16 +39,23 @@ extension StatusesViewModel {
|
|||
}
|
||||
|
||||
func statusViewModel(status: Status) -> StatusViewModel {
|
||||
var statusViewModel = Self.viewModelCache[status]
|
||||
?? StatusViewModel(statusService: statusListService.statusService(status: status))
|
||||
var statusViewModel: StatusViewModel
|
||||
|
||||
if let cachedViewModel = statusViewModelCache[status]?.0 {
|
||||
statusViewModel = cachedViewModel
|
||||
} else {
|
||||
statusViewModel = StatusViewModel(statusService: statusListService.statusService(status: status))
|
||||
statusViewModelCache[status] = (statusViewModel, statusViewModel.events
|
||||
.flatMap { $0 }
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.sink {})
|
||||
}
|
||||
|
||||
statusViewModel.isContextParent = status == contextParent
|
||||
statusViewModel.isPinned = statusListService.isPinned(status: status)
|
||||
statusViewModel.isReplyInContext = statusListService.isReplyInContext(status: status)
|
||||
statusViewModel.hasReplyFollowing = statusListService.hasReplyFollowing(status: status)
|
||||
|
||||
Self.viewModelCache[status] = statusViewModel
|
||||
|
||||
return statusViewModel
|
||||
}
|
||||
|
||||
|
@ -54,8 +65,6 @@ extension StatusesViewModel {
|
|||
}
|
||||
|
||||
private extension StatusesViewModel {
|
||||
static var viewModelCache = [Status: StatusViewModel]()
|
||||
|
||||
func determineIfScrollPositionShouldBeMaintained(newStatusSections: [[Status]]) {
|
||||
maintainScrollPositionOfStatusID = nil // clear old value
|
||||
|
||||
|
@ -64,4 +73,10 @@ private extension StatusesViewModel {
|
|||
maintainScrollPositionOfStatusID = contextParent.id
|
||||
}
|
||||
}
|
||||
|
||||
func cleanViewModelCache(newStatusSections: [[Status]]) {
|
||||
let newStatuses = Set(newStatusSections.reduce([], +))
|
||||
|
||||
statusViewModelCache = statusViewModelCache.filter { newStatuses.contains($0.key) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,7 +274,7 @@ extension StatusTableViewCell {
|
|||
}
|
||||
|
||||
@IBAction func favoriteButtonTapped(_ sender: UIButton) {
|
||||
|
||||
viewModel.toggleFavorited()
|
||||
}
|
||||
|
||||
@IBAction func actionsButtonTapped(_ sender: Any) {
|
||||
|
|
Loading…
Reference in a new issue