ios_app/Shared/Views/Tab/NewHomeTab.swift
2025-09-19 01:52:24 +03:00

203 lines
9.7 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.

import SwiftUI
struct NewHomeTab: View {
@ObservedObject var viewModel: NewHomeTabViewModel
// Ширина колонки теперь вычисляется в ViewModel, но нам она нужна и здесь для PostGridItem
private let columnWidth = (UIScreen.main.bounds.width - 14) / 2
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView("Загрузка ленты...")
} else {
RefreshableScrollView(isRefreshing: $viewModel.isRefreshing, onRefresh: {
viewModel.refreshData()
}) {
HStack(alignment: .top, spacing: 6) {
LazyVStack(spacing: 6) {
ForEach(viewModel.column1Posts) { post in
PostGridItem(post: post, width: columnWidth)
}
}
LazyVStack(spacing: 6) {
ForEach(viewModel.column2Posts) { post in
PostGridItem(post: post, width: columnWidth)
}
}
}
.padding(.horizontal, 4)
}
}
}
.onAppear {
viewModel.fetchDataIfNeeded()
}
// .background(Color(.secondarySystemBackground)) // Фон для всей вкладки
}
}
struct PostGridItem: View {
let post: Post
let width: CGFloat // Ширина элемента
@State private var isNavigationActive = false // Состояние для программной навигации
// Формируем URL для загрузки изображения
private var imageURL: URL? {
// Используем picsum.photos для получения уникального изображения для каждого поста
// Используем реальные размеры для большей вариативности
if let media = post.media.first, let w = media.width, let h = media.height {
return URL(string: "https://picsum.photos/seed/\(post.id.uuidString)/\(w)/\(h)")
}
return URL(string: "https://picsum.photos/seed/\(post.id.uuidString)/400/400")
}
// Вычисляем высоту изображения на основе данных из модели
private var imageHeight: CGFloat {
guard let media = post.media.first,
let mediaWidth = media.width,
let mediaHeight = media.height,
mediaWidth > 0 else {
return width // Возвращаем 1:1, если данных нет
}
let aspectRatio = CGFloat(mediaHeight) / CGFloat(mediaWidth)
return width * aspectRatio
}
var body: some View {
ZStack {
// Невидимая ссылка для навигации, активируемая состоянием
NavigationLink(destination: PostDetailView(post: post), isActive: $isNavigationActive) {
EmptyView()
}
.opacity(0)
// Видимый контент карточки
VStack(alignment: .leading, spacing: 0) { // Убираем отступ между картинкой и текстом
// 1. Медиа контент
if let url = imageURL {
// Создаем контейнер с четкими границами, чтобы избежать перекрытия
Color.clear
.frame(width: width, height: imageHeight) // Используем вычисленную высоту
.background(
RemoteImageView(url: url)
.scaledToFill()
)
.clipped()
.overlay(
// Наложение для дополнительной информации
ZStack {
// Верхний ряд: дата и иконка видео
VStack {
HStack {
Text(post.createdAt, style: .relative)
.font(.caption2)
.foregroundColor(.white)
.padding(.horizontal, 6)
.padding(.vertical, 3)
.background(Color.black.opacity(0.5))
.clipShape(Capsule())
Spacer()
if post.isVideo {
Image(systemName: "play.circle.fill")
.font(.title3)
.foregroundColor(.white)
.shadow(radius: 2)
}
}
Spacer()
}
.padding(6)
// Нижний ряд: просмотры
VStack {
Spacer()
HStack {
HStack(spacing: 4) {
Image(systemName: "eye.fill")
Text("\(post.views)")
}
.font(.caption2)
.foregroundColor(.white)
.padding(.horizontal, 6)
.padding(.vertical, 3)
.background(Color.black.opacity(0.5))
.clipShape(Capsule())
Spacer()
}
}
.padding(6)
}
)
.contentShape(Rectangle()) // Указываем форму для жеста
.onTapGesture {
isNavigationActive = true // Активируем навигацию по тапу на картинку
}
}
// Контейнер для текста, который создает эффект "расширения"
VStack(alignment: .leading, spacing: 8) {
// 2. Название поста
if let title = post.title, !title.isEmpty {
Text(title)
.font(.subheadline)
.lineLimit(2)
.contentShape(Rectangle()) // И по тапу на заголовок
.onTapGesture {
isNavigationActive = true
}
}
// 3. Информация об авторе и лайки
HStack {
Button(action: {
print("account \(post.id)")
// пока ничего не делаем
}) {
HStack(spacing: 4) {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.gray)
Text(post.authorUsername)
.font(.footnote)
.lineLimit(1)
.foregroundColor(.primary)
}
}
// .buttonStyle больше не нужен, т.к. нет конфликта
Spacer()
Button(action: {
print("like \(post.id)")
// пока ничего не делаем
}) {
HStack(spacing: 4) {
Image(systemName: post.isLikedByCurrentUser ? "heart.fill" : "heart")
.foregroundColor(post.isLikedByCurrentUser ? .red : .primary)
Text("\(post.likes)")
.font(.subheadline)
.foregroundColor(.primary)
}
}
}
}
.padding(8)
.background(Color(UIColor.systemBackground)) // Фон только для текстовой части
}
.cornerRadius(6) // Закругляем всю карточку
.clipped() // Обрезаем дочерние вью по закругленной форме родителя
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
}
}
}