Исправления UI: центрирование Saved Messages, размеры тулбара звонков, отображение "Connecting...", локальная отправка в Saved Messages

This commit is contained in:
2026-03-11 01:41:40 +05:00
parent 0f5094df10
commit fa003e9edb
15 changed files with 913 additions and 155 deletions

View File

@@ -262,9 +262,7 @@ private extension ChatListView {
ToolbarItem(placement: .principal) {
HStack(spacing: 4) {
ToolbarStoriesAvatar()
Text("Chats")
.font(.system(size: 17, weight: .semibold))
.foregroundStyle(RosettaColors.Adaptive.text)
ToolbarTitleView()
}
}
@@ -302,9 +300,7 @@ private extension ChatListView {
ToolbarItem(placement: .principal) {
HStack(spacing: 4) {
ToolbarStoriesAvatar()
Text("Chats")
.font(.system(size: 17, weight: .semibold))
.foregroundStyle(RosettaColors.Adaptive.text)
ToolbarTitleView()
}
}
@@ -355,6 +351,30 @@ private struct ChatListToolbarBackgroundModifier: ViewModifier {
}
}
// MARK: - Toolbar Title (observation-isolated)
/// Reads `ProtocolManager.shared.connectionState` in its own observation scope.
/// Connection state changes during handshake (4+ rapid transitions) are absorbed here,
/// not cascaded to the parent ChatListView / NavigationStack.
private struct ToolbarTitleView: View {
var body: some View {
let state = ProtocolManager.shared.connectionState
let title: String = switch state {
case .disconnected: "Connecting..."
case .connecting: "Connecting..."
case .connected: "Connected"
case .handshaking: "Authenticating..."
case .deviceVerificationRequired: "Device Verification..."
case .authenticated: "Chats"
}
Text(title)
.font(.system(size: 17, weight: .semibold))
.foregroundStyle(RosettaColors.Adaptive.text)
.contentTransition(.numericText())
.animation(.easeInOut(duration: 0.25), value: state)
}
}
// MARK: - Toolbar Stories Avatar (observation-isolated)
/// Reads `AccountManager` and `SessionManager` in its own observation scope.
@@ -470,23 +490,25 @@ private struct ChatListDialogContent: View {
.listRowSeparatorTint(RosettaColors.Adaptive.divider)
.alignmentGuide(.listRowSeparatorLeading) { _ in 82 }
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button {
viewModel.toggleMute(dialog)
Button(role: .destructive) {
withAnimation { viewModel.deleteDialog(dialog) }
} label: {
Label(
dialog.isMuted ? "Unmute" : "Mute",
systemImage: dialog.isMuted ? "bell" : "bell.slash"
)
Label("Delete", systemImage: "trash")
}
if !dialog.isSavedMessages {
Button {
viewModel.toggleMute(dialog)
} label: {
Label(
dialog.isMuted ? "Unmute" : "Mute",
systemImage: dialog.isMuted ? "bell" : "bell.slash"
)
}
.tint(dialog.isMuted ? .green : .indigo)
}
.tint(dialog.isMuted ? .green : .indigo)
}
.swipeActions(edge: .leading, allowsFullSwipe: true) {
Button {
viewModel.markAsRead(dialog)
} label: {
Label("Read", systemImage: "envelope.open")
}
.tint(RosettaColors.figmaBlue)
Button {
viewModel.togglePin(dialog)
} label: {