ios_app_v2/yobble/Views/Tab/ChatsTab.swift
2025-10-06 04:34:07 +03:00

239 lines
7.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ChatsTab.swift
// VolnahubApp
//
// Created by cheykrym on 09/06/2025.
//
import SwiftUI
struct ChatsTab: View {
@StateObject private var viewModel = PrivateChatsViewModel()
var body: some View {
content
.background(Color(UIColor.systemBackground))
.onAppear {
viewModel.loadInitialChats()
}
}
@ViewBuilder
private var content: some View {
if viewModel.isInitialLoading && viewModel.chats.isEmpty {
loadingState
} else if let message = viewModel.errorMessage, viewModel.chats.isEmpty {
errorState(message: message)
} else if viewModel.chats.isEmpty {
emptyState
} else {
chatList
}
}
private var chatList: some View {
List {
if let message = viewModel.errorMessage {
Section {
HStack(alignment: .top, spacing: 8) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
Text(message)
.font(.subheadline)
.foregroundColor(.orange)
Spacer(minLength: 0)
Button(action: { viewModel.refresh() }) {
Text(NSLocalizedString("Обновить", comment: ""))
.font(.subheadline)
}
}
.padding(.vertical, 4)
}
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
}
ForEach(viewModel.chats) { chat in
ChatRowView(chat: chat)
.contentShape(Rectangle())
.onAppear {
viewModel.loadMoreIfNeeded(currentItem: chat)
}
}
if viewModel.isLoadingMore {
loadingMoreRow
}
}
.listStyle(.plain)
}
private var loadingState: some View {
VStack(spacing: 12) {
ProgressView()
Text(NSLocalizedString("Загружаем чаты…", comment: ""))
.font(.subheadline)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private func errorState(message: String) -> some View {
VStack(spacing: 12) {
Image(systemName: "exclamationmark.bubble")
.font(.system(size: 48))
.foregroundColor(.orange)
Text(message)
.font(.body)
.multilineTextAlignment(.center)
.foregroundColor(.primary)
Button(action: { viewModel.loadInitialChats(force: true) }) {
Text(NSLocalizedString("Повторить", comment: ""))
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private var emptyState: some View {
VStack(spacing: 12) {
Image(systemName: "bubble.left")
.font(.system(size: 48))
.foregroundColor(.secondary)
Text(NSLocalizedString("Пока что у вас нет чатов", comment: ""))
.font(.body)
.foregroundColor(.secondary)
Button(action: { viewModel.refresh() }) {
Text(NSLocalizedString("Обновить", comment: ""))
}
.buttonStyle(.bordered)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private var loadingMoreRow: some View {
HStack {
Spacer()
ProgressView()
.padding(.vertical, 12)
Spacer()
}
.listRowSeparator(.hidden)
}
}
private struct ChatRowView: View {
let chat: PrivateChatListItem
private var title: String {
switch chat.chatType {
case .self:
return NSLocalizedString("Избранные сообщения", comment: "")
case .privateChat, .unknown:
if let custom = chat.chatData?.customName, !custom.isEmpty {
return custom
}
if let full = chat.chatData?.fullName, !full.isEmpty {
return full
}
if let login = chat.chatData?.login, !login.isEmpty {
return "@\(login)"
}
return NSLocalizedString("Неизвестный пользователь", comment: "")
}
}
private var subtitle: String {
guard let message = chat.lastMessage else {
return NSLocalizedString("Нет сообщений", comment: "")
}
if let content = message.content, !content.isEmpty {
return content
}
if message.mediaLink != nil {
return NSLocalizedString("Вложение", comment: "")
}
return NSLocalizedString("Сообщение", comment: "")
}
private var timestamp: String? {
let date = chat.lastMessage?.createdAt ?? chat.createdAt
guard let date else { return nil }
return ChatRowView.timeFormatter.string(from: date)
}
private var initial: String {
return String(title.prefix(1)).uppercased()
}
private var subtitleColor: Color {
chat.unreadCount > 0 ? .primary : .secondary
}
var body: some View {
HStack(spacing: 12) {
Circle()
.fill(Color.accentColor.opacity(0.15))
.frame(width: 44, height: 44)
.overlay(
Text(initial)
.font(.headline)
.foregroundColor(Color.accentColor)
)
VStack(alignment: .leading, spacing: 4) {
Text(title)
.fontWeight(chat.unreadCount > 0 ? .semibold : .regular)
.foregroundColor(.primary)
.lineLimit(1)
Text(subtitle)
.font(.subheadline)
.foregroundColor(subtitleColor)
.lineLimit(2)
}
Spacer()
VStack(alignment: .trailing, spacing: 6) {
if let timestamp {
Text(timestamp)
.font(.caption)
.foregroundColor(.secondary)
}
if chat.unreadCount > 0 {
Text("\(chat.unreadCount)")
.font(.caption2.bold())
.foregroundColor(.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(
Capsule().fill(Color.accentColor)
)
}
}
}
.padding(.vertical, 8)
}
private static let timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter
}()
}
struct ChatsTab_Previews: PreviewProvider {
static var previews: some View {
ChatsTab()
.environmentObject(ThemeManager())
}
}