diff --git a/Shared/Components/TopBarView.swift b/Shared/Components/TopBarView.swift index c2c5f73..b46c540 100644 --- a/Shared/Components/TopBarView.swift +++ b/Shared/Components/TopBarView.swift @@ -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) diff --git a/Shared/Media/placeholderPhoto.jpg b/Shared/Media/placeholderPhoto.jpg new file mode 100644 index 0000000..3776798 Binary files /dev/null and b/Shared/Media/placeholderPhoto.jpg differ diff --git a/Shared/Media/placeholderPhotoSquare.png b/Shared/Media/placeholderPhotoSquare.png new file mode 100644 index 0000000..fde5769 Binary files /dev/null and b/Shared/Media/placeholderPhotoSquare.png differ diff --git a/Shared/Media/placeholderVideo.mp4 b/Shared/Media/placeholderVideo.mp4 new file mode 100644 index 0000000..0cb9c90 Binary files /dev/null and b/Shared/Media/placeholderVideo.mp4 differ diff --git a/Shared/ViewModels/NewHomeTabViewModel.swift b/Shared/ViewModels/NewHomeTabViewModel.swift new file mode 100644 index 0000000..3a55530 --- /dev/null +++ b/Shared/ViewModels/NewHomeTabViewModel.swift @@ -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 + } + } + } +} diff --git a/Shared/Views/Tab/MainView.swift b/Shared/Views/Tab/MainView.swift index 758d715..4e3f89c 100644 --- a/Shared/Views/Tab/MainView.swift +++ b/Shared/Views/Tab/MainView.swift @@ -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: diff --git a/Shared/Views/Tab/NewHomeTab.swift b/Shared/Views/Tab/NewHomeTab.swift index 243731b..62cc25a 100644 --- a/Shared/Views/Tab/NewHomeTab.swift +++ b/Shared/Views/Tab/NewHomeTab.swift @@ -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) + } +} diff --git a/yobble.xcodeproj/project.pbxproj b/yobble.xcodeproj/project.pbxproj index 18e29b2..4276ba9 100644 --- a/yobble.xcodeproj/project.pbxproj +++ b/yobble.xcodeproj/project.pbxproj @@ -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 = ""; }; 1A7940F72DF7B7EC002569DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 1A79410B2DF7C81D002569DA /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = ""; }; + 1A9B014A2E4BF3CD00887E0B /* NewHomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewHomeTab.swift; sourceTree = ""; }; + 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 = ""; }; + 1A9B016D2E4BFB9000887E0B /* NewHomeTabViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewHomeTabViewModel.swift; sourceTree = ""; }; 1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountShareSheet.swift; sourceTree = ""; }; 1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersView.swift; sourceTree = ""; }; 1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = ""; }; @@ -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 = ""; }; + 1A9B01502E4BF49200887E0B /* Media */ = { + isa = PBXGroup; + children = ( + 1A9B01652E4BFA3600887E0B /* Media.xcassets */, + 1A9B015E2E4BF9C000887E0B /* placeholderPhoto.jpg */, + 1A9B015D2E4BF9C000887E0B /* placeholderPhotoSquare.png */, + 1A9B01572E4BF50D00887E0B /* placeholderVideo.mp4 */, + ); + path = Media; + sourceTree = ""; + }; 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 */,