Фикс: блокировка восстановления клавиатуры при свайп-бэк и скип read receipt для системных аккаунтов

This commit is contained in:
2026-03-30 23:08:52 +05:00
parent 6270f4d4a1
commit a2de309a0f
3 changed files with 55 additions and 2 deletions

View File

@@ -560,6 +560,12 @@ final class SessionManager {
for item in encryptedAttachments {
if item.original.type == .image, let image = UIImage(data: item.original.data) {
AttachmentCache.shared.saveImage(image, forAttachmentId: item.original.id)
} else if item.original.type == .file {
AttachmentCache.shared.saveFile(
item.original.data,
forAttachmentId: item.original.id,
fileName: item.original.fileName ?? "file"
)
}
}
@@ -998,6 +1004,7 @@ final class SessionManager {
let connState = ProtocolManager.shared.connectionState
guard normalized != currentPublicKey,
!normalized.isEmpty,
!SystemAccounts.isSystemAccount(normalized),
let hash = privateKeyHash,
connState == .authenticated
else {

View File

@@ -29,6 +29,11 @@ final class ComposerView: UIView, UITextViewDelegate {
private(set) var currentHeight: CGFloat = 0
/// When true, blocks becomeFirstResponder via textViewShouldBeginEditing.
/// Set by NativeMessageListController during swipe-back to prevent UIKit
/// from auto-restoring first responder on transition cancellation.
var isFocusBlocked = false
// MARK: - Subviews
// Attach button (glass circle, 42×42)
@@ -495,6 +500,10 @@ final class ComposerView: UIView, UITextViewDelegate {
// MARK: - UITextViewDelegate
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
!isFocusBlocked
}
func textViewDidBeginEditing(_ textView: UITextView) {
delegate?.composerFocusDidChange(self, isFocused: true)
}

View File

@@ -81,6 +81,10 @@ final class NativeMessageListController: UIViewController {
private var composerBottomConstraint: NSLayoutConstraint?
private var composerHeightConstraint: NSLayoutConstraint?
private var isKeyboardAnimating = false
private(set) var isDisappearing = false
/// Blocks programmatic re-focus after swipe-back dismisses keyboard.
/// Cleared only when isInputFocused becomes true (user tap or reply action).
fileprivate(set) var keyboardBlockedUntilUserTap = false
private var currentKeyboardHeight: CGFloat = 0
// MARK: - Scroll-to-Bottom Button
@@ -147,7 +151,26 @@ final class NativeMessageListController: UIViewController {
)
}
// Phase 7: No viewWillAppear/viewWillDisappear CADisplayLink removed entirely.
// Dismiss keyboard on swipe-back. Let keyboardWillChangeFrame update insets
// normally so content slides down, but skip offset compensation (isDisappearing flag).
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
isDisappearing = true
keyboardBlockedUntilUserTap = true
// Block UIKit first-responder restoration at the UITextView level.
composerView?.isFocusBlocked = true
view.endEditing(true)
onComposerFocusChange?(false)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
isDisappearing = false
// UIKit's first-responder restoration has already been blocked
// by isFocusBlocked during the transition. Safe to unblock now
// for future user taps.
composerView?.isFocusBlocked = false
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
@@ -849,8 +872,10 @@ final class NativeMessageListController: UIViewController {
let delta = newInsetTop - oldInsetTop
// Capture offset BEFORE animation UIKit may auto-clamp after inset change.
// Skip compensation during swipe-back let content slide down naturally.
let oldOffset = collectionView.contentOffset.y
let shouldCompensate = abs(delta) > 0.5
let shouldCompensate = !isDisappearing
&& abs(delta) > 0.5
&& !collectionView.isDragging
&& !collectionView.isDecelerating
@@ -1204,6 +1229,18 @@ struct NativeMessageListView: UIViewControllerRepresentable {
}
#endif
composer.setReply(senderName: replySenderName, previewText: replyPreviewText)
// After swipe-back dismisses keyboard, block programmatic re-focus
// until user taps text field OR SwiftUI sets isInputFocused=true (reply).
if controller.keyboardBlockedUntilUserTap {
if isInputFocused {
controller.keyboardBlockedUntilUserTap = false
// Fall through to setFocused below
} else {
return // Don't touch focus while blocked
}
}
composer.setFocused(isInputFocused)
}