Long press to boost / favorite from other accounts

This commit is contained in:
Justin Mazzocchi 2021-03-21 12:56:42 -07:00
parent bfcb999b25
commit f3040eaad5
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
25 changed files with 236 additions and 60 deletions

View file

@ -169,10 +169,12 @@ public extension IdentityDatabase {
.eraseToAnyPublisher()
}
func authenticatedIdentitiesPublisher() -> AnyPublisher<[Identity], Error> {
func authenticatedIdentitiesPublisher(excluding: Identity.Id) -> AnyPublisher<[Identity], Error> {
ValueObservation.tracking(
IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc))
.filter(IdentityRecord.Columns.authenticated == true && IdentityRecord.Columns.pending == false)
.filter(IdentityRecord.Columns.authenticated == true
&& IdentityRecord.Columns.pending == false
&& IdentityRecord.Columns.id != excluding)
.fetchAll)
.removeDuplicates()
.publisher(in: databaseWriter)

View file

@ -60,6 +60,7 @@
"account.unnotify" = "Turn off notifications";
"activity.open-in-default-browser" = "Open in default browser";
"add" = "Add";
"api-error.unable-to-fetch-remote-status" = "Unable to fetch remote status";
"apns-default-message" = "New notification";
"app-icon.brutalist" = "Brutalist";
"app-icon.rainbow-brutalist" = "Rainbow Brutalist";

View file

