Editor: Add image ALT

This commit is contained in:
Thomas Ricouard 2023-01-03 19:30:27 +01:00
parent 64c57a241a
commit 7ce3446030
5 changed files with 135 additions and 13 deletions

View file

@ -2,18 +2,26 @@ import Foundation
public enum Media: Endpoint { public enum Media: Endpoint {
case medias case medias
case media(id: String) case media(id: String, description: String?)
public func path() -> String { public func path() -> String {
switch self { switch self {
case .medias: case .medias:
return "media" return "media"
case let .media(id): case let .media(id, _):
return "media/\(id)" return "media/\(id)"
} }
} }
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self {
case let .media(_, description):
if let description {
return [.init(name: "description", value: description)]
}
return nil
default:
return nil return nil
} }
}
} }

View file

@ -0,0 +1,69 @@
import SwiftUI
import Models
import DesignSystem
import Shimmer
struct StatusEditorMediaEditView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@ObservedObject var viewModel: StatusEditorViewModel
let container: StatusEditorViewModel.ImageContainer
@State private var imageDescription: String = ""
var body: some View {
NavigationStack {
Form {
Section {
TextField("Image description", text: $imageDescription, axis: .horizontal)
}
.listRowBackground(theme.primaryBackgroundColor)
Section {
if let url = container.mediaAttachement?.url {
AsyncImage(
url: url,
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.cornerRadius(8)
.padding(8)
},
placeholder: {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray)
.frame(height: 200)
.shimmering()
})
}
}
.listRowBackground(theme.primaryBackgroundColor)
}
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.onAppear {
imageDescription = container.mediaAttachement?.description ?? ""
}
.navigationTitle("Edit Image")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Done") {
if !imageDescription.isEmpty {
Task {
await viewModel.addDescription(container: container, description: imageDescription)
}
}
dismiss()
}
}
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") {
dismiss()
}
}
}
}
}
}

View file

@ -6,6 +6,7 @@ import NukeUI
struct StatusEditorMediaView: View { struct StatusEditorMediaView: View {
@ObservedObject var viewModel: StatusEditorViewModel @ObservedObject var viewModel: StatusEditorViewModel
@State private var editingContainer: StatusEditorViewModel.ImageContainer?
var body: some View { var body: some View {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
@ -14,22 +15,24 @@ struct StatusEditorMediaView: View {
if container.image != nil { if container.image != nil {
makeLocalImage(container: container) makeLocalImage(container: container)
} else if let url = container.mediaAttachement?.url { } else if let url = container.mediaAttachement?.url {
ZStack(alignment: .topTrailing) { Menu {
makeLazyImage(url: url) makeImageMenu(container: container)
Button {
withAnimation {
viewModel.mediasImages.removeAll(where: { $0.id == container.id })
}
} label: { } label: {
Image(systemName: "xmark.circle") ZStack(alignment: .bottomTrailing) {
makeLazyImage(url: url)
if container.mediaAttachement?.description?.isEmpty == false {
altMarker
}
} }
.padding(8)
} }
} }
} }
} }
.padding(.horizontal, .layoutPadding) .padding(.horizontal, .layoutPadding)
} }
.sheet(item: $editingContainer) { container in
StatusEditorMediaEditView(viewModel: viewModel, container: container)
}
} }
private func makeLocalImage(container: StatusEditorViewModel.ImageContainer) -> some View { private func makeLocalImage(container: StatusEditorViewModel.ImageContainer) -> some View {
@ -85,4 +88,32 @@ struct StatusEditorMediaView: View {
.cornerRadius(8) .cornerRadius(8)
} }
@ViewBuilder
private func makeImageMenu(container: StatusEditorViewModel.ImageContainer) -> some View {
Button {
editingContainer = container
} label: {
Label(editingContainer?.mediaAttachement?.description?.isEmpty == false ?
"Edit description" : "Add description",
systemImage: "pencil.line")
}
Button(role: .destructive) {
withAnimation {
viewModel.mediasImages.removeAll(where: { $0.id == container.id })
}
} label: {
Label("Delete", systemImage: "trash")
}
}
private var altMarker: some View {
Button {
} label: {
Text("ALT")
.font(.caption2)
}
.padding(4)
.background(.thinMaterial)
.cornerRadius(8)
}
} }

View file

@ -305,6 +305,20 @@ public class StatusEditorViewModel: ObservableObject {
} }
} }
func addDescription(container: ImageContainer, description: String) async {
guard let client, let attachment = container.mediaAttachement else { return }
if let index = indexOf(container: container) {
let originalContainer = mediasImages[index]
do {
let media: MediaAttachement = try await client.put(endpoint: Media.media(id: attachment.id,
description: description))
mediasImages[index] = .init(image: nil, mediaAttachement: media, error: nil)
} catch {
}
}
}
private func uploadMedia(data: Data) async throws -> MediaAttachement? { private func uploadMedia(data: Data) async throws -> MediaAttachement? {
guard let client else { return nil } guard let client else { return nil }
return try await client.mediaUpload(mimeType: "image/jpeg", data: data) return try await client.mediaUpload(mimeType: "image/jpeg", data: data)

View file

@ -20,7 +20,7 @@ For contributors and myself, here is a todo list of features that could be added
- [ ] DM / Conversations - [ ] DM / Conversations
- [X] Lists support - [X] Lists support
- [X] Display images alt - [X] Display images alt
- [ ] Editor: Post image alts - [X] Editor: Post image alts
- [ ] Editor: Add / Edit polls - [ ] Editor: Add / Edit polls
- [ ] Editor: Support video types - [ ] Editor: Support video types
- [ ] Editor: Add photos from camera - [ ] Editor: Add photos from camera