feat: add mediaView for compose scene
This commit is contained in:
parent
9c9edcb717
commit
fc3750c377
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// View.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 2022/11/8.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
public func badgeView<Content>(_ content: Content) -> some View where Content: View {
|
||||
overlay(
|
||||
ZStack {
|
||||
content
|
||||
}
|
||||
.alignmentGuide(.top) { $0.height / 2 }
|
||||
.alignmentGuide(.trailing) { $0.width / 2 }
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -13,18 +13,17 @@ import AVKit
|
|||
|
||||
public struct AttachmentView: View {
|
||||
|
||||
static let size = CGSize(width: 56, height: 56)
|
||||
static let cornerRadius: CGFloat = 8
|
||||
|
||||
@ObservedObject var viewModel: AttachmentViewModel
|
||||
|
||||
let action: (Action) -> Void
|
||||
|
||||
@State var isCaptionEditorPresented = false
|
||||
@State var caption = ""
|
||||
|
||||
public var body: some View {
|
||||
Text("Hello")
|
||||
ZStack {
|
||||
let image = viewModel.thumbnail ?? .placeholder(color: .secondarySystemFill)
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
}
|
||||
// Menu {
|
||||
// menu
|
||||
// } label: {
|
||||
|
|
|
@ -22,6 +22,7 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
|
|||
var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
// input
|
||||
public let authContext: AuthContext
|
||||
public let input: Input
|
||||
@Published var caption = ""
|
||||
@Published var sizeLimit = SizeLimit()
|
||||
|
@ -33,13 +34,19 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
|
|||
@Published var error: Error?
|
||||
let progress = Progress() // upload progress
|
||||
|
||||
public init(input: Input) {
|
||||
public init(
|
||||
authContext: AuthContext,
|
||||
input: Input
|
||||
) {
|
||||
self.authContext = authContext
|
||||
self.input = input
|
||||
super.init()
|
||||
// end init
|
||||
|
||||
defer {
|
||||
load(input: input)
|
||||
Task {
|
||||
await load(input: input)
|
||||
}
|
||||
}
|
||||
|
||||
$output
|
||||
|
@ -53,6 +60,7 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
|
|||
return nil
|
||||
}
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: &$thumbnail)
|
||||
}
|
||||
|
||||
|
@ -86,13 +94,6 @@ extension AttachmentViewModel {
|
|||
case png
|
||||
case jpg
|
||||
}
|
||||
|
||||
public var twitterMediaCategory: TwitterMediaCategory {
|
||||
switch self {
|
||||
case .image: return .image
|
||||
case .video: return .amplifyVideo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct SizeLimit {
|
||||
|
@ -115,18 +116,13 @@ extension AttachmentViewModel {
|
|||
case invalidAttachmentType
|
||||
case attachmentTooLarge
|
||||
}
|
||||
|
||||
public enum TwitterMediaCategory: String {
|
||||
case image = "TWEET_IMAGE"
|
||||
case GIF = "TWEET_GIF"
|
||||
case video = "TWEET_VIDEO"
|
||||
case amplifyVideo = "AMPLIFY_VIDEO"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AttachmentViewModel {
|
||||
|
||||
private func load(input: Input) {
|
||||
@MainActor
|
||||
private func load(input: Input) async {
|
||||
switch input {
|
||||
case .image(let image):
|
||||
guard let data = image.pngData() else {
|
||||
|
@ -135,32 +131,26 @@ extension AttachmentViewModel {
|
|||
}
|
||||
output = .image(data, imageKind: .png)
|
||||
case .url(let url):
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let output = try await AttachmentViewModel.load(url: url)
|
||||
self.output = output
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
} // end Task
|
||||
do {
|
||||
let output = try await AttachmentViewModel.load(url: url)
|
||||
self.output = output
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
case .pickerResult(let pickerResult):
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let output = try await AttachmentViewModel.load(itemProvider: pickerResult.itemProvider)
|
||||
self.output = output
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
} // end Task
|
||||
do {
|
||||
let output = try await AttachmentViewModel.load(itemProvider: pickerResult.itemProvider)
|
||||
self.output = output
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
case .itemProvider(let itemProvider):
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let output = try await AttachmentViewModel.load(itemProvider: itemProvider)
|
||||
self.output = output
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
} // end Task
|
||||
do {
|
||||
let output = try await AttachmentViewModel.load(itemProvider: itemProvider)
|
||||
self.output = output
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -325,16 +325,10 @@ extension ComposeContentViewController: PHPickerViewControllerDelegate {
|
|||
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
picker.dismiss(animated: true, completion: nil)
|
||||
|
||||
// TODO:
|
||||
// let attachmentServices: [MastodonAttachmentService] = results.map { result in
|
||||
// let service = MastodonAttachmentService(
|
||||
// context: context,
|
||||
// pickerResult: result,
|
||||
// initialAuthenticationBox: viewModel.authenticationBox
|
||||
// )
|
||||
// return service
|
||||
// }
|
||||
// viewModel.attachmentServices = viewModel.attachmentServices + attachmentServices
|
||||
let attachmentViewModels: [AttachmentViewModel] = results.map { result in
|
||||
AttachmentViewModel(authContext: viewModel.authContext, input: .pickerResult(result))
|
||||
}
|
||||
viewModel.attachmentViewModels += attachmentViewModels
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import MastodonAsset
|
|||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
import Stripes
|
||||
import Kingfisher
|
||||
|
||||
public struct ComposeContentView: View {
|
||||
|
||||
|
@ -108,6 +109,9 @@ public struct ComposeContentView: View {
|
|||
// poll
|
||||
pollView
|
||||
.padding(.horizontal, ComposeContentView.margin)
|
||||
// media
|
||||
mediaView
|
||||
.padding(.horizontal, ComposeContentView.margin)
|
||||
}
|
||||
.background(
|
||||
GeometryReader { proxy in
|
||||
|
@ -194,6 +198,29 @@ extension ComposeContentView {
|
|||
}
|
||||
} // end VStack
|
||||
}
|
||||
|
||||
// MARK: - media
|
||||
var mediaView: some View {
|
||||
VStack(spacing: 16) {
|
||||
ForEach(viewModel.attachmentViewModels, id: \.self) { attachmentViewModel in
|
||||
Color.clear.aspectRatio(358.0/232.0, contentMode: .fill)
|
||||
.overlay(
|
||||
AttachmentView(viewModel: attachmentViewModel) { action in
|
||||
|
||||
}
|
||||
)
|
||||
.clipShape(Rectangle())
|
||||
.badgeView(
|
||||
Button {
|
||||
viewModel.attachmentViewModels.removeAll(where: { $0 === attachmentViewModel })
|
||||
} label: {
|
||||
Image(systemName: "minus.circle.fill")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
)
|
||||
} // end ForEach
|
||||
} // end VStack
|
||||
}
|
||||
}
|
||||
|
||||
//private struct ScrollOffsetPreferenceKey: PreferenceKey {
|
||||
|
|
|
@ -77,19 +77,6 @@ struct StatusAttachmentView: View {
|
|||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func badgeView<Content>(_ content: Content) -> some View where Content: View {
|
||||
overlay(
|
||||
ZStack {
|
||||
content
|
||||
}
|
||||
.alignmentGuide(.top) { $0.height / 2 }
|
||||
.alignmentGuide(.trailing) { $0.width / 2 }
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// ref: https://stackoverflow.com/a/57715771/3797903
|
||||
extension View {
|
||||
func placeholder<Content: View>(
|
||||
|
|
Loading…
Reference in New Issue