Compare commits
	
		
			7 Commits
		
	
	
		
			39f4c5bb7c
			...
			bb8c9a2b91
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bb8c9a2b91 | ||
| 
						 | 
					a477ce2f78 | ||
| 
						 | 
					cca9dd3ce3 | ||
| 
						 | 
					38de1cc204 | ||
| 
						 | 
					71b19f15ba | ||
| 
						 | 
					ff155b23e5 | ||
| 
						 | 
					2e821a0075 | 
							
								
								
									
										45
									
								
								Shared/Models/Post.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Shared/Models/Post.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PostIDWrapper: Identifiable {
 | 
				
			||||||
 | 
					    let id: UUID
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Post: Identifiable, Codable {
 | 
				
			||||||
 | 
					    let id: UUID                           // Уникальный идентификатор поста
 | 
				
			||||||
 | 
					    let title: String?                     // Название поста
 | 
				
			||||||
 | 
					    let description: String?               // Описание поста
 | 
				
			||||||
 | 
					    let media: [MediaItem]                 // Массив медиафайлов
 | 
				
			||||||
 | 
					    let mediaOrder: [UUID]?                // Порядок отображения медиа
 | 
				
			||||||
 | 
					    let thumbnailID: UUID?                 // Превью для видео
 | 
				
			||||||
 | 
					    let duration: Double?                  // Длительность видео/аудио
 | 
				
			||||||
 | 
					    let createdAt: Date                    // Дата создания
 | 
				
			||||||
 | 
					    let updatedAt: Date                    // Дата обновления
 | 
				
			||||||
 | 
					    let views: Int                         // Просмотры
 | 
				
			||||||
 | 
					    let likes: Int                         // Лайки
 | 
				
			||||||
 | 
					    let saves: Int                         // В сохранённом
 | 
				
			||||||
 | 
					    let commentsCount: Int                 // Кол-во комментариев
 | 
				
			||||||
 | 
					    let isLikedByCurrentUser: Bool         // Лайк текущим юзером
 | 
				
			||||||
 | 
					    let isSavedByCurrentUser: Bool         // Сохранено текущим юзером
 | 
				
			||||||
 | 
					    let authorID: String                   // Автор
 | 
				
			||||||
 | 
					    let authorUsername: String             // Имя пользователя автора
 | 
				
			||||||
 | 
					    let hashtags: [String]?                // Хэштеги
 | 
				
			||||||
 | 
					    let location: String?                  // Гео
 | 
				
			||||||
 | 
					    let languageCode: [String]?            // Язык
 | 
				
			||||||
 | 
					    let accessLevel: AccessLevel           // Доступ (публичный и т.п.)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum MediaType: String, Codable {
 | 
				
			||||||
 | 
					    case photo
 | 
				
			||||||
 | 
					    case video
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MediaItem: Identifiable, Codable {
 | 
				
			||||||
 | 
					    let id: UUID
 | 
				
			||||||
 | 
					    let type: MediaType
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum AccessLevel: String, Codable {
 | 
				
			||||||
 | 
					    case `public`
 | 
				
			||||||
 | 
					    case friends
 | 
				
			||||||
 | 
					    case archive
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										144
									
								
								Shared/Network/PostService.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								Shared/Network/PostService.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PostService {
 | 
				
			||||||
 | 
					    static let shared = PostService()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var posts: [Post] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init() {
 | 
				
			||||||
 | 
					        generateMockPosts()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func generateMockPosts() {
 | 
				
			||||||
 | 
					        guard posts.isEmpty else { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sampleTitles = [
 | 
				
			||||||
 | 
					            "Обзор TikTok UI",
 | 
				
			||||||
 | 
					            "Мой первый ролик",
 | 
				
			||||||
 | 
					            "Котик в кадре",
 | 
				
			||||||
 | 
					            "SwiftUI мастер-класс",
 | 
				
			||||||
 | 
					            "Анимации и переходы",
 | 
				
			||||||
 | 
					            "Съёмка с дрона",
 | 
				
			||||||
 | 
					            "Урок по дизайну",
 | 
				
			||||||
 | 
					            "Как сделать свайпы",
 | 
				
			||||||
 | 
					            "Лучший UI 2025",
 | 
				
			||||||
 | 
					            "Мой первый ролик",
 | 
				
			||||||
 | 
					            "Котик в кадре",
 | 
				
			||||||
 | 
					            "SwiftUI мастер-класс",
 | 
				
			||||||
 | 
					            "Анимации и переходы",
 | 
				
			||||||
 | 
					            "Съёмка с дрона",
 | 
				
			||||||
 | 
					            "Урок по дизайну",
 | 
				
			||||||
 | 
					            "Как сделать свайпы",
 | 
				
			||||||
 | 
					            "Лучший UI 2025",
 | 
				
			||||||
 | 
					            "Мой первый ролик",
 | 
				
			||||||
 | 
					            "Котик в кадре",
 | 
				
			||||||
 | 
					            "SwiftUI мастер-класс",
 | 
				
			||||||
 | 
					            "Анимации и переходы",
 | 
				
			||||||
 | 
					            "Съёмка с дрона",
 | 
				
			||||||
 | 
					            "Урок по дизайну",
 | 
				
			||||||
 | 
					            "Как сделать свайпы",
 | 
				
			||||||
 | 
					            "Лучший UI 2025",
 | 
				
			||||||
 | 
					            "Мой первый ролик",
 | 
				
			||||||
 | 
					            "Котик в кадре",
 | 
				
			||||||
 | 
					            "SwiftUI мастер-класс",
 | 
				
			||||||
 | 
					            "Анимации и переходы",
 | 
				
			||||||
 | 
					            "Съёмка с дрона",
 | 
				
			||||||
 | 
					            "Урок по дизайну",
 | 
				
			||||||
 | 
					            "Как сделать свайпы",
 | 
				
			||||||
 | 
					            "Лучший UI 2025",
 | 
				
			||||||
 | 
					            "Мой первый ролик",
 | 
				
			||||||
 | 
					            "Котик в кадре",
 | 
				
			||||||
 | 
					            "SwiftUI мастер-класс",
 | 
				
			||||||
 | 
					            "Анимации и переходы",
 | 
				
			||||||
 | 
					            "Съёмка с дрона",
 | 
				
			||||||
 | 
					            "Урок по дизайну",
 | 
				
			||||||
 | 
					            "Как сделать свайпы",
 | 
				
			||||||
 | 
					            "Лучший UI 2025",
 | 
				
			||||||
 | 
					            "Завершаем проект"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sampleDescriptions = [
 | 
				
			||||||
 | 
					            "Первый тестовый пост с видео",
 | 
				
			||||||
 | 
					            "Фейковый контент для ленты",
 | 
				
			||||||
 | 
					            "Видео с котиком 🐱",
 | 
				
			||||||
 | 
					            "Интерфейс в стиле TikTok",
 | 
				
			||||||
 | 
					            "Просто тестирую отображение",
 | 
				
			||||||
 | 
					            "Код и UI — любовь",
 | 
				
			||||||
 | 
					            "Видео в реальном времени",
 | 
				
			||||||
 | 
					            "Интересный UX пример",
 | 
				
			||||||
 | 
					            "Анимации, переходы, свайпы",
 | 
				
			||||||
 | 
					            "Фейковый контент для ленты",
 | 
				
			||||||
 | 
					            "Видео с котиком 🐱",
 | 
				
			||||||
 | 
					            "Интерфейс в стиле TikTok",
 | 
				
			||||||
 | 
					            "Просто тестирую отображение",
 | 
				
			||||||
 | 
					            "Код и UI — любовь",
 | 
				
			||||||
 | 
					            "Видео в реальном времени",
 | 
				
			||||||
 | 
					            "Интересный UX пример",
 | 
				
			||||||
 | 
					            "Анимации, переходы, свайпы",
 | 
				
			||||||
 | 
					            "Фейковый контент для ленты",
 | 
				
			||||||
 | 
					            "Видео с котиком 🐱",
 | 
				
			||||||
 | 
					            "Интерфейс в стиле TikTok",
 | 
				
			||||||
 | 
					            "Просто тестирую отображение",
 | 
				
			||||||
 | 
					            "Код и UI — любовь",
 | 
				
			||||||
 | 
					            "Видео в реальном времени",
 | 
				
			||||||
 | 
					            "Интересный UX пример",
 | 
				
			||||||
 | 
					            "Анимации, переходы, свайпы",
 | 
				
			||||||
 | 
					            "Фейковый контент для ленты",
 | 
				
			||||||
 | 
					            "Видео с котиком 🐱",
 | 
				
			||||||
 | 
					            "Интерфейс в стиле TikTok",
 | 
				
			||||||
 | 
					            "Просто тестирую отображение",
 | 
				
			||||||
 | 
					            "Код и UI — любовь",
 | 
				
			||||||
 | 
					            "Видео в реальном времени",
 | 
				
			||||||
 | 
					            "Интересный UX пример",
 | 
				
			||||||
 | 
					            "Анимации, переходы, свайпы",
 | 
				
			||||||
 | 
					            "Вот это да, пост работает!"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in 0..<32 {
 | 
				
			||||||
 | 
					            let mediaID = UUID()
 | 
				
			||||||
 | 
					            let thumbID = Bool.random() ? UUID() : nil
 | 
				
			||||||
 | 
					            let postID = UUID()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            let authorId = "user_\(Int.random(in: 1...5))"
 | 
				
			||||||
 | 
					            let post = Post(
 | 
				
			||||||
 | 
					                id: postID,
 | 
				
			||||||
 | 
					                title: sampleTitles[i],
 | 
				
			||||||
 | 
					                description: sampleDescriptions[i],
 | 
				
			||||||
 | 
					                media: [
 | 
				
			||||||
 | 
					                    MediaItem(id: mediaID, type: .video)
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                mediaOrder: [mediaID],
 | 
				
			||||||
 | 
					                thumbnailID: thumbID,
 | 
				
			||||||
 | 
					                duration: Double.random(in: 15.0...180.0),
 | 
				
			||||||
 | 
					                createdAt: Date(),
 | 
				
			||||||
 | 
					                updatedAt: Date(),
 | 
				
			||||||
 | 
					                views: Int.random(in: 1_000...10_000),
 | 
				
			||||||
 | 
					                likes: Int.random(in: 100...2_000),
 | 
				
			||||||
 | 
					                saves: Int.random(in: 10...500),
 | 
				
			||||||
 | 
					                commentsCount: Int.random(in: 0...100),
 | 
				
			||||||
 | 
					                isLikedByCurrentUser: Bool.random(),
 | 
				
			||||||
 | 
					                isSavedByCurrentUser: Bool.random(),
 | 
				
			||||||
 | 
					                authorID: authorId,
 | 
				
			||||||
 | 
					                authorUsername: "username_\(authorId.split(separator: "_").last ?? "")",
 | 
				
			||||||
 | 
					                hashtags: ["#тест", "#видео", "#swiftui", "#ui"].shuffled().prefix(2).map { $0 },
 | 
				
			||||||
 | 
					                location: Bool.random() ? "Москва" : nil,
 | 
				
			||||||
 | 
					                languageCode: Bool.random() ? ["ru", "en"] : ["ru"],
 | 
				
			||||||
 | 
					                accessLevel: [.public, .friends, .archive].randomElement()!
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            posts.append(post)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func fetchAllPosts(completion: @escaping ([Post]) -> Void) {
 | 
				
			||||||
 | 
					        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
 | 
				
			||||||
 | 
					            completion(self.posts)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func fetchPost(by id: UUID, completion: @escaping (Post?) -> Void) {
 | 
				
			||||||
 | 
					        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
 | 
				
			||||||
 | 
					            let result = self.posts.first { $0.id == id }
 | 
				
			||||||
 | 
					            completion(result)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								Shared/Views/Tab/HomeTab.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Shared/Views/Tab/HomeTab.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct HomeTab: View {
 | 
				
			||||||
 | 
					    @State private var posts: [Post] = []
 | 
				
			||||||
 | 
					    @State private var isLoading = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        NavigationView {
 | 
				
			||||||
 | 
					            VStack {
 | 
				
			||||||
 | 
					                if isLoading {
 | 
				
			||||||
 | 
					                    ProgressView("Загрузка ленты...")
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    ScrollView {
 | 
				
			||||||
 | 
					                        LazyVStack(spacing: 24) {
 | 
				
			||||||
 | 
					                            ForEach(posts) { post in
 | 
				
			||||||
 | 
					                                PostDetailView(post: post)
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .navigationTitle("Лента")
 | 
				
			||||||
 | 
					            .onAppear {
 | 
				
			||||||
 | 
					                if posts.isEmpty {
 | 
				
			||||||
 | 
					                    PostService.shared.fetchAllPosts { fetchedPosts in
 | 
				
			||||||
 | 
					                        self.posts = fetchedPosts
 | 
				
			||||||
 | 
					                        self.isLoading = false
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								Shared/Views/Tab/Profile/AccountShareSheet.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Shared/Views/Tab/Profile/AccountShareSheet.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					// AccountSwitchSheet.swift
 | 
				
			||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct AccountShareSheet: View {
 | 
				
			||||||
 | 
					    @Binding var isPresented: Bool
 | 
				
			||||||
 | 
					    @Binding var selectedAccount: String
 | 
				
			||||||
 | 
					    let accounts: [String]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        VStack(spacing: 24) {
 | 
				
			||||||
 | 
					            Text("Типо qr code")
 | 
				
			||||||
 | 
					                .font(.title2)
 | 
				
			||||||
 | 
					                .bold()
 | 
				
			||||||
 | 
					                .padding(.top, 32)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Spacer()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Button("Закрыть") {
 | 
				
			||||||
 | 
					                isPresented = false
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .padding()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								Shared/Views/Tab/Profile/FollowersView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Shared/Views/Tab/Profile/FollowersView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct FollowersView: View {
 | 
				
			||||||
 | 
					    let followers: [String]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        List(followers, id: \.self) { user in
 | 
				
			||||||
 | 
					            Text(user)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .navigationTitle("Подписчики")
 | 
				
			||||||
 | 
					        .navigationBarTitleDisplayMode(.inline)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								Shared/Views/Tab/Profile/FollowingView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Shared/Views/Tab/Profile/FollowingView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct FollowingView: View {
 | 
				
			||||||
 | 
					    let following: [String]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        List(following, id: \.self) { user in
 | 
				
			||||||
 | 
					            Text(user)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .navigationTitle("Подписки")
 | 
				
			||||||
 | 
					        .navigationBarTitleDisplayMode(.inline)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										85
									
								
								Shared/Views/Tab/Profile/PostDetailView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								Shared/Views/Tab/Profile/PostDetailView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PostDetailView: View {
 | 
				
			||||||
 | 
					    let post: Post
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        VStack(alignment: .leading, spacing: 0) {
 | 
				
			||||||
 | 
					            // Шапка поста
 | 
				
			||||||
 | 
					            HStack {
 | 
				
			||||||
 | 
					                Image(systemName: "person.crop.circle.fill")
 | 
				
			||||||
 | 
					                    .resizable()
 | 
				
			||||||
 | 
					                    .frame(width: 36, height: 36)
 | 
				
			||||||
 | 
					                    .foregroundColor(.gray)
 | 
				
			||||||
 | 
					                Text(post.authorUsername)
 | 
				
			||||||
 | 
					                    .font(.headline)
 | 
				
			||||||
 | 
					                Spacer()
 | 
				
			||||||
 | 
					                Button(action: {}) {
 | 
				
			||||||
 | 
					                    Image(systemName: "ellipsis")
 | 
				
			||||||
 | 
					                        .foregroundColor(.primary)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.horizontal)
 | 
				
			||||||
 | 
					            .padding(.vertical, 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Изображение поста
 | 
				
			||||||
 | 
					            Rectangle()
 | 
				
			||||||
 | 
					                .fill(Color.gray.opacity(0.3))
 | 
				
			||||||
 | 
					                .aspectRatio(1, contentMode: .fit)
 | 
				
			||||||
 | 
					                .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Панель действий
 | 
				
			||||||
 | 
					            HStack(spacing: 16) {
 | 
				
			||||||
 | 
					                Button(action: {}) {
 | 
				
			||||||
 | 
					                    Image(systemName: "heart")
 | 
				
			||||||
 | 
					                        .font(.system(size: 24))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Button(action: {}) {
 | 
				
			||||||
 | 
					                    Image(systemName: "message")
 | 
				
			||||||
 | 
					                        .font(.system(size: 24))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Button(action: {}) {
 | 
				
			||||||
 | 
					                    Image(systemName: "paperplane")
 | 
				
			||||||
 | 
					                        .font(.system(size: 24))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Spacer()
 | 
				
			||||||
 | 
					                Button(action: {}) {
 | 
				
			||||||
 | 
					                    Image(systemName: "square.and.arrow.down")
 | 
				
			||||||
 | 
					                        .font(.system(size: 24))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Button(action: {}) {
 | 
				
			||||||
 | 
					                    Image(systemName: "bookmark")
 | 
				
			||||||
 | 
					                        .font(.system(size: 24))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .foregroundColor(.primary)
 | 
				
			||||||
 | 
					            .padding(.horizontal)
 | 
				
			||||||
 | 
					            .padding(.top, 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Лайки и описание
 | 
				
			||||||
 | 
					            VStack(alignment: .leading, spacing: 4) {
 | 
				
			||||||
 | 
					                Text("\(post.likes) likes")
 | 
				
			||||||
 | 
					                    .font(.headline)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Text(post.description ?? "")
 | 
				
			||||||
 | 
					                    .font(.subheadline)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.horizontal)
 | 
				
			||||||
 | 
					            .padding(.top, 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Комментарии
 | 
				
			||||||
 | 
					            VStack(alignment: .leading, spacing: 4) {
 | 
				
			||||||
 | 
					                Text("View all \(post.commentsCount) comments")
 | 
				
			||||||
 | 
					                    .font(.caption)
 | 
				
			||||||
 | 
					                    .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					                    .padding(.top, 4)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Text("2 hours ago")
 | 
				
			||||||
 | 
					                    .font(.caption2)
 | 
				
			||||||
 | 
					                    .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					                    .padding(.top, 4)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.horizontal)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								Shared/Views/Tab/Profile/PostFeedView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Shared/Views/Tab/Profile/PostFeedView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PostFeedView: View {
 | 
				
			||||||
 | 
					    let posts: [Post]
 | 
				
			||||||
 | 
					    let selectedPostID: UUID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        ScrollViewReader { proxy in
 | 
				
			||||||
 | 
					            ScrollView {
 | 
				
			||||||
 | 
					                LazyVStack(spacing: 24) {
 | 
				
			||||||
 | 
					                    ForEach(posts) { post in
 | 
				
			||||||
 | 
					                        PostDetailView(post: post)
 | 
				
			||||||
 | 
					                            .id(post.id)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .navigationBarTitle("Feed", displayMode: .inline)
 | 
				
			||||||
 | 
					            .onAppear {
 | 
				
			||||||
 | 
					                proxy.scrollTo(selectedPostID, anchor: .center)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										112
									
								
								Shared/Views/Tab/Profile/ProfileContentGrid.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Shared/Views/Tab/Profile/ProfileContentGrid.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ProfileContentGrid: View {
 | 
				
			||||||
 | 
					    let isContentLoaded: Bool
 | 
				
			||||||
 | 
					    let selectedTabIndex: Int
 | 
				
			||||||
 | 
					    let searchQuery: String
 | 
				
			||||||
 | 
					    let selectedCategory: String
 | 
				
			||||||
 | 
					    let allPosts: [Post]
 | 
				
			||||||
 | 
					    let isLoading: Bool
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        VStack(alignment: .leading, spacing: 0) {
 | 
				
			||||||
 | 
					            if isLoading {
 | 
				
			||||||
 | 
					                VStack {
 | 
				
			||||||
 | 
					                    ProgressView("Загрузка...")
 | 
				
			||||||
 | 
					                        .padding()
 | 
				
			||||||
 | 
					                    Spacer()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .frame(maxWidth: .infinity, minHeight: 300) // 👈 гарантированная высота
 | 
				
			||||||
 | 
					            } else if filteredPosts.isEmpty {
 | 
				
			||||||
 | 
					                VStack {
 | 
				
			||||||
 | 
					                Text("Нет постов")
 | 
				
			||||||
 | 
					                    .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					                    .padding()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .frame(maxWidth: .infinity, minHeight: 300)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 3), spacing: 2) {
 | 
				
			||||||
 | 
					                    ForEach(filteredPosts) { post in
 | 
				
			||||||
 | 
					                        NavigationLink(destination: PostFeedView(posts: filteredPosts, selectedPostID: post.id)) {
 | 
				
			||||||
 | 
					                            Rectangle()
 | 
				
			||||||
 | 
					                                .fill(Color.gray.opacity(0.2))
 | 
				
			||||||
 | 
					                                .aspectRatio(1, contentMode: .fit)
 | 
				
			||||||
 | 
					                                .overlay(
 | 
				
			||||||
 | 
					                                    ZStack {
 | 
				
			||||||
 | 
					                                        VStack {
 | 
				
			||||||
 | 
					                                            HStack {
 | 
				
			||||||
 | 
					                                                Text("3 года назад") // Можно заменить на `post.createdAt`
 | 
				
			||||||
 | 
					                                                    .font(.caption2)
 | 
				
			||||||
 | 
					                                                    .foregroundColor(.white)
 | 
				
			||||||
 | 
					                                                    .padding(4)
 | 
				
			||||||
 | 
					                                                    .background(Color.black.opacity(0.6))
 | 
				
			||||||
 | 
					                                                    .cornerRadius(4)
 | 
				
			||||||
 | 
					                                                Spacer()
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                            Spacer()
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                        .padding(4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        VStack {
 | 
				
			||||||
 | 
					                                            Spacer()
 | 
				
			||||||
 | 
					                                            HStack {
 | 
				
			||||||
 | 
					                                                HStack(spacing: 4) {
 | 
				
			||||||
 | 
					                                                    Image(systemName: "play.fill")
 | 
				
			||||||
 | 
					                                                        .font(.caption2)
 | 
				
			||||||
 | 
					                                                    Text("\(post.views)")
 | 
				
			||||||
 | 
					                                                        .font(.caption2)
 | 
				
			||||||
 | 
					                                                }
 | 
				
			||||||
 | 
					                                                .foregroundColor(.white)
 | 
				
			||||||
 | 
					                                                .padding(4)
 | 
				
			||||||
 | 
					                                                .background(Color.black.opacity(0.6))
 | 
				
			||||||
 | 
					                                                .cornerRadius(4)
 | 
				
			||||||
 | 
					                                                Spacer()
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                            .padding(4)
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        .buttonStyle(PlainButtonStyle())
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .padding(.horizontal)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .frame(maxWidth: .infinity, alignment: .topLeading)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var filteredPosts: [Post] {
 | 
				
			||||||
 | 
					        var result: [Post]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch selectedTabIndex {
 | 
				
			||||||
 | 
					        case 1:
 | 
				
			||||||
 | 
					            result = allPosts.filter { $0.accessLevel == .archive }
 | 
				
			||||||
 | 
					        case 2:
 | 
				
			||||||
 | 
					            result = allPosts.filter { $0.isSavedByCurrentUser }
 | 
				
			||||||
 | 
					        case 3:
 | 
				
			||||||
 | 
					            result = allPosts.filter { $0.isLikedByCurrentUser }
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            result = allPosts
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 🔍 Поиск по названию или описанию
 | 
				
			||||||
 | 
					        if !searchQuery.isEmpty {
 | 
				
			||||||
 | 
					            result = result.filter {
 | 
				
			||||||
 | 
					                ($0.title?.localizedCaseInsensitiveContains(searchQuery) ?? false) ||
 | 
				
			||||||
 | 
					                ($0.description?.localizedCaseInsensitiveContains(searchQuery) ?? false)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 🏷️ Фильтрация по категории (если не "#все")
 | 
				
			||||||
 | 
					        if selectedCategory != "#все" {
 | 
				
			||||||
 | 
					            result = result.filter {
 | 
				
			||||||
 | 
					                $0.hashtags?.contains(where: { $0 == selectedCategory }) ?? false
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										148
									
								
								Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,148 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ProfileContentTabbedGrid: View {
 | 
				
			||||||
 | 
					    let isContentLoaded: Bool
 | 
				
			||||||
 | 
					    @State private var selectedTabIndex = 0
 | 
				
			||||||
 | 
					    @State private var selectedCategory = "#все"
 | 
				
			||||||
 | 
					    @State private var searchQuery = ""
 | 
				
			||||||
 | 
					    @State private var selectedSort = "По дате"
 | 
				
			||||||
 | 
					    @State private var allPosts: [Post] = []
 | 
				
			||||||
 | 
					    @State private var isLoading = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        VStack(spacing: 0) {
 | 
				
			||||||
 | 
					            // Вкладки профиля
 | 
				
			||||||
 | 
					            HStack(spacing: 32) {
 | 
				
			||||||
 | 
					                menuTab(index: 0)
 | 
				
			||||||
 | 
					                tabButton(index: 1, systemIcon: "lock")
 | 
				
			||||||
 | 
					                tabButton(index: 2, systemIcon: "bookmark")
 | 
				
			||||||
 | 
					                tabButton(index: 3, systemIcon: "heart")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.vertical, 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Фильтры
 | 
				
			||||||
 | 
					            HStack {
 | 
				
			||||||
 | 
					                if selectedTabIndex == 0 {
 | 
				
			||||||
 | 
					                    Menu {
 | 
				
			||||||
 | 
					                        Button("#все") { selectedCategory = "#все" }
 | 
				
			||||||
 | 
					                        Button("#влог") { selectedCategory = "#влог" }
 | 
				
			||||||
 | 
					                        Button("#игры") { selectedCategory = "#игры" }
 | 
				
			||||||
 | 
					                    } label: {
 | 
				
			||||||
 | 
					                        Label(selectedCategory, systemImage: "tag")
 | 
				
			||||||
 | 
					                            .font(.subheadline)
 | 
				
			||||||
 | 
					                            .padding(8)
 | 
				
			||||||
 | 
					                            .background(Color.gray.opacity(0.2))
 | 
				
			||||||
 | 
					                            .cornerRadius(8)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    TextField("Поиск", text: $searchQuery)
 | 
				
			||||||
 | 
					                        .padding(.horizontal, 10)
 | 
				
			||||||
 | 
					                        .padding(.vertical, 6)
 | 
				
			||||||
 | 
					                        .background(Color.gray.opacity(0.15))
 | 
				
			||||||
 | 
					                        .cornerRadius(8)
 | 
				
			||||||
 | 
					                        .font(.subheadline)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Button {
 | 
				
			||||||
 | 
					                        // Создать пост
 | 
				
			||||||
 | 
					                    } label: {
 | 
				
			||||||
 | 
					                        Label("Создать", systemImage: "plus")
 | 
				
			||||||
 | 
					                            .font(.subheadline)
 | 
				
			||||||
 | 
					                            .padding(8)
 | 
				
			||||||
 | 
					                            .background(Color.blue.opacity(0.2))
 | 
				
			||||||
 | 
					                            .cornerRadius(8)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    TextField("Поиск", text: $searchQuery)
 | 
				
			||||||
 | 
					                        .padding(.horizontal, 10)
 | 
				
			||||||
 | 
					                        .padding(.vertical, 6)
 | 
				
			||||||
 | 
					                        .background(Color.gray.opacity(0.15))
 | 
				
			||||||
 | 
					                        .cornerRadius(8)
 | 
				
			||||||
 | 
					                        .font(.subheadline)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.horizontal)
 | 
				
			||||||
 | 
					            .padding(.vertical, 12)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Контент с табами
 | 
				
			||||||
 | 
					            ProfileContentGrid(
 | 
				
			||||||
 | 
					                isContentLoaded: isContentLoaded,
 | 
				
			||||||
 | 
					                selectedTabIndex: selectedTabIndex,
 | 
				
			||||||
 | 
					                searchQuery: searchQuery,
 | 
				
			||||||
 | 
					                selectedCategory: selectedCategory,
 | 
				
			||||||
 | 
					                allPosts: allPosts,
 | 
				
			||||||
 | 
					                isLoading: isLoading
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .onAppear {
 | 
				
			||||||
 | 
					            if allPosts.isEmpty {
 | 
				
			||||||
 | 
					                isLoading = true
 | 
				
			||||||
 | 
					                PostService.shared.fetchAllPosts { result in
 | 
				
			||||||
 | 
					                    self.allPosts = result
 | 
				
			||||||
 | 
					                    self.isLoading = false
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // MARK: - Вкладка с меню
 | 
				
			||||||
 | 
					    @ViewBuilder
 | 
				
			||||||
 | 
					    func menuTab(index: Int) -> some View {
 | 
				
			||||||
 | 
					        if selectedTabIndex == index {
 | 
				
			||||||
 | 
					            Menu {
 | 
				
			||||||
 | 
					                Button("По дате") { selectedSort = "По дате" }
 | 
				
			||||||
 | 
					                Button("По популярности") { selectedSort = "По популярности" }
 | 
				
			||||||
 | 
					            } label: {
 | 
				
			||||||
 | 
					                VStack(spacing: 4) {
 | 
				
			||||||
 | 
					                    HStack(spacing: 4) {
 | 
				
			||||||
 | 
					                        Image(systemName: "rectangle.grid.3x2")
 | 
				
			||||||
 | 
					                            .font(.system(size: 18, weight: .medium))
 | 
				
			||||||
 | 
					                            .foregroundColor(.primary)
 | 
				
			||||||
 | 
					                        Image(systemName: "chevron.down")
 | 
				
			||||||
 | 
					                            .font(.system(size: 10))
 | 
				
			||||||
 | 
					                            .foregroundColor(.gray)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    Rectangle()
 | 
				
			||||||
 | 
					                        .frame(height: 2)
 | 
				
			||||||
 | 
					                        .foregroundColor(.primary)
 | 
				
			||||||
 | 
					                        .padding(.horizontal)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Button {
 | 
				
			||||||
 | 
					                selectedTabIndex = index
 | 
				
			||||||
 | 
					            } label: {
 | 
				
			||||||
 | 
					                VStack(spacing: 4) {
 | 
				
			||||||
 | 
					                    HStack(spacing: 4) {
 | 
				
			||||||
 | 
					                        Image(systemName: "rectangle.grid.3x2")
 | 
				
			||||||
 | 
					                            .font(.system(size: 18, weight: .medium))
 | 
				
			||||||
 | 
					                            .foregroundColor(.gray)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    Rectangle()
 | 
				
			||||||
 | 
					                        .frame(height: 2)
 | 
				
			||||||
 | 
					                        .foregroundColor(.clear)
 | 
				
			||||||
 | 
					                        .padding(.horizontal)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // MARK: - Остальные вкладки
 | 
				
			||||||
 | 
					    @ViewBuilder
 | 
				
			||||||
 | 
					    func tabButton(index: Int, systemIcon: String) -> some View {
 | 
				
			||||||
 | 
					        VStack(spacing: 4) {
 | 
				
			||||||
 | 
					            Image(systemName: systemIcon)
 | 
				
			||||||
 | 
					                .font(.system(size: 18, weight: .medium))
 | 
				
			||||||
 | 
					                .foregroundColor(selectedTabIndex == index ? .primary : .gray)
 | 
				
			||||||
 | 
					            Rectangle()
 | 
				
			||||||
 | 
					                .frame(height: 2)
 | 
				
			||||||
 | 
					                .foregroundColor(selectedTabIndex == index ? .primary : .clear)
 | 
				
			||||||
 | 
					                .padding(.horizontal)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					        .onTapGesture {
 | 
				
			||||||
 | 
					            selectedTabIndex = index
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										147
									
								
								Shared/Views/Tab/Profile/ProfileTab.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								Shared/Views/Tab/Profile/ProfileTab.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ProfileTab: View {
 | 
				
			||||||
 | 
					    @ObservedObject var viewModel: LoginViewModel
 | 
				
			||||||
 | 
					//    @State private var selectedTabIndex = 0
 | 
				
			||||||
 | 
					//    @State private var selectedSort = "По дате"
 | 
				
			||||||
 | 
					//    @State private var selectedCategory = "#все"
 | 
				
			||||||
 | 
					//    @State private var searchQuery = ""
 | 
				
			||||||
 | 
					//    @State private var searchQueryArchive = ""
 | 
				
			||||||
 | 
					//    @State private var searchQuerySaved = ""
 | 
				
			||||||
 | 
					//    @State private var searchQueryLiked = ""
 | 
				
			||||||
 | 
					    @State private var isContentLoaded = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @State private var accounts = ["@user1", "@user2", "@user3"]
 | 
				
			||||||
 | 
					    @State private var selectedAccount = "@user1"
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let followers = ["@alice", "@bob", "@charlie"]
 | 
				
			||||||
 | 
					    let following = ["@dev", "@design", "@ios"]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @State private var sheetType: SheetType? = nil
 | 
				
			||||||
 | 
					    enum SheetType: Identifiable {
 | 
				
			||||||
 | 
					        case accountShare
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var id: Int {
 | 
				
			||||||
 | 
					            switch self {
 | 
				
			||||||
 | 
					            case .accountShare: return 1
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        NavigationView {
 | 
				
			||||||
 | 
					            if !isContentLoaded{
 | 
				
			||||||
 | 
					                SplashScreenView()
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					            GeometryReader { geometry in
 | 
				
			||||||
 | 
					                VStack(spacing: 0) {
 | 
				
			||||||
 | 
					                    ScrollView {
 | 
				
			||||||
 | 
					                        // ───── Шапка профиля (занимает ~50% экрана) ─────
 | 
				
			||||||
 | 
					                        header
 | 
				
			||||||
 | 
					                            .frame(minHeight: geometry.size.height * 0.5)
 | 
				
			||||||
 | 
					                            .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					                            .background(Color(.systemBackground))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // ───── Скролл с контентом ─────
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                        ProfileContentTabbedGrid(isContentLoaded: isContentLoaded)
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .frame(maxWidth: .infinity, maxHeight: .infinity)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .navigationBarTitleDisplayMode(.inline)
 | 
				
			||||||
 | 
					            .toolbar {
 | 
				
			||||||
 | 
					                ToolbarItem(placement: .principal) {
 | 
				
			||||||
 | 
					                    Button(action: {
 | 
				
			||||||
 | 
					                        sheetType = .accountShare
 | 
				
			||||||
 | 
					                    }) {
 | 
				
			||||||
 | 
					                        HStack(spacing: 4) {
 | 
				
			||||||
 | 
					                            Text("custom_user_name")
 | 
				
			||||||
 | 
					                                .font(.headline)
 | 
				
			||||||
 | 
					                                .foregroundColor(.primary)
 | 
				
			||||||
 | 
					                            Image(systemName: "chevron.down")
 | 
				
			||||||
 | 
					                                .font(.subheadline)
 | 
				
			||||||
 | 
					                                .foregroundColor(.gray)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ToolbarItem(placement: .navigationBarTrailing) {
 | 
				
			||||||
 | 
					                    NavigationLink(destination: SettingsView(viewModel: viewModel)) {
 | 
				
			||||||
 | 
					                        Image(systemName: "wrench")
 | 
				
			||||||
 | 
					                            .imageScale(.large)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .sheet(item: $sheetType) { type in
 | 
				
			||||||
 | 
					                switch type {
 | 
				
			||||||
 | 
					                case .accountShare:
 | 
				
			||||||
 | 
					                    AccountShareSheet(
 | 
				
			||||||
 | 
					                        isPresented: Binding(
 | 
				
			||||||
 | 
					                            get: { sheetType != nil },
 | 
				
			||||||
 | 
					                            set: { if !$0 { sheetType = nil } }
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        selectedAccount: $selectedAccount,
 | 
				
			||||||
 | 
					                        accounts: accounts
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .navigationViewStyle(StackNavigationViewStyle())
 | 
				
			||||||
 | 
					//        .onAppear {
 | 
				
			||||||
 | 
					//            if !isContentLoaded {
 | 
				
			||||||
 | 
					//                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
 | 
				
			||||||
 | 
					//                    isContentLoaded = true
 | 
				
			||||||
 | 
					//                }
 | 
				
			||||||
 | 
					//            }
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // MARK: - Шапка профиля
 | 
				
			||||||
 | 
					    private var header: some View {
 | 
				
			||||||
 | 
					        VStack(spacing: 12) {
 | 
				
			||||||
 | 
					            // Аватар и имя
 | 
				
			||||||
 | 
					            VStack(spacing: 6) {
 | 
				
			||||||
 | 
					                Image(systemName: "person.crop.circle.fill")
 | 
				
			||||||
 | 
					                    .resizable()
 | 
				
			||||||
 | 
					                    .frame(width: 72, height: 72)
 | 
				
			||||||
 | 
					                    .foregroundColor(.gray)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Text(selectedAccount)
 | 
				
			||||||
 | 
					                    .font(.headline)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.top, 16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Text("iOS разработчик, делаю интерфейсы, иногда снимаю блоги и делюсь кодом. Всегда рад новым подписчикам и интересным проектам.")
 | 
				
			||||||
 | 
					                .font(.subheadline)
 | 
				
			||||||
 | 
					                .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					                .multilineTextAlignment(.center)
 | 
				
			||||||
 | 
					                .padding(.horizontal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Статистика
 | 
				
			||||||
 | 
					            HStack(spacing: 32) {
 | 
				
			||||||
 | 
					                statView("24", "Посты")
 | 
				
			||||||
 | 
					                NavigationLink(destination: FollowersView(followers: followers)) {
 | 
				
			||||||
 | 
					                    statView("1.2k", "Подписчики")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NavigationLink(destination: FollowingView(following: following)) {
 | 
				
			||||||
 | 
					                    statView("156", "Подписки")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .padding(.horizontal)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // MARK: - Статистика
 | 
				
			||||||
 | 
					    func statView(_ value: String, _ label: String) -> some View {
 | 
				
			||||||
 | 
					        VStack {
 | 
				
			||||||
 | 
					            Text(value)
 | 
				
			||||||
 | 
					                .font(.headline)
 | 
				
			||||||
 | 
					            Text(label)
 | 
				
			||||||
 | 
					                .font(.caption)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct AppPreferencesView: View {
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        Text("Настройки приложения")
 | 
				
			||||||
 | 
					            .navigationTitle("Приложение")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								Shared/Views/Tab/Profile/Settings/EditProfileView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Shared/Views/Tab/Profile/Settings/EditProfileView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct EditProfileView: View {
 | 
				
			||||||
 | 
					    @State private var displayName = ""
 | 
				
			||||||
 | 
					    @State private var description = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        Form {
 | 
				
			||||||
 | 
					            Section(header: Text("Публичная информация")) {
 | 
				
			||||||
 | 
					                TextField("Отображаемое имя", text: $displayName)
 | 
				
			||||||
 | 
					                TextField("Описание", text: $description)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Button(action: {
 | 
				
			||||||
 | 
					                // Действие для сохранения профиля
 | 
				
			||||||
 | 
					                print("DisplayName: \(displayName)")
 | 
				
			||||||
 | 
					                print("Description: \(description)")
 | 
				
			||||||
 | 
					            }) {
 | 
				
			||||||
 | 
					                Text("Применить")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .navigationTitle("Редактировать профиль")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SecuritySettingsView: View {
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        Text("Настройки безопасности")
 | 
				
			||||||
 | 
					            .navigationTitle("Безопасность")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								Shared/Views/Tab/Profile/Settings/SettingsView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								Shared/Views/Tab/Profile/Settings/SettingsView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SettingsView: View {
 | 
				
			||||||
 | 
					    @ObservedObject var viewModel: LoginViewModel
 | 
				
			||||||
 | 
					    @AppStorage("isDarkMode") private var isDarkMode: Bool = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        Form {
 | 
				
			||||||
 | 
					            // MARK: - Профиль
 | 
				
			||||||
 | 
					            Section(header: Text("Профиль")) {
 | 
				
			||||||
 | 
					                NavigationLink(destination: EditProfileView()) {
 | 
				
			||||||
 | 
					                    Label("Мой профиль", systemImage: "person.crop.circle")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // MARK: - Безопасность
 | 
				
			||||||
 | 
					            Section(header: Text("Безопасность")) {
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Сменить пароль")) {
 | 
				
			||||||
 | 
					                    Label("Сменить пароль", systemImage: "key")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Двухфакторная аутентификация")) {
 | 
				
			||||||
 | 
					                    Label("Двухфакторная аутентификация", systemImage: "lock.shield")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Активные сессии")) {
 | 
				
			||||||
 | 
					                    Label("Активные сессии", systemImage: "iphone")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // MARK: - Приложение
 | 
				
			||||||
 | 
					            Section(header: Text("Приложение")) {
 | 
				
			||||||
 | 
					                Button(action: openLanguageSettings) {
 | 
				
			||||||
 | 
					                    Label("Язык", systemImage: "globe")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Toggle(isOn: $isDarkMode) {
 | 
				
			||||||
 | 
					                    Label("Тёмная тема", systemImage: "moon.fill")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Хранилище данных")) {
 | 
				
			||||||
 | 
					                    Label("Данные", systemImage: "externaldrive")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Другие настройки")) {
 | 
				
			||||||
 | 
					                    Label("Другое", systemImage: "ellipsis.circle")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // MARK: - Уведомления
 | 
				
			||||||
 | 
					            Section(header: Text("Уведомления")) {
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Push-уведомления")) {
 | 
				
			||||||
 | 
					                    Label("Push-уведомления", systemImage: "bell")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // MARK: - Поддержка
 | 
				
			||||||
 | 
					            Section(header: Text("Поддержка")) {
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Частые вопросы")) {
 | 
				
			||||||
 | 
					                    Label("Частые вопросы", systemImage: "questionmark.circle")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NavigationLink(destination: Text("Заглушка: Обратная связь")) {
 | 
				
			||||||
 | 
					                    Label("Обратная связь", systemImage: "paperplane")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // MARK: - О приложении
 | 
				
			||||||
 | 
					            Section(header: Text("О приложении")) {
 | 
				
			||||||
 | 
					                VStack(alignment: .leading, spacing: 6) {
 | 
				
			||||||
 | 
					                    Text(AppInfo.text_1)
 | 
				
			||||||
 | 
					                    Text(AppInfo.text_2)
 | 
				
			||||||
 | 
					                    Text(AppInfo.text_3)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .font(.footnote)
 | 
				
			||||||
 | 
					                .foregroundColor(.gray)
 | 
				
			||||||
 | 
					                .padding(.vertical, 4)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // MARK: - Выход
 | 
				
			||||||
 | 
					            Section {
 | 
				
			||||||
 | 
					                Button(action: {
 | 
				
			||||||
 | 
					                    viewModel.logoutCurrentUser()
 | 
				
			||||||
 | 
					                }) {
 | 
				
			||||||
 | 
					                    HStack {
 | 
				
			||||||
 | 
					                        Image(systemName: "arrow.backward.square")
 | 
				
			||||||
 | 
					                        Text("Выйти из аккаунта")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    .foregroundColor(.red)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .navigationTitle("Настройки")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    private func openLanguageSettings() {
 | 
				
			||||||
 | 
					        guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
 | 
				
			||||||
 | 
					        UIApplication.shared.open(url)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,16 +0,0 @@
 | 
				
			|||||||
import SwiftUI
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct HomeTab: View {
 | 
					 | 
				
			||||||
    var body: some View {
 | 
					 | 
				
			||||||
        NavigationView {
 | 
					 | 
				
			||||||
            VStack {
 | 
					 | 
				
			||||||
                Text("Домой")
 | 
					 | 
				
			||||||
                    .font(.largeTitle)
 | 
					 | 
				
			||||||
                    .bold()
 | 
					 | 
				
			||||||
                    .padding()
 | 
					 | 
				
			||||||
                Spacer()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            .navigationTitle("Домой")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,297 +0,0 @@
 | 
				
			|||||||
import SwiftUI
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct ProfileTab: View {
 | 
					 | 
				
			||||||
    @ObservedObject var viewModel: LoginViewModel
 | 
					 | 
				
			||||||
    @State private var showingAccountSwitch = false
 | 
					 | 
				
			||||||
    @State private var selectedTabIndex = 0
 | 
					 | 
				
			||||||
    @State private var selectedSort = "По дате"
 | 
					 | 
				
			||||||
    @State private var selectedCategory = "#все"
 | 
					 | 
				
			||||||
    @State private var searchQuery = ""
 | 
					 | 
				
			||||||
    @State private var searchQueryArchive = ""
 | 
					 | 
				
			||||||
    @State private var searchQuerySaved = ""
 | 
					 | 
				
			||||||
    @State private var searchQueryLiked = ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var body: some View {
 | 
					 | 
				
			||||||
        NavigationView {
 | 
					 | 
				
			||||||
            GeometryReader { geometry in
 | 
					 | 
				
			||||||
                VStack(spacing: 0) {
 | 
					 | 
				
			||||||
                    ScrollView {
 | 
					 | 
				
			||||||
                        VStack(spacing: 12) {
 | 
					 | 
				
			||||||
                            // Аватар и имя
 | 
					 | 
				
			||||||
                            VStack(spacing: 6) {
 | 
					 | 
				
			||||||
                                Image(systemName: "person.crop.circle.fill")
 | 
					 | 
				
			||||||
                                    .resizable()
 | 
					 | 
				
			||||||
                                    .frame(width: 72, height: 72)
 | 
					 | 
				
			||||||
                                    .foregroundColor(.gray)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                Text("@username")
 | 
					 | 
				
			||||||
                                    .font(.headline)
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            .padding(.top, 16)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            // Статистика
 | 
					 | 
				
			||||||
                            HStack(spacing: 32) {
 | 
					 | 
				
			||||||
                                statView("24", "Посты")
 | 
					 | 
				
			||||||
                                statView("1.2k", "Подписчики")
 | 
					 | 
				
			||||||
                                statView("156", "Подписки")
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            // Вкладки профиля
 | 
					 | 
				
			||||||
                            HStack(spacing: 32) {
 | 
					 | 
				
			||||||
                                menuTab(index: 0)
 | 
					 | 
				
			||||||
                                tabButton(index: 1, systemIcon: "lock")
 | 
					 | 
				
			||||||
                                tabButton(index: 2, systemIcon: "bookmark")
 | 
					 | 
				
			||||||
                                tabButton(index: 3, systemIcon: "heart")
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            .padding(.vertical, 12)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        .padding(.horizontal)
 | 
					 | 
				
			||||||
                        .frame(width: geometry.size.width)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        if selectedTabIndex == 0 {
 | 
					 | 
				
			||||||
                            HStack {
 | 
					 | 
				
			||||||
                                Menu {
 | 
					 | 
				
			||||||
                                    // Здесь список категорий
 | 
					 | 
				
			||||||
                                    Button("#все") { selectedCategory = "#все" }
 | 
					 | 
				
			||||||
                                    Button("#влог") { selectedCategory = "#влог" }
 | 
					 | 
				
			||||||
                                    Button("#игры") { selectedCategory = "#игры" }
 | 
					 | 
				
			||||||
                                    Button("#путешествия") { selectedCategory = "#путешествия" }
 | 
					 | 
				
			||||||
                                } label: {
 | 
					 | 
				
			||||||
                                    Label(selectedCategory, systemImage: "tag")
 | 
					 | 
				
			||||||
                                        .font(.subheadline)
 | 
					 | 
				
			||||||
                                        .padding(8)
 | 
					 | 
				
			||||||
                                        .background(Color.gray.opacity(0.2))
 | 
					 | 
				
			||||||
                                        .cornerRadius(8)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                // Поиск
 | 
					 | 
				
			||||||
                                TextField("Поиск", text: $searchQuery)
 | 
					 | 
				
			||||||
                                    .padding(.horizontal, 10)
 | 
					 | 
				
			||||||
                                    .padding(.vertical, 6)
 | 
					 | 
				
			||||||
                                    .background(Color.gray.opacity(0.15))
 | 
					 | 
				
			||||||
                                    .cornerRadius(8)
 | 
					 | 
				
			||||||
                                    .font(.subheadline)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                Button(action: {
 | 
					 | 
				
			||||||
                                    // Создание поста
 | 
					 | 
				
			||||||
                                }) {
 | 
					 | 
				
			||||||
                                    Label("Создать", systemImage: "plus")
 | 
					 | 
				
			||||||
                                        .font(.subheadline)
 | 
					 | 
				
			||||||
                                        .padding(8)
 | 
					 | 
				
			||||||
                                        .background(Color.blue.opacity(0.2))
 | 
					 | 
				
			||||||
                                        .cornerRadius(8)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            .padding(.horizontal)
 | 
					 | 
				
			||||||
                        } else if selectedTabIndex == 1 {
 | 
					 | 
				
			||||||
                            HStack {
 | 
					 | 
				
			||||||
                            // Поиск
 | 
					 | 
				
			||||||
                            TextField("Поиск", text: $searchQueryArchive)
 | 
					 | 
				
			||||||
                                .padding(.horizontal, 10)
 | 
					 | 
				
			||||||
                                .padding(.vertical, 6)
 | 
					 | 
				
			||||||
                                .background(Color.gray.opacity(0.15))
 | 
					 | 
				
			||||||
                                .cornerRadius(8)
 | 
					 | 
				
			||||||
                                .font(.subheadline)
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            .padding(.horizontal)
 | 
					 | 
				
			||||||
                        } else if selectedTabIndex == 2 {
 | 
					 | 
				
			||||||
                            HStack {
 | 
					 | 
				
			||||||
                            // Поиск
 | 
					 | 
				
			||||||
                            TextField("Поиск", text: $searchQuerySaved)
 | 
					 | 
				
			||||||
                                .padding(.horizontal, 10)
 | 
					 | 
				
			||||||
                                .padding(.vertical, 6)
 | 
					 | 
				
			||||||
                                .background(Color.gray.opacity(0.15))
 | 
					 | 
				
			||||||
                                .cornerRadius(8)
 | 
					 | 
				
			||||||
                                .font(.subheadline)
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            .padding(.horizontal)
 | 
					 | 
				
			||||||
                        } else if selectedTabIndex == 3 {
 | 
					 | 
				
			||||||
                            HStack {
 | 
					 | 
				
			||||||
                                // Поиск
 | 
					 | 
				
			||||||
                                TextField("Поиск", text: $searchQueryLiked)
 | 
					 | 
				
			||||||
                                    .padding(.horizontal, 10)
 | 
					 | 
				
			||||||
                                    .padding(.vertical, 6)
 | 
					 | 
				
			||||||
                                    .background(Color.gray.opacity(0.15))
 | 
					 | 
				
			||||||
                                    .cornerRadius(8)
 | 
					 | 
				
			||||||
                                    .font(.subheadline)
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            .padding(.horizontal)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        
 | 
					 | 
				
			||||||
                        // Контентная часть
 | 
					 | 
				
			||||||
                        TabView(selection: $selectedTabIndex) {
 | 
					 | 
				
			||||||
                            ForEach(0..<4) { index in
 | 
					 | 
				
			||||||
                                LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 3), spacing: 2) {
 | 
					 | 
				
			||||||
                                    ForEach(0..<36) { _ in
 | 
					 | 
				
			||||||
                                        Rectangle()
 | 
					 | 
				
			||||||
                                            .fill(contentColor(for: index))
 | 
					 | 
				
			||||||
                                            .aspectRatio(1, contentMode: .fit)
 | 
					 | 
				
			||||||
                                            .overlay(
 | 
					 | 
				
			||||||
                                                ZStack {
 | 
					 | 
				
			||||||
                                                    // Верхний левый угол — дата
 | 
					 | 
				
			||||||
                                                    VStack {
 | 
					 | 
				
			||||||
                                                        HStack {
 | 
					 | 
				
			||||||
                                                            Text("3 года назад")
 | 
					 | 
				
			||||||
                                                                .font(.caption2)
 | 
					 | 
				
			||||||
                                                                .foregroundColor(.white)
 | 
					 | 
				
			||||||
                                                                .padding(4)
 | 
					 | 
				
			||||||
                                                                .background(Color.black.opacity(0.6))
 | 
					 | 
				
			||||||
                                                                .cornerRadius(4)
 | 
					 | 
				
			||||||
                                                            Spacer()
 | 
					 | 
				
			||||||
                                                        }
 | 
					 | 
				
			||||||
                                                        Spacer()
 | 
					 | 
				
			||||||
                                                    }
 | 
					 | 
				
			||||||
                                                    .padding(4)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                                    // Нижний левый угол — просмотры
 | 
					 | 
				
			||||||
                                                    VStack {
 | 
					 | 
				
			||||||
                                                        Spacer()
 | 
					 | 
				
			||||||
                                                        HStack {
 | 
					 | 
				
			||||||
                                                            HStack(spacing: 4) {
 | 
					 | 
				
			||||||
                                                                Image(systemName: "play.fill")
 | 
					 | 
				
			||||||
                                                                    .font(.caption2)
 | 
					 | 
				
			||||||
                                                                Text("1.2k")
 | 
					 | 
				
			||||||
                                                                    .font(.caption2)
 | 
					 | 
				
			||||||
                                                            }
 | 
					 | 
				
			||||||
                                                            .foregroundColor(.white)
 | 
					 | 
				
			||||||
                                                            .padding(4)
 | 
					 | 
				
			||||||
                                                            .background(Color.black.opacity(0.6))
 | 
					 | 
				
			||||||
                                                            .cornerRadius(4)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                                            Spacer()
 | 
					 | 
				
			||||||
                                                        }
 | 
					 | 
				
			||||||
                                                        .padding(4)
 | 
					 | 
				
			||||||
                                                    }
 | 
					 | 
				
			||||||
                                                }
 | 
					 | 
				
			||||||
                                            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
 | 
					 | 
				
			||||||
//                        .frame(height: geometry.size.width * 1.2) // Динамическая высота
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            .navigationBarTitleDisplayMode(.inline)
 | 
					 | 
				
			||||||
            .toolbar {
 | 
					 | 
				
			||||||
                ToolbarItem(placement: .principal) {
 | 
					 | 
				
			||||||
                    Button(action: { showingAccountSwitch.toggle() }) {
 | 
					 | 
				
			||||||
                        HStack(spacing: 4) {
 | 
					 | 
				
			||||||
                            Text("custom_user_name")
 | 
					 | 
				
			||||||
                                .font(.headline)
 | 
					 | 
				
			||||||
                                .foregroundColor(.primary)
 | 
					 | 
				
			||||||
                            Image(systemName: "chevron.down")
 | 
					 | 
				
			||||||
                                .font(.subheadline)
 | 
					 | 
				
			||||||
                                .foregroundColor(.gray)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Левый верх — ручка (редактирование)
 | 
					 | 
				
			||||||
                ToolbarItem(placement: .navigationBarLeading) {
 | 
					 | 
				
			||||||
                    Button(action: {
 | 
					 | 
				
			||||||
                        // действие редактирования профиля
 | 
					 | 
				
			||||||
                    }) {
 | 
					 | 
				
			||||||
                        Image(systemName: "paintbrush")
 | 
					 | 
				
			||||||
                            .imageScale(.large)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                ToolbarItem(placement: .navigationBarTrailing) {
 | 
					 | 
				
			||||||
                    Button(action: {
 | 
					 | 
				
			||||||
                        // перейти к настройкам
 | 
					 | 
				
			||||||
                    }) {
 | 
					 | 
				
			||||||
                        Image(systemName: "wrench")
 | 
					 | 
				
			||||||
                            .imageScale(.large)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            .sheet(isPresented: $showingAccountSwitch) {
 | 
					 | 
				
			||||||
                VStack {
 | 
					 | 
				
			||||||
                    Text("Выбор аккаунта")
 | 
					 | 
				
			||||||
                        .font(.title)
 | 
					 | 
				
			||||||
                        .padding()
 | 
					 | 
				
			||||||
                    Button("Закрыть") {
 | 
					 | 
				
			||||||
                        showingAccountSwitch = false
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    .padding()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // MARK: - Статистика
 | 
					 | 
				
			||||||
    func statView(_ value: String, _ label: String) -> some View {
 | 
					 | 
				
			||||||
        VStack {
 | 
					 | 
				
			||||||
            Text(value)
 | 
					 | 
				
			||||||
                .font(.headline)
 | 
					 | 
				
			||||||
            Text(label)
 | 
					 | 
				
			||||||
                .font(.caption)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // MARK: - Вкладка с меню
 | 
					 | 
				
			||||||
    @ViewBuilder
 | 
					 | 
				
			||||||
    func menuTab(index: Int) -> some View {
 | 
					 | 
				
			||||||
        Menu {
 | 
					 | 
				
			||||||
            if selectedTabIndex == index {
 | 
					 | 
				
			||||||
                Button("По дате") { selectedSort = "По дате" }
 | 
					 | 
				
			||||||
                Button("По популярности") { selectedSort = "По популярности" }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } label: {
 | 
					 | 
				
			||||||
            VStack(spacing: 4) {
 | 
					 | 
				
			||||||
                HStack(spacing: 4) {
 | 
					 | 
				
			||||||
                    Image(systemName: "rectangle.grid.3x2")
 | 
					 | 
				
			||||||
                        .font(.system(size: 18, weight: .medium))
 | 
					 | 
				
			||||||
                        .foregroundColor(.primary)
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    // Показываем стрелку вниз только если вкладка активна
 | 
					 | 
				
			||||||
                    if selectedTabIndex == index {
 | 
					 | 
				
			||||||
                        Image(systemName: "chevron.down")
 | 
					 | 
				
			||||||
                            .font(.system(size: 10))
 | 
					 | 
				
			||||||
                            .foregroundColor(.gray)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Rectangle()
 | 
					 | 
				
			||||||
                    .frame(height: 2)
 | 
					 | 
				
			||||||
                    .foregroundColor(selectedTabIndex == index ? .primary : .clear)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            .frame(maxWidth: .infinity)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .onTapGesture {
 | 
					 | 
				
			||||||
            selectedTabIndex = index
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // MARK: - Остальные вкладки
 | 
					 | 
				
			||||||
    @ViewBuilder
 | 
					 | 
				
			||||||
    func tabButton(index: Int, systemIcon: String) -> some View {
 | 
					 | 
				
			||||||
        VStack(spacing: 4) {
 | 
					 | 
				
			||||||
            Image(systemName: systemIcon)
 | 
					 | 
				
			||||||
                .font(.system(size: 18, weight: .medium))
 | 
					 | 
				
			||||||
                .foregroundColor(selectedTabIndex == index ? .primary : .gray)
 | 
					 | 
				
			||||||
            Rectangle()
 | 
					 | 
				
			||||||
                .frame(height: 2)
 | 
					 | 
				
			||||||
                .foregroundColor(selectedTabIndex == index ? .primary : .clear)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .frame(maxWidth: .infinity)
 | 
					 | 
				
			||||||
        .onTapGesture {
 | 
					 | 
				
			||||||
            selectedTabIndex = index
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // MARK: - Цвет контента
 | 
					 | 
				
			||||||
    func contentColor(for tab: Int) -> Color {
 | 
					 | 
				
			||||||
        switch tab {
 | 
					 | 
				
			||||||
        case 0: return Color.gray.opacity(0.3)
 | 
					 | 
				
			||||||
        case 1: return Color.blue.opacity(0.3)
 | 
					 | 
				
			||||||
        case 2: return Color.green.opacity(0.3)
 | 
					 | 
				
			||||||
        case 3: return Color.red.opacity(0.3)
 | 
					 | 
				
			||||||
        default: return Color.gray.opacity(0.3)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -10,3 +10,9 @@ struct AppConfig {
 | 
				
			|||||||
    static let APP_BUILD = "freestore"
 | 
					    static let APP_BUILD = "freestore"
 | 
				
			||||||
    static let APP_VERSION = "0.1"
 | 
					    static let APP_VERSION = "0.1"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct AppInfo {
 | 
				
			||||||
 | 
					    static let text_1 = "\(NSLocalizedString("profile_down_text_1", comment: "")) yobble"
 | 
				
			||||||
 | 
					    static let text_2 = "\(NSLocalizedString("profile_down_text_2", comment: "")) 0.1test"
 | 
				
			||||||
 | 
					    static let text_3 = "\(NSLocalizedString("profile_down_text_3", comment: ""))2025"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,19 @@
 | 
				
			|||||||
		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 */; };
 | 
				
			||||||
 | 
							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 */; };
 | 
				
			||||||
 | 
							1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE60F72E22F3DC00B37AC5 /* SettingsView.swift */; };
 | 
				
			||||||
 | 
							1ACE61012E22F55C00B37AC5 /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61002E22F55C00B37AC5 /* EditProfileView.swift */; };
 | 
				
			||||||
 | 
							1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61042E22F56800B37AC5 /* SecuritySettingsView.swift */; };
 | 
				
			||||||
 | 
							1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61082E22F57100B37AC5 /* AppPreferencesView.swift */; };
 | 
				
			||||||
 | 
							1ACE61152E22FE2000B37AC5 /* PostDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61142E22FE2000B37AC5 /* PostDetailView.swift */; };
 | 
				
			||||||
 | 
							1ACE61192E22FF1400B37AC5 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61182E22FF1400B37AC5 /* Post.swift */; };
 | 
				
			||||||
 | 
							1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ACE61202E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift */; };
 | 
				
			||||||
 | 
							1AD757CD2E27608C0069C1FD /* PostFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD757CC2E27608C0069C1FD /* PostFeedView.swift */; };
 | 
				
			||||||
 | 
							1AE587052E23264800254F06 /* PostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE587042E23264800254F06 /* PostService.swift */; };
 | 
				
			||||||
 | 
							1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE587242E23337000254F06 /* ProfileContentGrid.swift */; };
 | 
				
			||||||
		1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */; };
 | 
							1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */; };
 | 
				
			||||||
		1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */; };
 | 
							1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */; };
 | 
				
			||||||
		1AEE5EB32E21A85800A3DCA3 /* SearchTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */; };
 | 
							1AEE5EB32E21A85800A3DCA3 /* SearchTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */; };
 | 
				
			||||||
@ -57,6 +70,19 @@
 | 
				
			|||||||
		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>"; };
 | 
				
			||||||
 | 
							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>"; };
 | 
				
			||||||
 | 
							1ACE60F72E22F3DC00B37AC5 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1ACE61002E22F55C00B37AC5 /* EditProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileView.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1ACE61042E22F56800B37AC5 /* SecuritySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecuritySettingsView.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1ACE61082E22F57100B37AC5 /* AppPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreferencesView.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1ACE61142E22FE2000B37AC5 /* PostDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostDetailView.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1ACE61182E22FF1400B37AC5 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1ACE61202E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileContentTabbedGrid.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1AD757CC2E27608C0069C1FD /* PostFeedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostFeedView.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1AE587042E23264800254F06 /* PostService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostService.swift; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							1AE587242E23337000254F06 /* ProfileContentGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileContentGrid.swift; sourceTree = "<group>"; };
 | 
				
			||||||
		1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTab.swift; sourceTree = "<group>"; };
 | 
							1AEE5EAA2E21A83200A3DCA3 /* HomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTab.swift; sourceTree = "<group>"; };
 | 
				
			||||||
		1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicsTab.swift; sourceTree = "<group>"; };
 | 
							1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicsTab.swift; sourceTree = "<group>"; };
 | 
				
			||||||
		1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTab.swift; sourceTree = "<group>"; };
 | 
							1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTab.swift; sourceTree = "<group>"; };
 | 
				
			||||||
@ -135,7 +161,7 @@
 | 
				
			|||||||
		1A79409D2DF77DB5002569DA /* Views */ = {
 | 
							1A79409D2DF77DB5002569DA /* Views */ = {
 | 
				
			||||||
			isa = PBXGroup;
 | 
								isa = PBXGroup;
 | 
				
			||||||
			children = (
 | 
								children = (
 | 
				
			||||||
				1AEE5EA92E21A4FC00A3DCA3 /* tab */,
 | 
									1AEE5EA92E21A4FC00A3DCA3 /* Tab */,
 | 
				
			||||||
				1AEE5EA62E2194CD00A3DCA3 /* contacts */,
 | 
									1AEE5EA62E2194CD00A3DCA3 /* contacts */,
 | 
				
			||||||
				1AEE5EA52E21947B00A3DCA3 /* login */,
 | 
									1AEE5EA52E21947B00A3DCA3 /* login */,
 | 
				
			||||||
				1A79407B2DF77BC2002569DA /* ContentView.swift */,
 | 
									1A79407B2DF77BC2002569DA /* ContentView.swift */,
 | 
				
			||||||
@ -156,6 +182,7 @@
 | 
				
			|||||||
		1A79409F2DF77DC4002569DA /* Models */ = {
 | 
							1A79409F2DF77DC4002569DA /* Models */ = {
 | 
				
			||||||
			isa = PBXGroup;
 | 
								isa = PBXGroup;
 | 
				
			||||||
			children = (
 | 
								children = (
 | 
				
			||||||
 | 
									1ACE61182E22FF1400B37AC5 /* Post.swift */,
 | 
				
			||||||
				1A7940A52DF77DF5002569DA /* User.swift */,
 | 
									1A7940A52DF77DF5002569DA /* User.swift */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			path = Models;
 | 
								path = Models;
 | 
				
			||||||
@ -166,6 +193,7 @@
 | 
				
			|||||||
			children = (
 | 
								children = (
 | 
				
			||||||
				1A7940A12DF77DE9002569DA /* AuthService.swift */,
 | 
									1A7940A12DF77DE9002569DA /* AuthService.swift */,
 | 
				
			||||||
				1A0276022DF909F900D8BC53 /* refreshtokenex.swift */,
 | 
									1A0276022DF909F900D8BC53 /* refreshtokenex.swift */,
 | 
				
			||||||
 | 
									1AE587042E23264800254F06 /* PostService.swift */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			path = Network;
 | 
								path = Network;
 | 
				
			||||||
			sourceTree = "<group>";
 | 
								sourceTree = "<group>";
 | 
				
			||||||
@ -186,6 +214,17 @@
 | 
				
			|||||||
			path = Resources;
 | 
								path = Resources;
 | 
				
			||||||
			sourceTree = "<group>";
 | 
								sourceTree = "<group>";
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
							1ACE60FF2E22F54700B37AC5 /* Settings */ = {
 | 
				
			||||||
 | 
								isa = PBXGroup;
 | 
				
			||||||
 | 
								children = (
 | 
				
			||||||
 | 
									1ACE60F72E22F3DC00B37AC5 /* SettingsView.swift */,
 | 
				
			||||||
 | 
									1ACE61002E22F55C00B37AC5 /* EditProfileView.swift */,
 | 
				
			||||||
 | 
									1ACE61042E22F56800B37AC5 /* SecuritySettingsView.swift */,
 | 
				
			||||||
 | 
									1ACE61082E22F57100B37AC5 /* AppPreferencesView.swift */,
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								path = Settings;
 | 
				
			||||||
 | 
								sourceTree = "<group>";
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
		1AEE5EA52E21947B00A3DCA3 /* login */ = {
 | 
							1AEE5EA52E21947B00A3DCA3 /* login */ = {
 | 
				
			||||||
			isa = PBXGroup;
 | 
								isa = PBXGroup;
 | 
				
			||||||
			children = (
 | 
								children = (
 | 
				
			||||||
@ -202,10 +241,10 @@
 | 
				
			|||||||
			path = contacts;
 | 
								path = contacts;
 | 
				
			||||||
			sourceTree = "<group>";
 | 
								sourceTree = "<group>";
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		1AEE5EA92E21A4FC00A3DCA3 /* tab */ = {
 | 
							1AEE5EA92E21A4FC00A3DCA3 /* Tab */ = {
 | 
				
			||||||
			isa = PBXGroup;
 | 
								isa = PBXGroup;
 | 
				
			||||||
			children = (
 | 
								children = (
 | 
				
			||||||
				1AEE5ECC2E21C9D100A3DCA3 /* profile */,
 | 
									1AEE5ECC2E21C9D100A3DCA3 /* Profile */,
 | 
				
			||||||
				1A7940C52DF7A98E002569DA /* ContactsTab.swift */,
 | 
									1A7940C52DF7A98E002569DA /* ContactsTab.swift */,
 | 
				
			||||||
				1A7940C92DF7A99B002569DA /* ChatsTab.swift */,
 | 
									1A7940C92DF7A99B002569DA /* ChatsTab.swift */,
 | 
				
			||||||
				1A7940B52DF77F21002569DA /* MainView.swift */,
 | 
									1A7940B52DF77F21002569DA /* MainView.swift */,
 | 
				
			||||||
@ -213,15 +252,23 @@
 | 
				
			|||||||
				1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */,
 | 
									1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */,
 | 
				
			||||||
				1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */,
 | 
									1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			path = tab;
 | 
								path = Tab;
 | 
				
			||||||
			sourceTree = "<group>";
 | 
								sourceTree = "<group>";
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		1AEE5ECC2E21C9D100A3DCA3 /* profile */ = {
 | 
							1AEE5ECC2E21C9D100A3DCA3 /* Profile */ = {
 | 
				
			||||||
			isa = PBXGroup;
 | 
								isa = PBXGroup;
 | 
				
			||||||
			children = (
 | 
								children = (
 | 
				
			||||||
 | 
									1AD757CC2E27608C0069C1FD /* PostFeedView.swift */,
 | 
				
			||||||
 | 
									1ACE60FF2E22F54700B37AC5 /* Settings */,
 | 
				
			||||||
				1A7940CD2DF7A9AA002569DA /* ProfileTab.swift */,
 | 
									1A7940CD2DF7A9AA002569DA /* ProfileTab.swift */,
 | 
				
			||||||
 | 
									1AB4F8CC2E22E341002B6E40 /* AccountShareSheet.swift */,
 | 
				
			||||||
 | 
									1AE587242E23337000254F06 /* ProfileContentGrid.swift */,
 | 
				
			||||||
 | 
									1ACE61202E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift */,
 | 
				
			||||||
 | 
									1AB4F8F22E22EC9F002B6E40 /* FollowersView.swift */,
 | 
				
			||||||
 | 
									1AB4F8F62E22ECAC002B6E40 /* FollowingView.swift */,
 | 
				
			||||||
 | 
									1ACE61142E22FE2000B37AC5 /* PostDetailView.swift */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			path = profile;
 | 
								path = Profile;
 | 
				
			||||||
			sourceTree = "<group>";
 | 
								sourceTree = "<group>";
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
/* End PBXGroup section */
 | 
					/* End PBXGroup section */
 | 
				
			||||||
@ -324,23 +371,36 @@
 | 
				
			|||||||
			buildActionMask = 2147483647;
 | 
								buildActionMask = 2147483647;
 | 
				
			||||||
			files = (
 | 
								files = (
 | 
				
			||||||
				1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */,
 | 
									1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */,
 | 
				
			||||||
 | 
									1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */,
 | 
				
			||||||
 | 
									1AD757CD2E27608C0069C1FD /* PostFeedView.swift in Sources */,
 | 
				
			||||||
				1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
 | 
									1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
 | 
				
			||||||
 | 
									1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */,
 | 
				
			||||||
 | 
									1ACE61052E22F56800B37AC5 /* SecuritySettingsView.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 */,
 | 
				
			||||||
				1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */,
 | 
									1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */,
 | 
				
			||||||
				1A7940CE2DF7A9AA002569DA /* ProfileTab.swift in Sources */,
 | 
									1A7940CE2DF7A9AA002569DA /* ProfileTab.swift in Sources */,
 | 
				
			||||||
 | 
									1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */,
 | 
				
			||||||
 | 
									1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */,
 | 
				
			||||||
				1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
 | 
									1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
 | 
				
			||||||
 | 
									1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */,
 | 
				
			||||||
				1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */,
 | 
									1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */,
 | 
				
			||||||
				1A7940B62DF77F21002569DA /* MainView.swift in Sources */,
 | 
									1A7940B62DF77F21002569DA /* MainView.swift in Sources */,
 | 
				
			||||||
				1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */,
 | 
									1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */,
 | 
				
			||||||
				1A7940A62DF77DF5002569DA /* User.swift in Sources */,
 | 
									1A7940A62DF77DF5002569DA /* User.swift in Sources */,
 | 
				
			||||||
				1A7940A22DF77DE9002569DA /* AuthService.swift in Sources */,
 | 
									1A7940A22DF77DE9002569DA /* AuthService.swift in Sources */,
 | 
				
			||||||
				1AEE5EB32E21A85800A3DCA3 /* SearchTab.swift in Sources */,
 | 
									1AEE5EB32E21A85800A3DCA3 /* SearchTab.swift in Sources */,
 | 
				
			||||||
 | 
									1ACE61152E22FE2000B37AC5 /* PostDetailView.swift in Sources */,
 | 
				
			||||||
 | 
									1ACE61192E22FF1400B37AC5 /* Post.swift in Sources */,
 | 
				
			||||||
				1A79408F2DF77BC3002569DA /* ContentView.swift in Sources */,
 | 
									1A79408F2DF77BC3002569DA /* ContentView.swift in Sources */,
 | 
				
			||||||
 | 
									1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */,
 | 
				
			||||||
 | 
									1ACE61012E22F55C00B37AC5 /* EditProfileView.swift in Sources */,
 | 
				
			||||||
				1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */,
 | 
									1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */,
 | 
				
			||||||
 | 
									1AE587052E23264800254F06 /* PostService.swift in Sources */,
 | 
				
			||||||
				1A7940CA2DF7A99B002569DA /* ChatsTab.swift in Sources */,
 | 
									1A7940CA2DF7A99B002569DA /* ChatsTab.swift in Sources */,
 | 
				
			||||||
				1A7940AA2DF77E05002569DA /* LoginViewModel.swift in Sources */,
 | 
									1A7940AA2DF77E05002569DA /* LoginViewModel.swift in Sources */,
 | 
				
			||||||
 | 
									1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */,
 | 
				
			||||||
				1A79408D2DF77BC3002569DA /* yobbleApp.swift in Sources */,
 | 
									1A79408D2DF77BC3002569DA /* yobbleApp.swift in Sources */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
								runOnlyForDeploymentPostprocessing = 0;
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,16 @@
 | 
				
			|||||||
			<key>orderHint</key>
 | 
								<key>orderHint</key>
 | 
				
			||||||
			<integer>0</integer>
 | 
								<integer>0</integer>
 | 
				
			||||||
		</dict>
 | 
							</dict>
 | 
				
			||||||
 | 
							<key>yobble (iOS).xcscheme_^#shared#^_</key>
 | 
				
			||||||
 | 
							<dict>
 | 
				
			||||||
 | 
								<key>orderHint</key>
 | 
				
			||||||
 | 
								<integer>0</integer>
 | 
				
			||||||
 | 
							</dict>
 | 
				
			||||||
 | 
							<key>yobble (macOS).xcscheme_^#shared#^_</key>
 | 
				
			||||||
 | 
							<dict>
 | 
				
			||||||
 | 
								<key>orderHint</key>
 | 
				
			||||||
 | 
								<integer>1</integer>
 | 
				
			||||||
 | 
							</dict>
 | 
				
			||||||
	</dict>
 | 
						</dict>
 | 
				
			||||||
</dict>
 | 
					</dict>
 | 
				
			||||||
</plist>
 | 
					</plist>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user