add messageprofileview

This commit is contained in:
cheykrym 2025-12-10 04:32:07 +03:00
parent b34f3c2a1a
commit ac274ec885
2 changed files with 160 additions and 151 deletions

View File

@ -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
}
}

View File

@ -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 {