add messageprofileview
This commit is contained in:
parent
b34f3c2a1a
commit
ac274ec885
159
yobble/Views/Chat/MessageProfileView.swift
Normal file
159
yobble/Views/Chat/MessageProfileView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user