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_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 */; };
 | 
			
		||||
		1A7940F02DF7B7A3002569DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A7940F22DF7B7A3002569DA /* Localizable.strings */; };
 | 
			
		||||
		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 */; };
 | 
			
		||||
		1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.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>"; };
 | 
			
		||||
		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>"; };
 | 
			
		||||
		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>"; };
 | 
			
		||||
		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>"; };
 | 
			
		||||
@ -135,7 +161,7 @@
 | 
			
		||||
		1A79409D2DF77DB5002569DA /* Views */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1AEE5EA92E21A4FC00A3DCA3 /* tab */,
 | 
			
		||||
				1AEE5EA92E21A4FC00A3DCA3 /* Tab */,
 | 
			
		||||
				1AEE5EA62E2194CD00A3DCA3 /* contacts */,
 | 
			
		||||
				1AEE5EA52E21947B00A3DCA3 /* login */,
 | 
			
		||||
				1A79407B2DF77BC2002569DA /* ContentView.swift */,
 | 
			
		||||
@ -156,6 +182,7 @@
 | 
			
		||||
		1A79409F2DF77DC4002569DA /* Models */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1ACE61182E22FF1400B37AC5 /* Post.swift */,
 | 
			
		||||
				1A7940A52DF77DF5002569DA /* User.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Models;
 | 
			
		||||
@ -166,6 +193,7 @@
 | 
			
		||||
			children = (
 | 
			
		||||
				1A7940A12DF77DE9002569DA /* AuthService.swift */,
 | 
			
		||||
				1A0276022DF909F900D8BC53 /* refreshtokenex.swift */,
 | 
			
		||||
				1AE587042E23264800254F06 /* PostService.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = Network;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
@ -186,6 +214,17 @@
 | 
			
		||||
			path = Resources;
 | 
			
		||||
			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 */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
@ -202,10 +241,10 @@
 | 
			
		||||
			path = contacts;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1AEE5EA92E21A4FC00A3DCA3 /* tab */ = {
 | 
			
		||||
		1AEE5EA92E21A4FC00A3DCA3 /* Tab */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1AEE5ECC2E21C9D100A3DCA3 /* profile */,
 | 
			
		||||
				1AEE5ECC2E21C9D100A3DCA3 /* Profile */,
 | 
			
		||||
				1A7940C52DF7A98E002569DA /* ContactsTab.swift */,
 | 
			
		||||
				1A7940C92DF7A99B002569DA /* ChatsTab.swift */,
 | 
			
		||||
				1A7940B52DF77F21002569DA /* MainView.swift */,
 | 
			
		||||
@ -213,15 +252,23 @@
 | 
			
		||||
				1AEE5EAE2E21A84500A3DCA3 /* PublicsTab.swift */,
 | 
			
		||||
				1AEE5EB22E21A85800A3DCA3 /* SearchTab.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = tab;
 | 
			
		||||
			path = Tab;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
		};
 | 
			
		||||
		1AEE5ECC2E21C9D100A3DCA3 /* profile */ = {
 | 
			
		||||
		1AEE5ECC2E21C9D100A3DCA3 /* Profile */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				1AD757CC2E27608C0069C1FD /* PostFeedView.swift */,
 | 
			
		||||
				1ACE60FF2E22F54700B37AC5 /* Settings */,
 | 
			
		||||
				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>";
 | 
			
		||||
		};
 | 
			
		||||
/* End PBXGroup section */
 | 
			
		||||
@ -324,23 +371,36 @@
 | 
			
		||||
			buildActionMask = 2147483647;
 | 
			
		||||
			files = (
 | 
			
		||||
				1AEE5EAB2E21A83200A3DCA3 /* HomeTab.swift in Sources */,
 | 
			
		||||
				1ACE60F82E22F3DC00B37AC5 /* SettingsView.swift in Sources */,
 | 
			
		||||
				1AD757CD2E27608C0069C1FD /* PostFeedView.swift in Sources */,
 | 
			
		||||
				1A7940C62DF7A98E002569DA /* ContactsTab.swift in Sources */,
 | 
			
		||||
				1ACE61092E22F57100B37AC5 /* AppPreferencesView.swift in Sources */,
 | 
			
		||||
				1ACE61052E22F56800B37AC5 /* SecuritySettingsView.swift in Sources */,
 | 
			
		||||
				1A7940E72DF7B5E5002569DA /* SplashScreenView.swift in Sources */,
 | 
			
		||||
				1A7940B02DF77E26002569DA /* LoginView.swift in Sources */,
 | 
			
		||||
				1A79410C2DF7C81D002569DA /* RegistrationView.swift in Sources */,
 | 
			
		||||
				1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */,
 | 
			
		||||
				1A7940CE2DF7A9AA002569DA /* ProfileTab.swift in Sources */,
 | 
			
		||||
				1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */,
 | 
			
		||||
				1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */,
 | 
			
		||||
				1A7940DE2DF7B0D7002569DA /* config.swift in Sources */,
 | 
			
		||||
				1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */,
 | 
			
		||||
				1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */,
 | 
			
		||||
				1A7940B62DF77F21002569DA /* MainView.swift in Sources */,
 | 
			
		||||
				1A7940E22DF7B1C5002569DA /* KeychainService.swift in Sources */,
 | 
			
		||||
				1A7940A62DF77DF5002569DA /* User.swift in Sources */,
 | 
			
		||||
				1A7940A22DF77DE9002569DA /* AuthService.swift in Sources */,
 | 
			
		||||
				1AEE5EB32E21A85800A3DCA3 /* SearchTab.swift in Sources */,
 | 
			
		||||
				1ACE61152E22FE2000B37AC5 /* PostDetailView.swift in Sources */,
 | 
			
		||||
				1ACE61192E22FF1400B37AC5 /* Post.swift in Sources */,
 | 
			
		||||
				1A79408F2DF77BC3002569DA /* ContentView.swift in Sources */,
 | 
			
		||||
				1AB4F8CD2E22E341002B6E40 /* AccountShareSheet.swift in Sources */,
 | 
			
		||||
				1ACE61012E22F55C00B37AC5 /* EditProfileView.swift in Sources */,
 | 
			
		||||
				1AEE5EAF2E21A84500A3DCA3 /* PublicsTab.swift in Sources */,
 | 
			
		||||
				1AE587052E23264800254F06 /* PostService.swift in Sources */,
 | 
			
		||||
				1A7940CA2DF7A99B002569DA /* ChatsTab.swift in Sources */,
 | 
			
		||||
				1A7940AA2DF77E05002569DA /* LoginViewModel.swift in Sources */,
 | 
			
		||||
				1AB4F8F32E22EC9F002B6E40 /* FollowersView.swift in Sources */,
 | 
			
		||||
				1A79408D2DF77BC3002569DA /* yobbleApp.swift in Sources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,16 @@
 | 
			
		||||
			<key>orderHint</key>
 | 
			
		||||
			<integer>0</integer>
 | 
			
		||||
		</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>
 | 
			
		||||
</plist>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user