карточки в ленте

This commit is contained in:
cheykrym 2025-08-13 02:12:23 +03:00
parent d089276b39
commit b0d5429ae6
8 changed files with 170 additions and 9 deletions

View File

@ -45,7 +45,16 @@ struct TopBarView: View {
Spacer() Spacer()
} }
if isProfileTab { if isHomeTab{
// Заглушка кнопки
Button(action: {
// пока ничего не делаем
}) {
Image(systemName: "magnifyingglass")
.imageScale(.large)
.foregroundColor(.primary)
}
} else if isProfileTab {
NavigationLink(destination: SettingsView(viewModel: viewModel)) { NavigationLink(destination: SettingsView(viewModel: viewModel)) {
Image(systemName: "wrench") Image(systemName: "wrench")
.imageScale(.large) .imageScale(.large)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

View File

@ -0,0 +1,27 @@
import Foundation
import Combine
class NewHomeTabViewModel: ObservableObject {
@Published var posts: [Post] = []
@Published var isLoading = true
private var hasLoadedData = false
func fetchDataIfNeeded() {
// Загружаем данные только если они еще не были загружены
guard !hasLoadedData else { return }
isLoading = true
PostService.shared.fetchAllPosts { [weak self] fetchedPosts in
// Используем weak self чтобы избежать циклов сильных ссылок
guard let self = self else { return }
// Обновляем UI в основном потоке
DispatchQueue.main.async {
self.posts = fetchedPosts
self.isLoading = false
self.hasLoadedData = true
}
}
}
}

View File

@ -10,6 +10,7 @@ import SwiftUI
struct MainView: View { struct MainView: View {
@ObservedObject var viewModel: LoginViewModel @ObservedObject var viewModel: LoginViewModel
@State private var selectedTab: Int = 0 @State private var selectedTab: Int = 0
@StateObject private var newHomeTabViewModel = NewHomeTabViewModel()
// Состояния для TopBarView // Состояния для TopBarView
@State private var selectedAccount = "@user1" @State private var selectedAccount = "@user1"
@ -52,7 +53,8 @@ struct MainView: View {
ZStack { ZStack {
switch selectedTab { switch selectedTab {
case 0: case 0:
HomeTab(onScroll: handleScroll) // HomeTab(onScroll: handleScroll)
NewHomeTab(viewModel: newHomeTabViewModel)
case 1: case 1:
SearchTab() // SearchTab не имеет скролла, поэтому onScroll не нужен SearchTab() // SearchTab не имеет скролла, поэтому onScroll не нужен
case 2: case 2:

View File

@ -1,8 +1,99 @@
// import SwiftUI
// NewHomeTab.swift
// yobble (iOS)
//
// Created by cheykrym on 13/08/2025.
//
import Foundation struct NewHomeTab: View {
@ObservedObject var viewModel: NewHomeTabViewModel
private var column1Posts: [Post] {
viewModel.posts.enumerated().filter { $0.offset % 2 == 0 }.map { $0.element }
}
private var column2Posts: [Post] {
viewModel.posts.enumerated().filter { $0.offset % 2 != 0 }.map { $0.element }
}
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView("Загрузка ленты...")
} else {
ScrollView {
HStack(alignment: .top, spacing: 2) {
LazyVStack(spacing: 8) {
ForEach(column1Posts) { post in
PostGridItem(post: post)
}
}
LazyVStack(spacing: 8) {
ForEach(column2Posts) { post in
PostGridItem(post: post)
}
}
}
.padding(.horizontal, 2)
}
}
}
.onAppear {
viewModel.fetchDataIfNeeded()
}
}
}
struct PostGridItem: View {
let post: Post
private var randomHeight: CGFloat {
// Мы можем сделать высоту зависимой от типа контента, если нужно
// пока оставим случайной для визуального разнообразия
CGFloat.random(in: 150...300)
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// 1. Медиа контент
if let _ = post.media.first {
Image("placeholderPhoto") // Используем локальный плейсхолдер
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: randomHeight)
.clipped()
.cornerRadius(10)
}
// 2. Название поста
if let title = post.title, !title.isEmpty {
Text(title)
.font(.headline)
.lineLimit(2)
}
// 3. Информация об авторе и лайки
HStack {
// Аватар и имя пользователя
HStack(spacing: 4) {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(.gray)
Text(post.authorUsername)
.font(.subheadline)
.lineLimit(1)
}
Spacer()
// Лайки
HStack(spacing: 4) {
Image(systemName: post.isLikedByCurrentUser ? "heart.fill" : "heart")
.foregroundColor(post.isLikedByCurrentUser ? .red : .primary)
Text("\(post.likes)")
.font(.subheadline)
}
}
}
.padding(8)
.background(Color(UIColor.systemBackground)) // Для поддержки темной/светлой темы
.cornerRadius(12)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
}
}

View File

@ -29,6 +29,12 @@
1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */; }; 1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */; };
1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A7940F22DF7B7A3002569DA /* Localizable.strings */; }; 1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A7940F22DF7B7A3002569DA /* Localizable.strings */; };
1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79410B2DF7C81D002569DA /* RegistrationView.swift */; }; 1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79410B2DF7C81D002569DA /* RegistrationView.swift */; };
1A9B014B2E4BF3CD00887E0B /* NewHomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */; };
1A9B01582E4BF50D00887E0B /* placeholderVideo.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */; };
1A9B015F2E4BF9C000887E0B /* placeholderPhotoSquare.png in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */; };
1A9B01602E4BF9C000887E0B /* placeholderPhoto.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */; };
1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A9B01652E4BFA3600887E0B /* Media.xcassets */; };
1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */; };
1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */; }; 1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */; };
1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */; }; 1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */; };
1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */; }; 1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */; };
@ -76,6 +82,12 @@
1A7940F12DF7B7A3002569DA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; 1A7940F12DF7B7A3002569DA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; }; 1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; }; 1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewHomeTab.swift; sourceTree = "<group>"; };
1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; name = placeholderVideo.mp4; path = ../../../../Volumes/Untitled/xcode/volnahub/Shared/Media/placeholderVideo.mp4; sourceTree = DEVELOPER_DIR; };
1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = placeholderPhotoSquare.png; path = ../../../../Volumes/Untitled/xcode/volnahub/Shared/Media/placeholderPhotoSquare.png; sourceTree = DEVELOPER_DIR; };
1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = placeholderPhoto.jpg; path = ../../../../Volumes/Untitled/xcode/volnahub/Shared/Media/placeholderPhoto.jpg; sourceTree = DEVELOPER_DIR; };
1A9B01652E4BFA3600887E0B /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewHomeTabViewModel.swift; sourceTree = "<group>"; };
1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountShareSheet.swift; sourceTree = "<group>"; }; 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountShareSheet.swift; sourceTree = "<group>"; };
1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersView.swift; sourceTree = "<group>"; }; 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersView.swift; sourceTree = "<group>"; };
1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; }; 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = "<group>"; };
@ -127,6 +139,7 @@
1A7940792DF77BC2002569DA /* Shared */ = { 1A7940792DF77BC2002569DA /* Shared */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1A9B01502E4BF49200887E0B /* Media */,
1AB7F5132E32EBF1003756F3 /* Components */, 1AB7F5132E32EBF1003756F3 /* Components */,
1A7940FA2DF7B898002569DA /* Resources */, 1A7940FA2DF7B898002569DA /* Resources */,
1A7940E52DF7B341002569DA /* Services */, 1A7940E52DF7B341002569DA /* Services */,
@ -183,6 +196,7 @@
1A79409E2DF77DBD002569DA /* ViewModels */ = { 1A79409E2DF77DBD002569DA /* ViewModels */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */,
1A2302102E3050C60067BF4F /* SearchViewModel.swift */, 1A2302102E3050C60067BF4F /* SearchViewModel.swift */,
1A7940A92DF77E05002569DA /* LoginViewModel.swift */, 1A7940A92DF77E05002569DA /* LoginViewModel.swift */,
); );
@ -224,6 +238,17 @@
path = Resources; path = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
1A9B01502E4BF49200887E0B /* Media */ = {
isa = PBXGroup;
children = (
1A9B01652E4BFA3600887E0B /* Media.xcassets */,
1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */,
1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */,
1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */,
);
path = Media;
sourceTree = "<group>";
};
1AB7F5132E32EBF1003756F3 /* Components */ = { 1AB7F5132E32EBF1003756F3 /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -265,6 +290,7 @@
children = ( children = (
1A6FB9542E32D2B200E89EBE /* CustomTabBar.swift */, 1A6FB9542E32D2B200E89EBE /* CustomTabBar.swift */,
1A7940B52DF77F21002569DA /* MainView.swift */, 1A7940B52DF77F21002569DA /* MainView.swift */,
1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */,
1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */, 1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */,
1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */, 1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */,
1A7940C92DF7A99B002569DA /* ChatsTab.swift */, 1A7940C92DF7A99B002569DA /* ChatsTab.swift */,
@ -370,8 +396,12 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1A9B01582E4BF50D00887E0B /* placeholderVideo.mp4 in Resources */,
1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */, 1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */,
1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */, 1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */,
1A9B01602E4BF9C000887E0B /* placeholderPhoto.jpg in Resources */,
1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */,
1A9B015F2E4BF9C000887E0B /* placeholderPhotoSquare.png in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -396,6 +426,7 @@
1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */, 1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */, 1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */,
1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */, 1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */,
1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */,
1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */, 1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */,
1A7940B02DF77E26002569DA /* LoginView.swift in Sources */, 1A7940B02DF77E26002569DA /* LoginView.swift in Sources */,
1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */, 1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */,
@ -405,6 +436,7 @@
1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */, 1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */,
1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */, 1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */,
1A7940DE2DF7B0D7002569DA /* config.swift in Sources */, 1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
1A9B014B2E4BF3CD00887E0B /* NewHomeTab.swift in Sources */,
1A2302112E3050C60067BF4F /* SearchViewModel.swift in Sources */, 1A2302112E3050C60067BF4F /* SearchViewModel.swift in Sources */,
1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */, 1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */,
1A6FB9552E32D2B200E89EBE /* CustomTabBar.swift in Sources */, 1A6FB9552E32D2B200E89EBE /* CustomTabBar.swift in Sources */,