Баннер Telegram-паритета и прямой переход в чат по тапу
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user