2023-01-25 12:02:28 +00:00
|
|
|
import AVKit
|
2023-01-17 10:36:01 +00:00
|
|
|
import DesignSystem
|
2022-12-31 08:10:27 +00:00
|
|
|
import Env
|
|
|
|
import Models
|
|
|
|
import NukeUI
|
2023-01-17 10:36:01 +00:00
|
|
|
import SwiftUI
|
2022-12-31 08:10:27 +00:00
|
|
|
|
2023-02-18 06:26:48 +00:00
|
|
|
struct StatusEditorMediaView: View {
|
2023-01-15 15:39:08 +00:00
|
|
|
@EnvironmentObject private var theme: Theme
|
2023-09-18 05:01:23 +00:00
|
|
|
@Environment(CurrentInstance.self) private var currentInstance
|
|
|
|
var viewModel: StatusEditorViewModel
|
2023-01-22 08:09:35 +00:00
|
|
|
@State private var editingContainer: StatusEditorMediaContainer?
|
2023-01-27 19:36:40 +00:00
|
|
|
|
2023-01-27 07:54:41 +00:00
|
|
|
@State private var isErrorDisplayed: Bool = false
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-31 08:10:27 +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) {
|
2023-01-22 08:09:35 +00:00
|
|
|
if let attachement = container.mediaAttachment {
|
|
|
|
makeLazyImage(mediaAttachement: attachement)
|
|
|
|
} else if container.image != nil {
|
2023-01-15 15:39:08 +00:00
|
|
|
makeLocalImage(container: container)
|
2023-02-07 15:26:27 +00:00
|
|
|
} else if container.movieTransferable != nil || container.gifTransferable != nil {
|
2023-01-22 08:09:35 +00:00
|
|
|
makeVideoAttachement(container: container)
|
2023-01-27 07:54:41 +00:00
|
|
|
} else if let error = container.error as? ServerError {
|
|
|
|
makeErrorView(error: error)
|
2023-01-15 15:39:08 +00:00
|
|
|
}
|
2023-01-17 14:14:50 +00:00
|
|
|
if container.mediaAttachment?.description?.isEmpty == false {
|
2023-01-15 15:39:08 +00:00
|
|
|
altMarker
|
2022-12-31 08:10:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-03 06:41:29 +00:00
|
|
|
.padding(.horizontal, .layoutPadding)
|
2022-12-31 08:10:27 +00:00
|
|
|
}
|
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
|
|
|
}
|
2022-12-31 08:10:27 +00:00
|
|
|
}
|
2023-01-25 12:02:28 +00:00
|
|
|
|
2023-01-22 08:09:35 +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
|
|
|
|
2023-01-22 08:09:35 +00:00
|
|
|
private func makeLocalImage(container: StatusEditorMediaContainer) -> some View {
|
2022-12-31 08:10:27 +00:00
|
|
|
ZStack(alignment: .center) {
|
|
|
|
Image(uiImage: container.image!)
|
|
|
|
.resizable()
|
2023-01-17 14:14:50 +00:00
|
|
|
.blur(radius: container.mediaAttachment == nil ? 20 : 0)
|
2022-12-31 08:10:27 +00:00
|
|
|
.aspectRatio(contentMode: .fill)
|
|
|
|
.frame(width: 150, height: 150)
|
|
|
|
.cornerRadius(8)
|
|
|
|
if container.error != nil {
|
2023-02-23 18:53:16 +00:00
|
|
|
Text("status.editor.error.upload")
|
2023-01-17 14:14:50 +00:00
|
|
|
} else if container.mediaAttachment == nil {
|
2022-12-31 08:10:27 +00:00
|
|
|
ProgressView()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-01-22 08:09:35 +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
|
2023-02-18 06:25:10 +00:00
|
|
|
.resizable()
|
|
|
|
.aspectRatio(contentMode: .fill)
|
2023-01-22 08:09:35 +00:00
|
|
|
.frame(width: 150, height: 150)
|
|
|
|
} else {
|
|
|
|
placeholderView
|
|
|
|
}
|
|
|
|
}
|
2022-12-31 08:10:27 +00:00
|
|
|
} else {
|
2023-01-22 08:09:35 +00:00
|
|
|
placeholderView
|
|
|
|
}
|
|
|
|
if mediaAttachement.url == nil {
|
|
|
|
ProgressView()
|
|
|
|
}
|
|
|
|
if mediaAttachement.url != nil,
|
2023-01-25 12:02:28 +00:00
|
|
|
mediaAttachement.supportedType == .video || mediaAttachement.supportedType == .gifv
|
|
|
|
{
|
2023-01-22 08:09:35 +00:00
|
|
|
Image(systemName: "play.fill")
|
|
|
|
.font(.headline)
|
|
|
|
.tint(.white)
|
2022-12-31 08:10:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
.frame(width: 150, height: 150)
|
|
|
|
.cornerRadius(8)
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-01-03 18:30:27 +00:00
|
|
|
@ViewBuilder
|
2023-01-22 08:09:35 +00:00
|
|
|
private func makeImageMenu(container: StatusEditorMediaContainer) -> some View {
|
2023-03-03 17:47:25 +00:00
|
|
|
if container.mediaAttachment?.url != nil {
|
2023-02-11 20:38:33 +00:00
|
|
|
if currentInstance.isEditAltTextSupported || !viewModel.mode.isEditing {
|
2023-01-27 07:54:41 +00:00
|
|
|
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 {
|
2023-01-27 07:54:41 +00:00
|
|
|
isErrorDisplayed = true
|
2023-01-03 19:36:57 +00:00
|
|
|
} label: {
|
2023-01-27 07:54:41 +00:00
|
|
|
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: {
|
2023-01-19 17:14:08 +00:00
|
|
|
Label("action.delete", systemImage: "trash")
|
2023-01-03 18:30:27 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-27 19:36:40 +00:00
|
|
|
|
2023-01-27 07:54:41 +00:00
|
|
|
private func makeErrorView(error: ServerError) -> some View {
|
|
|
|
ZStack {
|
|
|
|
placeholderView
|
2023-02-23 18:53:16 +00:00
|
|
|
Text("status.editor.error.upload")
|
2023-01-27 07:54:41 +00:00
|
|
|
}
|
|
|
|
.alert("alert.error", isPresented: $isErrorDisplayed) {
|
2023-01-27 19:36:40 +00:00
|
|
|
Button("Ok", action: {})
|
2023-01-27 07:54:41 +00:00
|
|
|
} 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: {
|
2023-01-19 17:14:08 +00:00
|
|
|
Text("status.image.alt-text.abbreviation")
|
2023-01-03 18:30:27 +00:00
|
|
|
.font(.caption2)
|
|
|
|
}
|
|
|
|
.padding(4)
|
|
|
|
.background(.thinMaterial)
|
|
|
|
.cornerRadius(8)
|
|
|
|
}
|
2023-01-25 12:02:28 +00:00
|
|
|
|
2023-01-22 08:09:35 +00:00
|
|
|
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)
|
2023-01-22 08:09:35 +00:00
|
|
|
}
|
2022-12-31 08:10:27 +00:00
|
|
|
}
|