feat: restore content warning input with black-yellow strip edges
This commit is contained in:
parent
b12825a96a
commit
3100c59a3b
|
@ -126,6 +126,15 @@
|
||||||
"version" : "5.12.5"
|
"version" : "5.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "stripes",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/eneko/Stripes.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d533fd44b8043a3abbf523e733599173d6f98c11",
|
||||||
|
"version" : "0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-collections",
|
"identity" : "swift-collections",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|
|
@ -48,6 +48,7 @@ let package = Package(
|
||||||
.package(url: "https://github.com/vtourraine/ThirdPartyMailer.git", from: "2.1.0"),
|
.package(url: "https://github.com/vtourraine/ThirdPartyMailer.git", from: "2.1.0"),
|
||||||
.package(url: "https://github.com/woxtu/UIHostingConfigurationBackport.git", from: "0.1.0"),
|
.package(url: "https://github.com/woxtu/UIHostingConfigurationBackport.git", from: "0.1.0"),
|
||||||
.package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.12.0"),
|
.package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.12.0"),
|
||||||
|
.package(url: "https://github.com/eneko/Stripes.git", from: "0.2.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
|
@ -122,6 +123,7 @@ let package = Package(
|
||||||
.product(name: "MetaTextKit", package: "MetaTextKit"),
|
.product(name: "MetaTextKit", package: "MetaTextKit"),
|
||||||
.product(name: "CropViewController", package: "TOCropViewController"),
|
.product(name: "CropViewController", package: "TOCropViewController"),
|
||||||
.product(name: "PanModal", package: "PanModal"),
|
.product(name: "PanModal", package: "PanModal"),
|
||||||
|
.product(name: "Stripes", package: "Stripes"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
|
|
|
@ -260,6 +260,9 @@ extension ComposeContentViewController {
|
||||||
viewModel.$isPollActive.assign(to: &composeContentToolbarViewModel.$isPollActive)
|
viewModel.$isPollActive.assign(to: &composeContentToolbarViewModel.$isPollActive)
|
||||||
viewModel.$isEmojiActive.assign(to: &composeContentToolbarViewModel.$isEmojiActive)
|
viewModel.$isEmojiActive.assign(to: &composeContentToolbarViewModel.$isEmojiActive)
|
||||||
viewModel.$isContentWarningActive.assign(to: &composeContentToolbarViewModel.$isContentWarningActive)
|
viewModel.$isContentWarningActive.assign(to: &composeContentToolbarViewModel.$isContentWarningActive)
|
||||||
|
viewModel.$maxTextInputLimit.assign(to: &composeContentToolbarViewModel.$maxTextInputLimit)
|
||||||
|
viewModel.$contentWeightedLength.assign(to: &composeContentToolbarViewModel.$contentWeightedLength)
|
||||||
|
viewModel.$contentWarningWeightedLength.assign(to: &composeContentToolbarViewModel.$contentWarningWeightedLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +388,16 @@ extension ComposeContentViewController: ComposeContentToolbarViewDelegate {
|
||||||
self.viewModel.isEmojiActive.toggle()
|
self.viewModel.isEmojiActive.toggle()
|
||||||
case .contentWarning:
|
case .contentWarning:
|
||||||
self.viewModel.isContentWarningActive.toggle()
|
self.viewModel.isContentWarningActive.toggle()
|
||||||
|
if self.viewModel.isContentWarningActive {
|
||||||
|
Task { @MainActor in
|
||||||
|
try? await Task.sleep(nanoseconds: .second / 20) // 0.05s
|
||||||
|
self.viewModel.setContentWarningTextViewFirstResponderIfNeeds()
|
||||||
|
} // end Task
|
||||||
|
} else {
|
||||||
|
if self.viewModel.contentWarningMetaText?.textView.isFirstResponder == true {
|
||||||
|
self.viewModel.setContentTextViewFirstResponderIfNeeds()
|
||||||
|
}
|
||||||
|
}
|
||||||
case .visibility:
|
case .visibility:
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// ComposeContentViewModel+MetaTextDelegate.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by MainasuK on 2022/10/28.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import MetaTextKit
|
||||||
|
import TwitterMeta
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
|
// MARK: - MetaTextDelegate
|
||||||
|
extension ComposeContentViewModel: MetaTextDelegate {
|
||||||
|
|
||||||
|
public enum MetaTextViewKind: Int {
|
||||||
|
case none
|
||||||
|
case content
|
||||||
|
case contentWarning
|
||||||
|
}
|
||||||
|
|
||||||
|
public func metaText(
|
||||||
|
_ metaText: MetaText,
|
||||||
|
processEditing textStorage: MetaTextStorage
|
||||||
|
) -> MetaContent? {
|
||||||
|
let kind = MetaTextViewKind(rawValue: metaText.textView.tag) ?? .none
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case .none:
|
||||||
|
assertionFailure()
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case .content:
|
||||||
|
let textInput = textStorage.string
|
||||||
|
self.content = textInput
|
||||||
|
|
||||||
|
let content = MastodonContent(
|
||||||
|
content: textInput,
|
||||||
|
emojis: [:] // TODO: emojiViewModel?.emojis.asDictionary ?? [:]
|
||||||
|
)
|
||||||
|
let metaContent = MastodonMetaContent.convert(text: content)
|
||||||
|
return metaContent
|
||||||
|
|
||||||
|
case .contentWarning:
|
||||||
|
let textInput = textStorage.string.replacingOccurrences(of: "\n", with: " ")
|
||||||
|
self.contentWarning = textInput
|
||||||
|
|
||||||
|
let content = MastodonContent(
|
||||||
|
content: textInput,
|
||||||
|
emojis: [:] // emojiViewModel?.emojis.asDictionary ?? [:]
|
||||||
|
)
|
||||||
|
let metaContent = MastodonMetaContent.convert(text: content)
|
||||||
|
return metaContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,13 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import os.log
|
import os.log
|
||||||
import Foundation
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import Meta
|
import Meta
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
|
import MetaTextKit
|
||||||
|
|
||||||
public final class ComposeContentViewModel: NSObject, ObservableObject {
|
public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||||
|
|
||||||
|
@ -30,6 +31,42 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||||
@Published var viewLayoutFrame = ViewLayoutFrame()
|
@Published var viewLayoutFrame = ViewLayoutFrame()
|
||||||
@Published var authContext: AuthContext
|
@Published var authContext: AuthContext
|
||||||
|
|
||||||
|
// output
|
||||||
|
|
||||||
|
// limit
|
||||||
|
@Published public var maxTextInputLimit = 500
|
||||||
|
|
||||||
|
// content
|
||||||
|
public weak var contentMetaText: MetaText? {
|
||||||
|
didSet {
|
||||||
|
// guard let textView = contentMetaText?.textView else { return }
|
||||||
|
// customEmojiPickerInputViewModel.configure(textInput: textView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Published public var initialContent = ""
|
||||||
|
@Published public var content = ""
|
||||||
|
@Published public var contentWeightedLength = 0
|
||||||
|
@Published public var isContentEmpty = true
|
||||||
|
@Published public var isContentValid = true
|
||||||
|
@Published public var isContentEditing = false
|
||||||
|
|
||||||
|
// content warning
|
||||||
|
weak var contentWarningMetaText: MetaText? {
|
||||||
|
didSet {
|
||||||
|
//guard let textView = contentWarningMetaText?.textView else { return }
|
||||||
|
//customEmojiPickerInputViewModel.configure(textInput: textView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Published public var isContentWarningActive = false
|
||||||
|
@Published public var contentWarning = ""
|
||||||
|
@Published public var contentWarningWeightedLength = 0 // set 0 when not composing
|
||||||
|
@Published public var isContentWarningEditing = false
|
||||||
|
|
||||||
|
// author
|
||||||
|
@Published var avatarURL: URL?
|
||||||
|
@Published var name: MetaContent = PlaintextMetaContent(string: "")
|
||||||
|
@Published var username: String = ""
|
||||||
|
|
||||||
// poll
|
// poll
|
||||||
@Published var isPollActive = false
|
@Published var isPollActive = false
|
||||||
@Published public var pollOptions: [PollComposeItem.Option] = {
|
@Published public var pollOptions: [PollComposeItem.Option] = {
|
||||||
|
@ -42,23 +79,8 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||||
@Published public var pollExpireConfigurationOption: PollComposeItem.ExpireConfiguration.Option = .oneDay
|
@Published public var pollExpireConfigurationOption: PollComposeItem.ExpireConfiguration.Option = .oneDay
|
||||||
@Published public var maxPollOptionLimit = 4
|
@Published public var maxPollOptionLimit = 4
|
||||||
|
|
||||||
|
// emoji
|
||||||
@Published var isEmojiActive = false
|
@Published var isEmojiActive = false
|
||||||
@Published var isContentWarningActive = false
|
|
||||||
|
|
||||||
// output
|
|
||||||
|
|
||||||
// content
|
|
||||||
@Published public var initialContent = ""
|
|
||||||
@Published public var content = ""
|
|
||||||
@Published public var contentWeightedLength = 0
|
|
||||||
@Published public var isContentEmpty = true
|
|
||||||
@Published public var isContentValid = true
|
|
||||||
@Published public var isContentEditing = false
|
|
||||||
|
|
||||||
// author
|
|
||||||
@Published var avatarURL: URL?
|
|
||||||
@Published var name: MetaContent = PlaintextMetaContent(string: "")
|
|
||||||
@Published var username: String = ""
|
|
||||||
|
|
||||||
// UI & UX
|
// UI & UX
|
||||||
@Published var replyToCellFrame: CGRect = .zero
|
@Published var replyToCellFrame: CGRect = .zero
|
||||||
|
@ -87,6 +109,26 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||||
self.username = user.acctWithDomain
|
self.username = user.acctWithDomain
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
// bind text
|
||||||
|
$content
|
||||||
|
.map { $0.count }
|
||||||
|
.assign(to: &$contentWeightedLength)
|
||||||
|
|
||||||
|
Publishers.CombineLatest(
|
||||||
|
$contentWarning,
|
||||||
|
$isContentWarningActive
|
||||||
|
)
|
||||||
|
.map { $1 ? $0.count : 0 }
|
||||||
|
.assign(to: &$contentWarningWeightedLength)
|
||||||
|
|
||||||
|
Publishers.CombineLatest3(
|
||||||
|
$contentWeightedLength,
|
||||||
|
$contentWarningWeightedLength,
|
||||||
|
$maxTextInputLimit
|
||||||
|
)
|
||||||
|
.map { $0 + $1 <= $2 }
|
||||||
|
.assign(to: &$isContentValid)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -120,6 +162,72 @@ extension ComposeContentViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UITextViewDelegate
|
||||||
|
extension ComposeContentViewModel: UITextViewDelegate {
|
||||||
|
public func textViewDidBeginEditing(_ textView: UITextView) {
|
||||||
|
switch textView {
|
||||||
|
case contentMetaText?.textView:
|
||||||
|
isContentEditing = true
|
||||||
|
case contentWarningMetaText?.textView:
|
||||||
|
isContentWarningEditing = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func textViewDidEndEditing(_ textView: UITextView) {
|
||||||
|
switch textView {
|
||||||
|
case contentMetaText?.textView:
|
||||||
|
isContentEditing = false
|
||||||
|
case contentWarningMetaText?.textView:
|
||||||
|
isContentWarningEditing = false
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||||
|
switch textView {
|
||||||
|
case contentMetaText?.textView:
|
||||||
|
return true
|
||||||
|
case contentWarningMetaText?.textView:
|
||||||
|
let isReturn = text == "\n"
|
||||||
|
if isReturn {
|
||||||
|
setContentTextViewFirstResponderIfNeeds()
|
||||||
|
}
|
||||||
|
return !isReturn
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertContentText(text: String) {
|
||||||
|
guard let contentMetaText = self.contentMetaText else { return }
|
||||||
|
// FIXME: smart prefix and suffix
|
||||||
|
let string = contentMetaText.textStorage.string
|
||||||
|
let isEmpty = string.isEmpty
|
||||||
|
let hasPrefix = string.hasPrefix(" ")
|
||||||
|
if hasPrefix || isEmpty {
|
||||||
|
contentMetaText.textView.insertText(text)
|
||||||
|
} else {
|
||||||
|
contentMetaText.textView.insertText(" " + text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setContentTextViewFirstResponderIfNeeds() {
|
||||||
|
guard let contentMetaText = self.contentMetaText else { return }
|
||||||
|
guard !contentMetaText.textView.isFirstResponder else { return }
|
||||||
|
contentMetaText.textView.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setContentWarningTextViewFirstResponderIfNeeds() {
|
||||||
|
guard let contentWarningMetaText = self.contentWarningMetaText else { return }
|
||||||
|
guard !contentWarningMetaText.textView.isFirstResponder else { return }
|
||||||
|
contentWarningMetaText.textView.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - DeleteBackwardResponseTextFieldRelayDelegate
|
// MARK: - DeleteBackwardResponseTextFieldRelayDelegate
|
||||||
extension ComposeContentViewModel: DeleteBackwardResponseTextFieldRelayDelegate {
|
extension ComposeContentViewModel: DeleteBackwardResponseTextFieldRelayDelegate {
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,10 @@ extension ComposeContentToolbarView {
|
||||||
@Published var isEmojiActive = false
|
@Published var isEmojiActive = false
|
||||||
@Published var isContentWarningActive = false
|
@Published var isContentWarningActive = false
|
||||||
|
|
||||||
|
@Published public var maxTextInputLimit = 500
|
||||||
|
@Published public var contentWeightedLength = 0
|
||||||
|
@Published public var contentWarningWeightedLength = 0
|
||||||
|
|
||||||
// output
|
// output
|
||||||
|
|
||||||
init(delegate: ComposeContentToolbarViewDelegate) {
|
init(delegate: ComposeContentToolbarViewDelegate) {
|
||||||
|
|
|
@ -74,8 +74,18 @@ struct ComposeContentToolbarView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Hello")
|
let count: Int = {
|
||||||
.font(.system(size: 16, weight: .regular))
|
if viewModel.isContentWarningActive {
|
||||||
|
return viewModel.contentWeightedLength + viewModel.contentWarningWeightedLength
|
||||||
|
} else {
|
||||||
|
return viewModel.contentWeightedLength
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
let remains = viewModel.maxTextInputLimit - count
|
||||||
|
let isOverflow = remains < 0
|
||||||
|
Text("\(remains)")
|
||||||
|
.foregroundColor(Color(isOverflow ? UIColor.systemRed : UIColor.secondaryLabel))
|
||||||
|
.font(.system(size: isOverflow ? 18 : 16, weight: isOverflow ? .medium : .regular))
|
||||||
}
|
}
|
||||||
.padding(.leading, 4) // 4 + 12 = 16
|
.padding(.leading, 4) // 4 + 12 = 16
|
||||||
.padding(.trailing, 16)
|
.padding(.trailing, 16)
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
|
|
||||||
import os.log
|
import os.log
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import MastodonAsset
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
import Stripes
|
||||||
|
|
||||||
public struct ComposeContentView: View {
|
public struct ComposeContentView: View {
|
||||||
|
|
||||||
|
@ -22,14 +24,69 @@ public struct ComposeContentView: View {
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack(spacing: .zero) {
|
VStack(spacing: .zero) {
|
||||||
Group {
|
Group {
|
||||||
|
// 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
|
||||||
// author
|
// author
|
||||||
authorView
|
authorView
|
||||||
.padding(.top, 14)
|
.padding(.top, 14)
|
||||||
|
.padding(.horizontal, ComposeContentView.margin)
|
||||||
// content editor
|
// content editor
|
||||||
MetaTextViewRepresentable(
|
MetaTextViewRepresentable(
|
||||||
string: $viewModel.content,
|
string: $viewModel.content,
|
||||||
width: viewModel.viewLayoutFrame.layoutFrame.width - ComposeContentView.margin * 2,
|
width: viewModel.viewLayoutFrame.layoutFrame.width - ComposeContentView.margin * 2,
|
||||||
configurationHandler: { metaText in
|
configurationHandler: { metaText in
|
||||||
|
viewModel.contentMetaText = metaText
|
||||||
metaText.textView.attributedPlaceholder = {
|
metaText.textView.attributedPlaceholder = {
|
||||||
var attributes = metaText.textAttributes
|
var attributes = metaText.textAttributes
|
||||||
attributes[.foregroundColor] = UIColor.secondaryLabel
|
attributes[.foregroundColor] = UIColor.secondaryLabel
|
||||||
|
@ -39,16 +96,18 @@ public struct ComposeContentView: View {
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
metaText.textView.keyboardType = .twitter
|
metaText.textView.keyboardType = .twitter
|
||||||
// metaText.textView.tag = ComposeContentViewModel.MetaTextViewKind.content.rawValue
|
metaText.textView.tag = ComposeContentViewModel.MetaTextViewKind.content.rawValue
|
||||||
// metaText.textView.delegate = viewModel
|
metaText.textView.delegate = viewModel
|
||||||
// metaText.delegate = viewModel
|
metaText.delegate = viewModel
|
||||||
metaText.textView.becomeFirstResponder()
|
metaText.textView.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.frame(minHeight: 100)
|
.frame(minHeight: 100)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.padding(.horizontal, ComposeContentView.margin)
|
||||||
// poll
|
// poll
|
||||||
pollView
|
pollView
|
||||||
|
.padding(.horizontal, ComposeContentView.margin)
|
||||||
}
|
}
|
||||||
.background(
|
.background(
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
|
@ -65,8 +124,6 @@ public struct ComposeContentView: View {
|
||||||
)
|
)
|
||||||
Spacer()
|
Spacer()
|
||||||
} // end VStack
|
} // end VStack
|
||||||
.padding(.horizontal, ComposeContentView.margin)
|
|
||||||
// .frame(alignment: .top)
|
|
||||||
} // end body
|
} // end body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue