IceCubesApp/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift

164 lines
4.8 KiB
Swift
Raw Normal View History

import AVKit
2023-01-17 10:36:01 +00:00
import DesignSystem
import Env
import Models
import NukeUI
2023-01-17 10:36:01 +00:00
import SwiftUI
2023-02-18 06:26:48 +00:00
struct StatusEditorMediaView: View {
2023-01-15 15:39:08 +00:00
@EnvironmentObject private var theme: Theme
@Environment(CurrentInstance.self) private var currentInstance
var viewModel: StatusEditorViewModel
@State private var editingContainer: StatusEditorMediaContainer?
2023-01-27 19:36:40 +00:00
@State private var isErrorDisplayed: Bool = false
2023-01-17 10:36:01 +00:00
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(viewModel.mediasImages) { container in
2023-01-15 15:39:08 +00:00
Menu {
makeImageMenu(container: container)
} label: {
ZStack(alignment: .bottomTrailing) {
if let attachement = container.mediaAttachment {
makeLazyImage(mediaAttachement: attachement)
} else if container.image != nil {
2023-01-15 15:39:08 +00:00
makeLocalImage(container: container)
} else if container.movieTransferable != nil || container.gifTransferable != nil {
makeVideoAttachement(container: container)
} else if let error = container.error as? ServerError {
makeErrorView(error: error)
2023-01-15 15:39:08 +00:00
}
if container.mediaAttachment?.description?.isEmpty == false {
2023-01-15 15:39:08 +00:00
altMarker
}
}
}
}
}
.padding(.horizontal, .layoutPadding)
}
2023-01-03 18:30:27 +00:00
.sheet(item: $editingContainer) { container in
StatusEditorMediaEditView(viewModel: viewModel, container: container)
2023-01-15 15:39:08 +00:00
.preferredColorScheme(theme.selectedScheme == .dark ? .dark : .light)
2023-01-03 18:30:27 +00:00
}
}
private func makeVideoAttachement(container: StatusEditorMediaContainer) -> some View {
ZStack(alignment: .center) {
placeholderView
if container.mediaAttachment == nil {
ProgressView()
}
}
.cornerRadius(8)
.frame(width: 150, height: 150)
}
2023-01-17 10:36:01 +00:00
private func makeLocalImage(container: StatusEditorMediaContainer) -> some View {
ZStack(alignment: .center) {
Image(uiImage: container.image!)
.resizable()
.blur(radius: container.mediaAttachment == nil ? 20 : 0)
.aspectRatio(contentMode: .fill)
.frame(width: 150, height: 150)
.cornerRadius(8)
if container.error != nil {
Text("status.editor.error.upload")
} else if container.mediaAttachment == nil {
ProgressView()
}
}
}
2023-01-17 10:36:01 +00:00
private func makeLazyImage(mediaAttachement: MediaAttachment) -> some View {
ZStack(alignment: .center) {
if let url = mediaAttachement.url ?? mediaAttachement.previewUrl {
LazyImage(url: url) { state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 150, height: 150)
} else {
placeholderView
}
}
} else {
placeholderView
}
if mediaAttachement.url == nil {
ProgressView()
}
if mediaAttachement.url != nil,
mediaAttachement.supportedType == .video || mediaAttachement.supportedType == .gifv
{
Image(systemName: "play.fill")
.font(.headline)
.tint(.white)
}
}
.frame(width: 150, height: 150)
.cornerRadius(8)
}
2023-01-17 10:36:01 +00:00
2023-01-03 18:30:27 +00:00
@ViewBuilder
private func makeImageMenu(container: StatusEditorMediaContainer) -> some View {
if container.mediaAttachment?.url != nil {
if currentInstance.isEditAltTextSupported || !viewModel.mode.isEditing {
Button {
editingContainer = container
} label: {
Label(container.mediaAttachment?.description?.isEmpty == false ?
"status.editor.description.edit" : "status.editor.description.add",
systemImage: "pencil.line")
}
}
} else if container.error != nil {
2023-01-03 19:36:57 +00:00
Button {
isErrorDisplayed = true
2023-01-03 19:36:57 +00:00
} label: {
Label("action.view.error", systemImage: "exclamationmark.triangle")
2023-01-03 19:36:57 +00:00
}
2023-01-03 18:30:27 +00:00
}
2023-01-27 19:36:40 +00:00
2023-01-03 18:30:27 +00:00
Button(role: .destructive) {
withAnimation {
viewModel.mediasImages.removeAll(where: { $0.id == container.id })
}
} label: {
Label("action.delete", systemImage: "trash")
2023-01-03 18:30:27 +00:00
}
}
2023-01-27 19:36:40 +00:00
private func makeErrorView(error: ServerError) -> some View {
ZStack {
placeholderView
Text("status.editor.error.upload")
}
.alert("alert.error", isPresented: $isErrorDisplayed) {
2023-01-27 19:36:40 +00:00
Button("Ok", action: {})
} message: {
Text(error.error ?? "")
}
}
2023-01-17 10:36:01 +00:00
2023-01-03 18:30:27 +00:00
private var altMarker: some View {
2023-01-17 10:36:01 +00:00
Button {} label: {
Text("status.image.alt-text.abbreviation")
2023-01-03 18:30:27 +00:00
.font(.caption2)
}
.padding(4)
.background(.thinMaterial)
.cornerRadius(8)
}
private var placeholderView: some View {
Rectangle()
.foregroundColor(theme.secondaryBackgroundColor)
.frame(width: 150, height: 150)
Timeline & Timeline detail accessibility uplift (#1323) * Improve accessibility of StatusPollView Previously, this view did not provide the proper context to indicate that it represented a poll. Now, we’ve added - A container that will stay “Active poll” or “Poll results” when the cursor first hits one of the options; - A prefix to say “Option X of Y” before each option; - A Selected trait on the selected option(s), if present - Consolidating and adding an `.updatesFrequently` trait to the footer view with the countdown. * Add poll description in StatusRowView combinedAccessibilityLabel This largely duplicates the logic in `StatusPollView`. * Improve accessibility of media attachments Previously, the media attachments without alt text would not show up in the consolidated `StatusRowView`, nor would they be meaningfully explained on the status detail screen. Now, they are presented with their attachment type. * Change accessibilityRepresentation of AppAcountsSelectorView * Change Notifications tab title view accessibility representation to Menu Previously it would present as a button * Hide layout `Rectangle`s from accessibility * Consolidate `StatusRowDetailView` accessibility representation * Improve readability of Poll accessibility label * Ensure poll options don’t present as interactive when the poll is finished * Improve accessibility of StatusRowCardView Previously, it would present as four separate elements, including an image without a description, all interactive, none with an interactive trait. Now, it presents as a single element with the `.link` trait * Improve accessibility of StatusRowHeaderView Previously, it had no traits and no actions except inherited ones. Now it presents as a button, triggering its primary action. It also has custom actions corresponding to its context menu * Avoid applying the StatusRowView custom actions to every view when contained * Provide context for the application name * Add pauses to StatusRowView combinedAccessibilityLabel * Hide `TimelineView.scrollToTopView` from accessibility * Set appropriate font style on Notification header After the change the Text needed a `.headline` style to match the prior appearance. * Fix bug in accessibilityRepresentation of TimelineView nav bar title Previously, it would not display the proper label for .remoteLocal filter options. * Ensure that pop-up button nav bar titles are interactive * Ensure TextView responds to Environment.sizeCategory This resolves #1309 * Fix button --------- Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
2023-03-28 16:48:58 +00:00
.accessibilityHidden(true)
}
}