From ac274ec885d12c71d93dc0179409ca8ffe53e728 Mon Sep 17 00:00:00 2001 From: cheykrym Date: Wed, 10 Dec 2025 04:32:07 +0300 Subject: [PATCH] add messageprofileview --- yobble/Views/Chat/MessageProfileView.swift | 159 +++++++++++++++++++++ yobble/Views/Chat/PrivateChatView.swift | 152 +------------------- 2 files changed, 160 insertions(+), 151 deletions(-) create mode 100644 yobble/Views/Chat/MessageProfileView.swift diff --git a/yobble/Views/Chat/MessageProfileView.swift b/yobble/Views/Chat/MessageProfileView.swift new file mode 100644 index 0000000..afbbe71 --- /dev/null +++ b/yobble/Views/Chat/MessageProfileView.swift @@ -0,0 +1,159 @@ +// +// MessageProfileView.swift +// yobble +// +// Created by cheykrym on 10.12.2025. +// +import SwiftUI + + +struct MessageProfileView: View { + let chat: PrivateChatListItem + let currentUserId: String? + private let avatarSize: CGFloat = 96 + + var body: some View { + ScrollView { + VStack(spacing: 24) { + profileAvatar + + VStack(spacing: 4) { + Text(displayName) + .font(.title3) + .fontWeight(.semibold) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity) + + if let login = loginDisplay { + Text(login) + .font(.subheadline) + .foregroundColor(.secondary) + } + } + + Text(NSLocalizedString("Профиль для сообщений пока в разработке.", comment: "Message profile placeholder title")) + .font(.body) + .multilineTextAlignment(.center) + + Text(NSLocalizedString("Скоро здесь появится информация о собеседнике, статусе и дополнительных действиях.", comment: "Message profile placeholder description")) + .font(.footnote) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + } + .padding(.horizontal, 24) + .padding(.top, 60) + .frame(maxWidth: .infinity) + } + .background(Color(UIColor.systemBackground)) + .navigationTitle(NSLocalizedString("Профиль", comment: "Message profile placeholder nav title")) + .navigationBarTitleDisplayMode(.inline) + } + + private var displayName: String { + if let custom = trimmed(chat.chatData?.customName) { + return custom + } + if let full = trimmed(chat.chatData?.fullName) { + return full + } + if let login = trimmed(chat.chatData?.login) { + return "@\(login)" + } + return NSLocalizedString("Неизвестный пользователь", comment: "Message profile fallback title") + } + + private var loginDisplay: String? { + guard let login = trimmed(chat.chatData?.login) else { return nil } + return "@\(login)" + } + + private var isDeletedUser: Bool { + trimmed(chat.chatData?.login) == nil + } + + private var isOfficial: Bool { + chat.chatData?.isOfficial ?? false + } + + private var avatarBackgroundColor: Color { + if isDeletedUser { + return Color(.systemGray5) + } + return isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15) + } + + private var avatarTextColor: Color { + if isDeletedUser { + return Color.accentColor + } + return isOfficial ? Color.white : Color.accentColor + } + + private var avatarInitial: String { + if let name = trimmed(chat.chatData?.customName) ?? trimmed(chat.chatData?.fullName) { + let components = name.split(separator: " ") + let initials = components.prefix(2).compactMap { $0.first } + if !initials.isEmpty { + return initials.map { String($0) }.joined().uppercased() + } + } + + if let login = trimmed(chat.chatData?.login) { + return String(login.prefix(1)).uppercased() + } + + return "?" + } + + private var avatarUrl: URL? { + guard let chatData = chat.chatData, + let fileId = chatData.avatars?.current?.fileId else { + return nil + } + let userId = chatData.userId + return URL(string: "\(AppConfig.API_SERVER)/v1/storage/download/avatar/\(userId)?file_id=\(fileId)") + } + + @ViewBuilder + private var profileAvatar: some View { + if let url = avatarUrl, + let fileId = chat.chatData?.avatars?.current?.fileId, + let userId = currentUserId { + CachedAvatarView(url: url, fileId: fileId, userId: userId) { + placeholderAvatar + } + .aspectRatio(contentMode: .fill) + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + } else { + placeholderAvatar + } + } + + private var placeholderAvatar: some View { + Circle() + .fill(avatarBackgroundColor) + .frame(width: avatarSize, height: avatarSize) + .overlay( + Group { + if isDeletedUser { + Image(systemName: "person.slash") + .symbolRenderingMode(.hierarchical) + .font(.system(size: avatarSize * 0.45, weight: .semibold)) + .foregroundColor(avatarTextColor) + } else { + Text(avatarInitial) + .font(.system(size: avatarSize * 0.45, weight: .semibold)) + .foregroundColor(avatarTextColor) + } + } + ) + } + + private func trimmed(_ text: String?) -> String? { + guard let text = text?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else { + return nil + } + return text + } +} diff --git a/yobble/Views/Chat/PrivateChatView.swift b/yobble/Views/Chat/PrivateChatView.swift index a614473..f6d8b99 100644 --- a/yobble/Views/Chat/PrivateChatView.swift +++ b/yobble/Views/Chat/PrivateChatView.swift @@ -52,7 +52,7 @@ struct PrivateChatView: View { } NavigationLink( - destination: MessageProfilePlaceholderView(chat: chat, currentUserId: currentUserId), + destination: MessageProfileView(chat: chat, currentUserId: currentUserId), isActive: $isProfilePresented ) { EmptyView() @@ -723,156 +723,6 @@ private var headerPlaceholderAvatar: some View { } } -struct MessageProfilePlaceholderView: View { - let chat: PrivateChatListItem - let currentUserId: String? - private let avatarSize: CGFloat = 96 - - var body: some View { - ScrollView { - VStack(spacing: 24) { - profileAvatar - - VStack(spacing: 4) { - Text(displayName) - .font(.title3) - .fontWeight(.semibold) - .multilineTextAlignment(.center) - .frame(maxWidth: .infinity) - - if let login = loginDisplay { - Text(login) - .font(.subheadline) - .foregroundColor(.secondary) - } - } - - Text(NSLocalizedString("Профиль для сообщений пока в разработке.", comment: "Message profile placeholder title")) - .font(.body) - .multilineTextAlignment(.center) - - Text(NSLocalizedString("Скоро здесь появится информация о собеседнике, статусе и дополнительных действиях.", comment: "Message profile placeholder description")) - .font(.footnote) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - } - .padding(.horizontal, 24) - .padding(.top, 60) - .frame(maxWidth: .infinity) - } - .background(Color(UIColor.systemBackground)) - .navigationTitle(NSLocalizedString("Профиль", comment: "Message profile placeholder nav title")) - .navigationBarTitleDisplayMode(.inline) - } - - private var displayName: String { - if let custom = trimmed(chat.chatData?.customName) { - return custom - } - if let full = trimmed(chat.chatData?.fullName) { - return full - } - if let login = trimmed(chat.chatData?.login) { - return "@\(login)" - } - return NSLocalizedString("Неизвестный пользователь", comment: "Message profile fallback title") - } - - private var loginDisplay: String? { - guard let login = trimmed(chat.chatData?.login) else { return nil } - return "@\(login)" - } - - private var isDeletedUser: Bool { - trimmed(chat.chatData?.login) == nil - } - - private var isOfficial: Bool { - chat.chatData?.isOfficial ?? false - } - - private var avatarBackgroundColor: Color { - if isDeletedUser { - return Color(.systemGray5) - } - return isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15) - } - - private var avatarTextColor: Color { - if isDeletedUser { - return Color.accentColor - } - return isOfficial ? Color.white : Color.accentColor - } - - private var avatarInitial: String { - if let name = trimmed(chat.chatData?.customName) ?? trimmed(chat.chatData?.fullName) { - let components = name.split(separator: " ") - let initials = components.prefix(2).compactMap { $0.first } - if !initials.isEmpty { - return initials.map { String($0) }.joined().uppercased() - } - } - - if let login = trimmed(chat.chatData?.login) { - return String(login.prefix(1)).uppercased() - } - - return "?" - } - - private var avatarUrl: URL? { - guard let chatData = chat.chatData, - let fileId = chatData.avatars?.current?.fileId else { - return nil - } - let userId = chatData.userId - return URL(string: "\(AppConfig.API_SERVER)/v1/storage/download/avatar/\(userId)?file_id=\(fileId)") - } - - @ViewBuilder - private var profileAvatar: some View { - if let url = avatarUrl, - let fileId = chat.chatData?.avatars?.current?.fileId, - let userId = currentUserId { - CachedAvatarView(url: url, fileId: fileId, userId: userId) { - placeholderAvatar - } - .aspectRatio(contentMode: .fill) - .frame(width: avatarSize, height: avatarSize) - .clipShape(Circle()) - } else { - placeholderAvatar - } - } - - private var placeholderAvatar: some View { - Circle() - .fill(avatarBackgroundColor) - .frame(width: avatarSize, height: avatarSize) - .overlay( - Group { - if isDeletedUser { - Image(systemName: "person.slash") - .symbolRenderingMode(.hierarchical) - .font(.system(size: avatarSize * 0.45, weight: .semibold)) - .foregroundColor(avatarTextColor) - } else { - Text(avatarInitial) - .font(.system(size: avatarSize * 0.45, weight: .semibold)) - .foregroundColor(avatarTextColor) - } - } - ) - } - - private func trimmed(_ text: String?) -> String? { - guard let text = text?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else { - return nil - } - return text - } -} #if canImport(UIKit) private struct LegacyMultilineTextView: UIViewRepresentable {