Begin completely replacing CoreDataStack entities by Mastodon.Entity
This commit is contained in:
parent
c80a590306
commit
ec5ace4601
@ -6,8 +6,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreDataStack
|
|
||||||
import class CoreDataStack.Notification
|
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
@ -15,7 +13,7 @@ import MastodonLocalization
|
|||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToUserFollowAction(
|
static func responseToUserFollowAction(
|
||||||
dependency: NeedsDependency & AuthContextProvider,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
user: ManagedObjectRecord<MastodonUser>
|
user: Mastodon.Entity.Account
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
@ -31,41 +29,33 @@ extension DataSourceFacade {
|
|||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToUserFollowRequestAction(
|
static func responseToUserFollowRequestAction(
|
||||||
dependency: NeedsDependency & AuthContextProvider,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
notification: ManagedObjectRecord<Notification>,
|
notification: Mastodon.Entity.Notification,
|
||||||
query: Mastodon.API.Account.FollowRequestQuery
|
query: Mastodon.API.Account.FollowRequestQuery
|
||||||
) async throws {
|
) async throws {
|
||||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||||
await selectionFeedbackGenerator.selectionChanged()
|
await selectionFeedbackGenerator.selectionChanged()
|
||||||
|
|
||||||
let managedObjectContext = dependency.context.managedObjectContext
|
let managedObjectContext = dependency.context.managedObjectContext
|
||||||
let _userID: MastodonUser.ID? = try await managedObjectContext.perform {
|
let userID = notification.account.id
|
||||||
guard let notification = notification.object(in: managedObjectContext) else { return nil }
|
|
||||||
return notification.account.id
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let userID = _userID else {
|
// let state: MastodonFollowRequestState = try await managedObjectContext.perform {
|
||||||
assertionFailure()
|
// guard let notification = notification.object(in: managedObjectContext) else { return .init(state: .none) }
|
||||||
throw APIService.APIError.implicit(.badRequest)
|
// return notification.followRequestState
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// guard state.state == .none else {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
let state: MastodonFollowRequestState = try await managedObjectContext.perform {
|
// try? await managedObjectContext.performChanges {
|
||||||
guard let notification = notification.object(in: managedObjectContext) else { return .init(state: .none) }
|
// guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||||
return notification.followRequestState
|
// switch query {
|
||||||
}
|
// case .accept:
|
||||||
|
// notification.transientFollowRequestState = .init(state: .isAccepting)
|
||||||
guard state.state == .none else {
|
// case .reject:
|
||||||
return
|
// notification.transientFollowRequestState = .init(state: .isRejecting)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
try? await managedObjectContext.performChanges {
|
|
||||||
guard let notification = notification.object(in: managedObjectContext) else { return }
|
|
||||||
switch query {
|
|
||||||
case .accept:
|
|
||||||
notification.transientFollowRequestState = .init(state: .isAccepting)
|
|
||||||
case .reject:
|
|
||||||
notification.transientFollowRequestState = .init(state: .isRejecting)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
_ = try await dependency.context.apiService.followRequest(
|
_ = try await dependency.context.apiService.followRequest(
|
||||||
@ -75,22 +65,23 @@ extension DataSourceFacade {
|
|||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
// reset state when failure
|
// reset state when failure
|
||||||
try? await managedObjectContext.performChanges {
|
// try? await managedObjectContext.performChanges {
|
||||||
guard let notification = notification.object(in: managedObjectContext) else { return }
|
// guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||||
notification.transientFollowRequestState = .init(state: .none)
|
// notification.transientFollowRequestState = .init(state: .none)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if let error = error as? Mastodon.API.Error {
|
if let error = error as? Mastodon.API.Error {
|
||||||
switch error.httpResponseStatus {
|
switch error.httpResponseStatus {
|
||||||
case .notFound:
|
case .notFound:
|
||||||
let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
break
|
||||||
try await backgroundManagedObjectContext.performChanges {
|
// let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
||||||
guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
// try await backgroundManagedObjectContext.performChanges {
|
||||||
for feed in notification.feeds {
|
// guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
||||||
backgroundManagedObjectContext.delete(feed)
|
// for feed in notification.feeds {
|
||||||
}
|
// backgroundManagedObjectContext.delete(feed)
|
||||||
backgroundManagedObjectContext.delete(notification)
|
// }
|
||||||
}
|
// backgroundManagedObjectContext.delete(notification)
|
||||||
|
// }
|
||||||
default:
|
default:
|
||||||
let alertController = await UIAlertController(for: error, title: nil, preferredStyle: .alert)
|
let alertController = await UIAlertController(for: error, title: nil, preferredStyle: .alert)
|
||||||
let okAction = await UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
|
let okAction = await UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
|
||||||
@ -106,38 +97,38 @@ extension DataSourceFacade {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try? await managedObjectContext.performChanges {
|
// try? await managedObjectContext.performChanges {
|
||||||
guard let notification = notification.object(in: managedObjectContext) else { return }
|
// guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||||
switch query {
|
// switch query {
|
||||||
case .accept:
|
// case .accept:
|
||||||
notification.transientFollowRequestState = .init(state: .isAccept)
|
// notification.transientFollowRequestState = .init(state: .isAccept)
|
||||||
case .reject:
|
// case .reject:
|
||||||
// do nothing due to will delete notification
|
// // do nothing due to will delete notification
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
// let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
||||||
try? await backgroundManagedObjectContext.performChanges {
|
// try? await backgroundManagedObjectContext.performChanges {
|
||||||
guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
// guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
||||||
switch query {
|
// switch query {
|
||||||
case .accept:
|
// case .accept:
|
||||||
notification.followRequestState = .init(state: .isAccept)
|
// notification.followRequestState = .init(state: .isAccept)
|
||||||
case .reject:
|
// case .reject:
|
||||||
// delete notification
|
// // delete notification
|
||||||
for feed in notification.feeds {
|
// for feed in notification.feeds {
|
||||||
backgroundManagedObjectContext.delete(feed)
|
// backgroundManagedObjectContext.delete(feed)
|
||||||
}
|
// }
|
||||||
backgroundManagedObjectContext.delete(notification)
|
// backgroundManagedObjectContext.delete(notification)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DataSourceFacade {
|
extension DataSourceFacade {
|
||||||
static func responseToShowHideReblogAction(
|
static func responseToShowHideReblogAction(
|
||||||
dependency: NeedsDependency & AuthContextProvider,
|
dependency: NeedsDependency & AuthContextProvider,
|
||||||
user: ManagedObjectRecord<MastodonUser>
|
user: Mastodon.Entity.Account
|
||||||
) async throws {
|
) async throws {
|
||||||
_ = try await dependency.context.apiService.toggleShowReblogs(
|
_ = try await dependency.context.apiService.toggleShowReblogs(
|
||||||
for: user,
|
for: user,
|
||||||
|
@ -24,7 +24,7 @@ extension DataSourceFacade {
|
|||||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||||
|
|
||||||
try? await managedObjectContext.performChanges {
|
try? await managedObjectContext.performChanges {
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
guard let me = authenticationBox.authentication.user else { return }
|
||||||
guard let user = record.object(in: managedObjectContext) else { return }
|
guard let user = record.object(in: managedObjectContext) else { return }
|
||||||
_ = Persistence.SearchHistory.createOrMerge(
|
_ = Persistence.SearchHistory.createOrMerge(
|
||||||
in: managedObjectContext,
|
in: managedObjectContext,
|
||||||
@ -42,7 +42,7 @@ extension DataSourceFacade {
|
|||||||
switch tag {
|
switch tag {
|
||||||
case .entity(let entity):
|
case .entity(let entity):
|
||||||
try? await managedObjectContext.performChanges {
|
try? await managedObjectContext.performChanges {
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
guard let me = authenticationBox.authentication.user else { return }
|
||||||
|
|
||||||
let now = Date()
|
let now = Date()
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ extension DataSourceFacade {
|
|||||||
case .record(let record):
|
case .record(let record):
|
||||||
try? await managedObjectContext.performChanges {
|
try? await managedObjectContext.performChanges {
|
||||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
guard let me = authenticationBox.authentication.user else { return }
|
||||||
guard let tag = record.object(in: managedObjectContext) else { return }
|
guard let tag = record.object(in: managedObjectContext) else { return }
|
||||||
|
|
||||||
let now = Date()
|
let now = Date()
|
||||||
@ -99,7 +99,7 @@ extension DataSourceFacade {
|
|||||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
try await managedObjectContext.performChanges {
|
||||||
guard let _ = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
guard let _ = authenticationBox.authentication.user else { return }
|
||||||
let request = SearchHistory.sortedFetchRequest
|
let request = SearchHistory.sortedFetchRequest
|
||||||
request.predicate = SearchHistory.predicate(
|
request.predicate = SearchHistory.predicate(
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
|
@ -212,7 +212,7 @@ extension HomeTimelineViewController {
|
|||||||
let userDoesntFollowPeople: Bool
|
let userDoesntFollowPeople: Bool
|
||||||
if let managedObjectContext = self?.context.managedObjectContext,
|
if let managedObjectContext = self?.context.managedObjectContext,
|
||||||
let authContext = self?.authContext,
|
let authContext = self?.authContext,
|
||||||
let me = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext){
|
let me = authContext.mastodonAuthenticationBox.authentication.user{
|
||||||
userDoesntFollowPeople = me.followersCount == 0
|
userDoesntFollowPeople = me.followersCount == 0
|
||||||
} else {
|
} else {
|
||||||
userDoesntFollowPeople = true
|
userDoesntFollowPeople = true
|
||||||
|
@ -79,7 +79,7 @@ extension SearchResultOverviewCoordinator: SearchResultsOverviewTableViewControl
|
|||||||
return Persistence.Status.fetch(in: managedObjectContext, context: Persistence.Status.PersistContext(
|
return Persistence.Status.fetch(in: managedObjectContext, context: Persistence.Status.PersistContext(
|
||||||
domain: authContext.mastodonAuthenticationBox.domain,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
entity: status,
|
entity: status,
|
||||||
me: authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext),
|
me: authContext.mastodonAuthenticationBox.authentication.user,
|
||||||
statusCache: nil,
|
statusCache: nil,
|
||||||
userCache: nil,
|
userCache: nil,
|
||||||
networkDate: Date()))
|
networkDate: Date()))
|
||||||
|
@ -29,7 +29,6 @@ final class SendPostIntentHandler: NSObject {
|
|||||||
|
|
||||||
// MARK: - SendPostIntentHandling
|
// MARK: - SendPostIntentHandling
|
||||||
extension SendPostIntentHandler: SendPostIntentHandling {
|
extension SendPostIntentHandler: SendPostIntentHandling {
|
||||||
|
|
||||||
func handle(intent: SendPostIntent) async -> SendPostIntentResponse {
|
func handle(intent: SendPostIntent) async -> SendPostIntentResponse {
|
||||||
guard let content = intent.content else {
|
guard let content = intent.content else {
|
||||||
return SendPostIntentResponse(code: .failure, userActivity: nil)
|
return SendPostIntentResponse(code: .failure, userActivity: nil)
|
||||||
@ -59,7 +58,7 @@ extension SendPostIntentHandler: SendPostIntentHandling {
|
|||||||
}
|
}
|
||||||
mastodonAuthentications = [authentication]
|
mastodonAuthentications = [authentication]
|
||||||
} else {
|
} else {
|
||||||
mastodonAuthentications = try accounts.mastodonAuthentication(in: managedObjectContext)
|
mastodonAuthentications = AuthenticationServiceProvider.shared.authentications.sorted(by: { $0.activedAt > $1.activedAt })
|
||||||
}
|
}
|
||||||
|
|
||||||
let authenticationBoxes = mastodonAuthentications.map { authentication in
|
let authenticationBoxes = mastodonAuthentications.map { authentication in
|
||||||
|
@ -19,17 +19,15 @@ extension Account {
|
|||||||
let accounts: [Account] = try await managedObjectContext.perform {
|
let accounts: [Account] = try await managedObjectContext.perform {
|
||||||
let results = AuthenticationServiceProvider.shared.authentications
|
let results = AuthenticationServiceProvider.shared.authentications
|
||||||
let accounts = results.compactMap { mastodonAuthentication -> Account? in
|
let accounts = results.compactMap { mastodonAuthentication -> Account? in
|
||||||
guard let user = mastodonAuthentication.user(in: managedObjectContext) else {
|
let user = mastodonAuthentication.user
|
||||||
return nil
|
|
||||||
}
|
|
||||||
let account = Account(
|
let account = Account(
|
||||||
identifier: mastodonAuthentication.identifier.uuidString,
|
identifier: mastodonAuthentication.identifier.uuidString,
|
||||||
display: user.displayNameWithFallback,
|
display: user.displayNameWithFallback,
|
||||||
subtitle: user.acctWithDomain,
|
subtitle: user.acct, // TODO: CD check
|
||||||
image: user.avatarImageURL().flatMap { INImage(url: $0) }
|
image: user.avatarImageURL().flatMap { INImage(url: $0) }
|
||||||
)
|
)
|
||||||
account.name = user.displayNameWithFallback
|
account.name = user.displayNameWithFallback
|
||||||
account.username = user.acctWithDomain
|
account.username = user.acctWithDomainIfMissing(mastodonAuthentication.domain)
|
||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
return accounts
|
return accounts
|
||||||
|
@ -6,13 +6,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
public struct MastodonAuthenticationBox: UserIdentifier {
|
public struct MastodonAuthenticationBox: UserIdentifier {
|
||||||
public let authentication: MastodonAuthentication
|
public let authentication: MastodonAuthentication
|
||||||
public let domain: String
|
public let domain: String
|
||||||
public let userID: MastodonUser.ID
|
public let userID: Mastodon.Entity.Account.ID
|
||||||
public let appAuthorization: Mastodon.API.OAuth.Authorization
|
public let appAuthorization: Mastodon.API.OAuth.Authorization
|
||||||
public let userAuthorization: Mastodon.API.OAuth.Authorization
|
public let userAuthorization: Mastodon.API.OAuth.Authorization
|
||||||
public let inMemoryCache: MastodonAccountInMemoryCache
|
public let inMemoryCache: MastodonAccountInMemoryCache
|
||||||
@ -20,7 +19,7 @@ public struct MastodonAuthenticationBox: UserIdentifier {
|
|||||||
public init(
|
public init(
|
||||||
authentication: MastodonAuthentication,
|
authentication: MastodonAuthentication,
|
||||||
domain: String,
|
domain: String,
|
||||||
userID: MastodonUser.ID,
|
userID: Mastodon.Entity.Account.ID,
|
||||||
appAuthorization: Mastodon.API.OAuth.Authorization,
|
appAuthorization: Mastodon.API.OAuth.Authorization,
|
||||||
userAuthorization: Mastodon.API.OAuth.Authorization,
|
userAuthorization: Mastodon.API.OAuth.Authorization,
|
||||||
inMemoryCache: MastodonAccountInMemoryCache
|
inMemoryCache: MastodonAccountInMemoryCache
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import KeychainAccess
|
import KeychainAccess
|
||||||
import MastodonCommon
|
import MastodonCommon
|
||||||
import os.log
|
import os.log
|
||||||
|
import CoreDataStack
|
||||||
|
|
||||||
public class AuthenticationServiceProvider: ObservableObject {
|
public class AuthenticationServiceProvider: ObservableObject {
|
||||||
private let logger = Logger(subsystem: "AuthenticationServiceProvider", category: "Authentication")
|
private let logger = Logger(subsystem: "AuthenticationServiceProvider", category: "Authentication")
|
||||||
@ -23,15 +23,22 @@ public class AuthenticationServiceProvider: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(instance: Instance, where domain: String) {
|
func update(instance: Mastodon.Entity.Instance, where domain: String) {
|
||||||
authentications = authentications.map { authentication in
|
authentications = authentications.map { authentication in
|
||||||
guard authentication.domain == domain else { return authentication }
|
guard authentication.domain == domain else { return authentication }
|
||||||
return authentication.updating(instance: instance)
|
return authentication.updating(instance: instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update(instanceV2: Mastodon.Entity.V2.Instance, where domain: String) {
|
||||||
|
authentications = authentications.map { authentication in
|
||||||
|
guard authentication.domain == domain else { return authentication }
|
||||||
|
return authentication.updating(instanceV2: instanceV2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func delete(authentication: MastodonAuthentication) {
|
func delete(authentication: MastodonAuthentication) {
|
||||||
authentications.removeAll(where: { $0 == authentication })
|
authentications.removeAll(where: { $0.identifier == authentication.identifier })
|
||||||
}
|
}
|
||||||
|
|
||||||
func activateAuthentication(in domain: String, for userID: String) {
|
func activateAuthentication(in domain: String, for userID: String) {
|
||||||
@ -69,33 +76,47 @@ public extension AuthenticationServiceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func migrateLegacyAuthentications(in context: NSManagedObjectContext) {
|
func migrateLegacyAuthentications(in context: NSManagedObjectContext) {
|
||||||
do {
|
Task {
|
||||||
let legacyAuthentications = try context.fetch(MastodonAuthenticationLegacy.sortedFetchRequest)
|
do {
|
||||||
let migratedAuthentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in
|
let legacyAuthentications = try context.fetch(MastodonAuthenticationLegacy.sortedFetchRequest)
|
||||||
return MastodonAuthentication(
|
|
||||||
identifier: auth.identifier,
|
|
||||||
domain: auth.domain,
|
|
||||||
username: auth.username,
|
|
||||||
appAccessToken: auth.appAccessToken,
|
|
||||||
userAccessToken: auth.userAccessToken,
|
|
||||||
clientID: auth.clientID,
|
|
||||||
clientSecret: auth.clientSecret,
|
|
||||||
createdAt: auth.createdAt,
|
|
||||||
updatedAt: auth.updatedAt,
|
|
||||||
activedAt: auth.activedAt,
|
|
||||||
userID: auth.userID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if migratedAuthentications.count != legacyAuthentications.count {
|
var migratedAuthentications = [MastodonAuthentication]()
|
||||||
logger.log(level: .default, "Not all account authentications could be migrated.")
|
|
||||||
}
|
|
||||||
|
|
||||||
self.authentications = migratedAuthentications
|
for auth in legacyAuthentications {
|
||||||
userDefaults.didMigrateAuthentications = true
|
let user = try await Mastodon.API.Account.accountInfo(
|
||||||
} catch {
|
session: URLSession.shared,
|
||||||
userDefaults.didMigrateAuthentications = false
|
domain: auth.domain,
|
||||||
logger.log(level: .error, "Could not migrate legacy authentications")
|
userID: auth.userID,
|
||||||
|
authorization: .init(accessToken: auth.userAccessToken)
|
||||||
|
).singleOutput().value
|
||||||
|
|
||||||
|
let newAuth = MastodonAuthentication(
|
||||||
|
user: user,
|
||||||
|
identifier: auth.identifier,
|
||||||
|
domain: auth.domain,
|
||||||
|
username: auth.username,
|
||||||
|
appAccessToken: auth.appAccessToken,
|
||||||
|
userAccessToken: auth.userAccessToken,
|
||||||
|
clientID: auth.clientID,
|
||||||
|
clientSecret: auth.clientSecret,
|
||||||
|
createdAt: auth.createdAt,
|
||||||
|
updatedAt: auth.updatedAt,
|
||||||
|
activedAt: auth.activedAt,
|
||||||
|
userID: auth.userID
|
||||||
|
)
|
||||||
|
migratedAuthentications.append(newAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if migratedAuthentications.count != legacyAuthentications.count {
|
||||||
|
logger.log(level: .default, "Not all account authentications could be migrated.")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.authentications = migratedAuthentications
|
||||||
|
userDefaults.didMigrateAuthentications = true
|
||||||
|
} catch {
|
||||||
|
userDefaults.didMigrateAuthentications = false
|
||||||
|
logger.log(level: .error, "Could not migrate legacy authentications")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,57 +6,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
extension Instance {
|
|
||||||
public var configuration: Mastodon.Entity.Instance.Configuration? {
|
|
||||||
guard let configurationRaw = configurationRaw else { return nil }
|
|
||||||
guard let configuration = try? JSONDecoder().decode(Mastodon.Entity.Instance.Configuration.self, from: configurationRaw) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
static func encode(configuration: Mastodon.Entity.Instance.Configuration) -> Data? {
|
|
||||||
return try? JSONEncoder().encode(configuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Instance {
|
|
||||||
public var configurationV2: Mastodon.Entity.V2.Instance.Configuration? {
|
|
||||||
guard
|
|
||||||
let configurationRaw = configurationV2Raw,
|
|
||||||
let configuration = try? JSONDecoder().decode(
|
|
||||||
Mastodon.Entity.V2.Instance.Configuration.self,
|
|
||||||
from: configurationRaw
|
|
||||||
)
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
static func encodeV2(configuration: Mastodon.Entity.V2.Instance.Configuration) -> Data? {
|
|
||||||
return try? JSONEncoder().encode(configuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Instance {
|
|
||||||
public var canFollowTags: Bool {
|
|
||||||
version?.majorServerVersion(greaterThanOrEquals: 4) ?? false // following Tags is support beginning with Mastodon v4.0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
var isTranslationEnabled: Bool {
|
|
||||||
if let configuration = configurationV2 {
|
|
||||||
return configuration.translation?.enabled == true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
public func majorServerVersion(greaterThanOrEquals comparedVersion: Int) -> Bool {
|
public func majorServerVersion(greaterThanOrEquals comparedVersion: Int) -> Bool {
|
||||||
guard
|
guard
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
public struct MastodonAuthentication: Codable, Hashable {
|
public struct MastodonAuthentication: Codable {
|
||||||
public typealias ID = UUID
|
public typealias ID = UUID
|
||||||
|
|
||||||
|
public private(set) var user: Mastodon.Entity.Account
|
||||||
|
|
||||||
public private(set) var identifier: ID
|
public private(set) var identifier: ID
|
||||||
public private(set) var domain: String
|
public private(set) var domain: String
|
||||||
public private(set) var username: String
|
public private(set) var username: String
|
||||||
@ -21,13 +22,16 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||||||
public private(set) var activedAt: Date
|
public private(set) var activedAt: Date
|
||||||
|
|
||||||
public private(set) var userID: String
|
public private(set) var userID: String
|
||||||
public private(set) var instanceObjectIdURI: URL?
|
|
||||||
|
public private(set) var instance: Mastodon.Entity.Instance?
|
||||||
|
public private(set) var instanceV2: Mastodon.Entity.V2.Instance?
|
||||||
|
|
||||||
internal var persistenceIdentifier: String {
|
internal var persistenceIdentifier: String {
|
||||||
"\(username)@\(domain)"
|
"\(username)@\(domain)"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func createFrom(
|
public static func createFrom(
|
||||||
|
user: Mastodon.Entity.Account,
|
||||||
domain: String,
|
domain: String,
|
||||||
userID: String,
|
userID: String,
|
||||||
username: String,
|
username: String,
|
||||||
@ -38,6 +42,7 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let now = Date()
|
let now = Date()
|
||||||
return MastodonAuthentication(
|
return MastodonAuthentication(
|
||||||
|
user: user,
|
||||||
identifier: .init(),
|
identifier: .init(),
|
||||||
domain: domain,
|
domain: domain,
|
||||||
username: username,
|
username: username,
|
||||||
@ -49,11 +54,13 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
activedAt: now,
|
activedAt: now,
|
||||||
userID: userID,
|
userID: userID,
|
||||||
instanceObjectIdURI: nil
|
instance: nil,
|
||||||
|
instanceV2: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copy(
|
func copy(
|
||||||
|
user: Mastodon.Entity.Account? = nil,
|
||||||
identifier: ID? = nil,
|
identifier: ID? = nil,
|
||||||
domain: String? = nil,
|
domain: String? = nil,
|
||||||
username: String? = nil,
|
username: String? = nil,
|
||||||
@ -65,9 +72,11 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||||||
updatedAt: Date? = nil,
|
updatedAt: Date? = nil,
|
||||||
activedAt: Date? = nil,
|
activedAt: Date? = nil,
|
||||||
userID: String? = nil,
|
userID: String? = nil,
|
||||||
instanceObjectIdURI: URL? = nil
|
instance: Mastodon.Entity.Instance? = nil,
|
||||||
|
instanceV2: Mastodon.Entity.V2.Instance? = nil
|
||||||
) -> Self {
|
) -> Self {
|
||||||
MastodonAuthentication(
|
MastodonAuthentication(
|
||||||
|
user: user ?? self.user,
|
||||||
identifier: identifier ?? self.identifier,
|
identifier: identifier ?? self.identifier,
|
||||||
domain: domain ?? self.domain,
|
domain: domain ?? self.domain,
|
||||||
username: username ?? self.username,
|
username: username ?? self.username,
|
||||||
@ -79,26 +88,17 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||||||
updatedAt: updatedAt ?? self.updatedAt,
|
updatedAt: updatedAt ?? self.updatedAt,
|
||||||
activedAt: activedAt ?? self.activedAt,
|
activedAt: activedAt ?? self.activedAt,
|
||||||
userID: userID ?? self.userID,
|
userID: userID ?? self.userID,
|
||||||
instanceObjectIdURI: instanceObjectIdURI ?? self.instanceObjectIdURI
|
instance: instance ?? self.instance,
|
||||||
|
instanceV2: instanceV2 ?? self.instanceV2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func instance(in context: NSManagedObjectContext) -> Instance? {
|
func updating(instance: Mastodon.Entity.Instance) -> Self {
|
||||||
guard
|
copy(instance: instance)
|
||||||
let instanceObjectIdURI = instanceObjectIdURI,
|
|
||||||
let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI)
|
|
||||||
else { return nil }
|
|
||||||
|
|
||||||
return try? context.existingObject(with: objectID) as? Instance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func user(in context: NSManagedObjectContext) -> MastodonUser? {
|
func updating(instanceV2: Mastodon.Entity.V2.Instance) -> Self {
|
||||||
let userPredicate = MastodonUser.predicate(domain: domain, id: userID)
|
copy(instanceV2: instanceV2)
|
||||||
return MastodonUser.findOrFetch(in: context, matching: userPredicate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updating(instance: Instance) -> Self {
|
|
||||||
copy(instanceObjectIdURI: instance.objectID.uriRepresentation())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updating(activatedAt: Date) -> Self {
|
func updating(activatedAt: Date) -> Self {
|
||||||
|
@ -165,23 +165,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
|
|
||||||
for entity in response.value {
|
|
||||||
_ = Persistence.Tag.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Tag.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ extension APIService {
|
|||||||
let targetUserID: MastodonUser.ID
|
let targetUserID: MastodonUser.ID
|
||||||
let targetUsername: String
|
let targetUsername: String
|
||||||
let isBlocking: Bool
|
let isBlocking: Bool
|
||||||
let isFollowing: Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@ -61,39 +60,25 @@ extension APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func toggleBlock(
|
public func toggleBlock(
|
||||||
user: ManagedObjectRecord<MastodonUser>,
|
user: Mastodon.Entity.Account,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||||
|
let me = authenticationBox.authentication.user
|
||||||
|
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let blockedUsers = try await Mastodon.API.Account.blocks(
|
||||||
let blockContext: MastodonBlockContext = try await managedObjectContext.performChanges {
|
session: session,
|
||||||
let authentication = authenticationBox.authentication
|
domain: authenticationBox.domain,
|
||||||
|
sinceID: nil,
|
||||||
|
limit: nil,
|
||||||
|
authorization: authenticationBox.userAuthorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
guard
|
let blockContext = MastodonBlockContext(
|
||||||
let user = user.object(in: managedObjectContext),
|
sourceUserID: me.id,
|
||||||
let me = authentication.user(in: managedObjectContext)
|
targetUserID: user.id,
|
||||||
else {
|
targetUsername: user.username,
|
||||||
throw APIError.implicit(.badRequest)
|
isBlocking: blockedUsers.value.contains(user)
|
||||||
}
|
)
|
||||||
|
|
||||||
let isBlocking = user.blockingBy.contains(me)
|
|
||||||
let isFollowing = user.followingBy.contains(me)
|
|
||||||
// toggle block state
|
|
||||||
user.update(isBlocking: !isBlocking, by: me)
|
|
||||||
// update follow state implicitly
|
|
||||||
if !isBlocking {
|
|
||||||
// will do block action. set to unfollow
|
|
||||||
user.update(isFollowing: false, by: me)
|
|
||||||
}
|
|
||||||
|
|
||||||
return MastodonBlockContext(
|
|
||||||
sourceUserID: me.id,
|
|
||||||
targetUserID: user.id,
|
|
||||||
targetUsername: user.username,
|
|
||||||
isBlocking: isBlocking,
|
|
||||||
isFollowing: isFollowing
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||||
do {
|
do {
|
||||||
@ -118,33 +103,6 @@ extension APIService {
|
|||||||
result = .failure(error)
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let authentication = authenticationBox.authentication
|
|
||||||
|
|
||||||
guard
|
|
||||||
let user = user.object(in: managedObjectContext),
|
|
||||||
let me = authentication.user(in: managedObjectContext)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
let relationship = response.value
|
|
||||||
Persistence.MastodonUser.update(
|
|
||||||
mastodonUser: user,
|
|
||||||
context: Persistence.MastodonUser.RelationshipContext(
|
|
||||||
entity: relationship,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case .failure:
|
|
||||||
// rollback
|
|
||||||
user.update(isBlocking: blockContext.isBlocking, by: me)
|
|
||||||
user.update(isFollowing: blockContext.isFollowing, by: me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try result.get()
|
let response = try result.get()
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
@ -19,32 +19,23 @@ extension APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func bookmark(
|
public func bookmark(
|
||||||
record: ManagedObjectRecord<Status>,
|
status: Mastodon.Entity.Status,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
||||||
|
let authentication = authenticationBox.authentication
|
||||||
|
let me = authentication.user
|
||||||
|
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let isBookmarked = try await Mastodon.API.Bookmarks.bookmarkedStatus(
|
||||||
|
domain: authenticationBox.domain,
|
||||||
|
session: session,
|
||||||
|
authorization: authenticationBox.userAuthorization,
|
||||||
|
query: .init()
|
||||||
|
).singleOutput().value.contains(where: { $0.id == status.id }) // TODO: CD is this sufficient? Do we need to check the domain as well?
|
||||||
|
|
||||||
// update bookmark state and retrieve bookmark context
|
let bookmarkContext = MastodonBookmarkContext(
|
||||||
let bookmarkContext: MastodonBookmarkContext = try await managedObjectContext.performChanges {
|
statusID: status.id,
|
||||||
let authentication = authenticationBox.authentication
|
isBookmarked: isBookmarked
|
||||||
|
)
|
||||||
guard
|
|
||||||
let _status = record.object(in: managedObjectContext),
|
|
||||||
let me = authentication.user(in: managedObjectContext)
|
|
||||||
else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
let isBookmarked = status.bookmarkedBy.contains(me)
|
|
||||||
status.update(bookmarked: !isBookmarked, by: me)
|
|
||||||
let context = MastodonBookmarkContext(
|
|
||||||
statusID: status.id,
|
|
||||||
isBookmarked: isBookmarked
|
|
||||||
)
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
// request bookmark or undo bookmark
|
// request bookmark or undo bookmark
|
||||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Status>, Error>
|
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Status>, Error>
|
||||||
@ -61,36 +52,6 @@ extension APIService {
|
|||||||
result = .failure(error)
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update bookmark state
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let authentication = authenticationBox.authentication
|
|
||||||
|
|
||||||
guard
|
|
||||||
let _status = record.object(in: managedObjectContext),
|
|
||||||
let me = authentication.user(in: managedObjectContext)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case .failure:
|
|
||||||
// rollback
|
|
||||||
status.update(bookmarked: bookmarkContext.isBookmarked, by: me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try result.get()
|
let response = try result.get()
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@ -112,34 +73,6 @@ extension APIService {
|
|||||||
query: query
|
query: query
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
|
|
||||||
guard
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for entity in response.value {
|
|
||||||
let result = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result.status.update(bookmarked: true, by: me)
|
|
||||||
result.status.reblog?.update(bookmarked: true, by: me)
|
|
||||||
} // end for … in
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
@ -16,40 +16,24 @@ extension APIService {
|
|||||||
private struct MastodonFavoriteContext {
|
private struct MastodonFavoriteContext {
|
||||||
let statusID: Status.ID
|
let statusID: Status.ID
|
||||||
let isFavorited: Bool
|
let isFavorited: Bool
|
||||||
let favoritedCount: Int64
|
let favoritedCount: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
public func favorite(
|
public func favorite(
|
||||||
record: ManagedObjectRecord<Status>,
|
status: Mastodon.Entity.Status,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
||||||
|
let authentication = authenticationBox.authentication
|
||||||
|
let me = authentication.user
|
||||||
|
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let _status = status.reblog ?? status
|
||||||
|
let isFavorited = status.favourited ?? false
|
||||||
// update like state and retrieve like context
|
let favoritedCount = status.favouritesCount
|
||||||
let favoriteContext: MastodonFavoriteContext = try await managedObjectContext.performChanges {
|
let favoriteContext = MastodonFavoriteContext(
|
||||||
let authentication = authenticationBox.authentication
|
statusID: status.id,
|
||||||
|
isFavorited: isFavorited,
|
||||||
guard
|
favoritedCount: favoritedCount
|
||||||
let _status = record.object(in: managedObjectContext),
|
)
|
||||||
let me = authentication.user(in: managedObjectContext)
|
|
||||||
else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
let isFavorited = status.favouritedBy.contains(me)
|
|
||||||
let favoritedCount = status.favouritesCount
|
|
||||||
let favoriteCount = isFavorited ? favoritedCount - 1 : favoritedCount + 1
|
|
||||||
status.update(liked: !isFavorited, by: me)
|
|
||||||
status.update(favouritesCount: favoriteCount)
|
|
||||||
let context = MastodonFavoriteContext(
|
|
||||||
statusID: status.id,
|
|
||||||
isFavorited: isFavorited,
|
|
||||||
favoritedCount: favoritedCount
|
|
||||||
)
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
// request like or undo like
|
// request like or undo like
|
||||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Status>, Error>
|
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Status>, Error>
|
||||||
@ -66,40 +50,6 @@ extension APIService {
|
|||||||
result = .failure(error)
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update like state
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let authentication = authenticationBox.authentication
|
|
||||||
|
|
||||||
guard
|
|
||||||
let _status = record.object(in: managedObjectContext),
|
|
||||||
let me = authentication.user(in: managedObjectContext)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if favoriteContext.isFavorited {
|
|
||||||
status.update(favouritesCount: max(0, status.favouritesCount - 1)) // undo API return count has delay. Needs -1 local
|
|
||||||
}
|
|
||||||
case .failure:
|
|
||||||
// rollback
|
|
||||||
status.update(liked: favoriteContext.isFavorited, by: me)
|
|
||||||
status.update(favouritesCount: favoriteContext.favoritedCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try result.get()
|
let response = try result.get()
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@ -121,73 +71,24 @@ extension APIService {
|
|||||||
query: query
|
query: query
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for entity in response.value {
|
|
||||||
let result = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result.status.update(liked: true, by: me)
|
|
||||||
result.status.reblog?.update(liked: true, by: me)
|
|
||||||
} // end for … in
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
||||||
public func favoritedBy(
|
public func favoritedBy(
|
||||||
status: ManagedObjectRecord<Status>,
|
status: Mastodon.Entity.Status,
|
||||||
query: Mastodon.API.Statuses.FavoriteByQuery,
|
query: Mastodon.API.Statuses.FavoriteByQuery,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
|
||||||
let _statusID: Status.ID? = try? await managedObjectContext.perform {
|
|
||||||
guard let _status = status.object(in: managedObjectContext) else { return nil }
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
return status.id
|
|
||||||
}
|
|
||||||
guard let statusID = _statusID else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try await Mastodon.API.Statuses.favoriteBy(
|
let response = try await Mastodon.API.Statuses.favoriteBy(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
statusID: statusID,
|
statusID: status.reblog?.id ?? status.id,
|
||||||
query: query,
|
query: query,
|
||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
for entity in response.value {
|
|
||||||
_ = Persistence.MastodonUser.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: .init(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: entity,
|
|
||||||
cache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} // end for … in
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ extension APIService {
|
|||||||
let sourceUserID: MastodonUser.ID
|
let sourceUserID: MastodonUser.ID
|
||||||
let targetUserID: MastodonUser.ID
|
let targetUserID: MastodonUser.ID
|
||||||
let isFollowing: Bool
|
let isFollowing: Bool
|
||||||
let isPending: Bool
|
|
||||||
let needsUnfollow: Bool
|
let needsUnfollow: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,46 +29,28 @@ extension APIService {
|
|||||||
/// - activeMastodonAuthenticationBox: `AuthenticationService.MastodonAuthenticationBox`
|
/// - activeMastodonAuthenticationBox: `AuthenticationService.MastodonAuthenticationBox`
|
||||||
/// - Returns: publisher for `Relationship`
|
/// - Returns: publisher for `Relationship`
|
||||||
public func toggleFollow(
|
public func toggleFollow(
|
||||||
user: ManagedObjectRecord<MastodonUser>,
|
user: Mastodon.Entity.Account,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||||
|
let authentication = authenticationBox.authentication
|
||||||
|
let me = authentication.user
|
||||||
|
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let otherUser = try await Mastodon.API.Account.followers(
|
||||||
let _followContext: MastodonFollowContext? = try await managedObjectContext.performChanges {
|
session: session,
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return nil }
|
domain: authentication.domain,
|
||||||
guard let user = user.object(in: managedObjectContext) else { return nil }
|
userID: user.id,
|
||||||
|
query: .init(maxID: nil, limit: nil),
|
||||||
|
authorization: authenticationBox.userAuthorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
let isFollowing = user.followingBy.contains(me)
|
let isFollowing = otherUser.value.contains(me)
|
||||||
let isPending = user.followRequestedBy.contains(me)
|
|
||||||
let needsUnfollow = isFollowing || isPending
|
|
||||||
|
|
||||||
if needsUnfollow {
|
let followContext = MastodonFollowContext(
|
||||||
// unfollow
|
sourceUserID: me.id,
|
||||||
user.update(isFollowing: false, by: me)
|
targetUserID: user.id,
|
||||||
user.update(isFollowRequested: false, by: me)
|
isFollowing: isFollowing,
|
||||||
} else {
|
needsUnfollow: isFollowing
|
||||||
// follow
|
)
|
||||||
if user.locked {
|
|
||||||
user.update(isFollowing: false, by: me)
|
|
||||||
user.update(isFollowRequested: true, by: me)
|
|
||||||
} else {
|
|
||||||
user.update(isFollowing: true, by: me)
|
|
||||||
user.update(isFollowRequested: false, by: me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let context = MastodonFollowContext(
|
|
||||||
sourceUserID: me.id,
|
|
||||||
targetUserID: user.id,
|
|
||||||
isFollowing: isFollowing,
|
|
||||||
isPending: isPending,
|
|
||||||
needsUnfollow: needsUnfollow
|
|
||||||
)
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let followContext = _followContext else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// request follow or unfollow
|
// request follow or unfollow
|
||||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||||
@ -86,48 +67,28 @@ extension APIService {
|
|||||||
result = .failure(error)
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update friendship state
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext),
|
|
||||||
let user = user.object(in: managedObjectContext)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
Persistence.MastodonUser.update(
|
|
||||||
mastodonUser: user,
|
|
||||||
context: Persistence.MastodonUser.RelationshipContext(
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case .failure:
|
|
||||||
// rollback
|
|
||||||
user.update(isFollowing: followContext.isFollowing, by: me)
|
|
||||||
user.update(isFollowRequested: followContext.isPending, by: me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try result.get()
|
let response = try result.get()
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
public func toggleShowReblogs(
|
public func toggleShowReblogs(
|
||||||
for user: ManagedObjectRecord<MastodonUser>,
|
for user: Mastodon.Entity.Account,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||||
|
let me = authenticationBox.authentication.user
|
||||||
|
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let relationship = try await Mastodon.API.Account.relationships(
|
||||||
guard let user = user.object(in: managedObjectContext),
|
session: session,
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
domain: authenticationBox.domain,
|
||||||
else { throw APIError.implicit(.badRequest) }
|
query: .init(ids: [user.id]),
|
||||||
|
authorization: authenticationBox.userAuthorization
|
||||||
|
).singleOutput()
|
||||||
|
|
||||||
|
let oldShowReblogs = relationship.value.first?.showingReblogs ?? false
|
||||||
|
let newShowReblogs = !oldShowReblogs
|
||||||
|
|
||||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||||
|
|
||||||
let oldShowReblogs = me.showingReblogsBy.contains(user)
|
|
||||||
let newShowReblogs = (oldShowReblogs == false)
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let response = try await Mastodon.API.Account.follow(
|
let response = try await Mastodon.API.Account.follow(
|
||||||
session: session,
|
session: session,
|
||||||
@ -142,25 +103,6 @@ extension APIService {
|
|||||||
result = .failure(error)
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
Persistence.MastodonUser.update(
|
|
||||||
mastodonUser: user,
|
|
||||||
context: Persistence.MastodonUser.RelationshipContext(
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case .failure:
|
|
||||||
// rollback
|
|
||||||
user.update(isShowingReblogs: oldShowReblogs, by: me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return try result.get()
|
return try result.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,27 +26,6 @@ extension APIService {
|
|||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let request = MastodonUser.sortedFetchRequest
|
|
||||||
request.predicate = MastodonUser.predicate(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
id: authenticationBox.userID
|
|
||||||
)
|
|
||||||
request.fetchLimit = 1
|
|
||||||
guard let user = managedObjectContext.safeFetch(request).first else { return }
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
|
||||||
|
|
||||||
Persistence.MastodonUser.update(
|
|
||||||
mastodonUser: user,
|
|
||||||
context: Persistence.MastodonUser.RelationshipContext(
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,26 +33,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
|
|
||||||
for entity in response.value {
|
|
||||||
let result = Persistence.MastodonUser.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.MastodonUser.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
cache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
let user = result.user
|
|
||||||
me?.update(isFollowing: true, by: user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,29 +34,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
|
|
||||||
for entity in response.value {
|
|
||||||
let result = Persistence.MastodonUser.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.MastodonUser.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
cache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if let me = me {
|
|
||||||
let user = result.user
|
|
||||||
user.update(isFollowing: true, by: me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,25 +42,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
|
|
||||||
for entity in response.value {
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,71 +54,6 @@ extension APIService {
|
|||||||
|
|
||||||
NotificationCenter.default.post(name: .userFetched, object: nil)
|
NotificationCenter.default.post(name: .userFetched, object: nil)
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// persist status
|
|
||||||
var statuses: [Status] = []
|
|
||||||
for entity in response.value {
|
|
||||||
let result = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil, // TODO: add cache
|
|
||||||
userCache: nil, // TODO: add cache
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
statuses.append(result.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// locate anchor status
|
|
||||||
let anchorStatus: Status? = {
|
|
||||||
guard let maxID = maxID else { return nil }
|
|
||||||
let request = Status.sortedFetchRequest
|
|
||||||
request.predicate = Status.predicate(domain: domain, id: maxID)
|
|
||||||
request.fetchLimit = 1
|
|
||||||
return try? managedObjectContext.fetch(request).first
|
|
||||||
}()
|
|
||||||
|
|
||||||
// update hasMore flag for anchor status
|
|
||||||
let acct = Feed.Acct.mastodon(domain: authenticationBox.domain, userID: authenticationBox.userID)
|
|
||||||
if let anchorStatus = anchorStatus,
|
|
||||||
let feed = anchorStatus.feed(kind: .home, acct: acct) {
|
|
||||||
feed.update(hasMore: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// persist Feed relationship
|
|
||||||
let sortedStatuses = statuses.sorted(by: { $0.createdAt < $1.createdAt })
|
|
||||||
let oldestStatus = sortedStatuses.first
|
|
||||||
for status in sortedStatuses {
|
|
||||||
let _feed = status.feed(kind: .home, acct: acct)
|
|
||||||
if let feed = _feed {
|
|
||||||
feed.update(updatedAt: response.networkDate)
|
|
||||||
} else {
|
|
||||||
let feedProperty = Feed.Property(
|
|
||||||
acct: acct,
|
|
||||||
kind: .home,
|
|
||||||
hasMore: false,
|
|
||||||
createdAt: status.createdAt,
|
|
||||||
updatedAt: response.networkDate
|
|
||||||
)
|
|
||||||
let feed = Feed.insert(into: managedObjectContext, property: feedProperty)
|
|
||||||
status.attach(feed: feed)
|
|
||||||
|
|
||||||
// set hasMore on oldest status if is new feed
|
|
||||||
if status === oldestStatus {
|
|
||||||
feed.update(hasMore: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ import MastodonSDK
|
|||||||
extension APIService {
|
extension APIService {
|
||||||
|
|
||||||
private struct MastodonMuteContext {
|
private struct MastodonMuteContext {
|
||||||
let sourceUserID: MastodonUser.ID
|
let sourceUserID: Mastodon.Entity.Account.ID
|
||||||
let targetUserID: MastodonUser.ID
|
let targetUserID: Mastodon.Entity.Account.ID
|
||||||
let targetUsername: String
|
let targetUsername: String
|
||||||
let isMuting: Bool
|
let isMuting: Bool
|
||||||
}
|
}
|
||||||
@ -32,7 +32,6 @@ extension APIService {
|
|||||||
limit: Int?,
|
limit: Int?,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
|
||||||
let response = try await Mastodon.API.Account.mutes(
|
let response = try await Mastodon.API.Account.mutes(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
@ -41,51 +40,24 @@ extension APIService {
|
|||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let userIDs = response.value.map { $0.id }
|
|
||||||
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
|
||||||
|
|
||||||
let fetchRequest = MastodonUser.fetchRequest()
|
|
||||||
fetchRequest.predicate = predicate
|
|
||||||
fetchRequest.includesPropertyValues = false
|
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser]
|
|
||||||
|
|
||||||
for user in users {
|
|
||||||
user.deleteStatusAndNotificationFeeds(in: managedObjectContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
public func toggleMute(
|
public func toggleMute(
|
||||||
user: ManagedObjectRecord<MastodonUser>,
|
user: Mastodon.Entity.Account,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox,
|
||||||
|
isMuting: Bool
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||||
|
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let managedObjectContext = backgroundManagedObjectContext
|
||||||
let muteContext: MastodonMuteContext = try await managedObjectContext.performChanges {
|
let me = authenticationBox.authentication.user
|
||||||
let authentication = authenticationBox.authentication
|
|
||||||
|
|
||||||
guard
|
let muteContext: MastodonMuteContext = MastodonMuteContext(
|
||||||
let user = user.object(in: managedObjectContext),
|
sourceUserID: me.id,
|
||||||
let me = authentication.user(in: managedObjectContext)
|
targetUserID: user.id,
|
||||||
else {
|
targetUsername: user.username,
|
||||||
throw APIError.implicit(.badRequest)
|
isMuting: isMuting
|
||||||
}
|
)
|
||||||
|
|
||||||
let isMuting = user.mutingBy.contains(me)
|
|
||||||
|
|
||||||
// toggle mute state
|
|
||||||
user.update(isMuting: !isMuting, by: me)
|
|
||||||
return MastodonMuteContext(
|
|
||||||
sourceUserID: me.id,
|
|
||||||
targetUserID: user.id,
|
|
||||||
targetUsername: user.username,
|
|
||||||
isMuting: isMuting
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||||
do {
|
do {
|
||||||
@ -112,28 +84,6 @@ extension APIService {
|
|||||||
result = .failure(error)
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let user = user.object(in: managedObjectContext),
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
let relationship = response.value
|
|
||||||
Persistence.MastodonUser.update(
|
|
||||||
mastodonUser: user,
|
|
||||||
context: Persistence.MastodonUser.RelationshipContext(
|
|
||||||
entity: relationship,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case .failure:
|
|
||||||
// rollback
|
|
||||||
user.update(isMuting: muteContext.isMuting, by: me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try result.get()
|
let response = try result.get()
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
@ -86,74 +86,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var notifications: [Notification] = []
|
|
||||||
for entity in response.value {
|
|
||||||
let result = Persistence.Notification.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Notification.PersistContext(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
notifications.append(result.notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
// locate anchor notification
|
|
||||||
let anchorNotification: Notification? = {
|
|
||||||
guard let maxID = query.maxID else { return nil }
|
|
||||||
let request = Notification.sortedFetchRequest
|
|
||||||
request.predicate = Notification.predicate(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
userID: authenticationBox.userID,
|
|
||||||
id: maxID
|
|
||||||
)
|
|
||||||
request.fetchLimit = 1
|
|
||||||
return try? managedObjectContext.fetch(request).first
|
|
||||||
}()
|
|
||||||
|
|
||||||
// update hasMore flag for anchor status
|
|
||||||
let acct = Feed.Acct.mastodon(domain: authenticationBox.domain, userID: authenticationBox.userID)
|
|
||||||
let kind: Feed.Kind = scope == .everything ? .notificationAll : .notificationMentions
|
|
||||||
if let anchorNotification = anchorNotification,
|
|
||||||
let feed = anchorNotification.feed(kind: kind, acct: acct) {
|
|
||||||
feed.update(hasMore: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// persist Feed relationship
|
|
||||||
let sortedNotifications = notifications.sorted(by: { $0.createAt < $1.createAt })
|
|
||||||
let oldestNotification = sortedNotifications.first
|
|
||||||
for notification in notifications {
|
|
||||||
let _feed = notification.feed(kind: kind, acct: acct)
|
|
||||||
if let feed = _feed {
|
|
||||||
feed.update(updatedAt: response.networkDate)
|
|
||||||
} else {
|
|
||||||
let feedProperty = Feed.Property(
|
|
||||||
acct: acct,
|
|
||||||
kind: kind,
|
|
||||||
hasMore: false,
|
|
||||||
createdAt: notification.createAt,
|
|
||||||
updatedAt: response.networkDate
|
|
||||||
)
|
|
||||||
let feed = Feed.insert(into: managedObjectContext, property: feedProperty)
|
|
||||||
notification.attach(feed: feed)
|
|
||||||
|
|
||||||
// set hasMore on oldest notification if is new feed
|
|
||||||
if notification === oldestNotification {
|
|
||||||
feed.update(hasMore: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,20 +106,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
|
||||||
_ = Persistence.Notification.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Notification.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,39 +14,18 @@ import MastodonSDK
|
|||||||
extension APIService {
|
extension APIService {
|
||||||
|
|
||||||
public func poll(
|
public func poll(
|
||||||
poll: ManagedObjectRecord<Poll>,
|
poll: Mastodon.Entity.Poll,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Poll> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Poll> {
|
||||||
let authorization = authenticationBox.userAuthorization
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
let pollID: Poll.ID = try await managedObjectContext.perform {
|
|
||||||
guard let poll = poll.object(in: managedObjectContext) else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
return poll.id
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try await Mastodon.API.Polls.poll(
|
let response = try await Mastodon.API.Polls.poll(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
pollID: pollID,
|
pollID: poll.id,
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
_ = Persistence.Poll.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Poll.PersistContext(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,41 +34,19 @@ extension APIService {
|
|||||||
extension APIService {
|
extension APIService {
|
||||||
|
|
||||||
public func vote(
|
public func vote(
|
||||||
poll: ManagedObjectRecord<Poll>,
|
poll: Mastodon.Entity.Poll,
|
||||||
choices: [Int],
|
choices: [Int],
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Poll> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Poll> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
|
||||||
let _pollID: Poll.ID? = try await managedObjectContext.perform {
|
|
||||||
guard let poll = poll.object(in: managedObjectContext) else { return nil }
|
|
||||||
return poll.id
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let pollID = _pollID else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try await Mastodon.API.Polls.vote(
|
let response = try await Mastodon.API.Polls.vote(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
pollID: pollID,
|
pollID: poll.id,
|
||||||
query: Mastodon.API.Polls.VoteQuery(choices: choices),
|
query: Mastodon.API.Polls.VoteQuery(choices: choices),
|
||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
_ = Persistence.Poll.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Poll.PersistContext(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,24 +27,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
for entity in response.value {
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
|
|
||||||
|
@ -16,40 +16,24 @@ extension APIService {
|
|||||||
private struct MastodonReblogContext {
|
private struct MastodonReblogContext {
|
||||||
let statusID: Status.ID
|
let statusID: Status.ID
|
||||||
let isReblogged: Bool
|
let isReblogged: Bool
|
||||||
let rebloggedCount: Int64
|
let rebloggedCount: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reblog(
|
public func reblog(
|
||||||
record: ManagedObjectRecord<Status>,
|
status: Mastodon.Entity.Status,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let managedObjectContext = backgroundManagedObjectContext
|
||||||
|
|
||||||
// update repost state and retrieve repost context
|
let _status = status.reblog ?? status
|
||||||
let _reblogContext: MastodonReblogContext? = try await managedObjectContext.performChanges {
|
let isReblogged = status.reblogged ?? false
|
||||||
let authentication = authenticationBox.authentication
|
let rebloggedCount = status.reblogsCount
|
||||||
|
let reblogCount = isReblogged ? rebloggedCount - 1 : rebloggedCount + 1
|
||||||
guard
|
let reblogContext = MastodonReblogContext(
|
||||||
let me = authentication.user(in: managedObjectContext),
|
statusID: status.id,
|
||||||
let _status = record.object(in: managedObjectContext)
|
isReblogged: isReblogged,
|
||||||
else { return nil }
|
rebloggedCount: rebloggedCount
|
||||||
|
)
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
let isReblogged = status.rebloggedBy.contains(me)
|
|
||||||
let rebloggedCount = status.reblogsCount
|
|
||||||
let reblogCount = isReblogged ? rebloggedCount - 1 : rebloggedCount + 1
|
|
||||||
status.update(reblogged: !isReblogged, by: me)
|
|
||||||
status.update(reblogsCount: Int64(max(0, reblogCount)))
|
|
||||||
let reblogContext = MastodonReblogContext(
|
|
||||||
statusID: status.id,
|
|
||||||
isReblogged: isReblogged,
|
|
||||||
rebloggedCount: rebloggedCount
|
|
||||||
)
|
|
||||||
return reblogContext
|
|
||||||
}
|
|
||||||
guard let reblogContext = _reblogContext else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// request repost or undo repost
|
// request repost or undo repost
|
||||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Status>, Error>
|
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Status>, Error>
|
||||||
@ -66,40 +50,6 @@ extension APIService {
|
|||||||
result = .failure(error)
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update repost state
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let authentication = authenticationBox.authentication
|
|
||||||
|
|
||||||
guard
|
|
||||||
let me = authentication.user(in: managedObjectContext),
|
|
||||||
let _status = record.object(in: managedObjectContext)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let response):
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: authentication.domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if reblogContext.isReblogged {
|
|
||||||
status.update(reblogsCount: max(0, status.reblogsCount - 1)) // undo API return count has delay. Needs -1 local
|
|
||||||
}
|
|
||||||
case .failure:
|
|
||||||
// rollback
|
|
||||||
status.update(reblogged: reblogContext.isReblogged, by: me)
|
|
||||||
status.update(reblogsCount: reblogContext.rebloggedCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try result.get()
|
let response = try result.get()
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@ -108,42 +58,19 @@ extension APIService {
|
|||||||
|
|
||||||
extension APIService {
|
extension APIService {
|
||||||
public func rebloggedBy(
|
public func rebloggedBy(
|
||||||
status: ManagedObjectRecord<Status>,
|
status: Mastodon.Entity.Status,
|
||||||
query: Mastodon.API.Statuses.RebloggedByQuery,
|
query: Mastodon.API.Statuses.RebloggedByQuery,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
|
||||||
let _statusID: Status.ID? = try? await managedObjectContext.perform {
|
|
||||||
guard let _status = status.object(in: managedObjectContext) else { return nil }
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
return status.id
|
|
||||||
}
|
|
||||||
guard let statusID = _statusID else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try await Mastodon.API.Statuses.rebloggedBy(
|
let response = try await Mastodon.API.Statuses.rebloggedBy(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
statusID: statusID,
|
statusID: status.reblog?.id ?? status.id,
|
||||||
query: query,
|
query: query,
|
||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
for entity in response.value {
|
|
||||||
_ = Persistence.MastodonUser.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: .init(
|
|
||||||
domain: authenticationBox.domain,
|
|
||||||
entity: entity,
|
|
||||||
cache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} // end for … in
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
@ -14,17 +14,16 @@ import MastodonSDK
|
|||||||
extension APIService {
|
extension APIService {
|
||||||
|
|
||||||
public func relationship(
|
public func relationship(
|
||||||
records: [ManagedObjectRecord<MastodonUser>],
|
accounts: [Mastodon.Entity.Account],
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Relationship]> {
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Relationship]> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let managedObjectContext = backgroundManagedObjectContext
|
||||||
|
|
||||||
let _query: Mastodon.API.Account.RelationshipQuery? = try? await managedObjectContext.perform {
|
let _query: Mastodon.API.Account.RelationshipQuery? = try? await managedObjectContext.perform {
|
||||||
var ids: [MastodonUser.ID] = []
|
var ids: [MastodonUser.ID] = []
|
||||||
for record in records {
|
for account in accounts {
|
||||||
guard let user = record.object(in: managedObjectContext) else { continue }
|
guard account.id != authenticationBox.userID else { continue }
|
||||||
guard user.id != authenticationBox.userID else { continue }
|
ids.append(account.id)
|
||||||
ids.append(user.id)
|
|
||||||
}
|
}
|
||||||
guard !ids.isEmpty else { return nil }
|
guard !ids.isEmpty else { return nil }
|
||||||
return Mastodon.API.Account.RelationshipQuery(ids: ids)
|
return Mastodon.API.Account.RelationshipQuery(ids: ids)
|
||||||
@ -40,28 +39,6 @@ extension APIService {
|
|||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
|
||||||
// assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let relationships = response.value
|
|
||||||
for record in records {
|
|
||||||
guard let user = record.object(in: managedObjectContext) else { continue }
|
|
||||||
guard let relationship = relationships.first(where: { $0.id == user.id }) else { continue }
|
|
||||||
|
|
||||||
Persistence.MastodonUser.update(
|
|
||||||
mastodonUser: user,
|
|
||||||
context: Persistence.MastodonUser.RelationshipContext(
|
|
||||||
entity: relationship,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} // end for in
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,39 +25,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
|
|
||||||
// user
|
|
||||||
for entity in response.value.accounts {
|
|
||||||
_ = Persistence.MastodonUser.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.MastodonUser.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
cache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// statuses
|
|
||||||
for entity in response.value.statuses {
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} // ent try await managedObjectContext.performChanges { … }
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,31 +72,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
#if !APP_EXTENSION
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
let status = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
Persistence.StatusEdit.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
statusEdits: responseHistory.value,
|
|
||||||
forStatus: status.status
|
|
||||||
)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,24 +30,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
#if !APP_EXTENSION
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,53 +27,22 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteStatus(
|
public func deleteStatus(
|
||||||
status: ManagedObjectRecord<Status>,
|
status: Mastodon.Entity.Status,
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Status> {
|
||||||
let authorization = authenticationBox.userAuthorization
|
let authorization = authenticationBox.userAuthorization
|
||||||
|
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
|
||||||
let _query: Mastodon.API.Statuses.DeleteStatusQuery? = try? await managedObjectContext.perform {
|
|
||||||
guard let _status = status.object(in: managedObjectContext) else { return nil }
|
|
||||||
let status = _status.reblog ?? _status
|
|
||||||
return Mastodon.API.Statuses.DeleteStatusQuery(id: status.id)
|
|
||||||
}
|
|
||||||
guard let query = _query else {
|
|
||||||
throw APIError.implicit(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = try await Mastodon.API.Statuses.deleteStatus(
|
let response = try await Mastodon.API.Statuses.deleteStatus(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
query: query,
|
query: Mastodon.API.Statuses.DeleteStatusQuery(id: status.id),
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
guard let status = status.object(in: managedObjectContext) else { return }
|
|
||||||
managedObjectContext.delete(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
|
|
||||||
public func followTag(
|
public func followTag(
|
||||||
@ -44,7 +44,7 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
|
|
||||||
public func unfollowTag(
|
public func unfollowTag(
|
||||||
@ -61,31 +61,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension APIService {
|
|
||||||
func persistTag(
|
|
||||||
from response: Mastodon.Response.Content<Mastodon.Entity.Tag>,
|
|
||||||
domain: String,
|
|
||||||
authenticationBox: MastodonAuthenticationBox
|
|
||||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Tag> {
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
|
|
||||||
_ = Persistence.Tag.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Tag.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: response.value,
|
|
||||||
me: me,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -27,26 +27,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
let value = response.value.ancestors + response.value.descendants
|
|
||||||
|
|
||||||
for entity in value {
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
}
|
}
|
||||||
|
@ -43,24 +43,6 @@ extension APIService {
|
|||||||
authorization: authorization
|
authorization: authorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
|
||||||
for entity in response.value {
|
|
||||||
_ = Persistence.Status.createOrMerge(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: Persistence.Status.PersistContext(
|
|
||||||
domain: domain,
|
|
||||||
entity: entity,
|
|
||||||
me: me,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} // end func
|
} // end func
|
||||||
|
|
||||||
|
@ -1,75 +1,74 @@
|
|||||||
|
////
|
||||||
|
//// APIService+CoreData+Instance.swift
|
||||||
|
//// Mastodon
|
||||||
|
////
|
||||||
|
//// Created by Cirno MainasuK on 2021-10-9.
|
||||||
|
////
|
||||||
//
|
//
|
||||||
// APIService+CoreData+Instance.swift
|
//import Foundation
|
||||||
// Mastodon
|
//import CoreData
|
||||||
|
//import MastodonSDK
|
||||||
//
|
//
|
||||||
// Created by Cirno MainasuK on 2021-10-9.
|
//extension APIService.CoreData {
|
||||||
//
|
//
|
||||||
|
// static func createOrMergeInstance(
|
||||||
import Foundation
|
// into managedObjectContext: NSManagedObjectContext,
|
||||||
import CoreData
|
// domain: String,
|
||||||
import CoreDataStack
|
// entity: Mastodon.Entity.Instance,
|
||||||
import MastodonSDK
|
// networkDate: Date
|
||||||
|
// ) -> (instance: Instance, isCreated: Bool) {
|
||||||
extension APIService.CoreData {
|
// // fetch old mastodon user
|
||||||
|
// let old: Instance? = {
|
||||||
static func createOrMergeInstance(
|
// let request = Instance.sortedFetchRequest
|
||||||
into managedObjectContext: NSManagedObjectContext,
|
// request.predicate = Instance.predicate(domain: domain)
|
||||||
domain: String,
|
// request.fetchLimit = 1
|
||||||
entity: Mastodon.Entity.Instance,
|
// request.returnsObjectsAsFaults = false
|
||||||
networkDate: Date
|
// do {
|
||||||
) -> (instance: Instance, isCreated: Bool) {
|
// return try managedObjectContext.fetch(request).first
|
||||||
// fetch old mastodon user
|
// } catch {
|
||||||
let old: Instance? = {
|
// assertionFailure(error.localizedDescription)
|
||||||
let request = Instance.sortedFetchRequest
|
// return nil
|
||||||
request.predicate = Instance.predicate(domain: domain)
|
// }
|
||||||
request.fetchLimit = 1
|
// }()
|
||||||
request.returnsObjectsAsFaults = false
|
//
|
||||||
do {
|
// if let old = old {
|
||||||
return try managedObjectContext.fetch(request).first
|
// // merge old
|
||||||
} catch {
|
// APIService.CoreData.merge(
|
||||||
assertionFailure(error.localizedDescription)
|
// instance: old,
|
||||||
return nil
|
// entity: entity,
|
||||||
}
|
// domain: domain,
|
||||||
}()
|
// networkDate: networkDate
|
||||||
|
// )
|
||||||
if let old = old {
|
// return (old, false)
|
||||||
// merge old
|
// } else {
|
||||||
APIService.CoreData.merge(
|
// let instance = Instance.insert(
|
||||||
instance: old,
|
// into: managedObjectContext,
|
||||||
entity: entity,
|
// property: Instance.Property(domain: domain, version: entity.version)
|
||||||
domain: domain,
|
// )
|
||||||
networkDate: networkDate
|
// let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) }
|
||||||
)
|
// instance.update(configurationRaw: configurationRaw)
|
||||||
return (old, false)
|
//
|
||||||
} else {
|
// return (instance, true)
|
||||||
let instance = Instance.insert(
|
// }
|
||||||
into: managedObjectContext,
|
// }
|
||||||
property: Instance.Property(domain: domain, version: entity.version)
|
//
|
||||||
)
|
//}
|
||||||
let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) }
|
//
|
||||||
instance.update(configurationRaw: configurationRaw)
|
//extension APIService.CoreData {
|
||||||
|
//
|
||||||
return (instance, true)
|
// static func merge(
|
||||||
}
|
// instance: Instance,
|
||||||
}
|
// entity: Mastodon.Entity.Instance,
|
||||||
|
// domain: String,
|
||||||
}
|
// networkDate: Date
|
||||||
|
// ) {
|
||||||
extension APIService.CoreData {
|
// guard networkDate > instance.updatedAt else { return }
|
||||||
|
//
|
||||||
static func merge(
|
// let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) }
|
||||||
instance: Instance,
|
// instance.update(configurationRaw: configurationRaw)
|
||||||
entity: Mastodon.Entity.Instance,
|
// instance.version = entity.version
|
||||||
domain: String,
|
//
|
||||||
networkDate: Date
|
// instance.didUpdate(at: networkDate)
|
||||||
) {
|
// }
|
||||||
guard networkDate > instance.updatedAt else { return }
|
//
|
||||||
|
//}
|
||||||
let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) }
|
|
||||||
instance.update(configurationRaw: configurationRaw)
|
|
||||||
instance.version = entity.version
|
|
||||||
|
|
||||||
instance.didUpdate(at: networkDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,77 +1,76 @@
|
|||||||
import Foundation
|
//import Foundation
|
||||||
import CoreData
|
//import CoreData
|
||||||
import CoreDataStack
|
//import MastodonSDK
|
||||||
import MastodonSDK
|
//
|
||||||
|
//extension APIService.CoreData {
|
||||||
extension APIService.CoreData {
|
//
|
||||||
|
// public struct PersistContext {
|
||||||
public struct PersistContext {
|
// public let domain: String
|
||||||
public let domain: String
|
// public let entity: Mastodon.Entity.V2.Instance
|
||||||
public let entity: Mastodon.Entity.V2.Instance
|
// public let networkDate: Date
|
||||||
public let networkDate: Date
|
//
|
||||||
|
// public init(
|
||||||
public init(
|
// domain: String,
|
||||||
domain: String,
|
// entity: Mastodon.Entity.V2.Instance,
|
||||||
entity: Mastodon.Entity.V2.Instance,
|
// networkDate: Date
|
||||||
networkDate: Date
|
// ) {
|
||||||
) {
|
// self.domain = domain
|
||||||
self.domain = domain
|
// self.entity = entity
|
||||||
self.entity = entity
|
// self.networkDate = networkDate
|
||||||
self.networkDate = networkDate
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// static func createOrMergeInstance(
|
||||||
static func createOrMergeInstance(
|
// in managedObjectContext: NSManagedObjectContext,
|
||||||
in managedObjectContext: NSManagedObjectContext,
|
// context: PersistContext
|
||||||
context: PersistContext
|
// ) -> (instance: Instance, isCreated: Bool) {
|
||||||
) -> (instance: Instance, isCreated: Bool) {
|
// // fetch old mastodon user
|
||||||
// fetch old mastodon user
|
// let old: Instance? = {
|
||||||
let old: Instance? = {
|
// let request = Instance.sortedFetchRequest
|
||||||
let request = Instance.sortedFetchRequest
|
// request.predicate = Instance.predicate(domain: context.domain)
|
||||||
request.predicate = Instance.predicate(domain: context.domain)
|
// request.fetchLimit = 1
|
||||||
request.fetchLimit = 1
|
// request.returnsObjectsAsFaults = false
|
||||||
request.returnsObjectsAsFaults = false
|
// do {
|
||||||
do {
|
// return try managedObjectContext.fetch(request).first
|
||||||
return try managedObjectContext.fetch(request).first
|
// } catch {
|
||||||
} catch {
|
// assertionFailure(error.localizedDescription)
|
||||||
assertionFailure(error.localizedDescription)
|
// return nil
|
||||||
return nil
|
// }
|
||||||
}
|
// }()
|
||||||
}()
|
//
|
||||||
|
// if let old = old {
|
||||||
if let old = old {
|
// APIService.CoreData.merge(
|
||||||
APIService.CoreData.merge(
|
// instance: old,
|
||||||
instance: old,
|
// context: context
|
||||||
context: context
|
// )
|
||||||
)
|
// return (old, false)
|
||||||
return (old, false)
|
// } else {
|
||||||
} else {
|
// let instance = Instance.insert(
|
||||||
let instance = Instance.insert(
|
// into: managedObjectContext,
|
||||||
into: managedObjectContext,
|
// property: Instance.Property(domain: context.domain, version: context.entity.version)
|
||||||
property: Instance.Property(domain: context.domain, version: context.entity.version)
|
// )
|
||||||
)
|
// let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
|
||||||
let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
|
// instance.update(configurationV2Raw: configurationRaw)
|
||||||
instance.update(configurationV2Raw: configurationRaw)
|
//
|
||||||
|
// return (instance, true)
|
||||||
return (instance, true)
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
//}
|
||||||
}
|
//
|
||||||
|
//extension APIService.CoreData {
|
||||||
extension APIService.CoreData {
|
//
|
||||||
|
// static func merge(
|
||||||
static func merge(
|
// instance: Instance,
|
||||||
instance: Instance,
|
// context: PersistContext
|
||||||
context: PersistContext
|
// ) {
|
||||||
) {
|
// guard context.networkDate > instance.updatedAt else { return }
|
||||||
guard context.networkDate > instance.updatedAt else { return }
|
//
|
||||||
|
// let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
|
||||||
let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
|
// instance.update(configurationV2Raw: configurationRaw)
|
||||||
instance.update(configurationV2Raw: configurationRaw)
|
// instance.version = context.entity.version
|
||||||
instance.version = context.entity.version
|
//
|
||||||
|
// instance.didUpdate(at: context.networkDate)
|
||||||
instance.didUpdate(at: context.networkDate)
|
// }
|
||||||
}
|
//
|
||||||
|
//}
|
||||||
}
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
public final class InstanceService {
|
public final class InstanceService {
|
||||||
@ -16,7 +15,6 @@ public final class InstanceService {
|
|||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let backgroundManagedObjectContext: NSManagedObjectContext
|
|
||||||
weak var apiService: APIService?
|
weak var apiService: APIService?
|
||||||
weak var authenticationService: AuthenticationService?
|
weak var authenticationService: AuthenticationService?
|
||||||
|
|
||||||
@ -26,7 +24,6 @@ public final class InstanceService {
|
|||||||
apiService: APIService,
|
apiService: APIService,
|
||||||
authenticationService: AuthenticationService
|
authenticationService: AuthenticationService
|
||||||
) {
|
) {
|
||||||
self.backgroundManagedObjectContext = apiService.backgroundManagedObjectContext
|
|
||||||
self.apiService = apiService
|
self.apiService = apiService
|
||||||
self.authenticationService = authenticationService
|
self.authenticationService = authenticationService
|
||||||
|
|
||||||
@ -68,56 +65,38 @@ extension InstanceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateInstance(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.Instance>) -> AnyPublisher<Void, Error> {
|
private func updateInstance(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.Instance>) -> AnyPublisher<Void, Error> {
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
return Future<Void, Error> { promise in
|
||||||
return managedObjectContext.performChanges {
|
|
||||||
// get instance
|
|
||||||
let (instance, _) = APIService.CoreData.createOrMergeInstance(
|
|
||||||
into: managedObjectContext,
|
|
||||||
domain: domain,
|
|
||||||
entity: response.value,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
|
|
||||||
// update instance
|
// update instance
|
||||||
AuthenticationServiceProvider.shared.update(instance: instance, where: domain)
|
AuthenticationServiceProvider.shared.update(instance: response.value, where: domain)
|
||||||
}
|
promise(.success(()))
|
||||||
.setFailureType(to: Error.self)
|
|
||||||
.tryMap { result in
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
break
|
|
||||||
case .failure(let error):
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// .setFailureType(to: Error.self)
|
||||||
|
// .tryMap { result in
|
||||||
|
// switch result {
|
||||||
|
// case .success:
|
||||||
|
// break
|
||||||
|
// case .failure(let error):
|
||||||
|
// throw error
|
||||||
|
// }
|
||||||
|
// }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateInstanceV2(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.V2.Instance>) -> AnyPublisher<Void, Error> {
|
private func updateInstanceV2(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.V2.Instance>) -> AnyPublisher<Void, Error> {
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
return Future<Void, Error> { promise in
|
||||||
return managedObjectContext.performChanges {
|
|
||||||
// get instance
|
|
||||||
let (instance, _) = APIService.CoreData.createOrMergeInstance(
|
|
||||||
in: managedObjectContext,
|
|
||||||
context: .init(
|
|
||||||
domain: domain,
|
|
||||||
entity: response.value,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// update instance
|
// update instance
|
||||||
AuthenticationServiceProvider.shared.update(instance: instance, where: domain)
|
AuthenticationServiceProvider.shared.update(instanceV2: response.value, where: domain)
|
||||||
}
|
promise(.success(()))
|
||||||
.setFailureType(to: Error.self)
|
|
||||||
.tryMap { result in
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
break
|
|
||||||
case .failure(let error):
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// .setFailureType(to: Error.self)
|
||||||
|
// .tryMap { result in
|
||||||
|
// switch result {
|
||||||
|
// case .success:
|
||||||
|
// break
|
||||||
|
// case .failure(let error):
|
||||||
|
// throw error
|
||||||
|
// }
|
||||||
|
// }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,12 +101,12 @@ extension NotificationService {
|
|||||||
return try await managedObjectContext.perform {
|
return try await managedObjectContext.perform {
|
||||||
var items: [UIApplicationShortcutItem] = []
|
var items: [UIApplicationShortcutItem] = []
|
||||||
for authentication in AuthenticationServiceProvider.shared.authentications {
|
for authentication in AuthenticationServiceProvider.shared.authentications {
|
||||||
guard let user = authentication.user(in: managedObjectContext) else { continue }
|
let user = authentication.user
|
||||||
let accessToken = authentication.userAccessToken
|
let accessToken = authentication.userAccessToken
|
||||||
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken)
|
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken)
|
||||||
guard count > 0 else { continue }
|
guard count > 0 else { continue }
|
||||||
|
|
||||||
let title = "@\(user.acctWithDomain)"
|
let title = "@\(user.acctWithDomainIfMissing(authentication.domain))"
|
||||||
let subtitle = L10n.A11y.Plural.Count.Unread.notification(count)
|
let subtitle = L10n.A11y.Plural.Count.Unread.notification(count)
|
||||||
|
|
||||||
let item = UIApplicationShortcutItem(
|
let item = UIApplicationShortcutItem(
|
||||||
|
@ -61,8 +61,8 @@ extension ComposeContentViewModel {
|
|||||||
|
|
||||||
// configure status
|
// configure status
|
||||||
context.managedObjectContext.performAndWait {
|
context.managedObjectContext.performAndWait {
|
||||||
guard let replyTo = status.object(in: context.managedObjectContext) else { return }
|
|
||||||
cell.statusView.configure(status: replyTo)
|
cell.statusView.configure(status: status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
|
||||||
import Meta
|
import Meta
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
@ -23,7 +22,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||||||
|
|
||||||
public enum ComposeContext {
|
public enum ComposeContext {
|
||||||
case composeStatus
|
case composeStatus
|
||||||
case editStatus(status: Status, statusSource: Mastodon.Entity.StatusSource)
|
case editStatus(status: Mastodon.Entity.Status, statusSource: Mastodon.Entity.StatusSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
@ -156,31 +155,25 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||||||
self.visibility = {
|
self.visibility = {
|
||||||
// default private when user locked
|
// default private when user locked
|
||||||
var visibility: Mastodon.Entity.Status.Visibility = {
|
var visibility: Mastodon.Entity.Status.Visibility = {
|
||||||
guard let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else {
|
let author = authContext.mastodonAuthenticationBox.authentication.user
|
||||||
return .public
|
|
||||||
}
|
|
||||||
return author.locked ? .private : .public
|
return author.locked ? .private : .public
|
||||||
}()
|
}()
|
||||||
// set visibility for reply post
|
// set visibility for reply post
|
||||||
if case .reply(let record) = destination {
|
if case .reply(let status) = destination {
|
||||||
context.managedObjectContext.performAndWait {
|
let repliedStatusVisibility = status.visibility
|
||||||
guard let status = record.object(in: context.managedObjectContext) else {
|
switch repliedStatusVisibility {
|
||||||
assertionFailure()
|
case .public, .unlisted:
|
||||||
return
|
// keep default
|
||||||
}
|
break
|
||||||
let repliedStatusVisibility = status.visibility
|
case .private:
|
||||||
switch repliedStatusVisibility {
|
visibility = .private
|
||||||
case .public, .unlisted:
|
case .direct:
|
||||||
// keep default
|
visibility = .direct
|
||||||
break
|
case ._other:
|
||||||
case .private:
|
assertionFailure()
|
||||||
visibility = .private
|
break
|
||||||
case .direct:
|
case .none:
|
||||||
visibility = .direct
|
break
|
||||||
case ._other:
|
|
||||||
assertionFailure()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return visibility
|
return visibility
|
||||||
@ -191,7 +184,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if case let ComposeContext.editStatus(status, _) = composeContext {
|
if case let ComposeContext.editStatus(status, _) = composeContext {
|
||||||
if status.isContentSensitive {
|
if status.sensitive == true {
|
||||||
isContentWarningActive = true
|
isContentWarningActive = true
|
||||||
contentWarning = status.spoilerText ?? ""
|
contentWarning = status.spoilerText ?? ""
|
||||||
}
|
}
|
||||||
@ -201,7 +194,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||||||
if let pollExpiresAt = poll.expiresAt {
|
if let pollExpiresAt = poll.expiresAt {
|
||||||
pollExpireConfigurationOption = .init(closestDateToExpiry: pollExpiresAt)
|
pollExpireConfigurationOption = .init(closestDateToExpiry: pollExpiresAt)
|
||||||
}
|
}
|
||||||
pollOptions = poll.options.sortedByIndex().map {
|
pollOptions = poll.options.map {
|
||||||
let option = PollComposeItem.Option()
|
let option = PollComposeItem.Option()
|
||||||
option.text = $0.title
|
option.text = $0.title
|
||||||
return option
|
return option
|
||||||
@ -218,52 +211,40 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||||||
// setup initial value
|
// setup initial value
|
||||||
let initialContentWithSpace = initialContent.isEmpty ? "" : initialContent + " "
|
let initialContentWithSpace = initialContent.isEmpty ? "" : initialContent + " "
|
||||||
switch destination {
|
switch destination {
|
||||||
case .reply(let record):
|
case .reply(let status):
|
||||||
context.managedObjectContext.performAndWait {
|
let author = authContext.mastodonAuthenticationBox.authentication.user
|
||||||
guard let status = record.object(in: context.managedObjectContext) else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
|
||||||
|
|
||||||
var mentionAccts: [String] = []
|
var mentionAccts: [String] = []
|
||||||
if author?.id != status.author.id {
|
if author.id != status.account.id {
|
||||||
mentionAccts.append("@" + status.author.acct)
|
mentionAccts.append("@" + status.account.acct)
|
||||||
}
|
|
||||||
let mentions = status.mentions
|
|
||||||
.filter { author?.id != $0.id }
|
|
||||||
for mention in mentions {
|
|
||||||
let acct = "@" + mention.acct
|
|
||||||
guard !mentionAccts.contains(acct) else { continue }
|
|
||||||
mentionAccts.append(acct)
|
|
||||||
}
|
|
||||||
for acct in mentionAccts {
|
|
||||||
UITextChecker.learnWord(acct)
|
|
||||||
}
|
|
||||||
if let spoilerText = status.spoilerText, !spoilerText.isEmpty {
|
|
||||||
self.isContentWarningActive = true
|
|
||||||
self.contentWarning = spoilerText
|
|
||||||
}
|
|
||||||
|
|
||||||
let initialComposeContent = mentionAccts.joined(separator: " ")
|
|
||||||
let preInsertedContent = initialComposeContent.isEmpty ? "" : initialComposeContent + " "
|
|
||||||
self.initialContent = preInsertedContent + initialContentWithSpace
|
|
||||||
self.content = preInsertedContent + initialContentWithSpace
|
|
||||||
}
|
}
|
||||||
|
let mentions = status.mentions?
|
||||||
|
.filter { author.id != $0.id } ?? []
|
||||||
|
for mention in mentions {
|
||||||
|
let acct = "@" + mention.acct
|
||||||
|
guard !mentionAccts.contains(acct) else { continue }
|
||||||
|
mentionAccts.append(acct)
|
||||||
|
}
|
||||||
|
for acct in mentionAccts {
|
||||||
|
UITextChecker.learnWord(acct)
|
||||||
|
}
|
||||||
|
if let spoilerText = status.spoilerText, !spoilerText.isEmpty {
|
||||||
|
self.isContentWarningActive = true
|
||||||
|
self.contentWarning = spoilerText
|
||||||
|
}
|
||||||
|
|
||||||
|
let initialComposeContent = mentionAccts.joined(separator: " ")
|
||||||
|
let preInsertedContent = initialComposeContent.isEmpty ? "" : initialComposeContent + " "
|
||||||
|
self.initialContent = preInsertedContent + initialContentWithSpace
|
||||||
|
self.content = preInsertedContent + initialContentWithSpace
|
||||||
case .topLevel:
|
case .topLevel:
|
||||||
self.initialContent = initialContentWithSpace
|
self.initialContent = initialContentWithSpace
|
||||||
self.content = initialContentWithSpace
|
self.content = initialContentWithSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
// set limit
|
// set limit
|
||||||
let _configuration: Mastodon.Entity.Instance.Configuration? = {
|
let _configuration: Mastodon.Entity.Instance.Configuration? = authContext.mastodonAuthenticationBox.authentication.instance?.configuration
|
||||||
var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
|
||||||
context.managedObjectContext.performAndWait {
|
|
||||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
|
||||||
configuration = authentication.instance(in: context.managedObjectContext)?.configuration
|
|
||||||
}
|
|
||||||
return configuration
|
|
||||||
}()
|
|
||||||
if let configuration = _configuration {
|
if let configuration = _configuration {
|
||||||
// set character limit
|
// set character limit
|
||||||
if let maxCharacters = configuration.statuses?.maxCharacters {
|
if let maxCharacters = configuration.statuses?.maxCharacters {
|
||||||
@ -288,22 +269,22 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||||||
case .composeStatus:
|
case .composeStatus:
|
||||||
self.isVisibilityButtonEnabled = true
|
self.isVisibilityButtonEnabled = true
|
||||||
case let .editStatus(status, _):
|
case let .editStatus(status, _):
|
||||||
if let visibility = Mastodon.Entity.Status.Visibility(rawValue: status.visibility.rawValue) {
|
if let visibility = status.visibility {
|
||||||
self.visibility = visibility
|
self.visibility = visibility
|
||||||
}
|
}
|
||||||
self.isVisibilityButtonEnabled = false
|
self.isVisibilityButtonEnabled = false
|
||||||
self.attachmentViewModels = status.attachments.compactMap {
|
self.attachmentViewModels = status.mediaAttachments?.compactMap { attachment -> AttachmentViewModel? in
|
||||||
guard let assetURL = $0.assetURL, let url = URL(string: assetURL) else { return nil }
|
guard let assetURL = attachment.url, let url = URL(string: assetURL) else { return nil }
|
||||||
let attachmentViewModel = AttachmentViewModel(
|
let attachmentViewModel = AttachmentViewModel(
|
||||||
api: context.apiService,
|
api: context.apiService,
|
||||||
authContext: authContext,
|
authContext: authContext,
|
||||||
input: .mastodonAssetUrl(url, $0.id),
|
input: .mastodonAssetUrl(url, attachment.id),
|
||||||
sizeLimit: sizeLimit,
|
sizeLimit: sizeLimit,
|
||||||
delegate: self
|
delegate: self
|
||||||
)
|
)
|
||||||
attachmentViewModel.caption = $0.altDescription ?? ""
|
attachmentViewModel.caption = attachment.description ?? ""
|
||||||
return attachmentViewModel
|
return attachmentViewModel
|
||||||
}
|
} ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
bind()
|
bind()
|
||||||
@ -318,10 +299,10 @@ extension ComposeContentViewModel {
|
|||||||
$authContext
|
$authContext
|
||||||
.sink { [weak self] authContext in
|
.sink { [weak self] authContext in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let user = authContext.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else { return }
|
let user = authContext.mastodonAuthenticationBox.authentication.user
|
||||||
self.avatarURL = user.avatarImageURL()
|
self.avatarURL = user.avatarImageURL()
|
||||||
self.name = user.nameMetaContent ?? PlaintextMetaContent(string: user.displayNameWithFallback)
|
self.name = user.nameMetaContent ?? PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||||
self.username = user.acctWithDomain
|
self.username = user.acctWithDomainIfMissing(authContext.mastodonAuthenticationBox.domain)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
@ -503,7 +484,7 @@ extension ComposeContentViewModel {
|
|||||||
extension ComposeContentViewModel {
|
extension ComposeContentViewModel {
|
||||||
public enum Destination {
|
public enum Destination {
|
||||||
case topLevel
|
case topLevel
|
||||||
case reply(parent: ManagedObjectRecord<Status>)
|
case reply(parent: Mastodon.Entity.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ScrollViewState {
|
public enum ScrollViewState {
|
||||||
@ -562,10 +543,8 @@ extension ComposeContentViewModel {
|
|||||||
|
|
||||||
// author
|
// author
|
||||||
let managedObjectContext = self.context.managedObjectContext
|
let managedObjectContext = self.context.managedObjectContext
|
||||||
var _author: ManagedObjectRecord<MastodonUser>?
|
var _author: Mastodon.Entity.Account? = authContext.mastodonAuthenticationBox.authentication.user
|
||||||
managedObjectContext.performAndWait {
|
|
||||||
_author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord
|
|
||||||
}
|
|
||||||
guard let author = _author else {
|
guard let author = _author else {
|
||||||
throw AppError.badAuthentication
|
throw AppError.badAuthentication
|
||||||
}
|
}
|
||||||
@ -618,13 +597,7 @@ extension ComposeContentViewModel {
|
|||||||
|
|
||||||
// author
|
// author
|
||||||
let managedObjectContext = self.context.managedObjectContext
|
let managedObjectContext = self.context.managedObjectContext
|
||||||
var _author: ManagedObjectRecord<MastodonUser>?
|
var _author = authContext.mastodonAuthenticationBox.authentication.user
|
||||||
managedObjectContext.performAndWait {
|
|
||||||
_author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord
|
|
||||||
}
|
|
||||||
guard let author = _author else {
|
|
||||||
throw AppError.badAuthentication
|
|
||||||
}
|
|
||||||
|
|
||||||
// poll
|
// poll
|
||||||
_ = try {
|
_ = try {
|
||||||
@ -645,7 +618,7 @@ extension ComposeContentViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return MastodonEditStatusPublisher(statusID: status.id,
|
return MastodonEditStatusPublisher(statusID: status.id,
|
||||||
author: author,
|
author: _author,
|
||||||
isContentWarningComposing: isContentWarningActive,
|
isContentWarningComposing: isContentWarningActive,
|
||||||
contentWarning: contentWarning,
|
contentWarning: contentWarning,
|
||||||
content: content,
|
content: content,
|
||||||
@ -817,3 +790,27 @@ extension ComposeContentViewModel: AttachmentViewModelDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Mastodon.Entity.Account {
|
||||||
|
public var nameMetaContent: MastodonMetaContent? {
|
||||||
|
do {
|
||||||
|
let content = MastodonContent(content: displayNameWithFallback, emojis: emojis?.asDictionary ?? [:])
|
||||||
|
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||||
|
return metaContent
|
||||||
|
} catch {
|
||||||
|
assertionFailure()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var bioMetaContent: MastodonMetaContent? {
|
||||||
|
do {
|
||||||
|
let content = MastodonContent(content: note, emojis: emojis?.asDictionary ?? [:])
|
||||||
|
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||||
|
return metaContent
|
||||||
|
} catch {
|
||||||
|
assertionFailure()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import Combine
|
import Combine
|
||||||
@ -10,8 +8,8 @@ import Combine
|
|||||||
public final class MastodonEditStatusPublisher: NSObject, ProgressReporting {
|
public final class MastodonEditStatusPublisher: NSObject, ProgressReporting {
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
public let statusID: Status.ID
|
public let statusID: Mastodon.Entity.Status.ID
|
||||||
public let author: ManagedObjectRecord<MastodonUser>
|
public let author: Mastodon.Entity.Account
|
||||||
|
|
||||||
// content warning
|
// content warning
|
||||||
public let isContentWarningComposing: Bool
|
public let isContentWarningComposing: Bool
|
||||||
@ -40,8 +38,8 @@ public final class MastodonEditStatusPublisher: NSObject, ProgressReporting {
|
|||||||
public var reactor: StatusPublisherReactor?
|
public var reactor: StatusPublisherReactor?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
statusID: Status.ID,
|
statusID: Mastodon.Entity.Status.ID,
|
||||||
author: ManagedObjectRecord<MastodonUser>,
|
author: Mastodon.Entity.Account,
|
||||||
isContentWarningComposing: Bool,
|
isContentWarningComposing: Bool,
|
||||||
contentWarning: String,
|
contentWarning: String,
|
||||||
content: String,
|
content: String,
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
@ -17,9 +15,9 @@ public final class MastodonStatusPublisher: NSObject, ProgressReporting {
|
|||||||
// Input
|
// Input
|
||||||
|
|
||||||
// author
|
// author
|
||||||
public let author: ManagedObjectRecord<MastodonUser>
|
public let author: Mastodon.Entity.Account
|
||||||
// refer
|
// refer
|
||||||
public let replyTo: ManagedObjectRecord<Status>?
|
public let replyTo: Mastodon.Entity.Status?
|
||||||
// content warning
|
// content warning
|
||||||
public let isContentWarningComposing: Bool
|
public let isContentWarningComposing: Bool
|
||||||
public let contentWarning: String
|
public let contentWarning: String
|
||||||
@ -47,8 +45,8 @@ public final class MastodonStatusPublisher: NSObject, ProgressReporting {
|
|||||||
public var reactor: StatusPublisherReactor?
|
public var reactor: StatusPublisherReactor?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
author: ManagedObjectRecord<MastodonUser>,
|
author: Mastodon.Entity.Account,
|
||||||
replyTo: ManagedObjectRecord<Status>?,
|
replyTo: Mastodon.Entity.Status?,
|
||||||
isContentWarningComposing: Bool,
|
isContentWarningComposing: Bool,
|
||||||
contentWarning: String,
|
contentWarning: String,
|
||||||
content: String,
|
content: String,
|
||||||
@ -161,10 +159,7 @@ extension MastodonStatusPublisher: StatusPublisher {
|
|||||||
guard pollOptions != nil else { return nil }
|
guard pollOptions != nil else { return nil }
|
||||||
return self.pollExpireConfigurationOption.seconds
|
return self.pollExpireConfigurationOption.seconds
|
||||||
}()
|
}()
|
||||||
let inReplyToID: Mastodon.Entity.Status.ID? = try await api.backgroundManagedObjectContext.perform {
|
let inReplyToID: Mastodon.Entity.Status.ID? = self.replyTo?.id
|
||||||
guard let replyTo = self.replyTo?.object(in: api.backgroundManagedObjectContext) else { return nil }
|
|
||||||
return replyTo.id
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = Mastodon.API.Statuses.PublishStatusQuery(
|
let query = Mastodon.API.Statuses.PublishStatusQuery(
|
||||||
status: content,
|
status: content,
|
||||||
|
@ -228,20 +228,7 @@ extension NotificationView.ViewModel {
|
|||||||
|
|
||||||
let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed
|
let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed
|
||||||
|
|
||||||
lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = {
|
lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = self?.authContext?.mastodonAuthenticationBox.authentication.instanceV2?.configuration
|
||||||
guard
|
|
||||||
let self = self,
|
|
||||||
let context = self.context,
|
|
||||||
let authContext = self.authContext
|
|
||||||
else { return nil }
|
|
||||||
|
|
||||||
var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil
|
|
||||||
context.managedObjectContext.performAndWait {
|
|
||||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
|
||||||
configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2
|
|
||||||
}
|
|
||||||
return configuration
|
|
||||||
}()
|
|
||||||
|
|
||||||
let menuContext = NotificationView.AuthorMenuContext(
|
let menuContext = NotificationView.AuthorMenuContext(
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
@ -18,29 +17,11 @@ import NaturalLanguage
|
|||||||
extension StatusView {
|
extension StatusView {
|
||||||
|
|
||||||
static let statusFilterWorkingQueue = DispatchQueue(label: "StatusFilterWorkingQueue")
|
static let statusFilterWorkingQueue = DispatchQueue(label: "StatusFilterWorkingQueue")
|
||||||
|
|
||||||
public func configure(feed: Feed) {
|
|
||||||
switch feed.kind {
|
|
||||||
case .home:
|
|
||||||
guard let status = feed.status else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configure(status: status)
|
|
||||||
case .notificationAll:
|
|
||||||
assertionFailure("TODO")
|
|
||||||
case .notificationMentions:
|
|
||||||
assertionFailure("TODO")
|
|
||||||
case .none:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusView {
|
extension StatusView {
|
||||||
|
|
||||||
public func configure(status: Status, statusEdit: StatusEdit) {
|
public func configure(status: Mastodon.Entity.Status, statusEdit: Mastodon.Entity.StatusEdit) {
|
||||||
viewModel.objects.insert(status)
|
viewModel.objects.insert(status)
|
||||||
if let reblog = status.reblog {
|
if let reblog = status.reblog {
|
||||||
viewModel.objects.insert(reblog)
|
viewModel.objects.insert(reblog)
|
||||||
@ -66,7 +47,7 @@ extension StatusView {
|
|||||||
viewModel.isContentReveal = true
|
viewModel.isContentReveal = true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func configure(status: Status) {
|
public func configure(status: Mastodon.Entity.Status) {
|
||||||
viewModel.objects.insert(status)
|
viewModel.objects.insert(status)
|
||||||
if let reblog = status.reblog {
|
if let reblog = status.reblog {
|
||||||
viewModel.objects.insert(reblog)
|
viewModel.objects.insert(reblog)
|
||||||
@ -99,7 +80,7 @@ extension StatusView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension StatusView {
|
extension StatusView {
|
||||||
private func configureHeader(status: Status) {
|
private func configureHeader(status: Mastodon.Entity.Status) {
|
||||||
if let _ = status.reblog {
|
if let _ = status.reblog {
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
status.author.publisher(for: \.displayName),
|
status.author.publisher(for: \.displayName),
|
||||||
@ -189,7 +170,7 @@ extension StatusView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func configureAuthor(author: MastodonUser) {
|
public func configureAuthor(author: Mastodon.Entity.Account) {
|
||||||
// author avatar
|
// author avatar
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
author.publisher(for: \.avatar),
|
author.publisher(for: \.avatar),
|
||||||
@ -300,7 +281,7 @@ extension StatusView {
|
|||||||
configure(status: originalStatus)
|
configure(status: originalStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureTranslated(status: Status) {
|
func configureTranslated(status: Mastodon.Entity.Status) {
|
||||||
let translatedContent: Status.TranslatedContent? = {
|
let translatedContent: Status.TranslatedContent? = {
|
||||||
if let translatedContent = status.reblog?.translatedContent {
|
if let translatedContent = status.reblog?.translatedContent {
|
||||||
return translatedContent
|
return translatedContent
|
||||||
@ -330,7 +311,7 @@ extension StatusView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureContent(statusEdit: StatusEdit, status: Status) {
|
private func configureContent(statusEdit: Mastodon.Entity.StatusEdit, status: Mastodon.Entity.Status) {
|
||||||
statusEdit.spoilerText.map {
|
statusEdit.spoilerText.map {
|
||||||
viewModel.spoilerContent = PlaintextMetaContent(string: $0)
|
viewModel.spoilerContent = PlaintextMetaContent(string: $0)
|
||||||
}
|
}
|
||||||
@ -350,7 +331,7 @@ extension StatusView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureContent(status: Status) {
|
private func configureContent(status: Mastodon.Entity.Status) {
|
||||||
guard status.translatedContent == nil else {
|
guard status.translatedContent == nil else {
|
||||||
return configureTranslated(status: status)
|
return configureTranslated(status: status)
|
||||||
}
|
}
|
||||||
@ -404,7 +385,7 @@ extension StatusView {
|
|||||||
viewModel.mediaViewConfigurations = configurations
|
viewModel.mediaViewConfigurations = configurations
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configurePollHistory(statusEdit: StatusEdit) {
|
private func configurePollHistory(statusEdit: Mastodon.Entity.StatusEdit) {
|
||||||
guard let poll = statusEdit.poll else { return }
|
guard let poll = statusEdit.poll else { return }
|
||||||
|
|
||||||
let pollItems = poll.options.map { PollItem.history(option: $0) }
|
let pollItems = poll.options.map { PollItem.history(option: $0) }
|
||||||
@ -417,7 +398,7 @@ extension StatusView {
|
|||||||
pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(_snapshot)
|
pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(_snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configurePoll(status: Status) {
|
private func configurePoll(status: Mastodon.Entity.Status) {
|
||||||
let status = status.reblog ?? status
|
let status = status.reblog ?? status
|
||||||
|
|
||||||
if let poll = status.poll {
|
if let poll = status.poll {
|
||||||
@ -488,7 +469,7 @@ extension StatusView {
|
|||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureCard(status: Status) {
|
private func configureCard(status: Mastodon.Entity.Status) {
|
||||||
let status = status.reblog ?? status
|
let status = status.reblog ?? status
|
||||||
if viewModel.mediaViewConfigurations.isEmpty {
|
if viewModel.mediaViewConfigurations.isEmpty {
|
||||||
status.publisher(for: \.card)
|
status.publisher(for: \.card)
|
||||||
@ -499,7 +480,7 @@ extension StatusView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureToolbar(status: Status) {
|
private func configureToolbar(status: Mastodon.Entity.Status) {
|
||||||
let status = status.reblog ?? status
|
let status = status.reblog ?? status
|
||||||
|
|
||||||
status.publisher(for: \.repliesCount)
|
status.publisher(for: \.repliesCount)
|
||||||
@ -560,7 +541,7 @@ extension StatusView {
|
|||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureFilter(status: Status) {
|
private func configureFilter(status: Mastodon.Entity.Status) {
|
||||||
let status = status.reblog ?? status
|
let status = status.reblog ?? status
|
||||||
|
|
||||||
let content = status.content.lowercased()
|
let content = status.content.lowercased()
|
||||||
|
@ -671,7 +671,7 @@ extension StatusView.ViewModel {
|
|||||||
publishersTwo.eraseToAnyPublisher(),
|
publishersTwo.eraseToAnyPublisher(),
|
||||||
publishersThree.eraseToAnyPublisher()
|
publishersThree.eraseToAnyPublisher()
|
||||||
).eraseToAnyPublisher()
|
).eraseToAnyPublisher()
|
||||||
.sink { tupleOne, tupleTwo, tupleThree in
|
.sink { [weak self] tupleOne, tupleTwo, tupleThree in
|
||||||
let (authorName, isMyself) = tupleOne
|
let (authorName, isMyself) = tupleOne
|
||||||
let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo
|
let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo
|
||||||
let (translatedFromLanguage, language) = tupleThree
|
let (translatedFromLanguage, language) = tupleThree
|
||||||
@ -681,21 +681,8 @@ extension StatusView.ViewModel {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = {
|
lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? =
|
||||||
guard
|
self?.authContext?.mastodonAuthenticationBox.authentication.instanceV2?.configuration
|
||||||
let context = self.context,
|
|
||||||
let authContext = self.authContext
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil
|
|
||||||
context.managedObjectContext.performAndWait {
|
|
||||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
|
||||||
configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2
|
|
||||||
}
|
|
||||||
return configuration
|
|
||||||
}()
|
|
||||||
|
|
||||||
let menuContext = StatusAuthorView.AuthorMenuContext(
|
let menuContext = StatusAuthorView.AuthorMenuContext(
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -85,13 +85,7 @@ private extension FollowersCountWidgetProvider {
|
|||||||
return completion(.unconfigured)
|
return completion(.unconfigured)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard
|
let desiredAccount = configuration.account ?? authBox.authentication.user.acctWithDomainIfMissing(authBox.domain)
|
||||||
let desiredAccount = configuration.account ?? authBox.authentication.user(
|
|
||||||
in: WidgetExtension.appContext.managedObjectContext
|
|
||||||
)?.acctWithDomain
|
|
||||||
else {
|
|
||||||
return completion(.unconfigured)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let resultingAccount = try await WidgetExtension.appContext
|
let resultingAccount = try await WidgetExtension.appContext
|
||||||
|
@ -86,12 +86,9 @@ private extension MultiFollowersCountWidgetProvider {
|
|||||||
|
|
||||||
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
|
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
|
||||||
desiredAccounts = configuredAccounts
|
desiredAccounts = configuredAccounts
|
||||||
} else if let currentlyLoggedInAccount = authBox.authentication.user(
|
|
||||||
in: WidgetExtension.appContext.managedObjectContext
|
|
||||||
)?.acctWithDomain {
|
|
||||||
desiredAccounts = [currentlyLoggedInAccount]
|
|
||||||
} else {
|
} else {
|
||||||
return completion(.unconfigured)
|
let currentlyLoggedInAccount = authBox.authentication.user.acctWithDomainIfMissing(authBox.domain)
|
||||||
|
desiredAccounts = [currentlyLoggedInAccount]
|
||||||
}
|
}
|
||||||
|
|
||||||
var accounts = [MultiFollowersEntryAccountable]()
|
var accounts = [MultiFollowersEntryAccountable]()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user