@ -9,3 +9,8 @@ public struct APIError: Error, Codable {
extension APIError: LocalizedError {
public var errorDescription: String? { error }
}
public extension APIError {
static let unableToFetchRemoteStatus =
Self(error: NSLocalizedString("api-error.unable-to-fetch-remote-status", comment: ""))
}

View file

@ -0,0 +1,16 @@
// Copyright © 2021 Metabolist. All rights reserved.
import MastodonAPI
import Secrets
extension MastodonAPIClient {
static func forIdentity(id: Identity.Id, environment: AppEnvironment) throws -> Self {
let secrets = Secrets(identityId: id, keychain: environment.keychain)
let client = Self(session: environment.session, instanceURL: try secrets.getInstanceURL())
client.accessToken = try secrets.getAccessToken()
return client
}
}

View file

@ -22,6 +22,7 @@ public struct AccountListService {
private let accountIdsForRelationshipsSubject = PassthroughSubject<Set<Account.Id>, Never>()
init(endpoint: AccountsEndpoint,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase,
titleComponents: [String]? = nil) {
@ -32,7 +33,9 @@ public struct AccountListService {
sections = contentDatabase.accountListPublisher(id: listId, configuration: endpoint.configuration)
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
accountIdsForRelationships = accountIdsForRelationshipsSubject.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
}

View file

@ -10,16 +10,21 @@ public struct AccountService {
public let account: Account
public let navigationService: NavigationService
private let environment: AppEnvironment
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
public init(account: Account,
identityProofs: [IdentityProof] = [],
featuredTags: [FeaturedTag] = [],
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.account = account
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
self.environment = environment
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
}
@ -136,6 +141,7 @@ public extension AccountService {
func followingService() -> AccountListService {
AccountListService(
endpoint: .accountsFollowing(id: account.id),
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase,
titleComponents: ["account.followed-by-%@", "@".appending(account.acct)])
@ -144,6 +150,7 @@ public extension AccountService {
func followersService() -> AccountListService {
AccountListService(
endpoint: .accountsFollowers(id: account.id),
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase,
titleComponents: ["account.%@-followers", "@".appending(account.acct)])

View file

@ -39,10 +39,6 @@ public extension AllIdentitiesService {
database.immediateMostRecentlyUsedIdentityIdPublisher()
}
func authenticatedIdentitiesPublisher() -> AnyPublisher<[Identity], Error> {
database.authenticatedIdentitiesPublisher()
}
func mostRecentAuthenticatedIdentity() throws -> Identity? {
try database.mostRecentAuthenticatedIdentity()
}

View file

@ -14,12 +14,17 @@ public struct ContextService {
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
init(id: Status.Id, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(id: Status.Id,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.id = id
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
sections = contentDatabase.contextPublisher(id: id)
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
}

View file

@ -12,9 +12,13 @@ public struct ConversationService {
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
init(conversation: Conversation, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(conversation: Conversation,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.conversation = conversation
self.navigationService = NavigationService(
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
self.mastodonAPIClient = mastodonAPIClient

View file

@ -15,14 +15,17 @@ public struct ConversationsService {
private let contentDatabase: ContentDatabase
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
init(mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(environment: AppEnvironment, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
sections = contentDatabase.conversationsPublisher()
.map { [.init(items: $0.map(CollectionItem.conversation))] }
.eraseToAnyPublisher()
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
}

View file

@ -12,10 +12,12 @@ public struct ExploreService {
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
init(mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(environment: AppEnvironment, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
}

View file

@ -39,6 +39,7 @@ public struct IdentityService {
keychain: environment.keychain)
navigationService = NavigationService(
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
@ -98,6 +99,10 @@ public extension IdentityService {
identityDatabase.recentIdentitiesPublisher(excluding: id)
}
func otherAuthenticatedIdentitiesPublisher() -> AnyPublisher<[Identity], Error> {
identityDatabase.authenticatedIdentitiesPublisher(excluding: id)
}
func refreshLists() -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(ListsEndpoint.lists)
.flatMap(contentDatabase.setLists(_:))
@ -249,6 +254,7 @@ public extension IdentityService {
.map { _ in
NotificationService(
notification: notification,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
@ -259,27 +265,31 @@ public extension IdentityService {
func service(accountList: AccountsEndpoint, titleComponents: [String]? = nil) -> AccountListService {
AccountListService(
endpoint: accountList,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase,
titleComponents: titleComponents)
}
func exploreService() -> ExploreService {
ExploreService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
ExploreService(environment: environment, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
}
func searchService() -> SearchService {
SearchService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
SearchService(environment: environment, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
}
func notificationsService(excludeTypes: Set<MastodonNotification.NotificationType>) -> NotificationsService {
NotificationsService(excludeTypes: excludeTypes,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func conversationsService() -> ConversationsService {
ConversationsService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
ConversationsService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func domainBlocksService() -> DomainBlocksService {

View file

@ -17,11 +17,16 @@ public enum Navigation {
}
public struct NavigationService {
private let environment: AppEnvironment
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
private let status: Status?
init(mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase, status: Status? = nil) {
init(environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase,
status: Status? = nil) {
self.environment = environment
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
self.status = status
@ -35,6 +40,7 @@ public extension NavigationService {
.collection(
TimelineService(
timeline: .tag(tag),
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)))
.eraseToAnyPublisher()
@ -52,26 +58,38 @@ public extension NavigationService {
}
func contextService(id: Status.Id) -> ContextService {
ContextService(id: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
ContextService(id: id, environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func profileService(id: Account.Id) -> ProfileService {
ProfileService(id: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
ProfileService(id: id,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func profileService(account: Account, relationship: Relationship? = nil) -> ProfileService {
ProfileService(account: account,
relationship: relationship,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func statusService(status: Status) -> StatusService {
StatusService(status: status, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
StatusService(environment: environment,
status: status,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func accountService(account: Account) -> AccountService {
AccountService(account: account, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
AccountService(account: account,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func loadMoreService(loadMore: LoadMore) -> LoadMoreService {
@ -81,6 +99,7 @@ public extension NavigationService {
func notificationService(notification: MastodonNotification) -> NotificationService {
NotificationService(
notification: notification,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
@ -88,12 +107,16 @@ public extension NavigationService {
func conversationService(conversation: Conversation) -> ConversationService {
ConversationService(
conversation: conversation,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func timelineService(timeline: Timeline) -> TimelineService {
TimelineService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
TimelineService(timeline: timeline,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
}
@ -132,6 +155,7 @@ private extension NavigationService {
return .collection(
TimelineService(
timeline: .tag(tag.name),
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase))
} else if let account = results.accounts.first {

View file

@ -12,9 +12,13 @@ public struct NotificationService {
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
init(notification: MastodonNotification, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(notification: MastodonNotification,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.notification = notification
self.navigationService = NavigationService(
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase,
status: nil)

View file

@ -18,6 +18,7 @@ public struct NotificationsService {
private let nextPageMaxIdSubject: CurrentValueSubject<String, Never>
init(excludeTypes: Set<MastodonNotification.NotificationType>,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.excludeTypes = excludeTypes
@ -37,7 +38,9 @@ public struct NotificationsService {
})
.eraseToAnyPublisher()
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
}

View file

@ -10,25 +10,32 @@ public struct ProfileService {
public let profilePublisher: AnyPublisher<Profile, Error>
private let id: Account.Id
private let environment: AppEnvironment
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
init(account: Account,
relationship: Relationship?,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.init(
id: account.id,
account: account,
relationship: relationship,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
init(id: Account.Id, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(id: Account.Id,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.init(id: id,
account: nil,
relationship: nil,
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
@ -37,9 +44,11 @@ public struct ProfileService {
id: Account.Id,
account: Account?,
relationship: Relationship?,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.id = id
self.environment = environment
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
@ -60,6 +69,7 @@ public extension ProfileService {
func timelineService(profileCollection: ProfileCollection) -> TimelineService {
TimelineService(
timeline: .profile(accountId: id, profileCollection: profileCollection),
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}

View file

@ -16,11 +16,13 @@ public struct SearchService {
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
private let resultsSubject = PassthroughSubject<(Results, Search), Error>()
init(mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(environment: AppEnvironment, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
sections = resultsSubject.scan((.empty, nil)) {
let (results, search) = $1

View file

@ -9,15 +9,21 @@ import MastodonAPI
public struct StatusService {
public let status: Status
public let navigationService: NavigationService
private let environment: AppEnvironment
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
init(status: Status, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(environment: AppEnvironment,
status: Status,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.status = status
self.navigationService = NavigationService(
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase,
status: status.displayStatus)
self.environment = environment
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
}
@ -32,20 +38,28 @@ public extension StatusService {
contentDatabase.toggleShowAttachments(id: status.displayStatus.id)
}
func toggleReblogged() -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(status.displayStatus.reblogged
? StatusEndpoint.unreblog(id: status.displayStatus.id)
: StatusEndpoint.reblog(id: status.displayStatus.id))
.flatMap(contentDatabase.insert(status:))
.eraseToAnyPublisher()
func toggleReblogged(identityId: Identity.Id?) -> AnyPublisher<Never, Error> {
if let identityId = identityId {
return request(identityId: identityId, endpointClosure: StatusEndpoint.reblog(id:))
} else {
return mastodonAPIClient.request(status.displayStatus.reblogged
? StatusEndpoint.unreblog(id: status.displayStatus.id)
: StatusEndpoint.reblog(id: status.displayStatus.id))
.flatMap(contentDatabase.insert(status:))
.eraseToAnyPublisher()
}
}
func toggleFavorited() -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(status.displayStatus.favourited
? StatusEndpoint.unfavourite(id: status.displayStatus.id)
: StatusEndpoint.favourite(id: status.displayStatus.id))
.flatMap(contentDatabase.insert(status:))
.eraseToAnyPublisher()
func toggleFavorited(identityId: Identity.Id?) -> AnyPublisher<Never, Error> {
if let identityId = identityId {
return request(identityId: identityId, endpointClosure: StatusEndpoint.favourite(id:))
} else {
return mastodonAPIClient.request(status.displayStatus.favourited
? StatusEndpoint.unfavourite(id: status.displayStatus.id)
: StatusEndpoint.favourite(id: status.displayStatus.id))
.flatMap(contentDatabase.insert(status:))
.eraseToAnyPublisher()
}
}
func toggleBookmarked() -> AnyPublisher<Never, Error> {
@ -84,7 +98,8 @@ public extension StatusService {
if let inReplyToId = status.displayStatus.inReplyToId {
inReplyToPublisher = mastodonAPIClient.request(StatusEndpoint.status(id: inReplyToId))
.map {
Self(status: $0,
Self(environment: environment,
status: $0,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase) as Self?
}
@ -103,6 +118,7 @@ public extension StatusService {
func rebloggedByService() -> AccountListService {
AccountListService(
endpoint: .rebloggedBy(id: status.id),
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
@ -110,6 +126,7 @@ public extension StatusService {
func favoritedByService() -> AccountListService {
AccountListService(
endpoint: .favouritedBy(id: status.id),
environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
@ -130,3 +147,28 @@ public extension StatusService {
.eraseToAnyPublisher()
}
}
private extension StatusService {
func request(identityId: Identity.Id,
endpointClosure: @escaping (Status.Id) -> StatusEndpoint) -> AnyPublisher<Never, Error> {
let client: MastodonAPIClient
do {
client = try MastodonAPIClient.forIdentity(id: identityId, environment: environment)
} catch {
return Fail(error: error).eraseToAnyPublisher()
}
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 }
return id
}
.flatMap { client.request(endpointClosure($0)) }
.flatMap { _ in mastodonAPIClient.request(StatusEndpoint.status(id: status.displayStatus.id)) }
.flatMap(contentDatabase.insert(status:))
.eraseToAnyPublisher()
}
}

View file

@ -21,12 +21,17 @@ public struct TimelineService {
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
private let accountIdsForRelationshipsSubject = PassthroughSubject<Set<Account.Id>, Never>()
init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
init(timeline: Timeline,
environment: AppEnvironment,
mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) {
self.timeline = timeline
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
sections = contentDatabase.timelinePublisher(timeline)
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
navigationService = NavigationService(environment: environment,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
accountIdsForRelationships = accountIdsForRelationshipsSubject.eraseToAnyPublisher()

View file

@ -81,6 +81,7 @@ public extension ReportViewModel {
static let preview = ReportViewModel(
accountService: AccountService(
account: .preview,
environment: environment,
mastodonAPIClient: .preview,
contentDatabase: .preview),
identityContext: .preview)
@ -90,6 +91,7 @@ public extension MuteViewModel {
static let preview = MuteViewModel(
accountService: AccountService(
account: .preview,
environment: environment,
mastodonAPIClient: .preview,
contentDatabase: .preview),
identityContext: .preview)

View file

@ -6,6 +6,7 @@ import ServiceLayer
public final class IdentityContext: ObservableObject {
@Published private(set) public var identity: Identity
@Published private(set) public var authenticatedOtherIdentities = [Identity]()
@Published public var appPreferences: AppPreferences
let service: IdentityService
@ -19,6 +20,9 @@ public final class IdentityContext: ObservableObject {
DispatchQueue.main.async {
publisher.dropFirst().assign(to: &self.$identity)
service.otherAuthenticatedIdentitiesPublisher()
.replaceError(with: [])
.assign(to: &self.$authenticatedOtherIdentities)
}
}
}

View file

@ -9,7 +9,6 @@ public final class NewStatusViewModel: ObservableObject {
@Published public var visibility: Status.Visibility
@Published public private(set) var compositionViewModels = [CompositionViewModel]()
@Published public private(set) var identityContext: IdentityContext
@Published public private(set) var authenticatedIdentities = [Identity]()
@Published public var canPost = false
@Published public var alertItem: AlertItem?
@Published public private(set) var postingState = PostingState.composing
@ -87,14 +86,6 @@ public final class NewStatusViewModel: ObservableObject {
}
compositionViewModels = [compositionViewModel]
allIdentitiesService.authenticatedIdentitiesPublisher()
.assignErrorsToAlertItem(to: \.alertItem, on: self)
.combineLatest($identityContext)
.map { authenticatedIdentities, currentIdentity in
authenticatedIdentities.filter { $0.id != currentIdentity.identity.id }
}
.assign(to: &$authenticatedIdentities)
$compositionViewModels.flatMap { Publishers.MergeMany($0.map(\.$isPostable)) }
.receive(on: DispatchQueue.main) // hack to punt to next run loop, consider refactoring
.compactMap { [weak self] _ in self?.compositionViewModels.allSatisfy(\.isPostable) }

View file

@ -255,16 +255,16 @@ public extension StatusViewModel {
.eraseToAnyPublisher())
}
func toggleReblogged() {
func toggleReblogged(identityId: Identity.Id? = nil) {
eventsSubject.send(
statusService.toggleReblogged()
statusService.toggleReblogged(identityId: identityId)
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}
func toggleFavorited() {
func toggleFavorited(identityId: Identity.Id? = nil) {
eventsSubject.send(
statusService.toggleFavorited()
statusService.toggleFavorited(identityId: identityId)
.map { _ in .ignorableOutput }
.eraseToAnyPublisher())
}

View file

@ -74,7 +74,8 @@ private extension CompositionView {
avatarImageView.isUserInteractionEnabled = true
changeIdentityButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
changeIdentityButton.showsMenuAsPrimaryAction = true
changeIdentityButton.menu = changeIdentityMenu(identities: parentViewModel.authenticatedIdentities)
changeIdentityButton.menu =
changeIdentityMenu(identities: parentViewModel.identityContext.authenticatedOtherIdentities)
let stackView = UIStackView()
@ -205,7 +206,7 @@ private extension CompositionView {
}
.store(in: &cancellables)
parentViewModel.$authenticatedIdentities
parentViewModel.identityContext.$authenticatedOtherIdentities
.sink { [weak self] in self?.changeIdentityButton.menu = self?.changeIdentityMenu(identities: $0) }
.store(in: &cancellables)

View file

@ -616,9 +616,11 @@ private extension StatusView {
setReblogButtonColor(reblogged: viewModel.reblogged)
reblogButton.isEnabled = viewModel.canBeReblogged && isAuthenticated
reblogButton.menu = authenticatedIdentitiesMenu { viewModel.toggleReblogged(identityId: $0.id) }
setFavoriteButtonColor(favorited: viewModel.favorited)
favoriteButton.isEnabled = isAuthenticated
favoriteButton.menu = authenticatedIdentitiesMenu { viewModel.toggleFavorited(identityId: $0.id) }
shareButton.tag = viewModel.sharingURL?.hashValue ?? 0
@ -1127,6 +1129,38 @@ private extension StatusView {
return actions
}
func authenticatedIdentitiesMenu(action: @escaping (Identity) -> Void) -> UIMenu {
let imageTransformer = SDImageRoundCornerTransformer(
radius: .greatestFiniteMagnitude,
corners: .allCorners,
borderWidth: 0,
borderColor: nil)
return UIMenu(children: statusConfiguration.viewModel
.identityContext
.authenticatedOtherIdentities.map { identity in
UIDeferredMenuElement { completion in
let menuItemAction = UIAction(title: identity.handle) { _ in
action(identity)
}
if let image = identity.image {
SDWebImageManager.shared.loadImage(
with: image,
options: [.transformAnimatedImage],
context: [.imageTransformer: imageTransformer],
progress: nil) { (image, _, _, _, _, _) in
menuItemAction.image = image
completion([menuItemAction])
}
} else {
completion([menuItemAction])
}
}
})
}
}
private extension UIButton {