Compare commits
2 Commits
2000ddadc2
...
b47922694d
| Author | SHA1 | Date | |
|---|---|---|---|
| b47922694d | |||
| adba8fc568 |
@ -420,7 +420,7 @@
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 11;
|
||||
@ -460,7 +460,7 @@
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 11;
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
struct PrivateChatView: View {
|
||||
let chat: PrivateChatListItem
|
||||
@ -12,6 +15,7 @@ struct PrivateChatView: View {
|
||||
@State private var draftText: String = ""
|
||||
@State private var inputTab: ComposerTab = .chat
|
||||
@State private var isVideoPreferred: Bool = false
|
||||
@State private var legacyComposerHeight: CGFloat = 40
|
||||
@FocusState private var isComposerFocused: Bool
|
||||
@EnvironmentObject private var messageCenter: IncomingMessageCenter
|
||||
|
||||
@ -242,12 +246,31 @@ struct PrivateChatView: View {
|
||||
.frame(width: 36, height: 36)
|
||||
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
Group {
|
||||
if #available(iOS 160.0, *) {
|
||||
TextField(inputTab.placeholder, text: $draftText, axis: .vertical)
|
||||
.lineLimit(1...4)
|
||||
.focused($isComposerFocused)
|
||||
.submitLabel(.send)
|
||||
.disabled(currentUserId == nil)
|
||||
.onSubmit { sendCurrentMessage() }
|
||||
} else {
|
||||
LegacyMultilineTextView(
|
||||
text: $draftText,
|
||||
placeholder: inputTab.placeholder,
|
||||
isFocused: Binding(
|
||||
get: { isComposerFocused },
|
||||
set: { isComposerFocused = $0 }
|
||||
),
|
||||
isEnabled: currentUserId != nil,
|
||||
minHeight: 10,
|
||||
maxLines: 4,
|
||||
calculatedHeight: $legacyComposerHeight,
|
||||
onSubmit: sendCurrentMessage
|
||||
)
|
||||
.frame(height: legacyComposerHeight)
|
||||
}
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.leading, 12)
|
||||
.padding(.trailing, 44)
|
||||
@ -442,6 +465,155 @@ struct PrivateChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
private struct LegacyMultilineTextView: UIViewRepresentable {
|
||||
@Binding var text: String
|
||||
var placeholder: String
|
||||
@Binding var isFocused: Bool
|
||||
var isEnabled: Bool
|
||||
var minHeight: CGFloat
|
||||
var maxLines: Int
|
||||
@Binding var calculatedHeight: CGFloat
|
||||
var onSubmit: (() -> Void)?
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let textView = UITextView()
|
||||
textView.delegate = context.coordinator
|
||||
textView.backgroundColor = .clear
|
||||
textView.textContainerInset = .zero
|
||||
textView.textContainer.lineFragmentPadding = 0
|
||||
textView.isScrollEnabled = false
|
||||
textView.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
textView.text = text
|
||||
textView.returnKeyType = .send
|
||||
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
|
||||
let placeholderLabel = context.coordinator.placeholderLabel
|
||||
placeholderLabel.text = placeholder
|
||||
placeholderLabel.font = textView.font
|
||||
placeholderLabel.textColor = UIColor.secondaryLabel
|
||||
placeholderLabel.numberOfLines = 1
|
||||
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
textView.addSubview(placeholderLabel)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
placeholderLabel.topAnchor.constraint(equalTo: textView.topAnchor),
|
||||
placeholderLabel.leadingAnchor.constraint(equalTo: textView.leadingAnchor),
|
||||
placeholderLabel.trailingAnchor.constraint(lessThanOrEqualTo: textView.trailingAnchor)
|
||||
])
|
||||
|
||||
context.coordinator.updatePlaceholderVisibility(for: textView)
|
||||
DispatchQueue.main.async {
|
||||
Self.recalculateHeight(for: textView, result: calculatedHeightBinding, minHeight: minHeight, maxLines: maxLines)
|
||||
}
|
||||
|
||||
return textView
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UITextView, context: Context) {
|
||||
context.coordinator.parent = self
|
||||
|
||||
if uiView.text != text {
|
||||
uiView.text = text
|
||||
}
|
||||
|
||||
uiView.isEditable = isEnabled
|
||||
uiView.isSelectable = isEnabled
|
||||
uiView.textColor = isEnabled ? UIColor.label : UIColor.secondaryLabel
|
||||
|
||||
let placeholderLabel = context.coordinator.placeholderLabel
|
||||
if placeholderLabel.text != placeholder {
|
||||
placeholderLabel.text = placeholder
|
||||
}
|
||||
placeholderLabel.font = uiView.font
|
||||
context.coordinator.updatePlaceholderVisibility(for: uiView)
|
||||
|
||||
if isFocused && !uiView.isFirstResponder {
|
||||
uiView.becomeFirstResponder()
|
||||
} else if !isFocused && uiView.isFirstResponder {
|
||||
uiView.resignFirstResponder()
|
||||
}
|
||||
|
||||
Self.recalculateHeight(for: uiView, result: calculatedHeightBinding, minHeight: minHeight, maxLines: maxLines)
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(parent: self)
|
||||
}
|
||||
|
||||
private var calculatedHeightBinding: Binding<CGFloat> {
|
||||
Binding(get: { calculatedHeight }, set: { calculatedHeight = $0 })
|
||||
}
|
||||
|
||||
private static func recalculateHeight(for textView: UITextView, result: Binding<CGFloat>, minHeight: CGFloat, maxLines: Int) {
|
||||
let width = textView.bounds.width
|
||||
guard width > 0 else {
|
||||
DispatchQueue.main.async {
|
||||
recalculateHeight(for: textView, result: result, minHeight: minHeight, maxLines: maxLines)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let fittingSize = CGSize(width: width, height: .greatestFiniteMagnitude)
|
||||
let targetSize = textView.sizeThatFits(fittingSize)
|
||||
let lineHeight = textView.font?.lineHeight ?? UIFont.preferredFont(forTextStyle: .body).lineHeight
|
||||
let maxHeight = minHeight + lineHeight * CGFloat(max(maxLines - 1, 0))
|
||||
let clampedHeight = min(max(targetSize.height, minHeight), maxHeight)
|
||||
|
||||
if abs(result.wrappedValue - clampedHeight) > 0.5 {
|
||||
result.wrappedValue = clampedHeight
|
||||
}
|
||||
|
||||
textView.isScrollEnabled = targetSize.height > maxHeight + 0.5
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, UITextViewDelegate {
|
||||
var parent: LegacyMultilineTextView
|
||||
let placeholderLabel = UILabel()
|
||||
|
||||
init(parent: LegacyMultilineTextView) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
if !parent.isFocused {
|
||||
parent.isFocused = true
|
||||
}
|
||||
}
|
||||
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
if parent.isFocused {
|
||||
parent.isFocused = false
|
||||
}
|
||||
}
|
||||
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
if parent.text != textView.text {
|
||||
parent.text = textView.text
|
||||
}
|
||||
updatePlaceholderVisibility(for: textView)
|
||||
LegacyMultilineTextView.recalculateHeight(for: textView, result: parent.calculatedHeightBinding, minHeight: parent.minHeight, maxLines: parent.maxLines)
|
||||
}
|
||||
|
||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
if text == "\n" {
|
||||
if let onSubmit = parent.onSubmit {
|
||||
DispatchQueue.main.async {
|
||||
onSubmit()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func updatePlaceholderVisibility(for textView: UITextView) {
|
||||
placeholderLabel.isHidden = !textView.text.isEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
// Previews intentionally omitted - MessageItem has custom decoding-only initializer.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user