Исправления UI: центрирование Saved Messages, размеры тулбара звонков, отображение "Connecting...", локальная отправка в Saved Messages
This commit is contained in:
@@ -45,6 +45,7 @@ struct ChatDetailView: View {
|
||||
|
||||
private var subtitleText: String {
|
||||
if route.isSavedMessages { return "" }
|
||||
if ProtocolManager.shared.connectionState != .authenticated { return "connecting..." }
|
||||
if isTyping { return "typing..." }
|
||||
if let dialog, dialog.isOnline { return "online" }
|
||||
return "offline"
|
||||
@@ -168,14 +169,16 @@ private extension ChatDetailView {
|
||||
}
|
||||
}
|
||||
|
||||
Text(subtitleText)
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundStyle(
|
||||
isTyping || (dialog?.isOnline == true)
|
||||
? RosettaColors.online
|
||||
: RosettaColors.Adaptive.textSecondary
|
||||
)
|
||||
.lineLimit(1)
|
||||
if !subtitleText.isEmpty {
|
||||
Text(subtitleText)
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundStyle(
|
||||
isTyping || (dialog?.isOnline == true)
|
||||
? RosettaColors.online
|
||||
: RosettaColors.Adaptive.textSecondary
|
||||
)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.frame(height: 44)
|
||||
@@ -218,14 +221,16 @@ private extension ChatDetailView {
|
||||
}
|
||||
}
|
||||
|
||||
Text(subtitleText)
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundStyle(
|
||||
isTyping || (dialog?.isOnline == true)
|
||||
? RosettaColors.online
|
||||
: RosettaColors.Adaptive.textSecondary
|
||||
)
|
||||
.lineLimit(1)
|
||||
if !subtitleText.isEmpty {
|
||||
Text(subtitleText)
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundStyle(
|
||||
isTyping || (dialog?.isOnline == true)
|
||||
? RosettaColors.online
|
||||
: RosettaColors.Adaptive.textSecondary
|
||||
)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.frame(height: 44)
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user