Favoriting

This commit is contained in:
Justin Mazzocchi 2020-08-23 19:50:54 -07:00
parent 8ca6f43610
commit f340569295
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
6 changed files with 81 additions and 10 deletions

View file

@ -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 */,

View file

@ -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
}
}
}

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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) }
}
}

View file

@ -274,7 +274,7 @@ extension StatusTableViewCell {
}
@IBAction func favoriteButtonTapped(_ sender: UIButton) {
viewModel.toggleFavorited()
}
@IBAction func actionsButtonTapped(_ sender: Any) {