mirror of
https://github.com/metabolist/metatext.git
synced 2024-12-22 21:46:28 +00:00
Long press to boost / favorite from other accounts
This commit is contained in:
parent
bfcb999b25
commit
f3040eaad5
25 changed files with 236 additions and 60 deletions
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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: ""))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)])
|
||||
|
|
|
@ -39,10 +39,6 @@ public extension AllIdentitiesService {
|
|||
database.immediateMostRecentlyUsedIdentityIdPublisher()
|
||||
}
|
||||
|
||||
func authenticatedIdentitiesPublisher() -> AnyPublisher<[Identity], Error> {
|
||||
database.authenticatedIdentitiesPublisher()
|
||||
}
|
||||
|
||||
func mostRecentAuthenticatedIdentity() throws -> Identity? {
|
||||
try database.mostRecentAuthenticatedIdentity()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue