карточки в ленте
This commit is contained in:
parent
d089276b39
commit
b0d5429ae6
@ -45,7 +45,16 @@ struct TopBarView: View {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
if isProfileTab {
|
||||
if isHomeTab{
|
||||
// Заглушка кнопки
|
||||
Button(action: {
|
||||
// пока ничего не делаем
|
||||
}) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.imageScale(.large)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
} else if isProfileTab {
|
||||
NavigationLink(destination: SettingsView(viewModel: viewModel)) {
|
||||
Image(systemName: "wrench")
|
||||
.imageScale(.large)
|
||||
|
||||
BIN
Shared/Media/placeholderPhoto.jpg
Normal file
BIN
Shared/Media/placeholderPhoto.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Shared/Media/placeholderPhotoSquare.png
Normal file
BIN
Shared/Media/placeholderPhotoSquare.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
BIN
Shared/Media/placeholderVideo.mp4
Normal file
BIN
Shared/Media/placeholderVideo.mp4
Normal file
Binary file not shown.
27
Shared/ViewModels/NewHomeTabViewModel.swift
Normal file
27
Shared/ViewModels/NewHomeTabViewModel.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ import SwiftUI
|
||||
struct MainView: View {
|
||||
@ObservedObject var viewModel: LoginViewModel
|
||||
@State private var selectedTab: Int = 0
|
||||
@StateObject private var newHomeTabViewModel = NewHomeTabViewModel()
|
||||
|
||||
// Состояния для TopBarView
|
||||
@State private var selectedAccount = "@user1"
|
||||
@ -52,7 +53,8 @@ struct MainView: View {
|
||||
ZStack {
|
||||
switch selectedTab {
|
||||
case 0:
|
||||
HomeTab(onScroll: handleScroll)
|
||||
// HomeTab(onScroll: handleScroll)
|
||||
NewHomeTab(viewModel: newHomeTabViewModel)
|
||||
case 1:
|
||||
SearchTab() // SearchTab не имеет скролла, поэтому onScroll не нужен
|
||||
case 2:
|
||||
|
||||
@ -1,8 +1,99 @@
|
||||
//
|
||||
// NewHomeTab.swift
|
||||
// yobble (iOS)
|
||||
//
|
||||
// Created by cheykrym on 13/08/2025.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,12 @@
|
||||
1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7940E62DF7B5E5002569DA /* SplashScreenView.swift */; };
|
||||
1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A7940F22DF7B7A3002569DA /* Localizable.strings */; };
|
||||
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 */; };
|
||||
1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB4F8F22E22EC9F002B6E40 /* FollowersView.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -127,6 +139,7 @@
|
||||
1A7940792DF77BC2002569DA /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1A9B01502E4BF49200887E0B /* Media */,
|
||||
1AB7F5132E32EBF1003756F3 /* Components */,
|
||||
1A7940FA2DF7B898002569DA /* Resources */,
|
||||
1A7940E52DF7B341002569DA /* Services */,
|
||||
@ -183,6 +196,7 @@
|
||||
1A79409E2DF77DBD002569DA /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */,
|
||||
1A2302102E3050C60067BF4F /* SearchViewModel.swift */,
|
||||
1A7940A92DF77E05002569DA /* LoginViewModel.swift */,
|
||||
);
|
||||
@ -224,6 +238,17 @@
|
||||
path = Resources;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -265,6 +290,7 @@
|
||||
children = (
|
||||
1A6FB9542E32D2B200E89EBE /* CustomTabBar.swift */,
|
||||
1A7940B52DF77F21002569DA /* MainView.swift */,
|
||||
1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */,
|
||||
1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */,
|
||||
1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */,
|
||||
1A7940C92DF7A99B002569DA /* ChatsTab.swift */,
|
||||
@ -370,8 +396,12 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1A9B01582E4BF50D00887E0B /* placeholderVideo.mp4 in Resources */,
|
||||
1A7940912DF77BC3002569DA /* Assets.xcassets in Resources */,
|
||||
1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */,
|
||||
1A9B01602E4BF9C000887E0B /* placeholderPhoto.jpg in Resources */,
|
||||
1A9B01662E4BFA3600887E0B /* Media.xcassets in Resources */,
|
||||
1A9B015F2E4BF9C000887E0B /* placeholderPhotoSquare.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -396,6 +426,7 @@
|
||||
1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
|
||||
1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */,
|
||||
1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */,
|
||||
1A9B016E2E4BFB9000887E0B /* NewHomeTabViewModel.swift in Sources */,
|
||||
1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */,
|
||||
1A7940B02DF77E26002569DA /* LoginView.swift in Sources */,
|
||||
1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */,
|
||||
@ -405,6 +436,7 @@
|
||||
1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */,
|
||||
1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */,
|
||||
1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
|
||||
1A9B014B2E4BF3CD00887E0B /* NewHomeTab.swift in Sources */,
|
||||
1A2302112E3050C60067BF4F /* SearchViewModel.swift in Sources */,
|
||||
1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */,
|
||||
1A6FB9552E32D2B200E89EBE /* CustomTabBar.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user