commit
c0c795e473
|
@ -50,13 +50,6 @@ extension APIService {
|
||||||
domain: domain,
|
domain: domain,
|
||||||
authorization: authorization).singleOutput()
|
authorization: authorization).singleOutput()
|
||||||
|
|
||||||
_ = try await Mastodon.API.Statuses.editHistory(
|
|
||||||
forStatusID: statusID,
|
|
||||||
session: session,
|
|
||||||
domain: domain,
|
|
||||||
authorization: authorization
|
|
||||||
).singleOutput()
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ extension Mastodon.API.Media {
|
||||||
extension Mastodon.API.Media {
|
extension Mastodon.API.Media {
|
||||||
|
|
||||||
static func updateMediaEndpointURL(domain: String, attachmentID: Mastodon.Entity.Attachment.ID) -> URL {
|
static func updateMediaEndpointURL(domain: String, attachmentID: Mastodon.Entity.Attachment.ID) -> URL {
|
||||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("media").appendingPathComponent(attachmentID)
|
Mastodon.API.endpointURL(domain: domain).appendingPathComponent("media").appendingPathComponent(attachmentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update attachment
|
/// Update attachment
|
||||||
|
|
|
@ -106,12 +106,41 @@ extension Mastodon.API.Statuses {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Mastodon.API.Statuses {
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
|
public struct MediaAttributes: Codable {
|
||||||
|
let id: String
|
||||||
|
let description: String?
|
||||||
|
//TODO: Add focus at some point
|
||||||
|
|
||||||
|
public init(id: String, description: String?) {
|
||||||
|
self.id = id
|
||||||
|
self.description = description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Poll: Codable {
|
||||||
|
public let options: [String]?
|
||||||
|
public let expiresIn: Int?
|
||||||
|
public let multipleAnswers: Bool?
|
||||||
|
|
||||||
|
public init(options: [String]?, expiresIn: Int?, multipleAnswers: Bool?) {
|
||||||
|
self.options = options
|
||||||
|
self.expiresIn = expiresIn
|
||||||
|
self.multipleAnswers = multipleAnswers
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case options
|
||||||
|
case expiresIn = "expires_in"
|
||||||
|
case multipleAnswers = "multiple_answers"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct EditStatusQuery: Codable, PutQuery {
|
public struct EditStatusQuery: Codable, PutQuery {
|
||||||
public let status: String?
|
public let status: String?
|
||||||
public let mediaIDs: [String]?
|
public let mediaIDs: [String]?
|
||||||
public let pollOptions: [String]?
|
public let mediaAttributes: [MediaAttributes]?
|
||||||
public let pollExpiresIn: Int?
|
public let poll: Poll?
|
||||||
public let pollMultipleAnswers: Bool?
|
|
||||||
public let sensitive: Bool?
|
public let sensitive: Bool?
|
||||||
public let spoilerText: String?
|
public let spoilerText: String?
|
||||||
public let visibility: Mastodon.Entity.Status.Visibility?
|
public let visibility: Mastodon.Entity.Status.Visibility?
|
||||||
|
@ -120,9 +149,8 @@ extension Mastodon.API.Statuses {
|
||||||
public init(
|
public init(
|
||||||
status: String?,
|
status: String?,
|
||||||
mediaIDs: [String]?,
|
mediaIDs: [String]?,
|
||||||
pollOptions: [String]?,
|
mediaAttributes: [MediaAttributes]? = nil,
|
||||||
pollExpiresIn: Int?,
|
poll: Poll?,
|
||||||
pollMultipleAnswers: Bool?,
|
|
||||||
sensitive: Bool?,
|
sensitive: Bool?,
|
||||||
spoilerText: String?,
|
spoilerText: String?,
|
||||||
visibility: Mastodon.Entity.Status.Visibility?,
|
visibility: Mastodon.Entity.Status.Visibility?,
|
||||||
|
@ -130,37 +158,23 @@ extension Mastodon.API.Statuses {
|
||||||
) {
|
) {
|
||||||
self.status = status
|
self.status = status
|
||||||
self.mediaIDs = mediaIDs
|
self.mediaIDs = mediaIDs
|
||||||
self.pollOptions = pollOptions
|
self.mediaAttributes = mediaAttributes
|
||||||
self.pollExpiresIn = pollExpiresIn
|
self.poll = poll
|
||||||
self.pollMultipleAnswers = pollMultipleAnswers
|
|
||||||
self.sensitive = sensitive
|
self.sensitive = sensitive
|
||||||
self.spoilerText = spoilerText
|
self.spoilerText = spoilerText
|
||||||
self.visibility = visibility
|
self.visibility = visibility
|
||||||
self.language = language
|
self.language = language
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentType: String? {
|
enum CodingKeys: String, CodingKey {
|
||||||
return Self.multipartContentType()
|
case status
|
||||||
}
|
case mediaIDs = "media_ids"
|
||||||
|
case mediaAttributes = "media_attributes"
|
||||||
var body: Data? {
|
case poll
|
||||||
var data = Data()
|
case sensitive
|
||||||
|
case spoilerText = "spoiler_text"
|
||||||
status.flatMap { data.append(Data.multipart(key: "status", value: $0)) }
|
case visibility
|
||||||
for mediaID in mediaIDs ?? [] {
|
case language
|
||||||
data.append(Data.multipart(key: "media_ids[]", value: mediaID))
|
|
||||||
}
|
|
||||||
for pollOption in pollOptions ?? [] {
|
|
||||||
data.append(Data.multipart(key: "poll[options][]", value: pollOption))
|
|
||||||
}
|
|
||||||
pollExpiresIn.flatMap { data.append(Data.multipart(key: "poll[expires_in]", value: $0)) }
|
|
||||||
sensitive.flatMap { data.append(Data.multipart(key: "sensitive", value: $0)) }
|
|
||||||
spoilerText.flatMap { data.append(Data.multipart(key: "spoiler_text", value: $0)) }
|
|
||||||
visibility.flatMap { data.append(Data.multipart(key: "visibility", value: $0.rawValue)) }
|
|
||||||
language.flatMap { data.append(Data.multipart(key: "language", value: $0)) }
|
|
||||||
|
|
||||||
data.append(Data.multipartEnd())
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,11 +124,8 @@ extension AttachmentViewModel {
|
||||||
let query = Mastodon.API.Media.UploadMediaQuery(
|
let query = Mastodon.API.Media.UploadMediaQuery(
|
||||||
file: attachment,
|
file: attachment,
|
||||||
thumbnail: nil,
|
thumbnail: nil,
|
||||||
description: {
|
description: caption.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||||
let caption = caption.trimmingCharacters(in: .whitespacesAndNewlines)
|
focus: nil
|
||||||
return caption.isEmpty ? nil : caption
|
|
||||||
}(),
|
|
||||||
focus: nil // TODO:
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// upload + N * check upload
|
// upload + N * check upload
|
||||||
|
|
|
@ -47,6 +47,7 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
|
||||||
public let sizeLimit: SizeLimit
|
public let sizeLimit: SizeLimit
|
||||||
@Published var caption = ""
|
@Published var caption = ""
|
||||||
@Published public private(set) var isCaptionEditable = true
|
@Published public private(set) var isCaptionEditable = true
|
||||||
|
let isEditing: Bool
|
||||||
|
|
||||||
// output
|
// output
|
||||||
@Published public private(set) var output: Output?
|
@Published public private(set) var output: Output?
|
||||||
|
@ -75,15 +76,20 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
|
||||||
authContext: AuthContext,
|
authContext: AuthContext,
|
||||||
input: Input,
|
input: Input,
|
||||||
sizeLimit: SizeLimit,
|
sizeLimit: SizeLimit,
|
||||||
delegate: AttachmentViewModelDelegate
|
delegate: AttachmentViewModelDelegate,
|
||||||
|
isEditing: Bool = false,
|
||||||
|
caption: String? = nil
|
||||||
) {
|
) {
|
||||||
self.api = api
|
self.api = api
|
||||||
self.authContext = authContext
|
self.authContext = authContext
|
||||||
self.input = input
|
self.input = input
|
||||||
self.sizeLimit = sizeLimit
|
self.sizeLimit = sizeLimit
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
|
self.isEditing = isEditing
|
||||||
|
|
||||||
|
self.caption = caption ?? ""
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
// end init
|
|
||||||
|
|
||||||
Timer.publish(every: 1.0 / 60.0, on: .main, in: .common) // 60 FPS
|
Timer.publish(every: 1.0 / 60.0, on: .main, in: .common) // 60 FPS
|
||||||
.autoconnect()
|
.autoconnect()
|
||||||
|
@ -134,7 +140,9 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
|
||||||
|
|
||||||
switch input {
|
switch input {
|
||||||
case .mastodonAssetUrl:
|
case .mastodonAssetUrl:
|
||||||
|
if self.isEditing == false {
|
||||||
self.isCaptionEditable = false
|
self.isCaptionEditable = false
|
||||||
|
}
|
||||||
self.uploadState = .finish
|
self.uploadState = .finish
|
||||||
self.output = output
|
self.output = output
|
||||||
self.uploadResult = .exists
|
self.uploadResult = .exists
|
||||||
|
@ -258,7 +266,7 @@ extension AttachmentViewModel {
|
||||||
public enum Input: Hashable {
|
public enum Input: Hashable {
|
||||||
case image(UIImage)
|
case image(UIImage)
|
||||||
case url(URL)
|
case url(URL)
|
||||||
case mastodonAssetUrl(URL, String)
|
case mastodonAssetUrl(url: URL, attachmentId: String)
|
||||||
case pickerResult(PHPickerResult)
|
case pickerResult(PHPickerResult)
|
||||||
case itemProvider(NSItemProvider)
|
case itemProvider(NSItemProvider)
|
||||||
}
|
}
|
||||||
|
@ -321,4 +329,5 @@ extension AttachmentViewModel {
|
||||||
func update(uploadResult: UploadResult) {
|
func update(uploadResult: UploadResult) {
|
||||||
self.uploadResult = uploadResult
|
self.uploadResult = uploadResult
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,14 +265,16 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||||
self.isVisibilityButtonEnabled = false
|
self.isVisibilityButtonEnabled = false
|
||||||
self.attachmentViewModels = status.entity.mastodonAttachments.compactMap {
|
self.attachmentViewModels = status.entity.mastodonAttachments.compactMap {
|
||||||
guard let assetURL = $0.assetURL, let url = URL(string: assetURL) else { return nil }
|
guard let assetURL = $0.assetURL, 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: url, attachmentId: $0.id),
|
||||||
sizeLimit: sizeLimit,
|
sizeLimit: sizeLimit,
|
||||||
delegate: self
|
delegate: self,
|
||||||
|
isEditing: true,
|
||||||
|
caption: $0.altDescription
|
||||||
)
|
)
|
||||||
attachmentViewModel.caption = $0.altDescription ?? ""
|
|
||||||
return attachmentViewModel
|
return attachmentViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,8 +115,8 @@ extension MastodonEditStatusPublisher: StatusPublisher {
|
||||||
guard case let AttachmentViewModel.Input.mastodonAssetUrl(_, attachmentId) = attachmentViewModel.input else {
|
guard case let AttachmentViewModel.Input.mastodonAssetUrl(_, attachmentId) = attachmentViewModel.input else {
|
||||||
throw AppError.badRequest
|
throw AppError.badRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentIDs.append(attachmentId)
|
attachmentIDs.append(attachmentId)
|
||||||
break
|
|
||||||
case let .uploadedMastodonAttachment(attachment):
|
case let .uploadedMastodonAttachment(attachment):
|
||||||
attachmentIDs.append(attachment.id)
|
attachmentIDs.append(attachment.id)
|
||||||
|
|
||||||
|
@ -157,12 +157,21 @@ extension MastodonEditStatusPublisher: StatusPublisher {
|
||||||
return self.pollExpireConfigurationOption.seconds
|
return self.pollExpireConfigurationOption.seconds
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
let poll = Mastodon.API.Statuses.Poll(options: pollOptions, expiresIn: pollExpiresIn, multipleAnswers: self.pollMultipleConfigurationOption)
|
||||||
|
|
||||||
|
let mediaAttributes: [Mastodon.API.Statuses.MediaAttributes] = attachmentViewModels.compactMap {
|
||||||
|
if case let .mastodonAssetUrl(url: _, attachmentId: attachmentId) = $0.input {
|
||||||
|
return Mastodon.API.Statuses.MediaAttributes(id: attachmentId, description: $0.caption)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let query = Mastodon.API.Statuses.EditStatusQuery(
|
let query = Mastodon.API.Statuses.EditStatusQuery(
|
||||||
status: content,
|
status: content,
|
||||||
mediaIDs: attachmentIDs.isEmpty ? nil : attachmentIDs,
|
mediaIDs: attachmentIDs.isEmpty ? nil : attachmentIDs,
|
||||||
pollOptions: pollOptions,
|
mediaAttributes: mediaAttributes,
|
||||||
pollExpiresIn: pollExpiresIn,
|
poll: poll,
|
||||||
pollMultipleAnswers: pollMultipleConfigurationOption,
|
|
||||||
sensitive: isMediaSensitive,
|
sensitive: isMediaSensitive,
|
||||||
spoilerText: isContentWarningComposing ? contentWarning : nil,
|
spoilerText: isContentWarningComposing ? contentWarning : nil,
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
|
|
|
@ -125,16 +125,13 @@ extension MastodonStatusPublisher: StatusPublisher {
|
||||||
case let .uploadedMastodonAttachment(attachment):
|
case let .uploadedMastodonAttachment(attachment):
|
||||||
attachmentIDs.append(attachment.id)
|
attachmentIDs.append(attachment.id)
|
||||||
|
|
||||||
let caption = attachmentViewModel.caption
|
|
||||||
guard !caption.isEmpty else { continue }
|
|
||||||
|
|
||||||
_ = try await api.updateMedia(
|
_ = try await api.updateMedia(
|
||||||
domain: authContext.mastodonAuthenticationBox.domain,
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
attachmentID: attachment.id,
|
attachmentID: attachment.id,
|
||||||
query: .init(
|
query: .init(
|
||||||
file: nil,
|
file: nil,
|
||||||
thumbnail: nil,
|
thumbnail: nil,
|
||||||
description: caption,
|
description: attachmentViewModel.caption,
|
||||||
focus: nil
|
focus: nil
|
||||||
),
|
),
|
||||||
mastodonAuthenticationBox: authContext.mastodonAuthenticationBox
|
mastodonAuthenticationBox: authContext.mastodonAuthenticationBox
|
||||||
|
|
Loading…
Reference in New Issue