Онлайн-статусы, исправление навигации и UI чатов
- Реализован PacketOnlineSubscribe (0x04) для подписки на статус собеседника - Онлайн-статус загружается из результатов поиска (PacketSearch) при каждом хэндшейке - Toolbar capsule показывает online/offline/typing вместо @username - Зелёная точка онлайн-индикатора на аватаре в списке чатов (bottom-left, как в Android) - Убрана точка с аватара в toolbar (статус отображается текстом) - Исправлен баг двойного тапа при входе в чат (программная навигация вместо NavigationLink) - DialogRepository.updateUserInfo теперь принимает и сохраняет online-статус - Очистка requestedUserInfoKeys при реконнекте для обновления статусов - Добавлено логирование результатов поиска и отправки пакетов Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,17 +2,31 @@ import SwiftUI
|
||||
|
||||
// MARK: - ChatRowView
|
||||
|
||||
/// Chat row matching Figma "Row - Chats" component spec:
|
||||
/// Row: height 78, paddingLeft 10, paddingRight 16, vertical center
|
||||
/// Avatar: 62px circle, 10pt trailing padding
|
||||
/// Title: SF Pro Medium 17pt, tracking -0.43, primary color
|
||||
/// Message: SF Pro Regular 15pt, tracking -0.23, secondary color
|
||||
/// Time: SF Pro Regular 14pt, tracking -0.23, secondary color
|
||||
/// Badges gap: 6pt — verified 12px, muted 12px
|
||||
/// Trailing: pt 8, pb 14 — readStatus + time (gap 2), pin/count at bottom
|
||||
/// Chat row matching Figma "Row - Chats" component spec (node 3994:38947):
|
||||
///
|
||||
/// Row: height 78, pl-10, pr-16, items-center
|
||||
/// Avatar: 62px circle, pr-10
|
||||
/// Contents: flex-col, h-full, items-start, justify-center, pb-px
|
||||
/// Title and Trailing Accessories: flex-1, gap-6, items-center, w-full
|
||||
/// Title and Detail: flex-1, h-63, items-start, overflow-clip
|
||||
/// Title: gap-4, items-center — SF Pro Medium 17/22, tracking -0.43
|
||||
/// Message: h-41 — SF Pro Regular 15/20, tracking -0.23, secondary
|
||||
/// Accessories: h-full, items-center, justify-end
|
||||
/// Contents-Trailing: flex-col, h-full, items-end, justify-between, pt-8
|
||||
/// Time: SF Pro Regular 14/20, tracking -0.23, secondary
|
||||
/// Other: flex-1, items-end, justify-end, pb-14
|
||||
/// Badge: bg-#008BFF, min-w-20, max-w-37, px-4, rounded-full
|
||||
/// SF Pro Regular 15/20, black, tracking -0.23
|
||||
struct ChatRowView: View {
|
||||
let dialog: Dialog
|
||||
|
||||
var displayTitle: String {
|
||||
if dialog.isSavedMessages { return "Saved Messages" }
|
||||
if !dialog.opponentTitle.isEmpty { return dialog.opponentTitle }
|
||||
if !dialog.opponentUsername.isEmpty { return "@\(dialog.opponentUsername)" }
|
||||
return String(dialog.opponentKey.prefix(12))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
avatarSection
|
||||
@@ -41,32 +55,38 @@ private extension ChatRowView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Content Section (two-column: title+detail | trailing accessories)
|
||||
// MARK: - Content Section
|
||||
// Figma "Contents": flex-col, h-full, items-start, justify-center, pb-px
|
||||
// └─ "Title and Trailing Accessories": flex-1, gap-6, items-center
|
||||
|
||||
private extension ChatRowView {
|
||||
var contentSection: some View {
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
// Left column: title + message
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
// "Title and Detail": flex-1, h-63, items-start, overflow-clip
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
titleRow
|
||||
messageRow
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.frame(height: 63)
|
||||
.clipped()
|
||||
|
||||
// Right column: time + pin/badge
|
||||
// "Accessories and Grabber": h-full, items-center, justify-end
|
||||
trailingColumn
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.frame(height: 63)
|
||||
.frame(maxHeight: .infinity)
|
||||
.padding(.bottom, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Title Row (name + badges)
|
||||
// Figma "Title": gap-4, items-center, w-full
|
||||
|
||||
private extension ChatRowView {
|
||||
var titleRow: some View {
|
||||
HStack(spacing: 4) {
|
||||
Text(dialog.isSavedMessages ? "Saved Messages" : dialog.opponentTitle)
|
||||
Text(displayTitle)
|
||||
.font(.system(size: 17, weight: .medium))
|
||||
.tracking(-0.43)
|
||||
.foregroundStyle(RosettaColors.Adaptive.text)
|
||||
@@ -89,6 +109,7 @@ private extension ChatRowView {
|
||||
}
|
||||
|
||||
// MARK: - Message Row
|
||||
// Figma "Message": h-41, SF Pro Regular 15/20, tracking -0.23, secondary
|
||||
|
||||
private extension ChatRowView {
|
||||
var messageRow: some View {
|
||||
@@ -96,7 +117,8 @@ private extension ChatRowView {
|
||||
.font(.system(size: 15))
|
||||
.tracking(-0.23)
|
||||
.foregroundStyle(RosettaColors.Adaptive.textSecondary)
|
||||
.lineLimit(1)
|
||||
.lineLimit(2)
|
||||
.frame(height: 41, alignment: .topLeading)
|
||||
}
|
||||
|
||||
var messageText: String {
|
||||
@@ -107,7 +129,10 @@ private extension ChatRowView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Trailing Column (time + delivery on top, pin/badge on bottom)
|
||||
// MARK: - Trailing Column
|
||||
// Figma "Contents - Trailing": flex-col, h-full, items-end, justify-between, pt-8
|
||||
// ├─ "Read Status and Time": gap-2, items-center
|
||||
// └─ "Other": flex-1, items-end, justify-end, pb-14
|
||||
|
||||
private extension ChatRowView {
|
||||
var trailingColumn: some View {
|
||||
@@ -127,7 +152,7 @@ private extension ChatRowView {
|
||||
: RosettaColors.Adaptive.textSecondary
|
||||
)
|
||||
}
|
||||
.padding(.top, 2)
|
||||
.padding(.top, 8)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
@@ -144,7 +169,7 @@ private extension ChatRowView {
|
||||
unreadBadge
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
.padding(.bottom, 14)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,14 +206,18 @@ private extension ChatRowView {
|
||||
let count = dialog.unreadCount
|
||||
let text = count > 999 ? "\(count / 1000)K" : (count > 99 ? "99+" : "\(count)")
|
||||
let isMuted = dialog.isMuted
|
||||
let isSmall = count < 10
|
||||
|
||||
return Text(text)
|
||||
.font(.system(size: 15))
|
||||
.tracking(-0.23)
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, 4)
|
||||
.frame(minWidth: 20, minHeight: 20)
|
||||
.frame(maxWidth: 37)
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, isSmall ? 0 : 4)
|
||||
.frame(
|
||||
minWidth: 20,
|
||||
maxWidth: isSmall ? 20 : 37,
|
||||
minHeight: 20
|
||||
)
|
||||
.background {
|
||||
Capsule()
|
||||
.fill(isMuted ? Color(hex: 0x787880) : RosettaColors.figmaBlue)
|
||||
|
||||
Reference in New Issue
Block a user