Фикс: имя файла в пересланных сообщениях, потеря фоток/файлов при пересылке forwarded-сообщений, Фоллбэк при unwrap forwarded-сообщения, защита БД от перезаписи синком
This commit is contained in:
@@ -568,6 +568,11 @@ private struct ChatListDialogContent: View {
|
||||
|
||||
var body: some View {
|
||||
let _ = PerformanceLogger.shared.track("chatList.bodyEval")
|
||||
// CRITICAL: Read DialogRepository.dialogs directly to establish @Observable tracking.
|
||||
// Without this, ChatListDialogContent only observes viewModel (ObservableObject)
|
||||
// which never publishes objectWillChange for dialog mutations.
|
||||
// The read forces SwiftUI to re-evaluate body when dialogs dict changes.
|
||||
let _ = DialogRepository.shared.dialogs.count
|
||||
// Use pre-partitioned arrays from ViewModel (single-pass O(n) instead of 3× filter).
|
||||
let pinned = viewModel.allModePinned
|
||||
let unpinned = viewModel.allModeUnpinned
|
||||
@@ -636,6 +641,7 @@ private struct ChatListDialogContent: View {
|
||||
.scrollContentBackground(.hidden)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.scrollIndicators(.hidden)
|
||||
.modifier(ClassicSwipeActionsModifier())
|
||||
// Scroll-to-top: tap "Chats" in toolbar
|
||||
.onReceive(NotificationCenter.default.publisher(for: .chatListScrollToTop)) { _ in
|
||||
// Scroll to first dialog ID (pinned or unpinned)
|
||||
@@ -712,7 +718,7 @@ struct SyncAwareChatRow: View {
|
||||
|
||||
if !dialog.isSavedMessages {
|
||||
Button {
|
||||
viewModel.toggleMute(dialog)
|
||||
withAnimation { viewModel.toggleMute(dialog) }
|
||||
} label: {
|
||||
Label(
|
||||
dialog.isMuted ? "Unmute" : "Mute",
|
||||
@@ -724,7 +730,7 @@ struct SyncAwareChatRow: View {
|
||||
}
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
Button {
|
||||
viewModel.togglePin(dialog)
|
||||
withAnimation { viewModel.togglePin(dialog) }
|
||||
} label: {
|
||||
Label(dialog.isPinned ? "Unpin" : "Pin", systemImage: dialog.isPinned ? "pin.slash" : "pin")
|
||||
}
|
||||
@@ -777,6 +783,22 @@ private struct DeviceApprovalBanner: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - iOS 26+ Classic Swipe Actions
|
||||
|
||||
/// iOS 26: disable Liquid Glass on the List so swipe action buttons use
|
||||
/// solid colors (same as iOS < 26). Uses UIAppearance override.
|
||||
private struct ClassicSwipeActionsModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content.onAppear {
|
||||
if #available(iOS 26, *) {
|
||||
// Disable glass on UITableView-backed List swipe actions.
|
||||
let appearance = UITableView.appearance()
|
||||
appearance.backgroundColor = .clear
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Chat List") {
|
||||
ChatListView(isSearchActive: .constant(false), isDetailPresented: .constant(false))
|
||||
.preferredColorScheme(.dark)
|
||||
|
||||
@@ -141,7 +141,7 @@ private extension ChatRowView {
|
||||
if isTyping && !dialog.isSavedMessages {
|
||||
return "typing..."
|
||||
}
|
||||
if dialog.lastMessage.isEmpty {
|
||||
if dialog.lastMessage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
return "No messages yet"
|
||||
}
|
||||
if let cached = Self.messageTextCache[dialog.lastMessage] {
|
||||
@@ -195,11 +195,11 @@ private extension ChatRowView {
|
||||
.rotationEffect(.degrees(45))
|
||||
}
|
||||
|
||||
// Desktop parity: delivery icon and unread badge are
|
||||
// mutually exclusive — badge hidden when lastMessageFromMe.
|
||||
// Also hidden during sync (desktop hides badges while
|
||||
// protocolState == SYNCHRONIZATION).
|
||||
if dialog.unreadCount > 0 && !dialog.lastMessageFromMe && !isSyncing {
|
||||
// Show unread badge whenever there are unread messages.
|
||||
// Previously hidden when lastMessageFromMe (desktop parity),
|
||||
// but this caused invisible unreads when user sent a reply
|
||||
// without reading prior incoming messages first.
|
||||
if dialog.unreadCount > 0 && !isSyncing {
|
||||
unreadBadge
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,13 +71,9 @@ struct RequestChatsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
// Use TelegramGlass* for ALL iOS versions — SwiftUI .glassEffect() blocks touches.
|
||||
private func glassCapsule(strokeOpacity: Double = 0.18, strokeColor: Color = .white) -> some View {
|
||||
if #available(iOS 26.0, *) {
|
||||
Capsule().fill(.clear).glassEffect(.regular, in: .capsule)
|
||||
} else {
|
||||
TelegramGlassCapsule()
|
||||
}
|
||||
TelegramGlassCapsule()
|
||||
}
|
||||
|
||||
private func requestRow(_ dialog: Dialog, isFirst: Bool) -> some View {
|
||||
|
||||
Reference in New Issue
Block a user