Compare commits
2 Commits
2000ddadc2
...
b47922694d
| Author | SHA1 | Date | |
|---|---|---|---|
| b47922694d | |||
| adba8fc568 |
@ -420,7 +420,7 @@
|
|||||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait 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 = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11;
|
MACOSX_DEPLOYMENT_TARGET = 11;
|
||||||
@ -460,7 +460,7 @@
|
|||||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait 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 = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11;
|
MACOSX_DEPLOYMENT_TARGET = 11;
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
#endif
|
||||||
|
|
||||||
struct PrivateChatView: View {
|
struct PrivateChatView: View {
|
||||||
let chat: PrivateChatListItem
|
let chat: PrivateChatListItem
|
||||||
@ -12,6 +15,7 @@ struct PrivateChatView: View {
|
|||||||
@State private var draftText: String = ""
|
@State private var draftText: String = ""
|
||||||
@State private var inputTab: ComposerTab = .chat
|
@State private var inputTab: ComposerTab = .chat
|
||||||
@State private var isVideoPreferred: Bool = false
|
@State private var isVideoPreferred: Bool = false
|
||||||
|
@State private var legacyComposerHeight: CGFloat = 40
|
||||||
@FocusState private var isComposerFocused: Bool
|
@FocusState private var isComposerFocused: Bool
|
||||||
@EnvironmentObject private var messageCenter: IncomingMessageCenter
|
@EnvironmentObject private var messageCenter: IncomingMessageCenter
|
||||||
|
|
||||||
@ -242,12 +246,31 @@ struct PrivateChatView: View {
|
|||||||
.frame(width: 36, height: 36)
|
.frame(width: 36, height: 36)
|
||||||
|
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
|
Group {
|
||||||
|
if #available(iOS 160.0, *) {
|
||||||
TextField(inputTab.placeholder, text: $draftText, axis: .vertical)
|
TextField(inputTab.placeholder, text: $draftText, axis: .vertical)
|
||||||
.lineLimit(1...4)
|
.lineLimit(1...4)
|
||||||
.focused($isComposerFocused)
|
.focused($isComposerFocused)
|
||||||
.submitLabel(.send)
|
.submitLabel(.send)
|
||||||
.disabled(currentUserId == nil)
|
.disabled(currentUserId == nil)
|
||||||
.onSubmit { sendCurrentMessage() }
|
.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(.top, 10)
|
||||||
.padding(.leading, 12)
|
.padding(.leading, 12)
|
||||||
.padding(.trailing, 44)
|
.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
|
// MARK: - Preview
|
||||||
|
|
||||||
// Previews intentionally omitted - MessageItem has custom decoding-only initializer.
|
// Previews intentionally omitted - MessageItem has custom decoding-only initializer.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user