feat: add mediaView for compose scene

This commit is contained in:
CMK 2022-11-08 16:39:19 +08:00
parent 9c9edcb717
commit fc3750c377
6 changed files with 89 additions and 71 deletions

View File

@ -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)
)
}
}

View File

@ -13,18 +13,17 @@ import AVKit
public struct AttachmentView: View { public struct AttachmentView: View {
static let size = CGSize(width: 56, height: 56)
static let cornerRadius: CGFloat = 8
@ObservedObject var viewModel: AttachmentViewModel @ObservedObject var viewModel: AttachmentViewModel
let action: (Action) -> Void let action: (Action) -> Void
@State var isCaptionEditorPresented = false
@State var caption = ""
public var body: some View { public var body: some View {
Text("Hello") ZStack {
let image = viewModel.thumbnail ?? .placeholder(color: .secondarySystemFill)
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
}
// Menu { // Menu {
// menu // menu
// } label: { // } label: {

View File

@ -22,6 +22,7 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
var observations = Set<NSKeyValueObservation>() var observations = Set<NSKeyValueObservation>()
// input // input
public let authContext: AuthContext
public let input: Input public let input: Input
@Published var caption = "" @Published var caption = ""
@Published var sizeLimit = SizeLimit() @Published var sizeLimit = SizeLimit()
@ -33,13 +34,19 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
@Published var error: Error? @Published var error: Error?
let progress = Progress() // upload progress let progress = Progress() // upload progress
public init(input: Input) { public init(
authContext: AuthContext,
input: Input
) {
self.authContext = authContext
self.input = input self.input = input
super.init() super.init()
// end init // end init
defer { defer {
load(input: input) Task {
await load(input: input)
}
} }
$output $output
@ -53,6 +60,7 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
return nil return nil
} }
} }
.receive(on: DispatchQueue.main)
.assign(to: &$thumbnail) .assign(to: &$thumbnail)
} }
@ -86,13 +94,6 @@ extension AttachmentViewModel {
case png case png
case jpg case jpg
} }
public var twitterMediaCategory: TwitterMediaCategory {
switch self {
case .image: return .image
case .video: return .amplifyVideo
}
}
} }
public struct SizeLimit { public struct SizeLimit {
@ -115,18 +116,13 @@ extension AttachmentViewModel {
case invalidAttachmentType case invalidAttachmentType
case attachmentTooLarge case attachmentTooLarge
} }
public enum TwitterMediaCategory: String {
case image = "TWEET_IMAGE"
case GIF = "TWEET_GIF"
case video = "TWEET_VIDEO"
case amplifyVideo = "AMPLIFY_VIDEO"
}
} }
extension AttachmentViewModel { extension AttachmentViewModel {
private func load(input: Input) { @MainActor
private func load(input: Input) async {
switch input { switch input {
case .image(let image): case .image(let image):
guard let data = image.pngData() else { guard let data = image.pngData() else {
@ -135,32 +131,26 @@ extension AttachmentViewModel {
} }
output = .image(data, imageKind: .png) output = .image(data, imageKind: .png)
case .url(let url): case .url(let url):
Task { @MainActor in do {
do { let output = try await AttachmentViewModel.load(url: url)
let output = try await AttachmentViewModel.load(url: url) self.output = output
self.output = output } catch {
} catch { self.error = error
self.error = error }
}
} // end Task
case .pickerResult(let pickerResult): case .pickerResult(let pickerResult):
Task { @MainActor in do {
do { let output = try await AttachmentViewModel.load(itemProvider: pickerResult.itemProvider)
let output = try await AttachmentViewModel.load(itemProvider: pickerResult.itemProvider) self.output = output
self.output = output } catch {
} catch { self.error = error
self.error = error }
}
} // end Task
case .itemProvider(let itemProvider): case .itemProvider(let itemProvider):
Task { @MainActor in do {
do { let output = try await AttachmentViewModel.load(itemProvider: itemProvider)
let output = try await AttachmentViewModel.load(itemProvider: itemProvider) self.output = output
self.output = output } catch {
} catch { self.error = error
self.error = error }
}
} // end Task
} }
} }

View File

@ -325,16 +325,10 @@ extension ComposeContentViewController: PHPickerViewControllerDelegate {
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil) picker.dismiss(animated: true, completion: nil)
// TODO: let attachmentViewModels: [AttachmentViewModel] = results.map { result in
// let attachmentServices: [MastodonAttachmentService] = results.map { result in AttachmentViewModel(authContext: viewModel.authContext, input: .pickerResult(result))
// let service = MastodonAttachmentService( }
// context: context, viewModel.attachmentViewModels += attachmentViewModels
// pickerResult: result,
// initialAuthenticationBox: viewModel.authenticationBox
// )
// return service
// }
// viewModel.attachmentServices = viewModel.attachmentServices + attachmentServices
} }
} }

View File

@ -11,6 +11,7 @@ import MastodonAsset
import MastodonCore import MastodonCore
import MastodonLocalization import MastodonLocalization
import Stripes import Stripes
import Kingfisher
public struct ComposeContentView: View { public struct ComposeContentView: View {
@ -108,6 +109,9 @@ public struct ComposeContentView: View {
// poll // poll
pollView pollView
.padding(.horizontal, ComposeContentView.margin) .padding(.horizontal, ComposeContentView.margin)
// media
mediaView
.padding(.horizontal, ComposeContentView.margin)
} }
.background( .background(
GeometryReader { proxy in GeometryReader { proxy in
@ -194,6 +198,29 @@ extension ComposeContentView {
} }
} // end VStack } // 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 { //private struct ScrollOffsetPreferenceKey: PreferenceKey {

View File

@ -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 /// ref: https://stackoverflow.com/a/57715771/3797903
extension View { extension View {
func placeholder<Content: View>( func placeholder<Content: View>(