add history load
This commit is contained in:
parent
c9bfab0b14
commit
207187a439
@ -5,6 +5,11 @@ struct PrivateChatListData: Decodable {
|
|||||||
let hasMore: Bool
|
let hasMore: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PrivateChatHistoryData: Decodable {
|
||||||
|
let items: [MessageItem]
|
||||||
|
let hasMore: Bool
|
||||||
|
}
|
||||||
|
|
||||||
struct PrivateChatListItem: Decodable, Identifiable {
|
struct PrivateChatListItem: Decodable, Identifiable {
|
||||||
enum ChatType: String, Decodable {
|
enum ChatType: String, Decodable {
|
||||||
case `self`
|
case `self`
|
||||||
|
|||||||
@ -66,6 +66,51 @@ final class ChatService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchPrivateChatHistory(
|
||||||
|
chatId: String,
|
||||||
|
beforeMessageId: String?,
|
||||||
|
limit: Int,
|
||||||
|
completion: @escaping (Result<PrivateChatHistoryData, Error>) -> Void
|
||||||
|
) {
|
||||||
|
var query: [String: String?] = [
|
||||||
|
"chat_id": chatId,
|
||||||
|
"limit": String(limit)
|
||||||
|
]
|
||||||
|
|
||||||
|
if let beforeMessageId,
|
||||||
|
!beforeMessageId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
query["before_message_id"] = beforeMessageId
|
||||||
|
}
|
||||||
|
|
||||||
|
client.request(
|
||||||
|
path: "/v1/chat/private/history",
|
||||||
|
method: .get,
|
||||||
|
query: query,
|
||||||
|
requiresAuth: true
|
||||||
|
) { [decoder] result in
|
||||||
|
switch result {
|
||||||
|
case .success(let response):
|
||||||
|
do {
|
||||||
|
let apiResponse = try decoder.decode(APIResponse<PrivateChatHistoryData>.self, from: response.data)
|
||||||
|
guard apiResponse.status == "fine" else {
|
||||||
|
let message = apiResponse.detail ?? NSLocalizedString("Не удалось загрузить историю чата.", comment: "")
|
||||||
|
completion(.failure(ChatServiceError.unexpectedStatus(message)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
completion(.success(apiResponse.data))
|
||||||
|
} catch {
|
||||||
|
let debugMessage = Self.describeDecodingError(error: error, data: response.data)
|
||||||
|
if AppConfig.DEBUG {
|
||||||
|
print("[ChatService] decode private chat history failed: \(debugMessage)")
|
||||||
|
}
|
||||||
|
completion(.failure(ChatServiceError.decoding(debugDescription: debugMessage)))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func decodeDate(from decoder: Decoder) throws -> Date {
|
private static func decodeDate(from decoder: Decoder) throws -> Date {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
let string = try container.decode(String.self)
|
let string = try container.decode(String.self)
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"sourceLanguage" : "ru",
|
"sourceLanguage" : "ru",
|
||||||
"strings" : {
|
"strings" : {
|
||||||
|
"(без текста)" : {
|
||||||
|
|
||||||
|
},
|
||||||
"@%@" : {
|
"@%@" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -57,15 +60,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Chat ID:" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Companion ID" : {
|
"Companion ID" : {
|
||||||
"comment" : "Search placeholder companion title"
|
"comment" : "Search placeholder companion title"
|
||||||
},
|
|
||||||
"Companion ID:" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"DEBUG UPDATE" : {
|
"DEBUG UPDATE" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -210,6 +207,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"В чате пока нет сообщений." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Ваш e-mail" : {
|
"Ваш e-mail" : {
|
||||||
"comment" : "feedback: email placeholder",
|
"comment" : "feedback: email placeholder",
|
||||||
@ -414,6 +414,9 @@
|
|||||||
},
|
},
|
||||||
"Заглушка: Хранилище данных" : {
|
"Заглушка: Хранилище данных" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Загружаем ранние сообщения…" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Загружаем чаты…" : {
|
"Загружаем чаты…" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -424,6 +427,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Загрузка сообщений…" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Загрузка..." : {
|
"Загрузка..." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -872,6 +878,9 @@
|
|||||||
},
|
},
|
||||||
"Не удалось выполнить поиск." : {
|
"Не удалось выполнить поиск." : {
|
||||||
"comment" : "Search error fallback\nSearch service decoding error"
|
"comment" : "Search error fallback\nSearch service decoding error"
|
||||||
|
},
|
||||||
|
"Не удалось загрузить историю чата." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Не удалось загрузить профиль." : {
|
"Не удалось загрузить профиль." : {
|
||||||
"comment" : "Profile service decoding error\nProfile unexpected status"
|
"comment" : "Profile service decoding error\nProfile unexpected status"
|
||||||
@ -1048,9 +1057,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Неизвестный" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Неизвестный пользователь" : {
|
"Неизвестный пользователь" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -2232,9 +2238,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Экран чата в разработке" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Язык" : {
|
"Язык" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
128
yobble/ViewModels/PrivateChatViewModel.swift
Normal file
128
yobble/ViewModels/PrivateChatViewModel.swift
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class PrivateChatViewModel: ObservableObject {
|
||||||
|
@Published private(set) var messages: [MessageItem] = []
|
||||||
|
@Published private(set) var isInitialLoading: Bool = false
|
||||||
|
@Published private(set) var isLoadingMore: Bool = false
|
||||||
|
@Published var errorMessage: String?
|
||||||
|
|
||||||
|
private let chatService: ChatService
|
||||||
|
private let chatId: String
|
||||||
|
private let pageSize: Int
|
||||||
|
private var hasMore: Bool = true
|
||||||
|
private var didLoadInitially: Bool = false
|
||||||
|
|
||||||
|
init(chatId: String, chatService: ChatService = ChatService(), pageSize: Int = 30) {
|
||||||
|
self.chatId = chatId
|
||||||
|
self.chatService = chatService
|
||||||
|
self.pageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadInitialHistory(force: Bool = false) {
|
||||||
|
if !force && didLoadInitially { return }
|
||||||
|
guard !isInitialLoading else { return }
|
||||||
|
|
||||||
|
isInitialLoading = true
|
||||||
|
errorMessage = nil
|
||||||
|
hasMore = true
|
||||||
|
|
||||||
|
chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: nil, limit: pageSize) { [weak self] result in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let data):
|
||||||
|
self.hasMore = data.hasMore
|
||||||
|
self.messages = Self.merge(existing: [], newMessages: data.items)
|
||||||
|
self.didLoadInitially = true
|
||||||
|
case .failure(let error):
|
||||||
|
self.errorMessage = self.message(for: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isInitialLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refresh() {
|
||||||
|
didLoadInitially = false
|
||||||
|
loadInitialHistory(force: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMoreIfNeeded(for message: MessageItem) {
|
||||||
|
guard didLoadInitially, !isInitialLoading, hasMore, !isLoadingMore else { return }
|
||||||
|
guard let first = messages.first, first.id == message.id else { return }
|
||||||
|
|
||||||
|
isLoadingMore = true
|
||||||
|
|
||||||
|
chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: message.id, limit: pageSize) { [weak self] result in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let data):
|
||||||
|
self.hasMore = data.hasMore
|
||||||
|
if !data.items.isEmpty {
|
||||||
|
self.messages = Self.merge(existing: self.messages, newMessages: data.items)
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
self.errorMessage = self.message(for: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isLoadingMore = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func message(for error: Error) -> String {
|
||||||
|
if let chatError = error as? ChatServiceError {
|
||||||
|
return chatError.errorDescription ?? NSLocalizedString("Не удалось загрузить историю чата.", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let networkError = error as? NetworkError {
|
||||||
|
switch networkError {
|
||||||
|
case .unauthorized:
|
||||||
|
return NSLocalizedString("Сессия истекла. Войдите снова.", comment: "")
|
||||||
|
case .invalidURL, .noResponse:
|
||||||
|
return NSLocalizedString("Ошибка соединения с сервером.", comment: "")
|
||||||
|
case .network(let underlying):
|
||||||
|
return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), underlying.localizedDescription)
|
||||||
|
case .server(let statusCode, _):
|
||||||
|
return String(format: NSLocalizedString("Ошибка сервера (%@).", comment: ""), "\(statusCode)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NSLocalizedString("Неизвестная ошибка. Попробуйте позже.", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func merge(existing: [MessageItem], newMessages: [MessageItem]) -> [MessageItem] {
|
||||||
|
var combined: [MessageItem] = []
|
||||||
|
combined.reserveCapacity(existing.count + newMessages.count)
|
||||||
|
|
||||||
|
var seen: Set<String> = []
|
||||||
|
|
||||||
|
for message in newMessages {
|
||||||
|
if seen.insert(message.id).inserted {
|
||||||
|
combined.append(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in existing {
|
||||||
|
if seen.insert(message.id).inserted {
|
||||||
|
combined.append(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
combined.sort(by: compare)
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func compare(lhs: MessageItem, rhs: MessageItem) -> Bool {
|
||||||
|
if let lhsDate = lhs.createdAt, let rhsDate = rhs.createdAt, lhsDate != rhsDate {
|
||||||
|
return lhsDate < rhsDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if let lhsId = Int(lhs.messageId), let rhsId = Int(rhs.messageId), lhsId != rhsId {
|
||||||
|
return lhsId < rhsId
|
||||||
|
}
|
||||||
|
|
||||||
|
return lhs.messageId < rhs.messageId
|
||||||
|
}
|
||||||
|
}
|
||||||
211
yobble/Views/Chat/PrivateChatView.swift
Normal file
211
yobble/Views/Chat/PrivateChatView.swift
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PrivateChatView: View {
|
||||||
|
let chat: PrivateChatListItem
|
||||||
|
let currentUserId: String?
|
||||||
|
|
||||||
|
@StateObject private var viewModel: PrivateChatViewModel
|
||||||
|
@State private var hasPositionedToBottom: Bool = false
|
||||||
|
|
||||||
|
init(chat: PrivateChatListItem, currentUserId: String?) {
|
||||||
|
self.chat = chat
|
||||||
|
self.currentUserId = currentUserId
|
||||||
|
_viewModel = StateObject(wrappedValue: PrivateChatViewModel(chatId: chat.chatId))
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollViewReader { proxy in
|
||||||
|
content
|
||||||
|
.onChange(of: viewModel.messages.count) { _ in
|
||||||
|
guard !viewModel.isLoadingMore,
|
||||||
|
let lastId = viewModel.messages.last?.id else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
|
proxy.scrollTo(lastId, anchor: .bottom)
|
||||||
|
}
|
||||||
|
hasPositionedToBottom = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle(title)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.task {
|
||||||
|
viewModel.loadInitialHistory()
|
||||||
|
}
|
||||||
|
.onChange(of: viewModel.isInitialLoading) { isLoading in
|
||||||
|
if isLoading {
|
||||||
|
hasPositionedToBottom = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var content: some View {
|
||||||
|
if viewModel.isInitialLoading && viewModel.messages.isEmpty {
|
||||||
|
ProgressView(NSLocalizedString("Загрузка сообщений…", comment: ""))
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
} else if let error = viewModel.errorMessage, viewModel.messages.isEmpty {
|
||||||
|
errorView(message: error)
|
||||||
|
} else {
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(alignment: .leading, spacing: 12) {
|
||||||
|
if viewModel.isLoadingMore {
|
||||||
|
loadingMoreView
|
||||||
|
} else if viewModel.messages.isEmpty {
|
||||||
|
emptyState
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEach(viewModel.messages) { message in
|
||||||
|
messageRow(for: message)
|
||||||
|
.id(message.id)
|
||||||
|
.onAppear {
|
||||||
|
guard hasPositionedToBottom else { return }
|
||||||
|
viewModel.loadMoreIfNeeded(for: message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let message = viewModel.errorMessage,
|
||||||
|
!message.isEmpty,
|
||||||
|
!viewModel.messages.isEmpty {
|
||||||
|
errorBanner(message: message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
}
|
||||||
|
.refreshable {
|
||||||
|
viewModel.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var emptyState: some View {
|
||||||
|
Text(NSLocalizedString("В чате пока нет сообщений.", comment: ""))
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
.padding(.top, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var loadingMoreView: some View {
|
||||||
|
HStack {
|
||||||
|
ProgressView()
|
||||||
|
Text(NSLocalizedString("Загружаем ранние сообщения…", comment: ""))
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func errorView(message: String) -> some View {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
Text(message)
|
||||||
|
.font(.body)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Button(action: { viewModel.refresh() }) {
|
||||||
|
Text(NSLocalizedString("Повторить", comment: ""))
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func messageRow(for message: MessageItem) -> some View {
|
||||||
|
let isCurrentUser = currentUserId.map { $0 == message.senderId } ?? false
|
||||||
|
return HStack {
|
||||||
|
if isCurrentUser { Spacer(minLength: 32) }
|
||||||
|
|
||||||
|
VStack(alignment: isCurrentUser ? .trailing : .leading, spacing: 6) {
|
||||||
|
if !isCurrentUser {
|
||||||
|
Text(senderName(for: message))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(contentText(for: message))
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(isCurrentUser ? .white : .primary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: isCurrentUser ? .trailing : .leading)
|
||||||
|
|
||||||
|
Text(timestamp(for: message))
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(isCurrentUser ? Color.white.opacity(0.8) : .secondary)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.background(isCurrentUser ? Color.accentColor : Color(.secondarySystemBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
||||||
|
|
||||||
|
if !isCurrentUser { Spacer(minLength: 32) }
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func senderName(for message: MessageItem) -> String {
|
||||||
|
if let full = message.senderData?.fullName, !full.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
return full
|
||||||
|
}
|
||||||
|
if let custom = message.senderData?.customName, !custom.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
return custom
|
||||||
|
}
|
||||||
|
if let login = message.senderData?.login, !login.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
return "@\(login)"
|
||||||
|
}
|
||||||
|
return message.senderId
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timestamp(for message: MessageItem) -> String {
|
||||||
|
guard let date = message.createdAt else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return Self.timeFormatter.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func contentText(for message: MessageItem) -> String {
|
||||||
|
guard let content = message.content?.trimmingCharacters(in: .whitespacesAndNewlines), !content.isEmpty else {
|
||||||
|
return NSLocalizedString("(без текста)", comment: "")
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
private func errorBanner(message: String) -> some View {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
Text(message)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color(.secondarySystemBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.top, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let timeFormatter: DateFormatter = {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .none
|
||||||
|
formatter.timeStyle = .short
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var title: String {
|
||||||
|
if let full = chat.chatData?.fullName, !full.isEmpty {
|
||||||
|
return full
|
||||||
|
}
|
||||||
|
if let custom = chat.chatData?.customName, !custom.isEmpty {
|
||||||
|
return custom
|
||||||
|
}
|
||||||
|
if let login = chat.chatData?.login, !login.isEmpty {
|
||||||
|
return "@\(login)"
|
||||||
|
}
|
||||||
|
return NSLocalizedString("Чат", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Preview
|
||||||
|
|
||||||
|
// Previews intentionally omitted - MessageItem has custom decoding-only initializer.
|
||||||
@ -320,7 +320,7 @@ struct ChatsTab: View {
|
|||||||
}
|
}
|
||||||
.background(
|
.background(
|
||||||
NavigationLink(
|
NavigationLink(
|
||||||
destination: ChatPlaceholderView(chat: chat),
|
destination: PrivateChatView(chat: chat, currentUserId: currentUserId),
|
||||||
tag: chat.chatId,
|
tag: chat.chatId,
|
||||||
selection: $selectedChatId
|
selection: $selectedChatId
|
||||||
) {
|
) {
|
||||||
@ -965,66 +965,6 @@ struct ChatsTab_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ChatPlaceholderView: View {
|
|
||||||
let chat: PrivateChatListItem
|
|
||||||
|
|
||||||
private var companionId: String {
|
|
||||||
if let profileId = chat.chatData?.userId {
|
|
||||||
return profileId
|
|
||||||
}
|
|
||||||
if let senderId = chat.lastMessage?.senderId {
|
|
||||||
return senderId
|
|
||||||
}
|
|
||||||
return NSLocalizedString("Неизвестный", comment: "")
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 16) {
|
|
||||||
Text(NSLocalizedString("Экран чата в разработке", comment: ""))
|
|
||||||
.font(.headline)
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
HStack {
|
|
||||||
Text("Chat ID:")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
Text(chat.chatId)
|
|
||||||
.font(.body.monospaced())
|
|
||||||
}
|
|
||||||
|
|
||||||
if companionId != NSLocalizedString("Неизвестный", comment: "") {
|
|
||||||
HStack {
|
|
||||||
Text("Companion ID:")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
Text(companionId)
|
|
||||||
.font(.body.monospaced())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.navigationTitle(title)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var title: String {
|
|
||||||
if let full = chat.chatData?.fullName, !full.isEmpty {
|
|
||||||
return full
|
|
||||||
}
|
|
||||||
if let custom = chat.chatData?.customName, !custom.isEmpty {
|
|
||||||
return custom
|
|
||||||
}
|
|
||||||
if let login = chat.chatData?.login, !login.isEmpty {
|
|
||||||
return "@\(login)"
|
|
||||||
}
|
|
||||||
return NSLocalizedString("Чат", comment: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
static let debugRefreshChats = Notification.Name("debugRefreshChats")
|
static let debugRefreshChats = Notification.Name("debugRefreshChats")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user