IOS-175: Remove CoreData status edit (#1148)
This commit is contained in:
parent
093be8bbc8
commit
5c834c7e09
|
@ -562,6 +562,7 @@ private extension SceneCoordinator {
|
||||||
//MARK: - Loading
|
//MARK: - Loading
|
||||||
|
|
||||||
public extension SceneCoordinator {
|
public extension SceneCoordinator {
|
||||||
|
@MainActor
|
||||||
func showLoading() {
|
func showLoading() {
|
||||||
guard let rootViewController else { return }
|
guard let rootViewController else { return }
|
||||||
|
|
||||||
|
|
|
@ -671,6 +671,8 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
||||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, showEditHistory button: UIButton) {
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, showEditHistory button: UIButton) {
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
|
await coordinator.showLoading()
|
||||||
|
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
guard let item = await self.item(from: source),
|
guard let item = await self.item(from: source),
|
||||||
case let .status(status) = item else {
|
case let .status(status) = item else {
|
||||||
|
@ -678,11 +680,20 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let status = status.object(in: context.managedObjectContext),
|
guard let status = status.object(in: context.managedObjectContext) else {
|
||||||
let edits = status.editHistory?.sorted(by: { $0.createdAt > $1.createdAt }) else { return }
|
return await coordinator.hideLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let edits = try await context.apiService.getHistory(forStatusID: status.id, authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||||
|
|
||||||
|
await coordinator.hideLoading()
|
||||||
|
|
||||||
let viewModel = StatusEditHistoryViewModel(status: status, edits: edits, appContext: context, authContext: authContext)
|
let viewModel = StatusEditHistoryViewModel(status: status, edits: edits, appContext: context, authContext: authContext)
|
||||||
_ = await coordinator.present(scene: .editHistory(viewModel: viewModel), from: self, transition: .show)
|
_ = await coordinator.present(scene: .editHistory(viewModel: viewModel), from: self, transition: .show)
|
||||||
|
} catch {
|
||||||
|
await coordinator.hideLoading()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import CoreDataStack
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
extension PollOptionView {
|
extension PollOptionView {
|
||||||
public func configure(pollOption option: PollOption) {
|
public func configure(pollOption option: PollOption) {
|
||||||
|
@ -103,7 +104,7 @@ extension PollOptionView {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PollOptionView {
|
extension PollOptionView {
|
||||||
public func configure(historyPollOption option: StatusEdit.Poll.Option) {
|
public func configure(historyPollOption option: Mastodon.Entity.StatusEdit.Poll.Option) {
|
||||||
// background
|
// background
|
||||||
viewModel.roundedBackgroundViewColor = SystemTheme.systemElevatedBackgroundColor
|
viewModel.roundedBackgroundViewColor = SystemTheme.systemElevatedBackgroundColor
|
||||||
// metaContent
|
// metaContent
|
||||||
|
|
|
@ -72,7 +72,7 @@ class StatusEditHistoryTableViewCell: UITableViewCell {
|
||||||
NSLayoutConstraint.activate(constraints)
|
NSLayoutConstraint.activate(constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure(status: Status, statusEdit: StatusEdit, dateText: String) {
|
func configure(status: Status, statusEdit: Mastodon.Entity.StatusEdit, dateText: String) {
|
||||||
dateLabel.text = dateText
|
dateLabel.text = dateText
|
||||||
statusHistoryView.statusView.configure(status: status, statusEdit: statusEdit)
|
statusHistoryView.statusView.configure(status: status, statusEdit: statusEdit)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ class StatusEditHistoryViewController: UIViewController {
|
||||||
|
|
||||||
private let tableView: UITableView
|
private let tableView: UITableView
|
||||||
|
|
||||||
var tableViewDataSource: UITableViewDiffableDataSource<Int, StatusEdit>?
|
var tableViewDataSource: UITableViewDiffableDataSource<Int, Mastodon.Entity.StatusEdit>?
|
||||||
var viewModel: StatusEditHistoryViewModel
|
var viewModel: StatusEditHistoryViewModel
|
||||||
private let dateFormatter: DateFormatter
|
private let dateFormatter: DateFormatter
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class StatusEditHistoryViewController: UIViewController {
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
let tableViewDataSource = UITableViewDiffableDataSource<Int, StatusEdit>(tableView: tableView) {tableView, indexPath, itemIdentifier in
|
let tableViewDataSource = UITableViewDiffableDataSource<Int, Mastodon.Entity.StatusEdit>(tableView: tableView) {tableView, indexPath, itemIdentifier in
|
||||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: StatusEditHistoryTableViewCell.identifier, for: indexPath) as? StatusEditHistoryTableViewCell else {
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: StatusEditHistoryTableViewCell.identifier, for: indexPath) as? StatusEditHistoryTableViewCell else {
|
||||||
fatalError("Wrong cell")
|
fatalError("Wrong cell")
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ class StatusEditHistoryViewController: UIViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Int, StatusEdit>()
|
var snapshot = NSDiffableDataSourceSnapshot<Int, Mastodon.Entity.StatusEdit>()
|
||||||
snapshot.appendSections([0])
|
snapshot.appendSections([0])
|
||||||
snapshot.appendItems(viewModel.edits)
|
snapshot.appendItems(viewModel.edits)
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,11 @@ import CoreDataStack
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
struct StatusEditHistoryViewModel {
|
struct StatusEditHistoryViewModel {
|
||||||
let status: Status
|
let status: Status
|
||||||
let edits: [StatusEdit]
|
let edits: [Mastodon.Entity.StatusEdit]
|
||||||
|
|
||||||
let appContext: AppContext
|
let appContext: AppContext
|
||||||
let authContext: AuthContext
|
let authContext: AuthContext
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22222" systemVersion="22G74" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22222" systemVersion="23B74" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Application" representedClassName="CoreDataStack.Application" syncable="YES">
|
<entity name="Application" representedClassName="CoreDataStack.Application" syncable="YES">
|
||||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||||
<attribute name="name" attributeType="String"/>
|
<attribute name="name" attributeType="String"/>
|
||||||
|
@ -225,7 +225,6 @@
|
||||||
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="statuses" inverseEntity="MastodonUser"/>
|
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="statuses" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="bookmarkedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
<relationship name="bookmarkedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="card" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Card" inverseName="status" inverseEntity="Card"/>
|
<relationship name="card" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Card" inverseName="status" inverseEntity="Card"/>
|
||||||
<relationship name="editHistory" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="StatusEdit" inverseName="status" inverseEntity="StatusEdit"/>
|
|
||||||
<relationship name="favouritedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
<relationship name="favouritedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="status" inverseEntity="Feed"/>
|
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="status" inverseEntity="Feed"/>
|
||||||
<relationship name="mutedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
<relationship name="mutedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||||
|
@ -239,17 +238,6 @@
|
||||||
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
||||||
<relationship name="searchHistories" toMany="YES" deletionRule="Cascade" destinationEntity="SearchHistory" inverseName="status" inverseEntity="SearchHistory"/>
|
<relationship name="searchHistories" toMany="YES" deletionRule="Cascade" destinationEntity="SearchHistory" inverseName="status" inverseEntity="SearchHistory"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="StatusEdit" representedClassName="CoreDataStack.StatusEdit" syncable="YES">
|
|
||||||
<attribute name="attachments" optional="YES" attributeType="Binary"/>
|
|
||||||
<attribute name="content" optional="YES" attributeType="String"/>
|
|
||||||
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
|
||||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
|
||||||
<attribute name="poll" optional="YES" attributeType="Binary"/>
|
|
||||||
<attribute name="sensitive" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
|
||||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
|
||||||
<relationship name="author" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
|
|
||||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="editHistory" inverseEntity="Status"/>
|
|
||||||
</entity>
|
|
||||||
<entity name="Subscription" representedClassName="CoreDataStack.Subscription" syncable="YES">
|
<entity name="Subscription" representedClassName="CoreDataStack.Subscription" syncable="YES">
|
||||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
|
|
@ -100,8 +100,6 @@ public final class Status: NSManagedObject {
|
||||||
@NSManaged public private(set) var notifications: Set<Notification>
|
@NSManaged public private(set) var notifications: Set<Notification>
|
||||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||||
|
|
||||||
@NSManaged public private(set) var editHistory: Set<StatusEdit>?
|
|
||||||
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||||
@NSManaged public private(set) var updatedAt: Date
|
@NSManaged public private(set) var updatedAt: Date
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||||
|
@ -590,10 +588,6 @@ extension Status: AutoUpdatableObject {
|
||||||
public func update(isReveal: Bool) {
|
public func update(isReveal: Bool) {
|
||||||
revealedAt = isReveal ? Date() : nil
|
revealedAt = isReveal ? Date() : nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(editHistory: Set<StatusEdit>) {
|
|
||||||
self.editHistory = editHistory
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Status {
|
extension Status {
|
||||||
|
|
|
@ -1,226 +0,0 @@
|
||||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreData
|
|
||||||
|
|
||||||
public final class StatusEdit: NSManagedObject {
|
|
||||||
public final class Poll: NSObject, Codable {
|
|
||||||
public final class Option: NSObject, Codable {
|
|
||||||
public let title: String
|
|
||||||
|
|
||||||
public init(title: String) {
|
|
||||||
self.title = title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public let options: [Option]
|
|
||||||
|
|
||||||
public init(options: [Option]) {
|
|
||||||
self.options = options
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
|
||||||
@NSManaged public var createdAt: Date
|
|
||||||
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
|
||||||
@NSManaged public var content: String
|
|
||||||
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
|
||||||
@NSManaged public var sensitive: Bool
|
|
||||||
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
|
||||||
@NSManaged public var spoilerText: String?
|
|
||||||
|
|
||||||
// MARK: - AutoGenerateProperty
|
|
||||||
// sourcery:inline:StatusEdit.AutoGenerateProperty
|
|
||||||
|
|
||||||
// Generated using Sourcery
|
|
||||||
// DO NOT EDIT
|
|
||||||
public struct Property {
|
|
||||||
public let createdAt: Date
|
|
||||||
public let content: String
|
|
||||||
public let sensitive: Bool
|
|
||||||
public let spoilerText: String?
|
|
||||||
public let emojis: [MastodonEmoji]
|
|
||||||
public let attachments: [MastodonAttachment]
|
|
||||||
public let poll: Poll?
|
|
||||||
|
|
||||||
public init(
|
|
||||||
createdAt: Date,
|
|
||||||
content: String,
|
|
||||||
sensitive: Bool,
|
|
||||||
spoilerText: String?,
|
|
||||||
emojis: [MastodonEmoji],
|
|
||||||
attachments: [MastodonAttachment],
|
|
||||||
poll: Poll?
|
|
||||||
) {
|
|
||||||
self.createdAt = createdAt
|
|
||||||
self.content = content
|
|
||||||
self.sensitive = sensitive
|
|
||||||
self.spoilerText = spoilerText
|
|
||||||
self.emojis = emojis
|
|
||||||
self.attachments = attachments
|
|
||||||
self.poll = poll
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func configure(property: Property) {
|
|
||||||
self.createdAt = property.createdAt
|
|
||||||
self.content = property.content
|
|
||||||
self.sensitive = property.sensitive
|
|
||||||
self.spoilerText = property.spoilerText
|
|
||||||
self.emojis = property.emojis
|
|
||||||
self.attachments = property.attachments
|
|
||||||
self.poll = property.poll
|
|
||||||
}
|
|
||||||
|
|
||||||
public func update(property: Property) {
|
|
||||||
update(createdAt: property.createdAt)
|
|
||||||
update(content: property.content)
|
|
||||||
update(sensitive: property.sensitive)
|
|
||||||
update(spoilerText: property.spoilerText)
|
|
||||||
update(emojis: property.emojis)
|
|
||||||
update(attachments: property.attachments)
|
|
||||||
update(poll: property.poll)
|
|
||||||
}
|
|
||||||
// sourcery:end
|
|
||||||
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
|
||||||
@objc public var emojis: [MastodonEmoji] {
|
|
||||||
get {
|
|
||||||
let keyPath = #keyPath(StatusEdit.emojis)
|
|
||||||
willAccessValue(forKey: keyPath)
|
|
||||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
|
||||||
didAccessValue(forKey: keyPath)
|
|
||||||
do {
|
|
||||||
guard let data = _data else { return [] }
|
|
||||||
let emojis = try JSONDecoder().decode([MastodonEmoji].self, from: data)
|
|
||||||
return emojis
|
|
||||||
} catch {
|
|
||||||
assertionFailure(error.localizedDescription)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
let keyPath = #keyPath(StatusEdit.emojis)
|
|
||||||
let data = try? JSONEncoder().encode(newValue)
|
|
||||||
willChangeValue(forKey: keyPath)
|
|
||||||
setPrimitiveValue(data, forKey: keyPath)
|
|
||||||
didChangeValue(forKey: keyPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusEdit {
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
|
||||||
@objc public var attachments: [MastodonAttachment] {
|
|
||||||
get {
|
|
||||||
let keyPath = #keyPath(StatusEdit.attachments)
|
|
||||||
willAccessValue(forKey: keyPath)
|
|
||||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
|
||||||
didAccessValue(forKey: keyPath)
|
|
||||||
do {
|
|
||||||
guard let data = _data else { return [] }
|
|
||||||
let attachments = try JSONDecoder().decode([MastodonAttachment].self, from: data)
|
|
||||||
return attachments
|
|
||||||
} catch {
|
|
||||||
assertionFailure(error.localizedDescription)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
let keyPath = #keyPath(StatusEdit.attachments)
|
|
||||||
let data = try? JSONEncoder().encode(newValue)
|
|
||||||
willChangeValue(forKey: keyPath)
|
|
||||||
setPrimitiveValue(data, forKey: keyPath)
|
|
||||||
didChangeValue(forKey: keyPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusEdit {
|
|
||||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
|
||||||
@objc public var poll: Poll? {
|
|
||||||
get {
|
|
||||||
let keyPath = #keyPath(StatusEdit.poll)
|
|
||||||
willAccessValue(forKey: keyPath)
|
|
||||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
|
||||||
didAccessValue(forKey: keyPath)
|
|
||||||
do {
|
|
||||||
guard let data = _data else { return nil }
|
|
||||||
let poll = try JSONDecoder().decode(Poll.self, from: data)
|
|
||||||
return poll
|
|
||||||
} catch {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
let keyPath = #keyPath(StatusEdit.poll)
|
|
||||||
let data = try? JSONEncoder().encode(newValue)
|
|
||||||
willChangeValue(forKey: keyPath)
|
|
||||||
setPrimitiveValue(data, forKey: keyPath)
|
|
||||||
didChangeValue(forKey: keyPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusEdit: Managed {
|
|
||||||
@discardableResult
|
|
||||||
public static func insert(
|
|
||||||
into context: NSManagedObjectContext,
|
|
||||||
property: Property
|
|
||||||
) -> StatusEdit {
|
|
||||||
let object: StatusEdit = context.insertObject()
|
|
||||||
|
|
||||||
object.configure(property: property)
|
|
||||||
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusEdit: AutoUpdatableObject {
|
|
||||||
// sourcery:inline:StatusEdit.AutoUpdatableObject
|
|
||||||
|
|
||||||
// Generated using Sourcery
|
|
||||||
// DO NOT EDIT
|
|
||||||
public func update(createdAt: Date) {
|
|
||||||
if self.createdAt != createdAt {
|
|
||||||
self.createdAt = createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public func update(content: String) {
|
|
||||||
if self.content != content {
|
|
||||||
self.content = content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public func update(sensitive: Bool) {
|
|
||||||
if self.sensitive != sensitive {
|
|
||||||
self.sensitive = sensitive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public func update(spoilerText: String?) {
|
|
||||||
if self.spoilerText != spoilerText {
|
|
||||||
self.spoilerText = spoilerText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public func update(emojis: [MastodonEmoji]) {
|
|
||||||
if self.emojis != emojis {
|
|
||||||
self.emojis = emojis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public func update(attachments: [MastodonAttachment]) {
|
|
||||||
if self.attachments != attachments {
|
|
||||||
self.attachments = attachments
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public func update(poll: Poll?) {
|
|
||||||
if self.poll != poll {
|
|
||||||
self.poll = poll
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sourcery:end
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
extension StatusEdit.Property {
|
|
||||||
init(entity: Mastodon.Entity.StatusEdit) {
|
|
||||||
self.init(
|
|
||||||
createdAt: entity.createdAt,
|
|
||||||
content: entity.content,
|
|
||||||
sensitive: entity.sensitive,
|
|
||||||
spoilerText: entity.spoilerText,
|
|
||||||
emojis: entity.mastodonEmojis,
|
|
||||||
attachments: entity.mastodonAttachments,
|
|
||||||
poll: entity.poll.map { StatusEdit.Poll(options: $0.options.map { StatusEdit.Poll.Option(title: $0.title) } ) } )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Mastodon.Entity.StatusEdit {
|
|
||||||
public var mastodonAttachments: [MastodonAttachment] {
|
|
||||||
mediaAttachments.mastodonAttachments
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,8 +8,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
public enum PollItem: Hashable {
|
public enum PollItem: Hashable {
|
||||||
case option(record: ManagedObjectRecord<PollOption>)
|
case option(record: ManagedObjectRecord<PollOption>)
|
||||||
case history(option: StatusEdit.Poll.Option)
|
case history(option: Mastodon.Entity.StatusEdit.Poll.Option)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
|
||||||
|
|
||||||
import CoreData
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
extension Persistence.StatusEdit {
|
|
||||||
|
|
||||||
public static func createOrMerge(
|
|
||||||
in managedObjectContext: NSManagedObjectContext,
|
|
||||||
statusEdits: [Mastodon.Entity.StatusEdit],
|
|
||||||
forStatus status: Status
|
|
||||||
) {
|
|
||||||
guard statusEdits.isEmpty == false else { return }
|
|
||||||
|
|
||||||
// remove all edits for status
|
|
||||||
|
|
||||||
if let editHistory = status.editHistory {
|
|
||||||
for statusEdit in Array(editHistory) {
|
|
||||||
managedObjectContext.delete(statusEdit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
status.update(editHistory: Set())
|
|
||||||
let persistedEdits = create(in: managedObjectContext, statusEdits: statusEdits, forStatus: status)
|
|
||||||
status.update(editHistory: Set(persistedEdits))
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func create(
|
|
||||||
in managedObjectContext: NSManagedObjectContext,
|
|
||||||
statusEdits: [Mastodon.Entity.StatusEdit],
|
|
||||||
forStatus status: Status
|
|
||||||
) -> [StatusEdit] {
|
|
||||||
|
|
||||||
var entries: [StatusEdit] = []
|
|
||||||
|
|
||||||
for statusEdit in statusEdits {
|
|
||||||
let property = StatusEdit.Property(createdAt: statusEdit.createdAt, content: statusEdit.content, sensitive: statusEdit.sensitive, spoilerText: statusEdit.spoilerText, emojis: statusEdit.mastodonEmojis, attachments: statusEdit.mastodonAttachments, poll: statusEdit.poll.map { StatusEdit.Poll(options: $0.options.map { StatusEdit.Poll.Option(title: $0.title) } ) })
|
|
||||||
let statusEditEntry = StatusEdit.insert(into: managedObjectContext, property: property)
|
|
||||||
|
|
||||||
entries.append(statusEditEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
status.update(editHistory: Set(entries))
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ extension Persistence {
|
||||||
public enum Tag { }
|
public enum Tag { }
|
||||||
public enum SearchHistory { }
|
public enum SearchHistory { }
|
||||||
public enum Notification { }
|
public enum Notification { }
|
||||||
public enum StatusEdit {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Persistence {
|
extension Persistence {
|
||||||
|
|
|
@ -33,21 +33,6 @@ extension APIService {
|
||||||
domain: domain,
|
domain: domain,
|
||||||
authorization: authorization).singleOutput()
|
authorization: authorization).singleOutput()
|
||||||
|
|
||||||
guard response.value.isEmpty == false else { return response }
|
|
||||||
|
|
||||||
let managedObjectContext = self.backgroundManagedObjectContext
|
|
||||||
|
|
||||||
try await managedObjectContext.performChanges {
|
|
||||||
// get status
|
|
||||||
guard let status = Status.fetch(in: managedObjectContext, configurationBlock: {
|
|
||||||
$0.predicate = Status.predicate(domain: domain, id: statusID)
|
|
||||||
}).first else { return }
|
|
||||||
|
|
||||||
Persistence.StatusEdit.createOrMerge(in: managedObjectContext,
|
|
||||||
statusEdits: response.value,
|
|
||||||
forStatus: status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,31 +57,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,16 @@ extension Mastodon.Entity {
|
||||||
/// [Document](https://docs.joinmastodon.org/entities/statusedit/)
|
/// [Document](https://docs.joinmastodon.org/entities/statusedit/)
|
||||||
public class StatusEdit: Codable {
|
public class StatusEdit: Codable {
|
||||||
public class Poll: Codable {
|
public class Poll: Codable {
|
||||||
public class Option: Codable {
|
public class Option: Codable, Hashable {
|
||||||
public let title: String
|
public let title: String
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: Mastodon.Entity.StatusEdit.Poll.Option, rhs: Mastodon.Entity.StatusEdit.Poll.Option) -> Bool {
|
||||||
|
lhs.title == rhs.title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public let options: [Option]
|
public let options: [Option]
|
||||||
public let title: String?
|
public let title: String?
|
||||||
|
@ -40,5 +48,17 @@ extension Mastodon.Entity {
|
||||||
case mediaAttachments = "media_attachments"
|
case mediaAttachments = "media_attachments"
|
||||||
case emojis
|
case emojis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Mastodon.Entity.StatusEdit: Hashable, Equatable {
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(createdAt)
|
||||||
|
hasher.combine(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: Mastodon.Entity.StatusEdit, rhs: Mastodon.Entity.StatusEdit) -> Bool {
|
||||||
|
lhs.createdAt == rhs.createdAt && lhs.content == rhs.content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,3 @@ public protocol StatusCompatible {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Status: StatusCompatible {}
|
extension Status: StatusCompatible {}
|
||||||
|
|
||||||
extension StatusEdit: StatusCompatible {
|
|
||||||
public var reblog: Status? {
|
|
||||||
nil
|
|
||||||
}
|
|
||||||
|
|
||||||
public var isMediaSensitive: Bool {
|
|
||||||
sensitive
|
|
||||||
}
|
|
||||||
|
|
||||||
public var isSensitiveToggled: Bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import CoreDataStack
|
||||||
import Photos
|
import Photos
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
extension MediaView {
|
extension MediaView {
|
||||||
public class Configuration: Hashable {
|
public class Configuration: Hashable {
|
||||||
|
@ -243,3 +244,86 @@ extension MediaView {
|
||||||
return configurations
|
return configurations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MediaView {
|
||||||
|
public static func configuration(status: Mastodon.Entity.StatusEdit) -> [MediaView.Configuration] {
|
||||||
|
func aspectRatio(from attachment: Mastodon.Entity.Attachment) -> CGSize? {
|
||||||
|
if let width = attachment.meta?.original?.width, let height = attachment.meta?.original?.height {
|
||||||
|
return CGSize(width: width, height: height)
|
||||||
|
} else if let width = attachment.meta?.width, let height = attachment.meta?.height {
|
||||||
|
return CGSize(width: width, height: height)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func videoInfo(from attachment: Mastodon.Entity.Attachment) -> MediaView.Configuration.VideoInfo? {
|
||||||
|
guard let aspectRatio = aspectRatio(from: attachment) else { return nil }
|
||||||
|
return MediaView.Configuration.VideoInfo(
|
||||||
|
aspectRadio: aspectRatio,
|
||||||
|
assetURL: attachment.url,
|
||||||
|
previewURL: attachment.previewURL,
|
||||||
|
altDescription: attachment.description,
|
||||||
|
durationMS: {
|
||||||
|
guard let duration = attachment.meta?.duration else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return Int(duration)
|
||||||
|
}()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let attachments = status.mediaAttachments ?? []
|
||||||
|
let configurations = attachments.enumerated().compactMap { (idx, attachment) -> MediaView.Configuration? in
|
||||||
|
let configuration: MediaView.Configuration? = {
|
||||||
|
switch attachment.attachmentKind {
|
||||||
|
case .image:
|
||||||
|
guard let aspectRatio = aspectRatio(from: attachment) else { return nil }
|
||||||
|
let info = MediaView.Configuration.ImageInfo(
|
||||||
|
aspectRadio: aspectRatio,
|
||||||
|
assetURL: attachment.url,
|
||||||
|
altDescription: attachment.description
|
||||||
|
)
|
||||||
|
return .init(
|
||||||
|
info: .image(info: info),
|
||||||
|
blurhash: attachment.blurhash,
|
||||||
|
index: idx,
|
||||||
|
total: attachments.count
|
||||||
|
)
|
||||||
|
case .video:
|
||||||
|
guard let info = videoInfo(from: attachment) else { return nil }
|
||||||
|
return .init(
|
||||||
|
info: .video(info: info),
|
||||||
|
blurhash: attachment.blurhash,
|
||||||
|
index: idx,
|
||||||
|
total: attachments.count
|
||||||
|
)
|
||||||
|
case .gifv:
|
||||||
|
guard let info = videoInfo(from: attachment) else { return nil }
|
||||||
|
return .init(
|
||||||
|
info: .gif(info: info),
|
||||||
|
blurhash: attachment.blurhash,
|
||||||
|
index: idx,
|
||||||
|
total: attachments.count
|
||||||
|
)
|
||||||
|
case .audio:
|
||||||
|
guard let info = videoInfo(from: attachment) else { return nil }
|
||||||
|
return .init(
|
||||||
|
info: .video(info: info),
|
||||||
|
blurhash: attachment.blurhash,
|
||||||
|
index: idx,
|
||||||
|
total: attachments.count
|
||||||
|
)
|
||||||
|
case .none:
|
||||||
|
return nil
|
||||||
|
} // end switch
|
||||||
|
}()
|
||||||
|
|
||||||
|
configuration?.load()
|
||||||
|
configuration?.isReveal = true
|
||||||
|
|
||||||
|
return configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
return configurations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ extension StatusView {
|
||||||
|
|
||||||
extension StatusView {
|
extension StatusView {
|
||||||
|
|
||||||
public func configure(status: Status, statusEdit: StatusEdit) {
|
public func configure(status: 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)
|
||||||
|
@ -313,7 +313,7 @@ extension StatusView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureContent(statusEdit: StatusEdit, status: Status) {
|
private func configureContent(statusEdit: Mastodon.Entity.StatusEdit, status: Status) {
|
||||||
statusEdit.spoilerText.map {
|
statusEdit.spoilerText.map {
|
||||||
viewModel.spoilerContent = PlaintextMetaContent(string: $0)
|
viewModel.spoilerContent = PlaintextMetaContent(string: $0)
|
||||||
}
|
}
|
||||||
|
@ -322,7 +322,7 @@ extension StatusView {
|
||||||
viewModel.language = (status.reblog ?? status).language
|
viewModel.language = (status.reblog ?? status).language
|
||||||
// content
|
// content
|
||||||
do {
|
do {
|
||||||
let content = MastodonContent(content: statusEdit.content, emojis: statusEdit.emojis.asDictionary)
|
let content = MastodonContent(content: statusEdit.content, emojis: statusEdit.emojis?.asDictionary ?? [:])
|
||||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||||
viewModel.content = metaContent
|
viewModel.content = metaContent
|
||||||
viewModel.isCurrentlyTranslating = false
|
viewModel.isCurrentlyTranslating = false
|
||||||
|
@ -385,7 +385,14 @@ extension StatusView {
|
||||||
viewModel.mediaViewConfigurations = configurations
|
viewModel.mediaViewConfigurations = configurations
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configurePollHistory(statusEdit: StatusEdit) {
|
private func configureMedia(status: Mastodon.Entity.StatusEdit) {
|
||||||
|
viewModel.isMediaSensitive = status.sensitive
|
||||||
|
|
||||||
|
let configurations = MediaView.configuration(status: status)
|
||||||
|
viewModel.mediaViewConfigurations = configurations
|
||||||
|
}
|
||||||
|
|
||||||
|
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) }
|
||||||
|
@ -499,13 +506,6 @@ extension StatusView {
|
||||||
.assign(to: \.editedAt, on: viewModel)
|
.assign(to: \.editedAt, on: viewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
status.publisher(for: \.editHistory)
|
|
||||||
.compactMap({ guard let edits = $0 else { return nil }
|
|
||||||
return Array(edits)
|
|
||||||
})
|
|
||||||
.assign(to: \.statusEdits, on: viewModel)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
// relationship
|
// relationship
|
||||||
status.publisher(for: \.rebloggedBy)
|
status.publisher(for: \.rebloggedBy)
|
||||||
.map { [weak viewModel] rebloggedBy in
|
.map { [weak viewModel] rebloggedBy in
|
||||||
|
|
|
@ -100,7 +100,6 @@ extension StatusView {
|
||||||
@Published public var reblogCount: Int = 0
|
@Published public var reblogCount: Int = 0
|
||||||
@Published public var favoriteCount: Int = 0
|
@Published public var favoriteCount: Int = 0
|
||||||
|
|
||||||
@Published public var statusEdits: [StatusEdit] = []
|
|
||||||
@Published public var editedAt: Date? = nil
|
@Published public var editedAt: Date? = nil
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
|
|
Loading…
Reference in New Issue