Баннер Telegram-паритета и прямой переход в чат по тапу

This commit is contained in:
2026-04-13 23:34:54 +05:00
parent 05420337cc
commit 69ac9cd270
17 changed files with 1104 additions and 444 deletions

View File

@@ -96,6 +96,9 @@ struct ChatListView: View {
isDetailPresented = presented
}
)
// Force a fresh ChatDetailView when route changes at the same stack depth.
// This avoids stale message content when switching chats via notification/banner.
.id(route.publicKey)
}
.navigationDestination(isPresented: $showRequestChats) {
RequestChatsView(
@@ -145,17 +148,30 @@ struct ChatListView: View {
guard let route = notification.object as? ChatRoute else { return }
AppDelegate.pendingChatRoute = nil
AppDelegate.pendingChatRouteTimestamp = nil
// If already inside a chat, pop first then push after animation.
// Direct path replacement reuses the same ChatDetailView (SwiftUI optimization),
// which only updates the toolbar but keeps the old messages.
// Already showing this chat.
if !showRequestChats, navigationState.path.last?.publicKey == route.publicKey {
return
}
// If user is in a chat already, push target chat immediately on top.
// This avoids the list flash while still creating a fresh destination.
if !navigationState.path.isEmpty {
navigationState.path = []
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
navigationState.path.append(route)
return
}
// If Requests screen is open, close it first, then open chat.
if showRequestChats {
showRequestChats = false
DispatchQueue.main.async {
navigationState.path = [route]
}
} else {
navigationState.path = [route]
return
}
// Root chat-list state: open target chat directly.
navigationState.path = [route]
}
.onAppear {
// Cold start fallback: ChatListView didn't exist when notification was posted.

View File

@@ -136,9 +136,12 @@ final class RequestChatsController: UIViewController {
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .clear
collectionView.delegate = self
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.alwaysBounceHorizontal = false
collectionView.alwaysBounceVertical = true
collectionView.contentInset.bottom = 80
collectionView.contentInset.bottom = 0
collectionView.verticalScrollIndicatorInsets.bottom = 0
view.addSubview(collectionView)
NSLayoutConstraint.activate([

View File

@@ -73,6 +73,7 @@ final class ChatListCell: UICollectionViewCell {
private var isPinned = false
private var wasBadgeVisible = false
private var wasMentionBadgeVisible = false
private var isSystemChat = false
// MARK: - Init
@@ -354,8 +355,10 @@ final class ChatListCell: UICollectionViewCell {
)
} else {
authorLabel.frame = .zero
// System chats (Updates/Safe): keep preview visually farther from title.
let messageY: CGFloat = isSystemChat ? 29 : 21
messageLabel.frame = CGRect(
x: textLeft, y: 21,
x: textLeft, y: messageY,
width: max(0, messageMaxW), height: 38
)
}
@@ -389,6 +392,7 @@ final class ChatListCell: UICollectionViewCell {
func configure(with dialog: Dialog, isSyncing: Bool, typingUsers: Set<String>? = nil) {
let isDark = traitCollection.userInterfaceStyle == .dark
isPinned = dialog.isPinned
isSystemChat = isSystemDialog(dialog)
// Colors
let titleColor = isDark ? UIColor.white : UIColor.black
@@ -696,6 +700,15 @@ final class ChatListCell: UICollectionViewCell {
messageLabel.textColor = secondaryColor
}
private func isSystemDialog(_ dialog: Dialog) -> Bool {
if SystemAccounts.isSystemAccount(dialog.opponentKey) { return true }
if dialog.opponentTitle.caseInsensitiveCompare(SystemAccounts.updatesTitle) == .orderedSame { return true }
if dialog.opponentTitle.caseInsensitiveCompare(SystemAccounts.safeTitle) == .orderedSame { return true }
if dialog.opponentUsername.caseInsensitiveCompare("updates") == .orderedSame { return true }
if dialog.opponentUsername.caseInsensitiveCompare("safe") == .orderedSame { return true }
return false
}
// MARK: - Typing Indicator
private func configureTypingIndicator(dialog: Dialog, typingUsers: Set<String>, color: UIColor) {
@@ -859,6 +872,7 @@ final class ChatListCell: UICollectionViewCell {
wasMentionBadgeVisible = false
badgeContainer.transform = .identity
mentionImageView.transform = .identity
isSystemChat = false
}
// MARK: - Highlight

View File

@@ -45,6 +45,16 @@ final class ChatListCollectionController: UIViewController {
private var dataSource: UICollectionViewDiffableDataSource<Section, String>!
private var cellRegistration: UICollectionView.CellRegistration<ChatListCell, Dialog>!
private var requestsCellRegistration: UICollectionView.CellRegistration<ChatListRequestsCell, Int>!
private let floatingTabBarTotalHeight: CGFloat = 72
private var chatListBottomInset: CGFloat {
if #available(iOS 26, *) {
return 0
} else {
// contentInsetAdjustmentBehavior(.automatic) already contributes safe-area bottom.
// Add only the remaining space covered by the custom floating tab bar.
return max(0, floatingTabBarTotalHeight - view.safeAreaInsets.bottom)
}
}
// Dialog lookup by ID for cell configuration
private var dialogMap: [String: Dialog] = [:]
@@ -71,10 +81,11 @@ final class ChatListCollectionController: UIViewController {
collectionView.prefetchDataSource = self
collectionView.keyboardDismissMode = .onDrag
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.alwaysBounceVertical = true
collectionView.alwaysBounceHorizontal = false
collectionView.contentInsetAdjustmentBehavior = .automatic
// Bottom inset so last cells aren't hidden behind tab bar
collectionView.contentInset.bottom = 80
applyBottomInsets()
view.addSubview(collectionView)
NSLayoutConstraint.activate([
@@ -85,6 +96,23 @@ final class ChatListCollectionController: UIViewController {
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
applyBottomInsets()
}
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
applyBottomInsets()
}
private func applyBottomInsets() {
guard collectionView != nil else { return }
let inset = chatListBottomInset
collectionView.contentInset.bottom = inset
collectionView.verticalScrollIndicatorInsets.bottom = inset
}
private func createLayout() -> UICollectionViewCompositionalLayout {
var listConfig = UICollectionLayoutListConfiguration(appearance: .plain)
listConfig.showsSeparators = false