194 lines
7.6 KiB
Swift
194 lines
7.6 KiB
Swift
//Made by Lumaa
|
|
|
|
import SwiftUI
|
|
import UIKit
|
|
import AVKit
|
|
|
|
struct PostAttachment: View {
|
|
@Environment(AppDelegate.self) private var appDelegate: AppDelegate
|
|
var attachment: MediaAttachment
|
|
|
|
var isFeatured: Bool = true
|
|
var isImaging: Bool = false
|
|
|
|
@State private var player: AVPlayer?
|
|
|
|
var appLayoutWidth: CGFloat = 10
|
|
var availableWidth: CGFloat {
|
|
appDelegate.windowWidth * 0.8
|
|
}
|
|
var availableHeight: CGFloat {
|
|
appDelegate.windowHeight
|
|
}
|
|
private let imageMaxHeight: CGFloat = 300
|
|
|
|
|
|
var body: some View {
|
|
let mediaSize: CGSize = size(for: attachment) ?? .init(width: imageMaxHeight, height: imageMaxHeight)
|
|
let newSize = imageSize(from: mediaSize)
|
|
|
|
if !isImaging {
|
|
GeometryReader { _ in
|
|
// Audio later because it's a lil harder
|
|
if attachment.supportedType == .image {
|
|
if let url = attachment.url {
|
|
AsyncImage(url: url) { image in
|
|
image
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
.frame(width: !isFeatured ? imageMaxHeight / 1.5 : newSize.width, height: !isFeatured ? imageMaxHeight: newSize.height)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 15)
|
|
.stroke(.gray.opacity(0.3), lineWidth: 1)
|
|
)
|
|
} placeholder: {
|
|
ZStack(alignment: .center) {
|
|
Color.gray
|
|
|
|
ProgressView()
|
|
.progressViewStyle(.circular)
|
|
}
|
|
}
|
|
}
|
|
} else if attachment.supportedType == .gifv {
|
|
ZStack(alignment: .center) {
|
|
if player != nil {
|
|
NoControlsPlayerViewController(player: player!)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 15)
|
|
.stroke(.gray.opacity(0.3), lineWidth: 1)
|
|
)
|
|
} else {
|
|
Color.gray
|
|
|
|
ProgressView()
|
|
.progressViewStyle(.circular)
|
|
}
|
|
}
|
|
.onAppear {
|
|
if let url = attachment.url {
|
|
player = AVPlayer(url: url)
|
|
player?.preventsDisplaySleepDuringVideoPlayback = false
|
|
player?.audiovisualBackgroundPlaybackPolicy = .pauses
|
|
player?.isMuted = true
|
|
player?.play()
|
|
|
|
guard let player else { return }
|
|
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in
|
|
Task { @MainActor in
|
|
player.seek(to: CMTime.zero)
|
|
player.play()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onDisappear() {
|
|
guard player != nil else { return }
|
|
player?.pause()
|
|
}
|
|
|
|
} else if attachment.supportedType == .video {
|
|
ZStack {
|
|
if player != nil {
|
|
VideoPlayer(player: player)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 15)
|
|
.stroke(.gray.opacity(0.3), lineWidth: 1)
|
|
)
|
|
} else {
|
|
Color.gray
|
|
|
|
ProgressView()
|
|
.progressViewStyle(.circular)
|
|
}
|
|
}
|
|
.onAppear {
|
|
if let url = attachment.url {
|
|
player = AVPlayer(url: url)
|
|
player?.preventsDisplaySleepDuringVideoPlayback = false
|
|
player?.audiovisualBackgroundPlaybackPolicy = .pauses
|
|
player?.isMuted = true
|
|
player?.play()
|
|
}
|
|
}
|
|
.onDisappear() {
|
|
guard player != nil else { return }
|
|
player?.pause()
|
|
}
|
|
}
|
|
}
|
|
.frame(width: !isFeatured ? imageMaxHeight / 1.5 : newSize.width, height: !isFeatured ? imageMaxHeight: newSize.height)
|
|
.clipped()
|
|
.clipShape(.rect(cornerRadius: 15))
|
|
.contentShape(Rectangle())
|
|
} else {
|
|
imaging
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
var imaging: some View {
|
|
let mediaSize: CGSize = size(for: attachment) ?? .init(width: imageMaxHeight, height: imageMaxHeight)
|
|
let newSize = imageSize(from: mediaSize)
|
|
|
|
GeometryReader { _ in
|
|
// Audio later because it's a lil harder
|
|
if let url = attachment.previewUrl {
|
|
OnlineImage(url: url, size: !isFeatured ? imageMaxHeight / 1.5 : newSize.width, priority: .veryHigh)
|
|
}
|
|
}
|
|
.frame(width: !isFeatured ? imageMaxHeight / 1.5 : newSize.width, height: !isFeatured ? imageMaxHeight: newSize.height)
|
|
.clipped()
|
|
.clipShape(.rect(cornerRadius: 15))
|
|
.contentShape(Rectangle())
|
|
}
|
|
|
|
private func size(for media: MediaAttachment) -> CGSize? {
|
|
guard let width = media.meta?.original?.width,
|
|
let height = media.meta?.original?.height
|
|
else { return nil }
|
|
|
|
return .init(width: CGFloat(width), height: CGFloat(height))
|
|
}
|
|
|
|
private func imageSize(from: CGSize) -> CGSize {
|
|
let boxWidth = availableWidth - appLayoutWidth
|
|
let boxHeight = availableHeight * 0.8 // use only 80% of window height to leave room for text
|
|
|
|
if from.width <= boxWidth, from.height <= boxHeight {
|
|
// intrinsic size of image fits just fine
|
|
return from
|
|
}
|
|
|
|
// shrink image proportionally to fit inside the box
|
|
let xRatio = boxWidth / from.width
|
|
let yRatio = boxHeight / from.height
|
|
if xRatio < yRatio {
|
|
return .init(width: boxWidth, height: from.height * xRatio)
|
|
} else {
|
|
return .init(width: from.width * yRatio, height: boxHeight)
|
|
}
|
|
}
|
|
}
|
|
|
|
class NoControlsAVPlayerViewController: AVPlayerViewController {
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
self.showsPlaybackControls = false
|
|
}
|
|
}
|
|
|
|
struct NoControlsPlayerViewController: UIViewControllerRepresentable {
|
|
let player: AVPlayer
|
|
|
|
func updateUIViewController(_ uiViewController: NoControlsAVPlayerViewController, context: Context) {
|
|
// update
|
|
}
|
|
|
|
func makeUIViewController(context: Context) -> NoControlsAVPlayerViewController {
|
|
let customPlayerVC = NoControlsAVPlayerViewController()
|
|
customPlayerVC.player = player // Set the AVPlayer
|
|
return customPlayerVC
|
|
}
|
|
}
|