Tusker/Packages/ComposeUI/Sources/ComposeUI/Controllers/FocusedAttachmentController...

123 lines
4.2 KiB
Swift

//
// FocusedAttachmentController.swift
// ComposeUI
//
// Created by Shadowfacts on 4/29/23.
//
import SwiftUI
import MatchedGeometryPresentation
import AVKit
class FocusedAttachmentController: ViewController {
unowned let parent: ComposeController
let attachment: DraftAttachment
let thumbnailController: AttachmentThumbnailController
private let player: AVPlayer?
init(parent: ComposeController, attachment: DraftAttachment, thumbnailController: AttachmentThumbnailController) {
self.parent = parent
self.attachment = attachment
self.thumbnailController = thumbnailController
if case let .file(url, type) = attachment.data,
type.conforms(to: .movie) {
self.player = AVPlayer(url: url)
self.player!.isMuted = true
} else {
self.player = nil
}
}
var view: some View {
FocusedAttachmentView(attachment: attachment)
}
struct FocusedAttachmentView: View {
@ObservedObject var attachment: DraftAttachment
@EnvironmentObject private var controller: FocusedAttachmentController
@Environment(\.dismiss) private var dismiss
@FocusState private var textEditorFocused: Bool
@EnvironmentObject private var matchedGeomState: MatchedGeometryState
var body: some View {
VStack(spacing: 0) {
Spacer(minLength: 0)
if let player = controller.player {
VideoPlayer(player: player)
.matchedGeometryDestination(id: attachment.id)
.onAppear {
player.play()
}
} else if #available(iOS 16.0, *) {
ZoomableScrollView {
attachmentView
.matchedGeometryDestination(id: attachment.id)
}
} else {
attachmentView
.matchedGeometryDestination(id: attachment.id)
}
Spacer(minLength: 0)
FocusedAttachmentDescriptionView(attachment: attachment)
.environment(\.colorScheme, .dark)
.matchedGeometryDestination(id: AttachmentDescriptionTextViewID(attachment))
.frame(height: 150)
.focused($textEditorFocused)
}
.background(.black)
.overlay(alignment: .topLeading, content: {
Button {
// set the mode to dismissing immediately, so that layout changes due to the keyboard hiding
// (which happens before the dismiss animation controller starts running) don't alter the destination frames
if textEditorFocused {
matchedGeomState.mode = .dismissing
}
dismiss()
} label: {
Image(systemName: "arrow.down.forward.and.arrow.up.backward")
}
.buttonStyle(DismissFocusedAttachmentButtonStyle())
.padding([.top, .leading], 4)
})
}
private var attachmentView: some View {
ControllerView(controller: { controller.thumbnailController })
.environment(\.attachmentThumbnailConfiguration, .init(contentMode: .fit, fullSize: true))
}
}
}
private struct DismissFocusedAttachmentButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
ZStack {
RoundedRectangle(cornerRadius: 4)
.fill(.black.opacity(0.5))
configuration.label
.foregroundColor(.white)
.imageScale(.large)
}
.frame(width: 40, height: 40)
}
}
struct AttachmentDescriptionTextViewID: Hashable {
let attachmentID: UUID
init(_ attachment: DraftAttachment) {
self.attachmentID = attachment.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(attachmentID)
hasher.combine("descriptionTextView")
}
}