diff --git a/Mastodon/Diffable/Status/StatusSection.swift b/Mastodon/Diffable/Status/StatusSection.swift
index 38b8e641f..8ccb32c0c 100644
--- a/Mastodon/Diffable/Status/StatusSection.swift
+++ b/Mastodon/Diffable/Status/StatusSection.swift
@@ -27,6 +27,7 @@ extension StatusSection {
static let logger = Logger(subsystem: "StatusSection", category: "logic")
struct Configuration {
+ let context: AppContext
let authContext: AuthContext
weak var statusTableViewCellDelegate: StatusTableViewCellDelegate?
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
@@ -250,6 +251,7 @@ extension StatusSection {
statusView: cell.statusView
)
+ cell.statusView.viewModel.context = configuration.context
cell.statusView.viewModel.authContext = configuration.authContext
cell.configure(
diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift
index 64b4d3b6a..caa1f8460 100644
--- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift
+++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift
@@ -18,6 +18,7 @@ extension DiscoveryCommunityViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,
diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift
index afa0594d5..f36812538 100644
--- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift
+++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift
@@ -18,6 +18,7 @@ extension DiscoveryPostsViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,
diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift
index 8d8b0126a..c7c0a3bd7 100644
--- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift
+++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift
@@ -20,6 +20,7 @@ extension HashtagTimelineViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,
diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift
index 29bff623b..ff3224d3d 100644
--- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift
+++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift
@@ -22,6 +22,7 @@ extension HomeTimelineViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,
diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift
index 69075a8ce..bb9148687 100644
--- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift
+++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift
@@ -17,6 +17,7 @@ extension BookmarkViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,
diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift
index 3723dae5d..e0f741f62 100644
--- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift
+++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift
@@ -17,6 +17,7 @@ extension FavoriteViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,
diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift
index 863d7b44e..4992e653a 100644
--- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift
+++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift
@@ -18,6 +18,7 @@ extension UserTimelineViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,
diff --git a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift
index 834d478e6..8846c8b95 100644
--- a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift
+++ b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift
@@ -24,6 +24,7 @@ extension ThreadViewModel {
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
+ context: context,
authContext: authContext,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,
diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion
index 2145ac780..e660b0a08 100644
--- a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion
+++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion
@@ -3,6 +3,6 @@
_XCCurrentVersionName
- CoreData 5.xcdatamodel
+ CoreData 6.xcdatamodel
diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 6.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 6.xcdatamodel/contents
new file mode 100644
index 000000000..3249c5510
--- /dev/null
+++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 6.xcdatamodel/contents
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift
index cc21e8351..c11a92b76 100644
--- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift
+++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift
@@ -16,7 +16,8 @@ public final class Instance: NSManagedObject {
@NSManaged public private(set) var updatedAt: Date
@NSManaged public private(set) var configurationRaw: Data?
-
+ @NSManaged public private(set) var configurationV2Raw: Data?
+
// MARK: one-to-many relationships
@NSManaged public var authentications: Set
}
@@ -44,6 +45,10 @@ extension Instance {
self.configurationRaw = configurationRaw
}
+ public func update(configurationV2Raw: Data?) {
+ self.configurationV2Raw = configurationV2Raw
+ }
+
public func didUpdate(at networkDate: Date) {
self.updatedAt = networkDate
}
diff --git a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift
index 7e925b665..619abc91e 100644
--- a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift
+++ b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift
@@ -25,8 +25,42 @@ extension Instance {
}
extension Instance {
- public var canFollowTags: Bool {
- guard let majorVersionString = version?.split(separator: ".").first else { return false }
- return Int(majorVersionString) == 4 // following Tags is support beginning with Mastodon v4.0.0
+ public var configurationV2: Mastodon.Entity.V2.Instance.Configuration? {
+ guard let configurationRaw = configurationV2Raw else { return nil }
+ guard 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
+ }
+}
+
+extension String {
+ public func majorServerVersion(greaterThanOrEquals comparedVersion: Int) -> Bool {
+ guard
+ let majorVersionString = split(separator: ".").first,
+ let majorVersionInt = Int(majorVersionString)
+ else { return false }
+
+ return majorVersionInt >= comparedVersion
+ }
+}
+
+extension Instance {
+ var isTranslationEnabled: Bool {
+ if let configuration = configurationV2 {
+ return configuration.translation?.enabled == true
+ }
+ return false
}
}
diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift
index 93bfcf09a..eb39b5585 100644
--- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift
+++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift
@@ -20,4 +20,9 @@ extension APIService {
return Mastodon.API.Instance.instance(session: session, domain: domain)
}
+ public func instanceV2(
+ domain: String
+ ) -> AnyPublisher, Error> {
+ return Mastodon.API.V2.Instance.instance(session: session, domain: domain)
+ }
}
diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+InstanceV2.swift b/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+InstanceV2.swift
new file mode 100644
index 000000000..17ebb5f55
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+InstanceV2.swift
@@ -0,0 +1,69 @@
+import os.log
+import Foundation
+import CoreData
+import CoreDataStack
+import MastodonSDK
+
+extension APIService.CoreData {
+
+ static func createOrMergeInstanceV2(
+ into managedObjectContext: NSManagedObjectContext,
+ domain: String,
+ entity: Mastodon.Entity.V2.Instance,
+ networkDate: Date,
+ log: Logger
+ ) -> (instance: Instance, isCreated: Bool) {
+ // fetch old mastodon user
+ let old: Instance? = {
+ let request = Instance.sortedFetchRequest
+ request.predicate = Instance.predicate(domain: domain)
+ request.fetchLimit = 1
+ request.returnsObjectsAsFaults = false
+ do {
+ return try managedObjectContext.fetch(request).first
+ } catch {
+ assertionFailure(error.localizedDescription)
+ return nil
+ }
+ }()
+
+ if let old = old {
+ APIService.CoreData.mergeV2(
+ instance: old,
+ entity: entity,
+ domain: domain,
+ networkDate: networkDate
+ )
+ return (old, false)
+ } else {
+ let instance = Instance.insert(
+ into: managedObjectContext,
+ property: Instance.Property(domain: domain, version: entity.version)
+ )
+ let configurationRaw = entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
+ instance.update(configurationV2Raw: configurationRaw)
+
+ return (instance, true)
+ }
+ }
+
+}
+
+extension APIService.CoreData {
+
+ static func mergeV2(
+ instance: Instance,
+ entity: Mastodon.Entity.V2.Instance,
+ domain: String,
+ networkDate: Date
+ ) {
+ guard networkDate > instance.updatedAt else { return }
+
+ let configurationRaw = entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
+ instance.update(configurationV2Raw: configurationRaw)
+ instance.version = entity.version
+
+ instance.didUpdate(at: networkDate)
+ }
+
+}
diff --git a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift
index 99ad6d0a2..02946ca6c 100644
--- a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift
+++ b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift
@@ -50,42 +50,18 @@ extension InstanceService {
func updateInstance(domain: String) {
guard let apiService = self.apiService else { return }
apiService.instance(domain: domain)
- .flatMap { response -> AnyPublisher, Error> in
- let managedObjectContext = self.backgroundManagedObjectContext
- return managedObjectContext.performChanges {
- // get instance
- let (instance, _) = APIService.CoreData.createOrMergeInstance(
- into: managedObjectContext,
- domain: domain,
- entity: response.value,
- networkDate: response.networkDate,
- log: Logger(subsystem: "Update", category: "InstanceService")
- )
-
- // update relationship
- let request = MastodonAuthentication.sortedFetchRequest
- request.predicate = MastodonAuthentication.predicate(domain: domain)
- request.returnsObjectsAsFaults = false
- do {
- let authentications = try managedObjectContext.fetch(request)
- for authentication in authentications {
- authentication.update(instance: instance)
- }
- } catch {
- assertionFailure(error.localizedDescription)
- }
+ .flatMap { [unowned self] response -> AnyPublisher in
+ if response.value.version?.majorServerVersion(greaterThanOrEquals: 4) == true {
+ return apiService.instanceV2(domain: domain)
+ .flatMap { return self.updateInstanceV2(domain: domain, response: $0) }
+ .eraseToAnyPublisher()
+ } else {
+ return self.updateInstance(domain: domain, response: response)
}
- .setFailureType(to: Error.self)
- .tryMap { result -> Mastodon.Response.Content in
- switch result {
- case .success:
- return response
- case .failure(let error):
- throw error
- }
- }
- .eraseToAnyPublisher()
}
+// .flatMap { [unowned self] response -> AnyPublisher in
+// return
+// }
.sink { [weak self] completion in
guard let self = self else { return }
switch completion {
@@ -100,6 +76,80 @@ extension InstanceService {
}
.store(in: &disposeBag)
}
+
+ private func updateInstance(domain: String, response: Mastodon.Response.Content) -> AnyPublisher {
+ let managedObjectContext = self.backgroundManagedObjectContext
+ return managedObjectContext.performChanges {
+ // get instance
+ let (instance, _) = APIService.CoreData.createOrMergeInstance(
+ into: managedObjectContext,
+ domain: domain,
+ entity: response.value,
+ networkDate: response.networkDate,
+ log: Logger(subsystem: "Update", category: "InstanceService")
+ )
+
+ // update relationship
+ let request = MastodonAuthentication.sortedFetchRequest
+ request.predicate = MastodonAuthentication.predicate(domain: domain)
+ request.returnsObjectsAsFaults = false
+ do {
+ let authentications = try managedObjectContext.fetch(request)
+ for authentication in authentications {
+ authentication.update(instance: instance)
+ }
+ } catch {
+ assertionFailure(error.localizedDescription)
+ }
+ }
+ .setFailureType(to: Error.self)
+ .tryMap { result in
+ switch result {
+ case .success:
+ break
+ case .failure(let error):
+ throw error
+ }
+ }
+ .eraseToAnyPublisher()
+ }
+
+ private func updateInstanceV2(domain: String, response: Mastodon.Response.Content) -> AnyPublisher {
+ let managedObjectContext = self.backgroundManagedObjectContext
+ return managedObjectContext.performChanges {
+ // get instance
+ let (instance, _) = APIService.CoreData.createOrMergeInstanceV2(
+ into: managedObjectContext,
+ domain: domain,
+ entity: response.value,
+ networkDate: response.networkDate,
+ log: Logger(subsystem: "Update", category: "InstanceService")
+ )
+
+ // update relationship
+ let request = MastodonAuthentication.sortedFetchRequest
+ request.predicate = MastodonAuthentication.predicate(domain: domain)
+ request.returnsObjectsAsFaults = false
+ do {
+ let authentications = try managedObjectContext.fetch(request)
+ for authentication in authentications {
+ authentication.update(instance: instance)
+ }
+ } catch {
+ assertionFailure(error.localizedDescription)
+ }
+ }
+ .setFailureType(to: Error.self)
+ .tryMap { result in
+ switch result {
+ case .success:
+ break
+ case .failure(let error):
+ throw error
+ }
+ }
+ .eraseToAnyPublisher()
+ }
}
public extension InstanceService {
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Instance.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Instance.swift
new file mode 100644
index 000000000..e276fddba
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Instance.swift
@@ -0,0 +1,50 @@
+import Foundation
+import Combine
+
+extension Mastodon.API.V2.Instance {
+
+ private static func instanceEndpointURL(domain: String) -> URL {
+ return Mastodon.API.endpointV2URL(domain: domain).appendingPathComponent("instance")
+ }
+
+ /// Information about the server
+ ///
+ /// - Since: 4.0.0
+ /// - Version: 4.0.0
+ /// # Last Update
+ /// 2022/12/09
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/instance/)
+ /// - Parameters:
+ /// - session: `URLSession`
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - Returns: `AnyPublisher` contains `Instance` nested in the response
+ public static func instance(
+ session: URLSession,
+ domain: String
+ ) -> AnyPublisher, Error> {
+ let request = Mastodon.API.get(
+ url: instanceEndpointURL(domain: domain),
+ query: nil,
+ authorization: nil
+ )
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value: Mastodon.Entity.V2.Instance
+
+ do {
+ value = try Mastodon.API.decode(type: Mastodon.Entity.V2.Instance.self, from: data, response: response)
+ } catch {
+ if let response = response as? HTTPURLResponse, 400 ..< 500 ~= response.statusCode {
+ // For example, AUTHORIZED_FETCH may result in authentication errors
+ value = Mastodon.Entity.V2.Instance(domain: domain)
+ } else {
+ throw error
+ }
+ }
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
index a1eb47873..f85d50bd0 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
@@ -126,6 +126,7 @@ extension Mastodon.API.V2 {
public enum Search { }
public enum Suggestions { }
public enum Media { }
+ public enum Instance { }
}
extension Mastodon.API {
diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift
new file mode 100644
index 000000000..05913ebe9
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift
@@ -0,0 +1,158 @@
+import Foundation
+
+extension Mastodon.Entity.V2 {
+ /// Instance
+ ///
+ /// - Since: 4.0.0
+ /// - Version: 4.0.3
+ /// # Last Update
+ /// 2022/12/09
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/entities/instance/)
+ public struct Instance: Codable {
+
+ public let domain: String?
+ public let title: String
+ public let description: String
+ public let shortDescription: String?
+ public let email: String?
+ public let version: String?
+ public let languages: [String]? // (ISO 639 Part 1-5 language codes)
+ public let registrations: Mastodon.Entity.V2.Instance.Registrations?
+ public let approvalRequired: Bool?
+ public let invitesEnabled: Bool?
+ public let urls: Mastodon.Entity.Instance.InstanceURL?
+ public let statistics: Mastodon.Entity.Instance.Statistics?
+
+ public let thumbnail: Thumbnail?
+ public let contactAccount: Mastodon.Entity.Account?
+ public let rules: [Mastodon.Entity.Instance.Rule]?
+
+ // https://github.com/mastodon/mastodon/pull/16485
+ public let configuration: Configuration?
+
+ public init(domain: String, approvalRequired: Bool? = nil) {
+ self.domain = domain
+ self.title = domain
+ self.description = ""
+ self.shortDescription = nil
+ self.email = ""
+ self.version = nil
+ self.languages = nil
+ self.registrations = nil
+ self.approvalRequired = approvalRequired
+ self.invitesEnabled = nil
+ self.urls = nil
+ self.statistics = nil
+ self.thumbnail = nil
+ self.contactAccount = nil
+ self.rules = nil
+ self.configuration = nil
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case domain
+ case title
+ case description
+ case shortDescription = "short_description"
+ case email
+ case version
+ case languages
+ case registrations
+ case approvalRequired = "approval_required"
+ case invitesEnabled = "invites_enabled"
+ case urls
+ case statistics = "stats"
+
+ case thumbnail
+ case contactAccount = "contact_account"
+ case rules
+
+ case configuration
+ }
+ }
+}
+
+extension Mastodon.Entity.V2.Instance {
+ public struct Configuration: Codable {
+ public let statuses: Mastodon.Entity.Instance.Configuration.Statuses?
+ public let mediaAttachments: Mastodon.Entity.Instance.Configuration.MediaAttachments?
+ public let polls: Mastodon.Entity.Instance.Configuration.Polls?
+ public let translation: Mastodon.Entity.V2.Instance.Configuration.Translation?
+
+ enum CodingKeys: String, CodingKey {
+ case statuses
+ case mediaAttachments = "media_attachments"
+ case polls
+ case translation
+ }
+ }
+}
+
+extension Mastodon.Entity.V2.Instance {
+ public struct Registrations: Codable {
+ public let enabled: Bool
+ }
+}
+
+extension Mastodon.Entity.V2.Instance.Configuration {
+ public struct Translation: Codable {
+ public let enabled: Bool
+ }
+}
+
+extension Mastodon.Entity.V2.Instance {
+ public struct Thumbnail: Codable {
+ public let url: String?
+ }
+}
+
+//extension Mastodon.Entity.V2.Instance {
+// public struct Statuses: Codable {
+// public let maxCharacters: Int
+// public let maxMediaAttachments: Int
+// public let charactersReservedPerURL: Int
+//
+// enum CodingKeys: String, CodingKey {
+// case maxCharacters = "max_characters"
+// case maxMediaAttachments = "max_media_attachments"
+// case charactersReservedPerURL = "characters_reserved_per_url"
+// }
+// }
+//
+// public struct MediaAttachments: Codable {
+// public let supportedMIMETypes: [String]
+// public let imageSizeLimit: Int
+// public let imageMatrixLimit: Int
+// public let videoSizeLimit: Int
+// public let videoFrameRateLimit: Int
+// public let videoMatrixLimit: Int
+//
+// enum CodingKeys: String, CodingKey {
+// case supportedMIMETypes = "supported_mime_types"
+// case imageSizeLimit = "image_size_limit"
+// case imageMatrixLimit = "image_matrix_limit"
+// case videoSizeLimit = "video_size_limit"
+// case videoFrameRateLimit = "video_frame_rate_limit"
+// case videoMatrixLimit = "video_matrix_limit"
+// }
+// }
+//
+// public struct Polls: Codable {
+// public let maxOptions: Int
+// public let maxCharactersPerOption: Int
+// public let minExpiration: Int
+// public let maxExpiration: Int
+//
+// enum CodingKeys: String, CodingKey {
+// case maxOptions = "max_options"
+// case maxCharactersPerOption = "max_characters_per_option"
+// case minExpiration = "min_expiration"
+// case maxExpiration = "max_expiration"
+// }
+// }
+//
+// public struct Translation: Codable {
+// public let enabled: Bool
+// }
+//}
diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift
index 40c4f2870..ed038f47f 100644
--- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift
+++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift
@@ -23,7 +23,8 @@ extension NotificationView {
public var objects = Set()
let logger = Logger(subsystem: "NotificationView", category: "ViewModel")
-
+
+ @Published public var context: AppContext?
@Published public var authContext: AuthContext?
@Published public var type: MastodonNotificationType?
@@ -57,6 +58,9 @@ extension NotificationView.ViewModel {
bindAuthorMenu(notificationView: notificationView)
bindFollowRequest(notificationView: notificationView)
+ $context
+ .assign(to: \.context, on: notificationView.statusView.viewModel)
+ .store(in: &disposeBag)
$authContext
.assign(to: \.authContext, on: notificationView.statusView.viewModel)
.store(in: &disposeBag)
@@ -209,7 +213,7 @@ extension NotificationView.ViewModel {
$isTranslated
)
)
- .sink { authorName, isMuting, isBlocking, isMyselfIsTranslated in
+ .sink { [weak self] authorName, isMuting, isBlocking, isMyselfIsTranslated in
guard let name = authorName?.string else {
notificationView.menuButton.menu = nil
return
@@ -217,12 +221,29 @@ extension NotificationView.ViewModel {
let (isMyself, isTranslated) = isMyselfIsTranslated
+ lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.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 {
+ guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)
+ else { return }
+ configuration = authentication.instance?.configurationV2
+ }
+ return configuration
+ }()
+
let menuContext = NotificationView.AuthorMenuContext(
name: name,
isMuting: isMuting,
isBlocking: isBlocking,
isMyself: isMyself,
isBookmarking: false, // no bookmark action display for notification item
+ isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true,
isTranslated: isTranslated,
statusLanguage: ""
)
diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift
index c930a7b66..ef40ab7fc 100644
--- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift
+++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift
@@ -150,6 +150,7 @@ extension StatusAuthorView {
public let isMyself: Bool
public let isBookmarking: Bool
+ public let isTranslationEnabled: Bool
public let isTranslated: Bool
public let statusLanguage: String?
}
@@ -158,7 +159,7 @@ extension StatusAuthorView {
var actions = [MastodonMenu.Action]()
if !menuContext.isMyself {
- if let statusLanguage = menuContext.statusLanguage, !menuContext.isTranslated {
+ if let statusLanguage = menuContext.statusLanguage, menuContext.isTranslationEnabled, !menuContext.isTranslated {
actions.append(
.translateStatus(.init(language: statusLanguage))
)
diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift
index f45c07ea6..09995677c 100644
--- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift
+++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift
@@ -27,6 +27,7 @@ extension StatusView {
let logger = Logger(subsystem: "StatusView", category: "ViewModel")
+ public var context: AppContext?
public var authContext: AuthContext?
public var originalStatus: Status?
@@ -609,12 +610,28 @@ extension StatusView.ViewModel {
return
}
+ lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = {
+ guard
+ let context = self.context,
+ let authContext = self.authContext
+ else { return nil }
+
+ var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil
+ context.managedObjectContext.performAndWait {
+ guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)
+ else { return }
+ configuration = authentication.instance?.configurationV2
+ }
+ return configuration
+ }()
+
let menuContext = StatusAuthorView.AuthorMenuContext(
name: name,
isMuting: isMuting,
isBlocking: isBlocking,
isMyself: isMyself,
isBookmarking: isBookmark,
+ isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true,
isTranslated: translatedFromLanguage != nil,
statusLanguage: language
)