diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift index 46ec1b9d4..3ce5905ea 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift @@ -50,13 +50,6 @@ extension APIService { domain: domain, authorization: authorization).singleOutput() - _ = try await Mastodon.API.Statuses.editHistory( - forStatusID: statusID, - session: session, - domain: domain, - authorization: authorization - ).singleOutput() - return response } } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift index 861b48c15..0995edafb 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift @@ -179,7 +179,7 @@ extension Mastodon.API.Media { extension Mastodon.API.Media { 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 diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+StatusHistory.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+StatusHistory.swift index 096233c32..5ea6fe4c2 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+StatusHistory.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+StatusHistory.swift @@ -106,12 +106,41 @@ 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 let status: String? public let mediaIDs: [String]? - public let pollOptions: [String]? - public let pollExpiresIn: Int? - public let pollMultipleAnswers: Bool? + public let mediaAttributes: [MediaAttributes]? + public let poll: Poll? public let sensitive: Bool? public let spoilerText: String? public let visibility: Mastodon.Entity.Status.Visibility? @@ -120,9 +149,8 @@ extension Mastodon.API.Statuses { public init( status: String?, mediaIDs: [String]?, - pollOptions: [String]?, - pollExpiresIn: Int?, - pollMultipleAnswers: Bool?, + mediaAttributes: [MediaAttributes]? = nil, + poll: Poll?, sensitive: Bool?, spoilerText: String?, visibility: Mastodon.Entity.Status.Visibility?, @@ -130,37 +158,23 @@ extension Mastodon.API.Statuses { ) { self.status = status self.mediaIDs = mediaIDs - self.pollOptions = pollOptions - self.pollExpiresIn = pollExpiresIn - self.pollMultipleAnswers = pollMultipleAnswers + self.mediaAttributes = mediaAttributes + self.poll = poll self.sensitive = sensitive self.spoilerText = spoilerText self.visibility = visibility self.language = language } - var contentType: String? { - return Self.multipartContentType() - } - - var body: Data? { - var data = Data() - - status.flatMap { data.append(Data.multipart(key: "status", value: $0)) } - for mediaID in mediaIDs ?? [] { - 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 + enum CodingKeys: String, CodingKey { + case status + case mediaIDs = "media_ids" + case mediaAttributes = "media_attributes" + case poll + case sensitive + case spoilerText = "spoiler_text" + case visibility + case language } } } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Upload.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Upload.swift index 58259d28c..943fb9cfe 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Upload.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Upload.swift @@ -120,15 +120,12 @@ extension AttachmentViewModel { } let attachment = output.asAttachment - + let query = Mastodon.API.Media.UploadMediaQuery( file: attachment, thumbnail: nil, - description: { - let caption = caption.trimmingCharacters(in: .whitespacesAndNewlines) - return caption.isEmpty ? nil : caption - }(), - focus: nil // TODO: + description: caption.trimmingCharacters(in: .whitespacesAndNewlines), + focus: nil ) // upload + N * check upload diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift index 714db2a03..cf802e0b7 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift @@ -47,6 +47,7 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable public let sizeLimit: SizeLimit @Published var caption = "" @Published public private(set) var isCaptionEditable = true + let isEditing: Bool // output @Published public private(set) var output: Output? @@ -75,15 +76,20 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable authContext: AuthContext, input: Input, sizeLimit: SizeLimit, - delegate: AttachmentViewModelDelegate + delegate: AttachmentViewModelDelegate, + isEditing: Bool = false, + caption: String? = nil ) { self.api = api self.authContext = authContext self.input = input self.sizeLimit = sizeLimit self.delegate = delegate + self.isEditing = isEditing + + self.caption = caption ?? "" + super.init() - // end init Timer.publish(every: 1.0 / 60.0, on: .main, in: .common) // 60 FPS .autoconnect() @@ -134,7 +140,9 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable switch input { case .mastodonAssetUrl: - self.isCaptionEditable = false + if self.isEditing == false { + self.isCaptionEditable = false + } self.uploadState = .finish self.output = output self.uploadResult = .exists @@ -258,7 +266,7 @@ extension AttachmentViewModel { public enum Input: Hashable { case image(UIImage) case url(URL) - case mastodonAssetUrl(URL, String) + case mastodonAssetUrl(url: URL, attachmentId: String) case pickerResult(PHPickerResult) case itemProvider(NSItemProvider) } @@ -321,4 +329,5 @@ extension AttachmentViewModel { func update(uploadResult: UploadResult) { self.uploadResult = uploadResult } + } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift index bc1f14053..cdd0e88a4 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift @@ -265,14 +265,16 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { self.isVisibilityButtonEnabled = false self.attachmentViewModels = status.entity.mastodonAttachments.compactMap { guard let assetURL = $0.assetURL, let url = URL(string: assetURL) else { return nil } + let attachmentViewModel = AttachmentViewModel( api: context.apiService, authContext: authContext, - input: .mastodonAssetUrl(url, $0.id), + input: .mastodonAssetUrl(url: url, attachmentId: $0.id), sizeLimit: sizeLimit, - delegate: self + delegate: self, + isEditing: true, + caption: $0.altDescription ) - attachmentViewModel.caption = $0.altDescription ?? "" return attachmentViewModel } } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusEditPublisher.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusEditPublisher.swift index 893fb1a28..127598bb5 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusEditPublisher.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusEditPublisher.swift @@ -115,8 +115,8 @@ extension MastodonEditStatusPublisher: StatusPublisher { guard case let AttachmentViewModel.Input.mastodonAssetUrl(_, attachmentId) = attachmentViewModel.input else { throw AppError.badRequest } + attachmentIDs.append(attachmentId) - break case let .uploadedMastodonAttachment(attachment): attachmentIDs.append(attachment.id) @@ -157,12 +157,21 @@ extension MastodonEditStatusPublisher: StatusPublisher { 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( status: content, mediaIDs: attachmentIDs.isEmpty ? nil : attachmentIDs, - pollOptions: pollOptions, - pollExpiresIn: pollExpiresIn, - pollMultipleAnswers: pollMultipleConfigurationOption, + mediaAttributes: mediaAttributes, + poll: poll, sensitive: isMediaSensitive, spoilerText: isContentWarningComposing ? contentWarning : nil, visibility: visibility, diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift index 9db9faed9..42826976f 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift @@ -124,17 +124,14 @@ extension MastodonStatusPublisher: StatusPublisher { break case let .uploadedMastodonAttachment(attachment): attachmentIDs.append(attachment.id) - - let caption = attachmentViewModel.caption - guard !caption.isEmpty else { continue } - + _ = try await api.updateMedia( domain: authContext.mastodonAuthenticationBox.domain, attachmentID: attachment.id, query: .init( file: nil, thumbnail: nil, - description: caption, + description: attachmentViewModel.caption, focus: nil ), mastodonAuthenticationBox: authContext.mastodonAuthenticationBox