add contact add view

This commit is contained in:
cheykrym 2025-12-11 04:11:26 +03:00
parent f5009157ca
commit c2177278e2
3 changed files with 185 additions and 22 deletions

View File

@ -581,6 +581,9 @@
"Добавить контакт" : { "Добавить контакт" : {
"comment" : "Message profile add contact alert title" "comment" : "Message profile add contact alert title"
}, },
"Добавление контакта появится позже." : {
"comment" : "Contact add placeholder message"
},
"Добавление новых блокировок появится позже." : { "Добавление новых блокировок появится позже." : {
"comment" : "Add blocked user placeholder message" "comment" : "Add blocked user placeholder message"
}, },
@ -810,7 +813,7 @@
} }
}, },
"Изменение фото недоступно" : { "Изменение фото недоступно" : {
"comment" : "Contact edit avatar unavailable title", "comment" : "Contact add avatar unavailable title\nContact edit avatar unavailable title",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -1171,7 +1174,7 @@
"comment" : "Сообщение при недоступной отправке письма" "comment" : "Сообщение при недоступной отправке письма"
}, },
"Мы пока не можем обновить фото контакта." : { "Мы пока не можем обновить фото контакта." : {
"comment" : "Contact edit avatar unavailable message", "comment" : "Contact add avatar unavailable message\nContact edit avatar unavailable message",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -1661,6 +1664,9 @@
"Новое сообщение" : { "Новое сообщение" : {
"comment" : "Default banner subtitle" "comment" : "Default banner subtitle"
}, },
"Новый контакт" : {
"comment" : "Contact add title"
},
"Новый пароль" : { "Новый пароль" : {
"comment" : "Новый пароль", "comment" : "Новый пароль",
"localizations" : { "localizations" : {
@ -2459,7 +2465,7 @@
}, },
"Публичная информация" : { "Публичная информация" : {
"comment" : "Profile info section title" "comment" : "Contact add public info section title\nProfile info section title"
}, },
"Публичное имя" : { "Публичное имя" : {
@ -2806,7 +2812,7 @@
"comment" : "Сообщение после активации 2FA" "comment" : "Сообщение после активации 2FA"
}, },
"Сохранить" : { "Сохранить" : {
"comment" : "Contact edit save button", "comment" : "Contact add save button\nContact edit save button",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {

View File

@ -287,11 +287,23 @@ struct MessageProfileView: View {
if shouldShowRelationshipQuickActions { if shouldShowRelationshipQuickActions {
rowDivider rowDivider
filledActionButton( if let profile = currentChatProfile {
title: NSLocalizedString("Добавить в контакты", comment: "Message profile add to contacts title"), NavigationLink {
tint: Color.accentColor ContactAddView(contact: ContactEditInfo(profile: profile))
) { } label: {
handleAddContactTap() filledActionLabel(
title: NSLocalizedString("Добавить в контакты", comment: "Message profile add to contacts title"),
tint: Color.accentColor
)
}
.buttonStyle(.plain)
} else {
filledActionButton(
title: NSLocalizedString("Добавить в контакты", comment: "Message profile add to contacts title"),
tint: Color.accentColor
) {
handleAddContactTap()
}
} }
rowDivider rowDivider
@ -501,23 +513,31 @@ struct MessageProfileView: View {
action: @escaping () -> Void action: @escaping () -> Void
) -> some View { ) -> some View {
Button(action: action) { Button(action: action) {
VStack(alignment: .leading, spacing: 4) { filledActionLabel(title: title, subtitle: subtitle, tint: tint)
Text(title)
.font(.body)
.fontWeight(.semibold)
if let subtitle {
Text(subtitle)
.font(.caption)
.foregroundColor(tint.opacity(0.7))
}
}
.foregroundColor(tint)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 10)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
} }
private func filledActionLabel(
title: String,
subtitle: String? = nil,
tint: Color
) -> some View {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.body)
.fontWeight(.semibold)
if let subtitle {
Text(subtitle)
.font(.caption)
.foregroundColor(tint.opacity(0.7))
}
}
.foregroundColor(tint)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 10)
}
private func iconBackground<Content: View>(color: Color, @ViewBuilder content: () -> Content) -> some View { private func iconBackground<Content: View>(color: Color, @ViewBuilder content: () -> Content) -> some View {
RoundedRectangle(cornerRadius: 14, style: .continuous) RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(color) .fill(color)

View File

@ -0,0 +1,137 @@
import SwiftUI
struct ContactAddView: View {
let contact: ContactEditInfo
@State private var displayName: String
@State private var activeAlert: ContactAddAlert?
init(contact: ContactEditInfo) {
self.contact = contact
let initialName = contact.preferredName
_displayName = State(initialValue: initialName)
}
var body: some View {
Form {
avatarSection
Section(header: Text(NSLocalizedString("Публичная информация", comment: "Contact add public info section title"))) {
TextField(NSLocalizedString("Отображаемое имя", comment: "Display name field placeholder"), text: $displayName)
}
}
.navigationTitle(NSLocalizedString("Новый контакт", comment: "Contact add title"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(NSLocalizedString("Сохранить", comment: "Contact add save button")) {
handleSaveTap()
}
.disabled(!hasChanges)
}
}
.alert(item: $activeAlert) { item in
Alert(
title: Text(item.title),
message: Text(item.message),
dismissButton: .default(Text(NSLocalizedString("Понятно", comment: "Placeholder alert dismiss")))
)
}
}
private var avatarSection: some View {
Section {
HStack {
Spacer()
VStack(spacing: 8) {
avatarView
.frame(width: 120, height: 120)
.clipShape(Circle())
Button(NSLocalizedString("Изменить фото", comment: "Edit avatar button title")) {
showAvatarUnavailableAlert()
}
}
Spacer()
}
}
.listRowBackground(Color(UIColor.systemGroupedBackground))
}
@ViewBuilder
private var avatarView: some View {
if let url = avatarURL,
let fileId = contact.avatarFileId {
CachedAvatarView(url: url, fileId: fileId, userId: contact.userId) {
avatarPlaceholder
}
.aspectRatio(contentMode: .fill)
} else {
avatarPlaceholder
}
}
private var avatarPlaceholder: some View {
Circle()
.fill(Color.accentColor.opacity(0.15))
.overlay(
Text(avatarInitial)
.font(.system(size: 48, weight: .semibold))
.foregroundColor(.accentColor)
)
}
private var avatarInitial: String {
let trimmedName = displayName.trimmedNonEmpty ?? contact.preferredName
if let first = trimmedName.trimmingCharacters(in: .whitespacesAndNewlines).first {
return String(first).uppercased()
}
if let login = contact.login?.trimmingCharacters(in: .whitespacesAndNewlines), !login.isEmpty {
return String(login.prefix(1)).uppercased()
}
return "?"
}
private var avatarURL: URL? {
guard let fileId = contact.avatarFileId else { return nil }
return URL(string: "\(AppConfig.API_SERVER)/v1/storage/download/avatar/\(contact.userId)?file_id=\(fileId)")
}
private var hasChanges: Bool {
let trimmed = displayName.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return false }
if let existing = contact.customName?.trimmedNonEmpty {
return trimmed != existing
}
return true
}
private func showAvatarUnavailableAlert() {
activeAlert = ContactAddAlert(
title: NSLocalizedString("Изменение фото недоступно", comment: "Contact add avatar unavailable title"),
message: NSLocalizedString("Мы пока не можем обновить фото контакта.", comment: "Contact add avatar unavailable message")
)
}
private func handleSaveTap() {
activeAlert = ContactAddAlert(
title: NSLocalizedString("Скоро", comment: "Common soon title"),
message: NSLocalizedString("Добавление контакта появится позже.", comment: "Contact add placeholder message")
)
}
}
private struct ContactAddAlert: Identifiable {
let id = UUID()
let title: String
let message: String
}
private extension String {
var trimmedNonEmpty: String? {
let value = trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}
}