0.10.6
This commit is contained in:
commit
92660a4ba0
228 files changed
+24496
No files matched your search
@@ -0,0 +1,151 @@
|
||||
import SwiftUI
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#elseif os(macOS)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
@MainActor
|
||||
struct VideoAttachmentPreview: View {
|
||||
@ObservedObject var model: AppModel
|
||||
let message: ChatMessage
|
||||
let onFallbackTap: () -> Void
|
||||
|
||||
private let islandWidth: CGFloat = 268
|
||||
|
||||
var body: some View {
|
||||
Button(action: openViewer) {
|
||||
ZStack {
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.06, green: 0.17, blue: 0.29),
|
||||
Color(red: 0.09, green: 0.43, blue: 0.64)
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
|
||||
thumbnail
|
||||
.padding(6)
|
||||
|
||||
if model.isMediaPreviewLoading(message) {
|
||||
ProgressView()
|
||||
.tint(.white)
|
||||
.controlSize(.large)
|
||||
} else {
|
||||
Image(systemName: "play.fill")
|
||||
.font(.system(size: 22, weight: .bold))
|
||||
.foregroundStyle(.white)
|
||||
.padding(15)
|
||||
.background(.black.opacity(0.38), in: Circle())
|
||||
}
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(systemName: "arrow.up.left.and.arrow.down.right")
|
||||
.font(.caption.weight(.bold))
|
||||
.foregroundStyle(.white)
|
||||
.padding(7)
|
||||
.background(.black.opacity(0.42), in: Circle())
|
||||
}
|
||||
Spacer()
|
||||
HStack(spacing: 6) {
|
||||
if let filename = message.localFilename {
|
||||
Text(filename)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer(minLength: 6)
|
||||
Text(metadataText)
|
||||
.monospacedDigit()
|
||||
}
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 5)
|
||||
.background(.black.opacity(0.48), in: Capsule())
|
||||
}
|
||||
.padding(9)
|
||||
}
|
||||
.frame(width: islandWidth, height: islandHeight)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.stroke(Color.secondary.opacity(0.18), lineWidth: 1)
|
||||
)
|
||||
.contentShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.vertical, 5)
|
||||
.task(id: message.remoteAttachmentURL) {
|
||||
await model.prepareMediaPreview(message)
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel("Видео, \(metadataText)")
|
||||
.accessibilityHint("Открывает видео на весь экран")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var thumbnail: some View {
|
||||
if let data = model.mediaThumbnail(for: message) {
|
||||
#if os(iOS)
|
||||
if let image = ChatMediaImageCache.image(for: message, data: data) {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}
|
||||
#elseif os(macOS)
|
||||
if let image = ChatMediaImageCache.image(for: message, data: data) {
|
||||
Image(nsImage: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
Image(systemName: "film.fill")
|
||||
.font(.system(size: 38, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(0.65))
|
||||
}
|
||||
}
|
||||
|
||||
private var metadataText: String {
|
||||
var parts: [String] = []
|
||||
if let duration = message.duration {
|
||||
parts.append(MediaTimeFormatter.string(duration))
|
||||
}
|
||||
if let byteCount = message.byteCount {
|
||||
parts.append(ByteCountFormatter.string(fromByteCount: Int64(byteCount), countStyle: .file))
|
||||
}
|
||||
return parts.isEmpty ? "Видео" : parts.joined(separator: " · ")
|
||||
}
|
||||
|
||||
private var islandHeight: CGFloat {
|
||||
let contentWidth = islandWidth - 12
|
||||
let naturalHeight = contentWidth / thumbnailAspectRatio + 12
|
||||
return min(max(naturalHeight, 164), 250)
|
||||
}
|
||||
|
||||
private var thumbnailAspectRatio: CGFloat {
|
||||
guard let data = model.mediaThumbnail(for: message) else { return 16 / 9 }
|
||||
#if os(iOS)
|
||||
guard let image = ChatMediaImageCache.image(for: message, data: data),
|
||||
image.size.height > 0 else { return 16 / 9 }
|
||||
return max(0.7, min(2.2, image.size.width / image.size.height))
|
||||
#elseif os(macOS)
|
||||
guard let image = ChatMediaImageCache.image(for: message, data: data),
|
||||
image.size.height > 0 else { return 16 / 9 }
|
||||
return max(0.7, min(2.2, image.size.width / image.size.height))
|
||||
#endif
|
||||
}
|
||||
|
||||
private func openViewer() {
|
||||
Task {
|
||||
await model.presentMediaViewer(message)
|
||||
if model.mediaViewerItem?.id != message.id,
|
||||
message.remoteAttachmentURL == nil {
|
||||
onFallbackTap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user