IceCubesApp/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift

246 lines
7.5 KiB
Swift
Raw Normal View History

2022-12-17 12:37:46 +00:00
import SwiftUI
import Models
2022-12-22 09:53:36 +00:00
import Env
2022-12-22 18:00:23 +00:00
import Shimmer
2022-12-25 06:43:02 +00:00
import NukeUI
import DesignSystem
2022-12-17 12:37:46 +00:00
public struct StatusMediaPreviewView: View {
2022-12-22 09:53:36 +00:00
@EnvironmentObject private var quickLook: QuickLook
@EnvironmentObject private var theme: Theme
2022-12-22 09:53:36 +00:00
2022-12-17 12:37:46 +00:00
public let attachements: [MediaAttachement]
2023-01-07 16:44:25 +00:00
public let isNotifications: Bool
2022-12-24 07:29:45 +00:00
2022-12-22 09:53:36 +00:00
@State private var isQuickLookLoading: Bool = false
@State private var width: CGFloat = 0
2023-01-03 07:45:27 +00:00
@State private var altTextDisplayed: String?
@State private var isAltAlertDisplayed: Bool = false
private var imageMaxHeight: CGFloat {
2023-01-07 16:44:25 +00:00
if isNotifications {
2022-12-29 16:22:07 +00:00
return 50
}
2023-01-07 16:44:25 +00:00
if theme.statusDisplayStyle == .compact {
return 100
}
if attachements.count == 1 {
return 300
}
return attachements.count > 2 ? 100 : 200
}
private func size(for media: MediaAttachement) -> CGSize? {
2023-01-07 16:44:25 +00:00
if isNotifications {
2022-12-29 16:22:07 +00:00
return .init(width: 50, height: 50)
}
2023-01-07 16:44:25 +00:00
if theme.statusDisplayStyle == .compact {
return .init(width: 100, height: 100)
}
2023-01-05 12:27:04 +00:00
if let width = media.meta?.original?.width,
let height = media.meta?.original?.height {
return .init(width: CGFloat(width), height: CGFloat(height))
}
return nil
}
private func imageSize(from: CGSize, newWidth: CGFloat) -> CGSize {
2023-01-07 16:44:25 +00:00
if isNotifications {
2022-12-29 16:22:07 +00:00
return .init(width: 50, height: 50)
}
let ratio = newWidth / from.width
let newHeight = from.height * ratio
return .init(width: newWidth, height: newHeight)
}
2022-12-17 12:37:46 +00:00
public var body: some View {
Group {
if attachements.count == 1, let attachement = attachements.first {
makeFeaturedImagePreview(attachement: attachement)
.onTapGesture {
Task {
2022-12-27 15:16:25 +00:00
await quickLook.prepareFor(urls: attachements.compactMap{ $0.url }, selectedURL: attachement.url!)
}
}
} else {
2023-01-07 16:44:25 +00:00
if isNotifications || theme.statusDisplayStyle == .compact {
HStack {
2022-12-29 16:22:07 +00:00
makeAttachementView(for: 0)
makeAttachementView(for: 1)
makeAttachementView(for: 2)
makeAttachementView(for: 3)
}
2022-12-29 16:22:07 +00:00
} else {
VStack {
HStack {
makeAttachementView(for: 0)
makeAttachementView(for: 1)
}
2022-12-29 16:22:07 +00:00
HStack {
makeAttachementView(for: 2)
makeAttachementView(for: 3)
}
}
2022-12-17 12:37:46 +00:00
}
}
}
2022-12-22 09:53:36 +00:00
.overlay {
if quickLook.isPreparing {
quickLookLoadingView
2022-12-22 09:53:36 +00:00
.transition(.opacity)
}
2022-12-19 16:18:16 +00:00
}
2023-01-03 07:45:27 +00:00
.alert("Image description",
isPresented: $isAltAlertDisplayed) {
Button("Ok", action: { })
} message: {
Text(altTextDisplayed ?? "")
}
}
2022-12-29 16:22:07 +00:00
@ViewBuilder
private func makeAttachementView(for index: Int) -> some View {
if attachements.count > index {
makePreview(attachement: attachements[index])
}
}
@ViewBuilder
private func makeFeaturedImagePreview(attachement: MediaAttachement) -> some View {
switch attachement.supportedType {
case .image:
2023-01-07 16:44:25 +00:00
if theme.statusDisplayStyle == .large,
let size = size(for: attachement),
UIDevice.current.userInterfaceIdiom != .pad,
UIDevice.current.userInterfaceIdiom != .mac {
let avatarColumnWidth = theme.avatarPosition == .leading ? AvatarView.Size.status.size.width + .statusColumnsSpacing : 0
let availableWidth = UIScreen.main.bounds.width - (.layoutPadding * 2) - avatarColumnWidth
let newSize = imageSize(from: size,
newWidth: availableWidth)
2023-01-03 07:45:27 +00:00
ZStack(alignment: .bottomTrailing) {
LazyImage(url: attachement.url) { state in
if let image = state.image {
image
.resizingMode(.aspectFill)
.cornerRadius(4)
.frame(width: newSize.width, height: newSize.height)
} else {
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray)
.frame(width: newSize.width, height: newSize.height)
.shimmering()
}
}
2023-01-07 16:44:25 +00:00
if let alt = attachement.description, !alt.isEmpty, !isNotifications {
2023-01-03 07:45:27 +00:00
Button {
altTextDisplayed = alt
isAltAlertDisplayed = true
} label: {
Text("ALT")
}
.padding(8)
.background(.thinMaterial)
.cornerRadius(4)
}
}
} else {
AsyncImage(
url: attachement.url,
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxHeight: imageMaxHeight)
.frame(maxWidth: imageMaxHeight)
.cornerRadius(4)
},
placeholder: {
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray)
.frame(maxHeight: imageMaxHeight)
.frame(maxWidth: imageMaxHeight)
.shimmering()
})
}
case .gifv, .video, .audio:
2022-12-27 15:16:25 +00:00
if let url = attachement.url {
VideoPlayerView(viewModel: .init(url: url))
.frame(height: imageMaxHeight)
}
case .none:
EmptyView()
}
2022-12-19 16:18:16 +00:00
}
@ViewBuilder
private func makePreview(attachement: MediaAttachement) -> some View {
if let type = attachement.supportedType {
2022-12-20 07:14:57 +00:00
Group {
GeometryReader { proxy in
switch type {
case .image:
2023-01-03 07:45:27 +00:00
ZStack(alignment: .bottomTrailing) {
LazyImage(url: attachement.url) { state in
if let image = state.image {
image
.resizingMode(.aspectFill)
.cornerRadius(4)
} else if state.isLoading {
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray)
.frame(maxHeight: imageMaxHeight)
2023-01-07 16:44:25 +00:00
.frame(width: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width)
2023-01-03 07:45:27 +00:00
.shimmering()
}
}
2023-01-07 16:44:25 +00:00
.frame(width: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width)
2023-01-03 07:45:27 +00:00
.frame(height: imageMaxHeight)
2023-01-07 16:44:25 +00:00
if let alt = attachement.description, !alt.isEmpty, !isNotifications {
2023-01-03 07:45:27 +00:00
Button {
altTextDisplayed = alt
isAltAlertDisplayed = true
} label: {
Text("ALT")
.font(.footnote)
}
.padding(4)
.background(.thinMaterial)
.cornerRadius(4)
2022-12-19 16:18:16 +00:00
}
2022-12-25 06:43:02 +00:00
}
case .gifv, .video, .audio:
2022-12-27 15:16:25 +00:00
if let url = attachement.url {
VideoPlayerView(viewModel: .init(url: url))
2023-01-07 16:44:25 +00:00
.frame(width: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width)
2022-12-27 15:16:25 +00:00
.frame(height: imageMaxHeight)
}
2022-12-19 16:18:16 +00:00
}
}
2023-01-07 16:44:25 +00:00
.frame(width: isNotifications ? imageMaxHeight : nil)
.frame(height: imageMaxHeight)
2022-12-20 07:14:57 +00:00
}
.onTapGesture {
2022-12-22 09:53:36 +00:00
Task {
2022-12-27 15:16:25 +00:00
await quickLook.prepareFor(urls: attachements.compactMap{ $0.url }, selectedURL: attachement.url!)
2022-12-22 09:53:36 +00:00
}
2022-12-17 12:37:46 +00:00
}
2022-12-19 15:01:23 +00:00
}
2022-12-17 12:37:46 +00:00
}
2022-12-19 18:04:07 +00:00
2022-12-22 09:53:36 +00:00
private var quickLookLoadingView: some View {
ZStack(alignment: .center) {
VStack {
Spacer()
HStack {
Spacer()
ProgressView()
Spacer()
2022-12-19 18:04:07 +00:00
}
2022-12-22 09:53:36 +00:00
Spacer()
2022-12-19 18:04:07 +00:00
}
}
2022-12-22 09:53:36 +00:00
.background(.ultraThinMaterial)
2022-12-19 18:04:07 +00:00
}
2022-12-17 12:37:46 +00:00
}