2022-10-11 12:31:40 +02:00
|
|
|
//
|
|
|
|
// ComposeContentView.swift
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Created by MainasuK on 22/9/30.
|
|
|
|
//
|
|
|
|
|
|
|
|
import os.log
|
|
|
|
import SwiftUI
|
2022-10-28 13:06:18 +02:00
|
|
|
import MastodonAsset
|
2022-10-21 13:12:44 +02:00
|
|
|
import MastodonCore
|
2022-10-11 12:31:40 +02:00
|
|
|
import MastodonLocalization
|
2022-10-28 13:06:18 +02:00
|
|
|
import Stripes
|
2022-11-08 09:39:19 +01:00
|
|
|
import Kingfisher
|
2022-10-11 12:31:40 +02:00
|
|
|
|
|
|
|
public struct ComposeContentView: View {
|
|
|
|
|
|
|
|
static let logger = Logger(subsystem: "ComposeContentView", category: "View")
|
|
|
|
var logger: Logger { ComposeContentView.logger }
|
|
|
|
|
2022-11-13 09:04:29 +01:00
|
|
|
static let contentViewCoordinateSpace = "ComposeContentView.Content"
|
2022-10-11 12:31:40 +02:00
|
|
|
static var margin: CGFloat = 16
|
|
|
|
|
|
|
|
@ObservedObject var viewModel: ComposeContentViewModel
|
2022-11-13 09:04:29 +01:00
|
|
|
|
2022-10-11 12:31:40 +02:00
|
|
|
|
|
|
|
public var body: some View {
|
|
|
|
VStack(spacing: .zero) {
|
|
|
|
Group {
|
2022-10-28 13:06:18 +02:00
|
|
|
// content warning
|
|
|
|
if viewModel.isContentWarningActive {
|
|
|
|
MetaTextViewRepresentable(
|
|
|
|
string: $viewModel.contentWarning,
|
|
|
|
width: viewModel.viewLayoutFrame.layoutFrame.width - ComposeContentView.margin * 2,
|
|
|
|
configurationHandler: { metaText in
|
|
|
|
viewModel.contentWarningMetaText = metaText
|
|
|
|
metaText.textView.attributedPlaceholder = {
|
|
|
|
var attributes = metaText.textAttributes
|
|
|
|
attributes[.foregroundColor] = UIColor.secondaryLabel
|
|
|
|
return NSAttributedString(
|
|
|
|
string: L10n.Scene.Compose.contentInputPlaceholder,
|
|
|
|
attributes: attributes
|
|
|
|
)
|
|
|
|
}()
|
|
|
|
metaText.textView.returnKeyType = .next
|
|
|
|
metaText.textView.tag = ComposeContentViewModel.MetaTextViewKind.contentWarning.rawValue
|
|
|
|
metaText.textView.delegate = viewModel
|
|
|
|
metaText.delegate = viewModel
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
.padding(.horizontal, ComposeContentView.margin)
|
|
|
|
.background(
|
|
|
|
Color(UIColor.systemBackground)
|
|
|
|
.overlay(
|
|
|
|
HStack {
|
|
|
|
Stripes(config: StripesConfig(
|
|
|
|
background: Color.yellow,
|
|
|
|
foreground: Color.black,
|
|
|
|
degrees: 45,
|
|
|
|
barWidth: 2.5,
|
|
|
|
barSpacing: 3.5
|
|
|
|
))
|
|
|
|
.frame(width: ComposeContentView.margin * 0.5)
|
|
|
|
.frame(maxHeight: .infinity)
|
|
|
|
.id(UUID())
|
|
|
|
Spacer()
|
|
|
|
Stripes(config: StripesConfig(
|
|
|
|
background: Color.yellow,
|
|
|
|
foreground: Color.black,
|
|
|
|
degrees: 45,
|
|
|
|
barWidth: 2.5,
|
|
|
|
barSpacing: 3.5
|
|
|
|
))
|
|
|
|
.frame(width: ComposeContentView.margin * 0.5)
|
|
|
|
.frame(maxHeight: .infinity)
|
|
|
|
.scaleEffect(x: -1, y: 1, anchor: .center)
|
|
|
|
.id(UUID())
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} // end if viewModel.isContentWarningActive
|
2022-10-21 13:12:44 +02:00
|
|
|
// author
|
2022-10-11 12:31:40 +02:00
|
|
|
authorView
|
|
|
|
.padding(.top, 14)
|
2022-10-28 13:06:18 +02:00
|
|
|
.padding(.horizontal, ComposeContentView.margin)
|
2022-10-21 13:12:44 +02:00
|
|
|
// content editor
|
2022-10-11 12:31:40 +02:00
|
|
|
MetaTextViewRepresentable(
|
|
|
|
string: $viewModel.content,
|
|
|
|
width: viewModel.viewLayoutFrame.layoutFrame.width - ComposeContentView.margin * 2,
|
|
|
|
configurationHandler: { metaText in
|
2022-10-28 13:06:18 +02:00
|
|
|
viewModel.contentMetaText = metaText
|
2022-10-11 12:31:40 +02:00
|
|
|
metaText.textView.attributedPlaceholder = {
|
|
|
|
var attributes = metaText.textAttributes
|
|
|
|
attributes[.foregroundColor] = UIColor.secondaryLabel
|
|
|
|
return NSAttributedString(
|
|
|
|
string: L10n.Scene.Compose.contentInputPlaceholder,
|
|
|
|
attributes: attributes
|
|
|
|
)
|
|
|
|
}()
|
|
|
|
metaText.textView.keyboardType = .twitter
|
2022-10-28 13:06:18 +02:00
|
|
|
metaText.textView.tag = ComposeContentViewModel.MetaTextViewKind.content.rawValue
|
|
|
|
metaText.textView.delegate = viewModel
|
|
|
|
metaText.delegate = viewModel
|
2022-10-11 12:31:40 +02:00
|
|
|
metaText.textView.becomeFirstResponder()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.frame(minHeight: 100)
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
2022-10-28 13:06:18 +02:00
|
|
|
.padding(.horizontal, ComposeContentView.margin)
|
2022-11-13 09:04:29 +01:00
|
|
|
.background(
|
|
|
|
GeometryReader { proxy in
|
|
|
|
Color.clear.preference(key: ViewFramePreferenceKey.self, value: proxy.frame(in: .named(ComposeContentView.contentViewCoordinateSpace)))
|
|
|
|
}
|
|
|
|
.onPreferenceChange(ViewFramePreferenceKey.self) { frame in
|
|
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): content textView frame: \(frame.debugDescription)")
|
|
|
|
let rect = frame.standardized
|
|
|
|
viewModel.contentTextViewFrame = CGRect(
|
|
|
|
origin: frame.origin,
|
|
|
|
size: CGSize(width: floor(rect.width), height: floor(rect.height))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
2022-10-21 13:12:44 +02:00
|
|
|
// poll
|
|
|
|
pollView
|
2022-10-28 13:06:18 +02:00
|
|
|
.padding(.horizontal, ComposeContentView.margin)
|
2022-11-08 09:39:19 +01:00
|
|
|
// media
|
|
|
|
mediaView
|
|
|
|
.padding(.horizontal, ComposeContentView.margin)
|
2022-10-11 12:31:40 +02:00
|
|
|
}
|
|
|
|
.background(
|
|
|
|
GeometryReader { proxy in
|
|
|
|
Color.clear.preference(key: ViewFramePreferenceKey.self, value: proxy.frame(in: .local))
|
|
|
|
}
|
2022-10-21 13:12:44 +02:00
|
|
|
.onPreferenceChange(ViewFramePreferenceKey.self) { frame in
|
|
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): content frame: \(frame.debugDescription)")
|
2022-10-26 12:58:25 +02:00
|
|
|
let rect = frame.standardized
|
|
|
|
viewModel.contentCellFrame = CGRect(
|
|
|
|
origin: frame.origin,
|
|
|
|
size: CGSize(width: floor(rect.width), height: floor(rect.height))
|
|
|
|
)
|
2022-10-21 13:12:44 +02:00
|
|
|
}
|
2022-10-11 12:31:40 +02:00
|
|
|
)
|
|
|
|
Spacer()
|
|
|
|
} // end VStack
|
2022-11-13 09:04:29 +01:00
|
|
|
.coordinateSpace(name: ComposeContentView.contentViewCoordinateSpace)
|
2022-10-11 12:31:40 +02:00
|
|
|
} // end body
|
|
|
|
}
|
|
|
|
|
|
|
|
extension ComposeContentView {
|
|
|
|
var authorView: some View {
|
|
|
|
HStack(spacing: 8) {
|
|
|
|
AnimatedImage(imageURL: viewModel.avatarURL)
|
|
|
|
.frame(width: 46, height: 46)
|
|
|
|
.background(Color(UIColor.systemFill))
|
|
|
|
.cornerRadius(12)
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
|
|
Spacer()
|
|
|
|
MetaLabelRepresentable(
|
|
|
|
textStyle: .statusName,
|
|
|
|
metaContent: viewModel.name
|
|
|
|
)
|
|
|
|
Text(viewModel.username)
|
2022-10-26 12:35:10 +02:00
|
|
|
.font(.system(size: 15, weight: .regular))
|
2022-10-11 12:31:40 +02:00
|
|
|
.foregroundColor(.secondary)
|
|
|
|
Spacer()
|
|
|
|
}
|
|
|
|
Spacer()
|
|
|
|
}
|
2022-11-07 02:22:06 +01:00
|
|
|
.accessibilityElement(children: .ignore)
|
2022-11-08 19:50:23 +01:00
|
|
|
.accessibilityLabel(L10n.Scene.Compose.Accessibility.postingAs([viewModel.name.string, viewModel.username].joined(separator: ", ")))
|
2022-10-11 12:31:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-21 13:12:44 +02:00
|
|
|
extension ComposeContentView {
|
|
|
|
// MARK: - poll
|
|
|
|
var pollView: some View {
|
|
|
|
VStack {
|
|
|
|
if viewModel.isPollActive {
|
|
|
|
// poll option TextField
|
|
|
|
ReorderableForEach(
|
|
|
|
items: $viewModel.pollOptions
|
|
|
|
) { $pollOption in
|
|
|
|
let _index = viewModel.pollOptions.firstIndex(of: pollOption)
|
|
|
|
PollOptionRow(
|
|
|
|
viewModel: pollOption,
|
|
|
|
index: _index,
|
|
|
|
deleteBackwardResponseTextFieldRelayDelegate: viewModel
|
|
|
|
) { textField in
|
2022-11-13 12:42:50 +01:00
|
|
|
viewModel.customEmojiPickerInputViewModel.configure(textInput: textField)
|
2022-10-21 13:12:44 +02:00
|
|
|
}
|
|
|
|
}
|
2022-10-26 12:35:10 +02:00
|
|
|
if viewModel.maxPollOptionLimit != viewModel.pollOptions.count {
|
|
|
|
PollAddOptionRow()
|
|
|
|
.onTapGesture {
|
|
|
|
viewModel.createNewPollOptionIfCould()
|
|
|
|
}
|
|
|
|
}
|
2022-10-21 13:12:44 +02:00
|
|
|
Menu {
|
2022-10-26 12:58:25 +02:00
|
|
|
Picker(selection: $viewModel.pollExpireConfigurationOption) {
|
|
|
|
ForEach(PollComposeItem.ExpireConfiguration.Option.allCases, id: \.self) { option in
|
2022-10-21 13:12:44 +02:00
|
|
|
Text(option.title)
|
|
|
|
}
|
2022-10-26 12:58:25 +02:00
|
|
|
} label: {
|
|
|
|
Text(L10n.Scene.Compose.Poll.durationTime(viewModel.pollExpireConfigurationOption.title))
|
2022-10-21 13:12:44 +02:00
|
|
|
}
|
|
|
|
} label: {
|
|
|
|
HStack {
|
2022-10-26 12:58:25 +02:00
|
|
|
Text(L10n.Scene.Compose.Poll.durationTime(viewModel.pollExpireConfigurationOption.title))
|
|
|
|
.foregroundColor(Color(UIColor.label.withAlphaComponent(0.8))) // Gray/800
|
|
|
|
.font(Font(UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 13, weight: .semibold))))
|
|
|
|
Spacer()
|
2022-10-21 13:12:44 +02:00
|
|
|
}
|
2022-10-26 12:58:25 +02:00
|
|
|
.padding(.vertical, 8)
|
2022-10-21 13:12:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end VStack
|
|
|
|
}
|
2022-11-08 09:39:19 +01:00
|
|
|
|
|
|
|
// MARK: - media
|
|
|
|
var mediaView: some View {
|
|
|
|
VStack(spacing: 16) {
|
|
|
|
ForEach(viewModel.attachmentViewModels, id: \.self) { attachmentViewModel in
|
2022-11-13 17:57:44 +01:00
|
|
|
AttachmentView(viewModel: attachmentViewModel)
|
2022-11-15 15:44:51 +01:00
|
|
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
2022-11-08 09:39:19 +01:00
|
|
|
.badgeView(
|
|
|
|
Button {
|
|
|
|
viewModel.attachmentViewModels.removeAll(where: { $0 === attachmentViewModel })
|
|
|
|
} label: {
|
|
|
|
Image(systemName: "minus.circle.fill")
|
2022-11-15 15:44:51 +01:00
|
|
|
.resizable()
|
|
|
|
.frame(width: 20, height: 20)
|
2022-11-08 09:39:19 +01:00
|
|
|
.foregroundColor(.red)
|
2022-11-15 15:44:51 +01:00
|
|
|
.background(Color.white)
|
|
|
|
.clipShape(Circle())
|
2022-11-08 09:39:19 +01:00
|
|
|
}
|
|
|
|
)
|
|
|
|
} // end ForEach
|
|
|
|
} // end VStack
|
|
|
|
}
|
2022-10-21 13:12:44 +02:00
|
|
|
}
|
|
|
|
|
2022-10-11 12:31:40 +02:00
|
|
|
//private struct ScrollOffsetPreferenceKey: PreferenceKey {
|
|
|
|
// static var defaultValue: CGPoint = .zero
|
|
|
|
//
|
|
|
|
// static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
|
|
|
|
//}
|
|
|
|
|
|
|
|
private struct ViewFramePreferenceKey: PreferenceKey {
|
|
|
|
static var defaultValue: CGRect = .zero
|
|
|
|
|
|
|
|
static func reduce(value: inout CGRect, nextValue: () -> CGRect) { }
|
|
|
|
}
|
2022-10-21 13:12:44 +02:00
|
|
|
|
|
|
|
// MARK: - TypeIdentifiedItemProvider
|
|
|
|
extension PollComposeItem.Option: TypeIdentifiedItemProvider {
|
|
|
|
public static var typeIdentifier: String {
|
|
|
|
return Bundle(for: PollComposeItem.Option.self).bundleIdentifier! + String(describing: type(of: PollComposeItem.Option.self))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - NSItemProviderWriting
|
|
|
|
extension PollComposeItem.Option: NSItemProviderWriting {
|
|
|
|
public func loadData(
|
|
|
|
withTypeIdentifier typeIdentifier: String,
|
|
|
|
forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void
|
|
|
|
) -> Progress? {
|
|
|
|
completionHandler(nil, nil)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
public static var writableTypeIdentifiersForItemProvider: [String] {
|
|
|
|
return [Self.typeIdentifier]
|
|
|
|
}
|
|
|
|
}
|