profile update
This commit is contained in:
parent
1c8e80df1f
commit
76f3fdefdf
@ -20,6 +20,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"%@" : {
|
||||||
|
"comment" : "Message profile joined format"
|
||||||
|
},
|
||||||
"%@ %@" : {
|
"%@ %@" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
@ -270,6 +273,9 @@
|
|||||||
},
|
},
|
||||||
"Безопасность аккаунта" : {
|
"Безопасность аккаунта" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Биография" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Блокировка и жалобы доступны из профиля, как в Telegram." : {
|
"Блокировка и жалобы доступны из профиля, как в Telegram." : {
|
||||||
"comment" : "Message profile safety section description"
|
"comment" : "Message profile safety section description"
|
||||||
@ -531,6 +537,9 @@
|
|||||||
},
|
},
|
||||||
"Данные и кэш" : {
|
"Данные и кэш" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Дата регистрации в Yobble" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Двухфакторная аутентификация" : {
|
"Двухфакторная аутентификация" : {
|
||||||
"comment" : "Заголовок экрана 2FA\nПереход к настройкам двухфакторной аутентификации",
|
"comment" : "Заголовок экрана 2FA\nПереход к настройкам двухфакторной аутентификации",
|
||||||
@ -546,9 +555,6 @@
|
|||||||
"Двухфакторная аутентификация настроена." : {
|
"Двухфакторная аутентификация настроена." : {
|
||||||
"comment" : "Сообщение после успешного подтверждения кода 2FA"
|
"comment" : "Сообщение после успешного подтверждения кода 2FA"
|
||||||
},
|
},
|
||||||
"Действия в чате" : {
|
|
||||||
"comment" : "Message profile quick actions title"
|
|
||||||
},
|
|
||||||
"Десктоп" : {
|
"Десктоп" : {
|
||||||
"comment" : "Тип сессии — десктоп"
|
"comment" : "Тип сессии — десктоп"
|
||||||
},
|
},
|
||||||
@ -583,6 +589,9 @@
|
|||||||
},
|
},
|
||||||
"Добро пожаловать в Yobble!" : {
|
"Добро пожаловать в Yobble!" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Дополнительные действия." : {
|
||||||
|
"comment" : "Message profile more action description"
|
||||||
},
|
},
|
||||||
"Другие устройства (%d)" : {
|
"Другие устройства (%d)" : {
|
||||||
"comment" : "Заголовок секции других устройств с количеством"
|
"comment" : "Заголовок секции других устройств с количеством"
|
||||||
@ -610,6 +619,12 @@
|
|||||||
},
|
},
|
||||||
"Если предпочитаете классический вход, используйте логин и пароль." : {
|
"Если предпочитаете классический вход, используйте логин и пароль." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Ещё" : {
|
||||||
|
"comment" : "Message profile more action"
|
||||||
|
},
|
||||||
|
"Ещё…" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Жалоба" : {
|
"Жалоба" : {
|
||||||
"comment" : "Message profile report alert title"
|
"comment" : "Message profile report alert title"
|
||||||
@ -641,6 +656,9 @@
|
|||||||
"Завершить эту сессию?" : {
|
"Завершить эту сессию?" : {
|
||||||
"comment" : "Заголовок подтверждения завершения отдельной сессии"
|
"comment" : "Заголовок подтверждения завершения отдельной сессии"
|
||||||
},
|
},
|
||||||
|
"Заглушить" : {
|
||||||
|
"comment" : "Message profile mute action"
|
||||||
|
},
|
||||||
"Заглушка: Push-уведомления" : {
|
"Заглушка: Push-уведомления" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -837,9 +855,6 @@
|
|||||||
"Ищем пользователей…" : {
|
"Ищем пользователей…" : {
|
||||||
"comment" : "Global search loading"
|
"comment" : "Global search loading"
|
||||||
},
|
},
|
||||||
"Как в Telegram — главные кнопки всегда под рукой." : {
|
|
||||||
"comment" : "Message profile quick actions description"
|
|
||||||
},
|
|
||||||
"Как сбросить пароль?" : {
|
"Как сбросить пароль?" : {
|
||||||
"comment" : "FAQ question: reset password"
|
"comment" : "FAQ question: reset password"
|
||||||
},
|
},
|
||||||
@ -2400,6 +2415,9 @@
|
|||||||
},
|
},
|
||||||
"Публичная информация" : {
|
"Публичная информация" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Публичное имя" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Разблокировать" : {
|
"Разблокировать" : {
|
||||||
"comment" : "Message profile unblock alert title\nMessage profile unblock title\nUnblock confirmation action"
|
"comment" : "Message profile unblock alert title\nMessage profile unblock title\nUnblock confirmation action"
|
||||||
@ -2656,6 +2674,9 @@
|
|||||||
},
|
},
|
||||||
"Скоро появится разблокировка с подтверждением и синхронизацией." : {
|
"Скоро появится разблокировка с подтверждением и синхронизацией." : {
|
||||||
"comment" : "Message profile unblock alert message"
|
"comment" : "Message profile unblock alert message"
|
||||||
|
},
|
||||||
|
"Скрыть" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Слишком много запросов." : {
|
"Слишком много запросов." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -2851,9 +2872,6 @@
|
|||||||
},
|
},
|
||||||
"Тип диалога" : {
|
"Тип диалога" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Тишина" : {
|
|
||||||
"comment" : "Message profile mute action"
|
|
||||||
},
|
},
|
||||||
"Тишина включена. Чат не тревожит до включения сигнала." : {
|
"Тишина включена. Чат не тревожит до включения сигнала." : {
|
||||||
"comment" : "Message profile notifications subtitle off"
|
"comment" : "Message profile notifications subtitle off"
|
||||||
|
|||||||
@ -13,11 +13,13 @@ struct MessageProfileView: View {
|
|||||||
|
|
||||||
@State private var areNotificationsEnabled: Bool = true
|
@State private var areNotificationsEnabled: Bool = true
|
||||||
@State private var placeholderAlert: PlaceholderAlert?
|
@State private var placeholderAlert: PlaceholderAlert?
|
||||||
|
@State private var isBioExpanded: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
VStack(spacing: 24) {
|
VStack(spacing: 24) {
|
||||||
headerCard
|
headerCard
|
||||||
|
DescriptionSection
|
||||||
quickActionsSection
|
quickActionsSection
|
||||||
aboutSection
|
aboutSection
|
||||||
mediaPreviewSection
|
mediaPreviewSection
|
||||||
@ -59,11 +61,11 @@ struct MessageProfileView: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let login = loginDisplay {
|
// if let login = loginDisplay {
|
||||||
Text(login)
|
// Text(login)
|
||||||
.font(.subheadline)
|
// .font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
// .foregroundColor(.secondary)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if let status = presenceStatus {
|
if let status = presenceStatus {
|
||||||
HStack(spacing: 6) {
|
HStack(spacing: 6) {
|
||||||
@ -77,11 +79,11 @@ struct MessageProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let bio = profileBio {
|
// if let bio = profileBio {
|
||||||
Text(bio)
|
// Text(bio)
|
||||||
.font(.body)
|
// .font(.body)
|
||||||
.multilineTextAlignment(.center)
|
// .multilineTextAlignment(.center)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if !statusTags.isEmpty {
|
if !statusTags.isEmpty {
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 120), spacing: 8)], spacing: 8) {
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 120), spacing: 8)], spacing: 8) {
|
||||||
@ -115,6 +117,58 @@ struct MessageProfileView: View {
|
|||||||
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 10)
|
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var DescriptionSection: some View {
|
||||||
|
Group {
|
||||||
|
|
||||||
|
if let full = publicFullName{
|
||||||
|
Section(){
|
||||||
|
card {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
infoRow(
|
||||||
|
title: NSLocalizedString("Публичное имя", comment: ""),
|
||||||
|
value: full
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let bio = profileBio {
|
||||||
|
card {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
|
||||||
|
// Заголовок
|
||||||
|
Text("Биография")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
// Основной текст (обрезанный или полный)
|
||||||
|
Text(isBioExpanded ? bio : bioFirstLines(bio, count: 4))
|
||||||
|
.font(.body)
|
||||||
|
.textSelection(.enabled) // ← копирование
|
||||||
|
.animation(.easeInOut, value: isBioExpanded)
|
||||||
|
|
||||||
|
// Кнопка "ещё / скрыть" если строк больше 4
|
||||||
|
if bio.lineCount > 4 {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Button(action: { isBioExpanded.toggle() }) {
|
||||||
|
Text(isBioExpanded ? "Скрыть" : "Ещё…")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.top, 4)
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.padding(.top, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var officialBadge: some View {
|
private var officialBadge: some View {
|
||||||
if isOfficial {
|
if isOfficial {
|
||||||
@ -137,10 +191,7 @@ struct MessageProfileView: View {
|
|||||||
// MARK: - Sections
|
// MARK: - Sections
|
||||||
|
|
||||||
private var quickActionsSection: some View {
|
private var quickActionsSection: some View {
|
||||||
section(
|
section() {
|
||||||
title: NSLocalizedString("Действия в чате", comment: "Message profile quick actions title"),
|
|
||||||
description: NSLocalizedString("Как в Telegram — главные кнопки всегда под рукой.", comment: "Message profile quick actions description")
|
|
||||||
) {
|
|
||||||
card {
|
card {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
ForEach(quickActionItems) { action in
|
ForEach(quickActionItems) { action in
|
||||||
@ -175,22 +226,12 @@ struct MessageProfileView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var aboutSection: some View {
|
private var aboutSection: some View {
|
||||||
section(
|
Section(){
|
||||||
title: NSLocalizedString("О пользователе", comment: "Message profile about title"),
|
|
||||||
description: NSLocalizedString("Имя, логин и статус — как в профиле Telegram.", comment: "Message profile about description")
|
|
||||||
) {
|
|
||||||
card {
|
card {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
infoRow(
|
|
||||||
icon: "person.text.rectangle",
|
|
||||||
title: NSLocalizedString("Имя в чате", comment: ""),
|
|
||||||
value: displayName
|
|
||||||
)
|
|
||||||
|
|
||||||
if let login = loginDisplay {
|
if let login = loginDisplay {
|
||||||
rowDivider
|
|
||||||
infoRow(
|
infoRow(
|
||||||
icon: "at",
|
|
||||||
title: NSLocalizedString("Юзернейм", comment: ""),
|
title: NSLocalizedString("Юзернейм", comment: ""),
|
||||||
value: login
|
value: login
|
||||||
)
|
)
|
||||||
@ -199,30 +240,63 @@ struct MessageProfileView: View {
|
|||||||
if let membership = membershipDescription {
|
if let membership = membershipDescription {
|
||||||
rowDivider
|
rowDivider
|
||||||
infoRow(
|
infoRow(
|
||||||
icon: "calendar",
|
title: NSLocalizedString("Дата регистрации в Yobble", comment: ""),
|
||||||
title: NSLocalizedString("На Yobble", comment: ""),
|
|
||||||
value: membership
|
value: membership
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let status = presenceStatus, !status.isOnline {
|
|
||||||
rowDivider
|
|
||||||
infoRow(
|
|
||||||
icon: "clock",
|
|
||||||
title: NSLocalizedString("Последний визит", comment: ""),
|
|
||||||
value: status.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowDivider
|
|
||||||
infoRow(
|
|
||||||
icon: "lock.shield",
|
|
||||||
title: NSLocalizedString("Тип диалога", comment: ""),
|
|
||||||
value: chatTypeDescription
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// section(
|
||||||
|
// title: NSLocalizedString("О пользователе", comment: "Message profile about title"),
|
||||||
|
// description: NSLocalizedString("Имя, логин и статус — как в профиле Telegram.", comment: "Message profile about description")
|
||||||
|
// ) {
|
||||||
|
// card {
|
||||||
|
// VStack(spacing: 0) {
|
||||||
|
// infoRow(
|
||||||
|
// icon: "person.text.rectangle",
|
||||||
|
// title: NSLocalizedString("Имя в чате", comment: ""),
|
||||||
|
// value: displayName
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// if let login = loginDisplay {
|
||||||
|
// rowDivider
|
||||||
|
// infoRow(
|
||||||
|
// icon: "at",
|
||||||
|
// title: NSLocalizedString("Юзернейм", comment: ""),
|
||||||
|
// value: login
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if let membership = membershipDescription {
|
||||||
|
// rowDivider
|
||||||
|
// infoRow(
|
||||||
|
// icon: "calendar",
|
||||||
|
// title: NSLocalizedString("На Yobble", comment: ""),
|
||||||
|
// value: membership
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if let status = presenceStatus, !status.isOnline {
|
||||||
|
// rowDivider
|
||||||
|
// infoRow(
|
||||||
|
// icon: "clock",
|
||||||
|
// title: NSLocalizedString("Последний визит", comment: ""),
|
||||||
|
// value: status.text
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rowDivider
|
||||||
|
// infoRow(
|
||||||
|
// icon: "lock.shield",
|
||||||
|
// title: NSLocalizedString("Тип диалога", comment: ""),
|
||||||
|
// value: chatTypeDescription
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mediaPreviewSection: some View {
|
private var mediaPreviewSection: some View {
|
||||||
@ -407,22 +481,34 @@ struct MessageProfileView: View {
|
|||||||
|
|
||||||
// MARK: - Helper Builders
|
// MARK: - Helper Builders
|
||||||
|
|
||||||
private func section<Content: View>(title: String, description: String? = nil, @ViewBuilder content: () -> Content) -> some View {
|
private func section<Content: View>(
|
||||||
|
title: String? = nil,
|
||||||
|
description: String? = nil,
|
||||||
|
@ViewBuilder content: () -> Content
|
||||||
|
) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text(title)
|
// показать только если реально есть текст
|
||||||
.font(.headline)
|
if (title?.isEmpty == false) || (description?.isEmpty == false) {
|
||||||
if let description {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(description)
|
if let title, !title.isEmpty {
|
||||||
.font(.subheadline)
|
Text(title)
|
||||||
.foregroundColor(.secondary)
|
.font(.headline)
|
||||||
|
}
|
||||||
|
if let description, !description.isEmpty {
|
||||||
|
Text(description)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func card<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
private func card<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
content()
|
content()
|
||||||
@ -435,12 +521,14 @@ struct MessageProfileView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func infoRow(icon: String, title: String, value: String) -> some View {
|
private func infoRow(icon: String? = nil, title: String, value: String) -> some View {
|
||||||
HStack(alignment: .top, spacing: 12) {
|
HStack(alignment: .top, spacing: 12) {
|
||||||
iconBackground(color: .accentColor.opacity(0.18)) {
|
if let icon {
|
||||||
Image(systemName: icon)
|
iconBackground(color: .accentColor.opacity(0.18)) {
|
||||||
.font(.system(size: 17, weight: .semibold))
|
Image(systemName: icon)
|
||||||
.foregroundColor(.accentColor)
|
.font(.system(size: 17, weight: .semibold))
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
@ -522,7 +610,8 @@ struct MessageProfileView: View {
|
|||||||
guard let createdAt = chat.chatData?.createdAt else { return nil }
|
guard let createdAt = chat.chatData?.createdAt else { return nil }
|
||||||
let formatted = MessageProfileView.joinedFormatter.string(from: createdAt)
|
let formatted = MessageProfileView.joinedFormatter.string(from: createdAt)
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString("На платформе с %@", comment: "Message profile joined format"),
|
// format: NSLocalizedString("На платформе с %@", comment: "Message profile joined format"),
|
||||||
|
format: NSLocalizedString("%@", comment: "Message profile joined format"),
|
||||||
formatted
|
formatted
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -662,11 +751,17 @@ struct MessageProfileView: View {
|
|||||||
description: NSLocalizedString("Голосовые звонки пока недоступны. Как только включим WebRTC, кнопка оживёт.", comment: "Message profile call action description"),
|
description: NSLocalizedString("Голосовые звонки пока недоступны. Как только включим WebRTC, кнопка оживёт.", comment: "Message profile call action description"),
|
||||||
tint: .green
|
tint: .green
|
||||||
),
|
),
|
||||||
|
// ProfileQuickAction(
|
||||||
|
// icon: "video.fill",
|
||||||
|
// title: NSLocalizedString("Видео", comment: "Message profile video action"),
|
||||||
|
// description: NSLocalizedString("Видео созвоны появятся вместе с звонками. Интерфейс повторит Telegram.", comment: "Message profile video action description"),
|
||||||
|
// tint: .purple
|
||||||
|
// ),
|
||||||
ProfileQuickAction(
|
ProfileQuickAction(
|
||||||
icon: "video.fill",
|
icon: "bell.slash.fill",
|
||||||
title: NSLocalizedString("Видео", comment: "Message profile video action"),
|
title: NSLocalizedString("Заглушить", comment: "Message profile mute action"),
|
||||||
description: NSLocalizedString("Видео созвоны появятся вместе с звонками. Интерфейс повторит Telegram.", comment: "Message profile video action description"),
|
description: NSLocalizedString("Появится мутация на 1 час, 1 день или навсегда.", comment: "Message profile mute action description"),
|
||||||
tint: .purple
|
tint: .orange
|
||||||
),
|
),
|
||||||
ProfileQuickAction(
|
ProfileQuickAction(
|
||||||
icon: "magnifyingglass",
|
icon: "magnifyingglass",
|
||||||
@ -675,10 +770,10 @@ struct MessageProfileView: View {
|
|||||||
tint: .blue
|
tint: .blue
|
||||||
),
|
),
|
||||||
ProfileQuickAction(
|
ProfileQuickAction(
|
||||||
icon: "bell.slash.fill",
|
icon: "ellipsis.circle",
|
||||||
title: NSLocalizedString("Тишина", comment: "Message profile mute action"),
|
title: NSLocalizedString("Ещё", comment: "Message profile more action"),
|
||||||
description: NSLocalizedString("Появится мутация на 1 час, 1 день или навсегда.", comment: "Message profile mute action description"),
|
description: NSLocalizedString("Дополнительные действия.", comment: "Message profile more action description"),
|
||||||
tint: .orange
|
tint: .gray
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -782,6 +877,19 @@ struct MessageProfileView: View {
|
|||||||
return NSLocalizedString("Неизвестный пользователь", comment: "Message profile fallback title")
|
return NSLocalizedString("Неизвестный пользователь", comment: "Message profile fallback title")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var publicFullName: String? {
|
||||||
|
guard
|
||||||
|
let custom = trimmed(chat.chatData?.customName),
|
||||||
|
!custom.isEmpty, // custom должен быть
|
||||||
|
let full = trimmed(chat.chatData?.fullName),
|
||||||
|
!full.isEmpty // full должен быть
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return full
|
||||||
|
}
|
||||||
|
|
||||||
private var loginDisplay: String? {
|
private var loginDisplay: String? {
|
||||||
guard let login = trimmed(chat.chatData?.login) else { return nil }
|
guard let login = trimmed(chat.chatData?.login) else { return nil }
|
||||||
return "@\(login)"
|
return "@\(login)"
|
||||||
@ -809,6 +917,19 @@ struct MessageProfileView: View {
|
|||||||
formatter.timeStyle = .none
|
formatter.timeStyle = .none
|
||||||
return formatter
|
return formatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private func bioFirstLines(_ text: String, count: Int) -> String {
|
||||||
|
let lines = text.split(separator: "\n", omittingEmptySubsequences: false)
|
||||||
|
if lines.count <= count { return text }
|
||||||
|
return lines.prefix(count).joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
var lineCount: Int {
|
||||||
|
return self.split(separator: "\n", omittingEmptySubsequences: false).count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct PresenceStatus {
|
private struct PresenceStatus {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user