diff --git a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift index f9d443c..502cbfb 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift @@ -146,11 +146,24 @@ public extension StatusService { .flatMap { contentDatabase.update(id: status.displayStatus.id, poll: $0) } .eraseToAnyPublisher() } + + func asIdentity(id: Identity.Id) -> AnyPublisher { + fetchAs(identityId: id).tryMap { + Self(environment: environment, + status: $0, + mastodonAPIClient: try MastodonAPIClient.forIdentity(id: id, environment: environment), + contentDatabase: try ContentDatabase( + id: id, + useHomeTimelineLastReadId: true, + inMemory: environment.inMemoryContent, + appGroup: AppEnvironment.appGroup, + keychain: environment.keychain)) } + .eraseToAnyPublisher() + } } private extension StatusService { - func request(identityId: Identity.Id, - endpointClosure: @escaping (Status.Id) -> StatusEndpoint) -> AnyPublisher { + func fetchAs(identityId: Identity.Id) -> AnyPublisher { let client: MastodonAPIClient do { @@ -162,11 +175,25 @@ private extension StatusService { return client .request(ResultsEndpoint.search(.init(query: status.displayStatus.uri, resolve: true, limit: 1))) .tryMap { - guard let id = $0.statuses.first?.id else { throw APIError.unableToFetchRemoteStatus } + guard let status = $0.statuses.first else { throw APIError.unableToFetchRemoteStatus } - return id + return status } - .flatMap { client.request(endpointClosure($0)) } + .eraseToAnyPublisher() + } + + func request(identityId: Identity.Id, + endpointClosure: @escaping (Status.Id) -> StatusEndpoint) -> AnyPublisher { + let client: MastodonAPIClient + + do { + client = try MastodonAPIClient.forIdentity(id: identityId, environment: environment) + } catch { + return Fail(error: error).eraseToAnyPublisher() + } + + return fetchAs(identityId: identityId) + .flatMap { client.request(endpointClosure($0.id)) } .flatMap { _ in mastodonAPIClient.request(StatusEndpoint.status(id: status.displayStatus.id)) } .flatMap(contentDatabase.insert(status:)) .eraseToAnyPublisher() diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index 2e998ea..9606f64 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -533,8 +533,11 @@ private extension TableViewController { handle(navigation: navigation) case let .attachment(attachmentViewModel, statusViewModel): present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel) - case let .compose(inReplyToViewModel, redraft, directMessageTo): - compose(inReplyToViewModel: inReplyToViewModel, redraft: redraft, directMessageTo: directMessageTo) + case let .compose(identity, inReplyToViewModel, redraft, directMessageTo): + compose(identity: identity, + inReplyToViewModel: inReplyToViewModel, + redraft: redraft, + directMessageTo: directMessageTo) case let .confirmDelete(statusViewModel, redraft): confirmDelete(statusViewModel: statusViewModel, redraft: redraft) case let .confirmUnfollow(accountViewModel): @@ -613,9 +616,13 @@ private extension TableViewController { } } - func compose(inReplyToViewModel: StatusViewModel?, redraft: Status?, directMessageTo: AccountViewModel?) { + func compose(identity: Identity?, + inReplyToViewModel: StatusViewModel?, + redraft: Status?, + directMessageTo: AccountViewModel?) { rootViewModel?.navigationViewModel?.presentedNewStatusViewModel = rootViewModel?.newStatusViewModel( identityContext: viewModel.identityContext, + identity: identity, inReplyTo: inReplyToViewModel, redraft: redraft, directMessageTo: directMessageTo) diff --git a/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift index 3aec958..c937a48 100644 --- a/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift +++ b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift @@ -9,7 +9,10 @@ public enum CollectionItemEvent { case refresh case navigation(Navigation) case attachment(AttachmentViewModel, StatusViewModel) - case compose(inReplyTo: StatusViewModel? = nil, redraft: Status? = nil, directMessageTo: AccountViewModel? = nil) + case compose(identity: Identity? = nil, + inReplyTo: StatusViewModel? = nil, + redraft: Status? = nil, + directMessageTo: AccountViewModel? = nil) case confirmDelete(StatusViewModel, redraft: Bool) case confirmUnfollow(AccountViewModel) case confirmHideReblogs(AccountViewModel) diff --git a/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift b/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift index 66e0a50..4dfa4d7 100644 --- a/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift @@ -26,6 +26,7 @@ public final class NewStatusViewModel: ObservableObject { public init(allIdentitiesService: AllIdentitiesService, identityContext: IdentityContext, environment: AppEnvironment, + identity: Identity?, inReplyTo: StatusViewModel?, redraft: Status?, directMessageTo: AccountViewModel?, @@ -37,7 +38,7 @@ public final class NewStatusViewModel: ObservableObject { events = eventsSubject.eraseToAnyPublisher() visibility = redraft?.visibility ?? inReplyTo?.visibility - ?? identityContext.identity.preferences.postingDefaultVisibility + ?? (identity ?? identityContext.identity).preferences.postingDefaultVisibility if let inReplyTo = inReplyTo { switch inReplyTo.visibility { @@ -74,7 +75,7 @@ public final class NewStatusViewModel: ObservableObject { } mentions.formUnion(inReplyTo.mentions.map(\.acct) - .filter { $0 != identityContext.identity.account?.username } + .filter { $0 != (identity ?? identityContext.identity).account?.username } .map("@".appending)) compositionViewModel.text = mentions.joined(separator: " ").appending(" ") @@ -95,6 +96,10 @@ public final class NewStatusViewModel: ObservableObject { compositionEventsSubject .sink { [weak self] in self?.handle(event: $0) } .store(in: &cancellables) + + if let identity = identity { + setIdentity(identity) + } } } diff --git a/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift b/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift index d2bc3a0..cd31fc6 100644 --- a/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift @@ -65,6 +65,7 @@ public extension RootViewModel { func newStatusViewModel( identityContext: IdentityContext, + identity: Identity? = nil, inReplyTo: StatusViewModel? = nil, redraft: Status? = nil, directMessageTo: AccountViewModel? = nil) -> NewStatusViewModel { @@ -72,6 +73,7 @@ public extension RootViewModel { allIdentitiesService: allIdentitiesService, identityContext: identityContext, environment: environment, + identity: identity, inReplyTo: inReplyTo, redraft: redraft, directMessageTo: directMessageTo, diff --git a/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift b/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift index b2ca759..33c3b1d 100644 --- a/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift @@ -37,6 +37,7 @@ public extension ShareExtensionNavigationViewModel { allIdentitiesService: allIdentitiesService, identityContext: identityContext, environment: environment, + identity: nil, inReplyTo: nil, redraft: nil, directMessageTo: nil, diff --git a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift index 162c7c9..a6a45f3 100644 --- a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift @@ -242,17 +242,33 @@ public extension StatusViewModel { .eraseToAnyPublisher()) } - func reply() { - let replyViewModel = Self(statusService: statusService, - identityContext: identityContext, - eventsSubject: .init()) + func reply(identity: Identity? = nil) { + if let identity = identity { + let identityContext = self.identityContext + let configuration = self.configuration.reply() - replyViewModel.configuration = configuration.reply() + eventsSubject.send(statusService.asIdentity(id: identity.id).map { + let replyViewModel = Self(statusService: $0, + identityContext: identityContext, + eventsSubject: .init()) - eventsSubject.send( - Just(.compose(inReplyTo: replyViewModel)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher()) + replyViewModel.configuration = configuration + + return CollectionItemEvent.compose(identity: identity, inReplyTo: replyViewModel) + } + .eraseToAnyPublisher()) + } else { + let replyViewModel = Self(statusService: statusService, + identityContext: identityContext, + eventsSubject: .init()) + + replyViewModel.configuration = configuration.reply() + + eventsSubject.send( + Just(.compose(inReplyTo: replyViewModel)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher()) + } } func toggleReblogged(identityId: Identity.Id? = nil) { diff --git a/Views/UIKit/Content Views/StatusView.swift b/Views/UIKit/Content Views/StatusView.swift index 036c685..19b1c32 100644 --- a/Views/UIKit/Content Views/StatusView.swift +++ b/Views/UIKit/Content Views/StatusView.swift @@ -605,6 +605,7 @@ private extension StatusView { replyButton.setCountTitle(count: viewModel.repliesCount, isContextParent: isContextParent) replyButton.isEnabled = isAuthenticated + replyButton.menu = authenticatedIdentitiesMenu { viewModel.reply(identity: $0) } if viewModel.identityContext.appPreferences.showReblogAndFavoriteCounts || isContextParent { reblogButton.setCountTitle(count: viewModel.reblogsCount, isContextParent: isContextParent)