Фикс многострочного input (iOS 17-18), скругления glass iOS 26, откат swizzle таб-бара

This commit is contained in:
2026-03-25 17:39:22 +05:00
parent 872cdb88b0
commit f6afc79cd8
4 changed files with 32 additions and 5 deletions

View File

@@ -530,7 +530,7 @@
853F296C2F4B50420092AD05 /* Release */, 853F296C2F4B50420092AD05 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Debug;
}; };
853F296D2F4B50420092AD05 /* Build configuration list for PBXNativeTarget "Rosetta" */ = { 853F296D2F4B50420092AD05 /* Build configuration list for PBXNativeTarget "Rosetta" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
@@ -539,7 +539,7 @@
853F296F2F4B50420092AD05 /* Release */, 853F296F2F4B50420092AD05 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Debug;
}; };
B5D2E60ADEB8AE2E8F7615C6 /* Build configuration list for PBXNativeTarget "RosettaNotificationService" */ = { B5D2E60ADEB8AE2E8F7615C6 /* Build configuration list for PBXNativeTarget "RosettaNotificationService" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
@@ -548,7 +548,7 @@
0140D6320A9CF4B5E933E0B1 /* Debug */, 0140D6320A9CF4B5E933E0B1 /* Debug */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Debug;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */

View File

@@ -31,7 +31,7 @@
shouldAutocreateTestPlan = "YES"> shouldAutocreateTestPlan = "YES">
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Release" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"

View File

@@ -112,6 +112,9 @@ struct ChatTextInput: UIViewRepresentable {
var onKeyboardHeightChange: (CGFloat) -> Void var onKeyboardHeightChange: (CGFloat) -> Void
var onUserTextInsertion: () -> Void = {} var onUserTextInsertion: () -> Void = {}
var onMultilineChange: (Bool) -> Void = { _ in } var onMultilineChange: (Bool) -> Void = { _ in }
/// Reports the ideal height (clamped to max 5 lines) on every text change.
/// Used to drive `@State` frame height in the composer layout.
var onTextHeightChange: (CGFloat) -> Void = { _ in }
var font: UIFont = .systemFont(ofSize: 17, weight: .regular) var font: UIFont = .systemFont(ofSize: 17, weight: .regular)
var textColor: UIColor = .white var textColor: UIColor = .white
var placeholderColor: UIColor = UIColor.white.withAlphaComponent(0.35) var placeholderColor: UIColor = UIColor.white.withAlphaComponent(0.35)
@@ -255,6 +258,22 @@ struct ChatTextInput: UIViewRepresentable {
tv.setNeedsLayout() tv.setNeedsLayout()
tv.layoutIfNeeded() tv.layoutIfNeeded()
checkMultiline(tv) checkMultiline(tv)
reportHeight(tv)
}
/// Computes the ideal height (clamped to max 5 lines) and reports to SwiftUI.
/// This drives `@State textInputHeight` in the composer forces relayout.
private func reportHeight(_ tv: UITextView) {
let lineHeight = tv.font?.lineHeight ?? 20
let maxLines: CGFloat = 5
let insets = tv.textContainerInset
let maxTextHeight = lineHeight * maxLines
let maxTotalHeight = maxTextHeight + insets.top + insets.bottom
let fittingSize = tv.sizeThatFits(
CGSize(width: tv.bounds.width, height: .greatestFiniteMagnitude)
)
let height = max(36, min(fittingSize.height, maxTotalHeight))
parent.onTextHeightChange(height)
} }
private func checkMultiline(_ tv: UITextView) { private func checkMultiline(_ tv: UITextView) {

View File

@@ -39,6 +39,7 @@ struct ChatDetailView: View {
@State private var messageText = "" @State private var messageText = ""
@State private var isMultilineInput = false @State private var isMultilineInput = false
@State private var textInputHeight: CGFloat = 36
@State private var sendError: String? @State private var sendError: String?
@State private var isViewActive = false @State private var isViewActive = false
// markReadTask removed read receipts no longer sent from .onChange(of: messages.count) // markReadTask removed read receipts no longer sent from .onChange(of: messages.count)
@@ -995,11 +996,17 @@ private extension ChatDetailView {
isMultilineInput = multiline isMultilineInput = multiline
} }
}, },
onTextHeightChange: { h in
withAnimation(.easeInOut(duration: 0.2)) {
textInputHeight = h
}
},
textColor: UIColor(RosettaColors.Adaptive.text), textColor: UIColor(RosettaColors.Adaptive.text),
placeholderColor: UIColor(RosettaColors.Adaptive.textSecondary.opacity(0.5)) placeholderColor: UIColor(RosettaColors.Adaptive.textSecondary.opacity(0.5))
) )
.padding(.leading, 6) .padding(.leading, 6)
.frame(maxWidth: .infinity, minHeight: 36, alignment: .bottomLeading) .frame(maxWidth: .infinity, alignment: .bottomLeading)
.frame(height: textInputHeight)
HStack(alignment: .center, spacing: 0) { HStack(alignment: .center, spacing: 0) {
Button { Button {
@@ -1052,6 +1059,7 @@ private extension ChatDetailView {
.padding(3) .padding(3)
.frame(minHeight: 42, alignment: .bottom) .frame(minHeight: 42, alignment: .bottom)
.background { glass(shape: .rounded(isMultilineInput ? 16 : 21), strokeOpacity: 0.18) } .background { glass(shape: .rounded(isMultilineInput ? 16 : 21), strokeOpacity: 0.18) }
.clipShape(RoundedRectangle(cornerRadius: isMultilineInput ? 16 : 21, style: .continuous))
.padding(.leading, 6) .padding(.leading, 6)
Button(action: trailingAction) { Button(action: trailingAction) {